diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java index 655bf6f449..2d3dc57590 100644 --- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java @@ -48,13 +48,13 @@ import org.apache.syncope.core.persistence.api.dao.RelationshipTypeDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.dao.search.SearchCond; -import org.apache.syncope.core.persistence.api.entity.Any; import org.apache.syncope.core.persistence.api.entity.AnyType; import org.apache.syncope.core.persistence.api.entity.AnyTypeClass; import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory; import org.apache.syncope.core.persistence.api.entity.DerSchema; import org.apache.syncope.core.persistence.api.entity.DynGroupMembership; import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.GroupableRelatable; import org.apache.syncope.core.persistence.api.entity.Realm; import org.apache.syncope.core.persistence.api.entity.VirSchema; import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership; @@ -445,11 +445,17 @@ public GroupTO getGroupTO(final String key) { } protected static void populateTransitiveResources( - final Group group, final Any any, final Map> result) { + final Group group, + final GroupableRelatable any, + final Map> result) { PropagationByResource propByRes = new PropagationByResource<>(); group.getResources().forEach(resource -> { - if (!any.getResources().contains(resource)) { + // exclude from propagation those objects that have that resource assigned by some other membership(s) + if (!any.getResources().contains(resource) && any.getMemberships().stream() + .filter(otherGrpMemb -> !otherGrpMemb.getRightEnd().equals(group)) + .noneMatch(otherGrpMemb -> otherGrpMemb.getRightEnd().getResources().stream() + .anyMatch(r -> resource.getKey().equals(r.getKey())))) { propByRes.add(ResourceOperation.DELETE, resource.getKey()); } 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 13eee4c164..466e417577 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 @@ -18,6 +18,7 @@ */ package org.apache.syncope.fit.core; +import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -40,6 +41,8 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.naming.NamingException; import org.apache.commons.lang3.tuple.Triple; import org.apache.cxf.helpers.IOUtils; @@ -71,6 +74,7 @@ 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.PropagationTaskTO; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.PushTaskTO; import org.apache.syncope.common.lib.to.RealmTO; @@ -93,6 +97,7 @@ 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.ResourceOperation; import org.apache.syncope.common.lib.types.SchemaType; import org.apache.syncope.common.lib.types.StatusRType; import org.apache.syncope.common.lib.types.TaskType; @@ -100,6 +105,7 @@ import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.common.rest.api.beans.RealmQuery; import org.apache.syncope.common.rest.api.beans.ReconQuery; +import org.apache.syncope.common.rest.api.beans.TaskQuery; import org.apache.syncope.common.rest.api.service.UserService; import org.apache.syncope.core.provisioning.api.serialization.POJOHelper; import org.apache.syncope.core.provisioning.java.propagation.DBPasswordPropagationActions; @@ -1839,4 +1845,56 @@ void issueSYNCOPE1818() { jdbcTemplate.update("DELETE FROM TESTPULL WHERE USERNAME = 'rossini'"); } } + + @Test + void issueSYNCOPE1853() { + GroupTO cGroupForPropagation = createGroup( + new GroupCR.Builder(SyncopeConstants.ROOT_REALM, "cGroupForPropagation") + .resource(RESOURCE_NAME_LDAP) + .build()).getEntity(); + GroupTO dGroupForPropagation = createGroup( + new GroupCR.Builder(SyncopeConstants.ROOT_REALM, "dGroupForPropagation") + .resource(RESOURCE_NAME_LDAP) + .build()).getEntity(); + // 1. assign both groups cGroupForPropagation and dGroupForPropagation with resource-csv to bellini + updateUser(new UserUR.Builder("c9b2dec2-00a7-4855-97c0-d854842b4b24").memberships( + new MembershipUR.Builder(cGroupForPropagation.getKey()).build(), + new MembershipUR.Builder(dGroupForPropagation.getKey()).build()).build()); + // 2. assign cGroupForPropagation also to vivaldi + updateUser(new UserUR.Builder("b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee").membership( + new MembershipUR.Builder(dGroupForPropagation.getKey()).build()).build()); + // 3. propagation tasks cleanup + TASK_SERVICE.search( + new TaskQuery.Builder(TaskType.PROPAGATION) + .anyTypeKind(AnyTypeKind.USER) + .resource(RESOURCE_NAME_LDAP) + .entityKey("c9b2dec2-00a7-4855-97c0-d854842b4b24") + .build()).getResult() + .forEach(pt -> TASK_SERVICE.delete(TaskType.PROPAGATION, pt.getKey())); + TASK_SERVICE.search( + new TaskQuery.Builder(TaskType.PROPAGATION) + .anyTypeKind(AnyTypeKind.USER) + .resource(RESOURCE_NAME_LDAP) + .entityKey("b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee") + .build()).getResult() + .forEach(pt -> TASK_SERVICE.delete(TaskType.PROPAGATION, pt.getKey())); + // 4. delete group cGroupForPropagation: no deprovision should be fired on bellini, since there is already + // bGroupForPropagation, deprovision instead must be fired for vivaldi + GROUP_SERVICE.delete(cGroupForPropagation.getKey()); + await().during(5, TimeUnit.SECONDS).atMost(10, TimeUnit.SECONDS).until(() -> TASK_SERVICE.search( + new TaskQuery.Builder(TaskType.PROPAGATION) + .anyTypeKind(AnyTypeKind.USER) + .resource(RESOURCE_NAME_LDAP) + .entityKey("c9b2dec2-00a7-4855-97c0-d854842b4b24").build()) + .getResult().stream().map(PropagationTaskTO.class::cast) + .collect(Collectors.toList()).stream().noneMatch(pt -> ResourceOperation.DELETE == pt.getOperation())); + GROUP_SERVICE.delete(dGroupForPropagation.getKey()); + await().atMost(10, TimeUnit.SECONDS).until(() -> TASK_SERVICE.search( + new TaskQuery.Builder(TaskType.PROPAGATION) + .anyTypeKind(AnyTypeKind.USER) + .resource(RESOURCE_NAME_LDAP) + .entityKey("b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee").build()) + .getResult().stream().map(PropagationTaskTO.class::cast) + .collect(Collectors.toList()).stream().anyMatch(pt -> ResourceOperation.DELETE == pt.getOperation())); + } }