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

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


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

commit 55e7ed9224177051030ca45147d2435367699953
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 +
 .../core/persistence/jpa/PersistenceContext.java   |  4 +-
 .../core/persistence/jpa/entity/JPAAnyUtils.java   | 30 +++++++++
 .../persistence/jpa/entity/JPAAnyUtilsFactory.java | 29 ++++++++-
 .../AbstractPropagationTaskExecutor.java           | 15 +++++
 .../apache/syncope/fit/core/UserIssuesITCase.java  | 74 ++++++++++++++++++++++
 6 files changed, 150 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 0ceb427afe..92d65ba86d 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
@@ -60,4 +60,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-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
index 2f3cf23387..33f1e234c1 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
@@ -282,9 +282,11 @@ public class PersistenceContext {
             final @Lazy UserDAO userDAO,
             final @Lazy GroupDAO groupDAO,
             final @Lazy AnyObjectDAO anyObjectDAO,
+            final @Lazy PlainAttrDAO plainAttrDAO,
+            final @Lazy PlainAttrValueDAO plainAttrValueDAO,
             final @Lazy EntityFactory entityFactory) {
 
-        return new JPAAnyUtilsFactory(userDAO, groupDAO, anyObjectDAO, 
entityFactory);
+        return new JPAAnyUtilsFactory(userDAO, groupDAO, anyObjectDAO, 
plainAttrDAO, plainAttrValueDAO, entityFactory);
     }
 
     @ConditionalOnMissingBean
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
index 786709523b..fa7aca61e9 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtils.java
@@ -45,12 +45,15 @@ import 
org.apache.syncope.core.persistence.api.attrvalue.validation.PlainAttrVal
 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.PlainAttrDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
 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;
@@ -125,6 +128,10 @@ public class JPAAnyUtils implements AnyUtils {
 
     protected final AnyObjectDAO anyObjectDAO;
 
+    protected final PlainAttrDAO plainAttrDAO;
+    
+    protected final PlainAttrValueDAO plainAttrValueDAO;
+    
     protected final EntityFactory entityFactory;
 
     protected final AnyTypeKind anyTypeKind;
@@ -135,6 +142,8 @@ public class JPAAnyUtils implements AnyUtils {
             final UserDAO userDAO,
             final GroupDAO groupDAO,
             final AnyObjectDAO anyObjectDAO,
+            final PlainAttrDAO plainAttrDAO,
+            final PlainAttrValueDAO plainAttrValueDAO,
             final EntityFactory entityFactory,
             final AnyTypeKind anyTypeKind,
             final boolean linkedAccount) {
@@ -142,6 +151,8 @@ public class JPAAnyUtils implements AnyUtils {
         this.userDAO = userDAO;
         this.groupDAO = groupDAO;
         this.anyObjectDAO = anyObjectDAO;
+        this.plainAttrDAO = plainAttrDAO;
+        this.plainAttrValueDAO = plainAttrValueDAO;
         this.entityFactory = entityFactory;
         this.anyTypeKind = anyTypeKind;
         this.linkedAccount = linkedAccount;
@@ -454,4 +465,23 @@ public class JPAAnyUtils 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().find(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);
+            plainAttrDAO.delete(plainAttr);
+
+            dao().save(any);
+        }, () -> LOG.warn("Any {} does not contain {} PLAIN attribute", key, 
schema.getKey()));
+    }
 }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtilsFactory.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtilsFactory.java
index e48d0934b2..5e50979ba6 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtilsFactory.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAAnyUtilsFactory.java
@@ -23,6 +23,8 @@ import java.util.Map;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 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.PlainAttrDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
 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.AnyUtils;
@@ -41,6 +43,10 @@ public class JPAAnyUtilsFactory implements AnyUtilsFactory {
 
     protected final AnyObjectDAO anyObjectDAO;
 
+    protected final PlainAttrDAO plainAttrDAO;
+
+    protected final PlainAttrValueDAO plainAttrValueDAO;
+    
     protected final EntityFactory entityFactory;
 
     protected final Map<AnyTypeKind, AnyUtils> instances = new HashMap<>(3);
@@ -51,11 +57,15 @@ public class JPAAnyUtilsFactory implements AnyUtilsFactory {
             final UserDAO userDAO,
             final GroupDAO groupDAO,
             final AnyObjectDAO anyObjectDAO,
+            final PlainAttrDAO plainAttrDAO,
+            final PlainAttrValueDAO plainAttrValueDAO,
             final EntityFactory entityFactory) {
 
         this.userDAO = userDAO;
         this.groupDAO = groupDAO;
         this.anyObjectDAO = anyObjectDAO;
+        this.plainAttrDAO = plainAttrDAO;
+        this.plainAttrValueDAO = plainAttrValueDAO;
         this.entityFactory = entityFactory;
     }
 
@@ -65,7 +75,14 @@ public class JPAAnyUtilsFactory implements AnyUtilsFactory {
         synchronized (instances) {
             instance = instances.get(anyTypeKind);
             if (instance == null) {
-                instance = new JPAAnyUtils(userDAO, groupDAO, anyObjectDAO, 
entityFactory, anyTypeKind, false);
+                instance = new JPAAnyUtils(userDAO,
+                        groupDAO,
+                        anyObjectDAO,
+                        plainAttrDAO,
+                        plainAttrValueDAO,
+                        entityFactory,
+                        anyTypeKind,
+                        false);
                 
ApplicationContextProvider.getBeanFactory().autowireBean(instance);
                 instances.put(anyTypeKind, instance);
             }
@@ -96,8 +113,14 @@ public class JPAAnyUtilsFactory implements AnyUtilsFactory {
     public AnyUtils getLinkedAccountInstance() {
         synchronized (this) {
             if (linkedAccountInstance == null) {
-                linkedAccountInstance = new JPAAnyUtils(
-                        userDAO, groupDAO, anyObjectDAO, entityFactory, 
AnyTypeKind.USER, true);
+                linkedAccountInstance = new JPAAnyUtils(userDAO,
+                        groupDAO,
+                        anyObjectDAO,
+                        plainAttrDAO,
+                        plainAttrValueDAO,
+                        entityFactory,
+                        AnyTypeKind.USER,
+                        true);
             }
         }
         return linkedAccountInstance;
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 a20aa19cc3..5ff7933f82 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
@@ -230,6 +230,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,
@@ -383,6 +385,19 @@ 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.find(provision.getUidOnCreate()));
+                        publisher.publishEvent(new EntityLifecycleEvent<>(this,
+                                SyncDeltaType.UPDATE,
+                                anyUtils.dao().find(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 f72436fe17..41619d3fc6 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