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());
+ }
+ }
}