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