This is an automated email from the ASF dual-hosted git repository.

andreapatricelli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 36fdf832a1 [SYNCOPE-1809] Remove uidOnCreate attribute on delete 
propagation (#640)
36fdf832a1 is described below

commit 36fdf832a1274aeb7c47fcbec47804b5ec4fe808
Author: Andrea Patricelli <[email protected]>
AuthorDate: Wed Mar 6 18:27:25 2024 +0100

    [SYNCOPE-1809] Remove uidOnCreate attribute on delete propagation (#640)
    
    * [SYNCOPE-1809] Remove uidOnCreate attribute on delete propagation
---
 .../core/persistence/api/entity/AnyUtils.java      |  2 +
 .../common/CommonPersistenceContext.java           | 46 ++++++++++++--
 .../persistence/common/entity/DefaultAnyUtils.java | 30 +++++++++
 .../AbstractPropagationTaskExecutor.java           | 21 ++++++
 .../apache/syncope/fit/core/UserIssuesITCase.java  | 74 ++++++++++++++++++++++
 5 files changed, 169 insertions(+), 4 deletions(-)

diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
index 8db658e6e8..a625e61dd0 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/AnyUtils.java
@@ -61,4 +61,6 @@ public interface AnyUtils {
     Set<ExternalResource> getAllResources(Any<?> any);
 
     void addAttr(PlainAttrValidationManager validator, String key, PlainSchema 
schema, String value);
+    
+    void removeAttr(String key, PlainSchema schema);
 }
diff --git 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/CommonPersistenceContext.java
 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/CommonPersistenceContext.java
index 8725c08b32..d68c41c87d 100644
--- 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/CommonPersistenceContext.java
+++ 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/CommonPersistenceContext.java
@@ -24,6 +24,8 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import 
org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
@@ -78,9 +80,18 @@ public class CommonPersistenceContext {
             final @Lazy UserDAO userDAO,
             final @Lazy GroupDAO groupDAO,
             final @Lazy AnyObjectDAO anyObjectDAO,
+            final @Lazy PlainSchemaDAO plainSchemaDAO,
+            final @Lazy PlainAttrValueDAO plainAttrValueDAO,
             final @Lazy EntityFactory entityFactory) {
 
-        return new DefaultAnyUtils(userDAO, groupDAO, anyObjectDAO, 
entityFactory, AnyTypeKind.USER, false);
+        return new DefaultAnyUtils(userDAO,
+                groupDAO,
+                anyObjectDAO,
+                plainSchemaDAO,
+                plainAttrValueDAO,
+                entityFactory,
+                AnyTypeKind.USER,
+                false);
     }
 
     @Bean(name = "linkedAccountAnyUtils")
@@ -88,9 +99,18 @@ public class CommonPersistenceContext {
             final @Lazy UserDAO userDAO,
             final @Lazy GroupDAO groupDAO,
             final @Lazy AnyObjectDAO anyObjectDAO,
+            final @Lazy PlainSchemaDAO plainSchemaDAO,
+            final @Lazy PlainAttrValueDAO plainAttrValueDAO,
             final @Lazy EntityFactory entityFactory) {
 
-        return new DefaultAnyUtils(userDAO, groupDAO, anyObjectDAO, 
entityFactory, AnyTypeKind.USER, true);
+        return new DefaultAnyUtils(userDAO,
+                groupDAO,
+                anyObjectDAO,
+                plainSchemaDAO,
+                plainAttrValueDAO,
+                entityFactory,
+                AnyTypeKind.USER,
+                true);
     }
 
     @Bean(name = "groupAnyUtils")
@@ -98,9 +118,18 @@ public class CommonPersistenceContext {
             final @Lazy UserDAO userDAO,
             final @Lazy GroupDAO groupDAO,
             final @Lazy AnyObjectDAO anyObjectDAO,
+            final @Lazy PlainSchemaDAO plainSchemaDAO,
+            final @Lazy PlainAttrValueDAO plainAttrValueDAO,
             final @Lazy EntityFactory entityFactory) {
 
-        return new DefaultAnyUtils(userDAO, groupDAO, anyObjectDAO, 
entityFactory, AnyTypeKind.GROUP, false);
+        return new DefaultAnyUtils(userDAO,
+                groupDAO,
+                anyObjectDAO,
+                plainSchemaDAO,
+                plainAttrValueDAO,
+                entityFactory,
+                AnyTypeKind.GROUP,
+                false);
     }
 
     @Bean(name = "anyObjectAnyUtils")
@@ -108,9 +137,18 @@ public class CommonPersistenceContext {
             final @Lazy UserDAO userDAO,
             final @Lazy GroupDAO groupDAO,
             final @Lazy AnyObjectDAO anyObjectDAO,
+            final @Lazy PlainSchemaDAO plainSchemaDAO,
+            final @Lazy PlainAttrValueDAO plainAttrValueDAO,
             final @Lazy EntityFactory entityFactory) {
 
-        return new DefaultAnyUtils(userDAO, groupDAO, anyObjectDAO, 
entityFactory, AnyTypeKind.ANY_OBJECT, false);
+        return new DefaultAnyUtils(userDAO,
+                groupDAO,
+                anyObjectDAO,
+                plainSchemaDAO,
+                plainAttrValueDAO,
+                entityFactory,
+                AnyTypeKind.ANY_OBJECT,
+                false);
     }
 
     @ConditionalOnMissingBean
diff --git 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java
 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java
index 8ae01b937c..67949140cd 100644
--- 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java
+++ 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/entity/DefaultAnyUtils.java
@@ -47,12 +47,15 @@ import org.apache.syncope.core.persistence.api.dao.AnyDAO;
 import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.GroupablePlainAttr;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
@@ -106,6 +109,10 @@ public class DefaultAnyUtils implements AnyUtils {
 
     protected final AnyObjectDAO anyObjectDAO;
 
+    protected final PlainSchemaDAO plainSchemaDAO;
+    
+    protected final PlainAttrValueDAO plainAttrValueDAO;
+    
     protected final EntityFactory entityFactory;
 
     protected final AnyTypeKind anyTypeKind;
@@ -122,6 +129,8 @@ public class DefaultAnyUtils implements AnyUtils {
             final UserDAO userDAO,
             final GroupDAO groupDAO,
             final AnyObjectDAO anyObjectDAO,
+            final PlainSchemaDAO plainSchemaDAO,
+            final PlainAttrValueDAO plainAttrValueDAO,
             final EntityFactory entityFactory,
             final AnyTypeKind anyTypeKind,
             final boolean linkedAccount) {
@@ -129,6 +138,8 @@ public class DefaultAnyUtils implements AnyUtils {
         this.userDAO = userDAO;
         this.groupDAO = groupDAO;
         this.anyObjectDAO = anyObjectDAO;
+        this.plainSchemaDAO = plainSchemaDAO;
+        this.plainAttrValueDAO = plainAttrValueDAO;
         this.entityFactory = entityFactory;
         this.anyTypeKind = anyTypeKind;
         this.linkedAccount = linkedAccount;
@@ -451,4 +462,23 @@ public class DefaultAnyUtils implements AnyUtils {
             LOG.debug("{} has already {} set: {}", any, schema.getKey(), 
attr.getValuesAsStrings());
         }
     }
+
+    @Transactional
+    @Override
+    public void removeAttr(final String key, final PlainSchema schema) {
+        Any any = dao().findById(key).orElseThrow(() -> new 
NotFoundException(anyTypeKind + " " + key));
+
+        any.getPlainAttr(schema.getKey()).ifPresentOrElse(attr -> {
+            PlainAttr<?> plainAttr = (PlainAttr<?>) attr;
+            any.remove(plainAttr);
+            plainAttr.setOwner(null);
+            if (plainAttr instanceof GroupablePlainAttr) {
+                ((GroupablePlainAttr) plainAttr).setMembership(null);
+            }
+            plainAttrValueDAO.deleteAll(plainAttr, this);
+            plainSchemaDAO.delete(plainAttr);
+
+            dao().save(any);
+        }, () -> LOG.warn("Any {} does not contain {} PLAIN attribute", key, 
schema.getKey()));
+    }
 }
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 0ab8a48a85..4002e22450 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@ -231,6 +231,8 @@ public abstract class AbstractPropagationTaskExecutor 
implements PropagationTask
         taskInfo.getResource().getProvisionByAnyType(taskInfo.getAnyType()).
                 filter(provision -> provision.getUidOnCreate() != null).
                 ifPresent(provision -> {
+                    LOG.debug("Adding uidOnCreate [{}] attribute to [{}] on 
create", provision.getUidOnCreate(),
+                            taskInfo.getEntityKey());
                     AnyUtils anyUtils = 
anyUtilsFactory.getInstance(taskInfo.getAnyTypeKind());
                     anyUtils.addAttr(
                             validator,
@@ -388,6 +390,25 @@ public abstract class AbstractPropagationTaskExecutor 
implements PropagationTask
 
             connector.delete(objectClass, uid, null, propagationAttempted);
             result = uid;
+            taskInfo.getResource()
+                    .getProvisionByAnyType(taskInfo.getAnyType())
+                    .filter(provision -> provision.getUidOnCreate() != null)
+                    .ifPresent(provision -> {
+                        LOG.debug("Removing uidOnCreate [{}] attribute from 
[{}] on delete",
+                                provision.getUidOnCreate(), 
taskInfo.getEntityKey());
+                        AnyUtils anyUtils = 
anyUtilsFactory.getInstance(taskInfo.getAnyTypeKind());
+                        anyUtils.removeAttr(taskInfo.getEntityKey(),
+                                
plainSchemaDAO.findById(provision.getUidOnCreate())
+                                        .orElseThrow(() -> new 
NotFoundException(
+                                                "PlainSchema " + 
(provision.getUidOnCreate()))));
+                        publisher.publishEvent(new EntityLifecycleEvent<>(
+                                this,
+                                SyncDeltaType.UPDATE,
+                                
anyUtils.dao().findById(taskInfo.getEntityKey()).
+                                        orElseThrow(() -> new 
NotFoundException(
+                                        anyUtils.anyTypeKind() + "" + 
taskInfo.getEntityKey())),
+                                AuthContextUtils.getDomain()));
+                    });
         }
 
         return result;
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
index 7c115e3c90..bd0a0544d1 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
@@ -55,16 +55,19 @@ import org.apache.syncope.common.lib.request.GroupCR;
 import org.apache.syncope.common.lib.request.MembershipUR;
 import org.apache.syncope.common.lib.request.PasswordPatch;
 import org.apache.syncope.common.lib.request.ResourceAR;
+import org.apache.syncope.common.lib.request.ResourceDR;
 import org.apache.syncope.common.lib.request.StringPatchItem;
 import org.apache.syncope.common.lib.request.StringReplacePatchItem;
 import org.apache.syncope.common.lib.request.UserCR;
 import org.apache.syncope.common.lib.request.UserUR;
+import org.apache.syncope.common.lib.to.AnyTypeClassTO;
 import org.apache.syncope.common.lib.to.ConnObject;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.ImplementationTO;
 import org.apache.syncope.common.lib.to.Item;
 import org.apache.syncope.common.lib.to.Mapping;
 import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.RealmTO;
@@ -72,6 +75,7 @@ import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.to.RoleTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.CipherAlgorithm;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ExecStatus;
@@ -83,6 +87,8 @@ import org.apache.syncope.common.lib.types.MappingPurpose;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.lib.types.ResourceAssociationAction;
+import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
+import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.common.rest.api.beans.RealmQuery;
 import org.apache.syncope.common.rest.api.service.UserService;
@@ -1634,4 +1640,72 @@ public class UserIssuesITCase extends AbstractITCase {
                 
resource(RESOURCE_NAME_NOPROPAGATION).action(ResourceAssociationAction.ASSIGN).build());
         assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
     }
+
+    @Test
+    public void issueSYNCOPE1809() throws IOException {
+        // 1. add a new schema externalKey and update provision accordingly
+        PlainSchemaTO externalKeySchemaTO = new PlainSchemaTO();
+        externalKeySchemaTO.setKey("externalKey");
+        externalKeySchemaTO.setType(AttrSchemaType.String);
+        externalKeySchemaTO.setReadonly(true);
+        SCHEMA_SERVICE.create(SchemaType.PLAIN, externalKeySchemaTO);
+        try {
+            AnyTypeClassTO minimalUser = ANY_TYPE_CLASS_SERVICE.read("minimal 
user");
+            minimalUser.getPlainSchemas().add(externalKeySchemaTO.getKey());
+            ANY_TYPE_CLASS_SERVICE.update(minimalUser);
+            ResourceTO restResourceTO = 
RESOURCE_SERVICE.read(RESOURCE_NAME_REST);
+            restResourceTO.getProvision(AnyTypeKind.USER.name())
+                    .ifPresent(p -> 
p.setUidOnCreate(externalKeySchemaTO.getKey()));
+            RESOURCE_SERVICE.update(restResourceTO);
+            UserCR userCR = 
UserITCase.getUniqueSample("[email protected]");
+            userCR.getResources().clear();
+            userCR.getResources().add(RESOURCE_NAME_REST);
+
+            // 2. create
+            ProvisioningResult<UserTO> result = createUser(userCR);
+            assertEquals(1, result.getPropagationStatuses().size());
+            assertEquals(ExecStatus.SUCCESS, 
result.getPropagationStatuses().get(0).getStatus());
+            assertEquals(RESOURCE_NAME_REST, 
result.getPropagationStatuses().get(0).getResource());
+            assertEquals("surname", 
result.getEntity().getPlainAttr("surname").get().getValues().get(0));
+            // externalKey is going to be populated on create
+            
assertTrue(result.getEntity().getPlainAttr("externalKey").isPresent());
+            assertEquals(result.getEntity().getKey(),
+                    
result.getEntity().getPlainAttr("externalKey").get().getValues().get(0));
+            // 3. remove resource from the user
+            result = updateUser(new UserUR.Builder(result.getEntity()
+                    .getKey()).resource(new 
StringPatchItem.Builder().value(RESOURCE_NAME_REST)
+                    .operation(PatchOperation.DELETE)
+                    .build()).build());
+            assertEquals(ExecStatus.SUCCESS, 
result.getPropagationStatuses().get(0).getStatus());
+            // externalKey is going to be removed on resource unassignment
+            
assertFalse(result.getEntity().getPlainAttr("externalKey").isPresent());
+
+            // 4. create a new user and deprovision, attribute is cleared
+            userCR = UserITCase.getUniqueSample("[email protected]");
+            userCR.getResources().clear();
+            userCR.getResources().add(RESOURCE_NAME_REST);
+            result = createUser(userCR);
+            assertEquals(ExecStatus.SUCCESS, 
result.getPropagationStatuses().get(0).getStatus());
+            // this time fire a deprovision
+            assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(new 
ResourceDR.Builder().key(result.getEntity()
+                    
.getKey()).action(ResourceDeassociationAction.DEPROVISION).resource(RESOURCE_NAME_REST).build())));
+            UserTO restUserTO = USER_SERVICE.read(result.getEntity().getKey());
+            assertFalse(restUserTO.getPlainAttr("externalKey").isPresent());
+
+            // 5. create a new user and unlink, attribute is not cleared since 
provisioning hasn't been fired
+            userCR = UserITCase.getUniqueSample("[email protected]");
+            userCR.getResources().clear();
+            userCR.getResources().add(RESOURCE_NAME_REST);
+            result = createUser(userCR);
+            assertEquals(ExecStatus.SUCCESS, 
result.getPropagationStatuses().get(0).getStatus());
+            // this time deprovision
+            assertNotNull(parseBatchResponse(USER_SERVICE.deassociate(new 
ResourceDR.Builder().key(result.getEntity()
+                    
.getKey()).action(ResourceDeassociationAction.UNLINK).resource(RESOURCE_NAME_REST).build())));
+            restUserTO = USER_SERVICE.read(result.getEntity().getKey());
+            assertTrue(restUserTO.getPlainAttr("externalKey").isPresent());
+        } finally {
+            // remove additional externalKey schema
+            SCHEMA_SERVICE.delete(SchemaType.PLAIN, 
externalKeySchemaTO.getKey());
+        }
+    }
 }

Reply via email to