[SYNCOPE-1356] Enhancing LDAPMembershipPullActions / SetUMembershipsJob with 
capabilites from 1_2_X


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/4810fb79
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/4810fb79
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/4810fb79

Branch: refs/heads/2_0_X
Commit: 4810fb796b7d6e5035005a24e213e1c9cc495693
Parents: 16d290b
Author: Francesco Chicchiriccò <ilgro...@apache.org>
Authored: Thu Aug 16 12:55:12 2018 +0200
Committer: Francesco Chicchiriccò <ilgro...@apache.org>
Committed: Thu Aug 16 14:15:36 2018 +0200

----------------------------------------------------------------------
 .../core/migration/MigrationPullActions.java    |  5 +-
 .../java/job/SetUMembershipsJob.java            | 73 ++++++++++++++++----
 .../pushpull/LDAPMembershipPullActions.java     | 73 ++++++++++++--------
 .../apache/syncope/fit/core/PullTaskITCase.java | 45 +++++++++---
 4 files changed, 141 insertions(+), 55 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/4810fb79/core/migration/src/main/java/org/apache/syncope/core/migration/MigrationPullActions.java
----------------------------------------------------------------------
diff --git 
a/core/migration/src/main/java/org/apache/syncope/core/migration/MigrationPullActions.java
 
b/core/migration/src/main/java/org/apache/syncope/core/migration/MigrationPullActions.java
index aab5f8f..b6a19e5 100644
--- 
a/core/migration/src/main/java/org/apache/syncope/core/migration/MigrationPullActions.java
+++ 
b/core/migration/src/main/java/org/apache/syncope/core/migration/MigrationPullActions.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.core.migration;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -131,8 +132,8 @@ public class MigrationPullActions extends 
SchedulingPullActions {
     @Override
     public void afterAll(final ProvisioningProfile<?, ?> profile) throws 
JobExecutionException {
         Map<String, Object> jobMap = new HashMap<>();
-        jobMap.put(SetUMembershipsJob.MEMBERSHIPS_KEY, memberships);
+        jobMap.put(SetUMembershipsJob.MEMBERSHIPS_BEFORE_KEY, 
Collections.emptyMap());
+        jobMap.put(SetUMembershipsJob.MEMBERSHIPS_AFTER_KEY, memberships);
         schedule(SetUMembershipsJob.class, jobMap);
     }
-
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/4810fb79/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SetUMembershipsJob.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SetUMembershipsJob.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SetUMembershipsJob.java
index 8fe4b67..228a61f 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SetUMembershipsJob.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SetUMembershipsJob.java
@@ -18,8 +18,12 @@
  */
 package org.apache.syncope.core.provisioning.java.job;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
 import org.apache.syncope.common.lib.patch.MembershipPatch;
 import org.apache.syncope.common.lib.patch.UserPatch;
 import org.apache.syncope.common.lib.types.PatchOperation;
@@ -40,7 +44,9 @@ public class SetUMembershipsJob extends 
AbstractInterruptableJob {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(SetUMembershipsJob.class);
 
-    public static final String MEMBERSHIPS_KEY = "memberships";
+    public static final String MEMBERSHIPS_BEFORE_KEY = "membershipsBefore";
+
+    public static final String MEMBERSHIPS_AFTER_KEY = "membershipsAfter";
 
     @Autowired
     private UserProvisioningManager userProvisioningManager;
@@ -54,26 +60,64 @@ public class SetUMembershipsJob extends 
AbstractInterruptableJob {
                 @Override
                 public Void exec() {
                     @SuppressWarnings("unchecked")
-                    Map<String, Set<String>> memberships =
-                            (Map<String, Set<String>>) 
context.getMergedJobDataMap().get(MEMBERSHIPS_KEY);
+                    Map<String, Set<String>> membershipsBefore =
+                            (Map<String, Set<String>>) 
context.getMergedJobDataMap().get(MEMBERSHIPS_BEFORE_KEY);
+                    LOG.debug("Memberships before pull (User -> Groups) {}", 
membershipsBefore);
+
+                    @SuppressWarnings("unchecked")
+                    Map<String, Set<String>> membershipsAfter =
+                            (Map<String, Set<String>>) 
context.getMergedJobDataMap().get(MEMBERSHIPS_AFTER_KEY);
+                    LOG.debug("Memberships after pull (User -> Groups) {}", 
membershipsAfter);
 
-                    LOG.debug("About to set memberships (User -> Groups) {}", 
memberships);
+                    List<UserPatch> patches = new ArrayList<>();
 
-                    for (Map.Entry<String, Set<String>> membership : 
memberships.entrySet()) {
+                    for (Map.Entry<String, Set<String>> membership : 
membershipsAfter.entrySet()) {
                         UserPatch userPatch = new UserPatch();
                         userPatch.setKey(membership.getKey());
+                        patches.add(userPatch);
 
-                        for (String groupKey : membership.getValue()) {
-                            userPatch.getMemberships().add(
-                                    new MembershipPatch.Builder().
-                                            
operation(PatchOperation.ADD_REPLACE).
-                                            group(groupKey).
-                                            build());
+                        for (String group : membership.getValue()) {
+                            Set<String> before = 
membershipsBefore.get(membership.getKey());
+                            if (before == null || !before.contains(group)) {
+                                userPatch.getMemberships().add(
+                                        new MembershipPatch.Builder().
+                                                
operation(PatchOperation.ADD_REPLACE).
+                                                group(group).
+                                                build());
+                            }
                         }
+                    }
+
+                    for (final Map.Entry<String, Set<String>> membership : 
membershipsBefore.entrySet()) {
+                        UserPatch userPatch = IterableUtils.find(patches, new 
Predicate<UserPatch>() {
 
-                        if (!userPatch.isEmpty()) {
-                            LOG.debug("About to update User {}", 
userPatch.getKey());
-                            userProvisioningManager.update(userPatch, true);
+                            @Override
+                            public boolean evaluate(final UserPatch patch) {
+                                return 
membership.getKey().equals(patch.getKey());
+                            }
+                        });
+                        if (userPatch == null) {
+                            userPatch = new UserPatch();
+                            userPatch.setKey(membership.getKey());
+                            patches.add(userPatch);
+                        }
+
+                        for (String group : membership.getValue()) {
+                            Set<String> after = 
membershipsAfter.get(membership.getKey());
+                            if (after == null || !after.contains(group)) {
+                                userPatch.getMemberships().add(
+                                        new MembershipPatch.Builder().
+                                                
operation(PatchOperation.DELETE).
+                                                group(group).
+                                                build());
+                            }
+                        }
+                    }
+
+                    for (UserPatch patch : patches) {
+                        if (!patch.isEmpty()) {
+                            LOG.debug("About to update User {}", patch);
+                            userProvisioningManager.update(patch, true);
                         }
                     }
 
@@ -85,5 +129,4 @@ public class SetUMembershipsJob extends 
AbstractInterruptableJob {
             throw new JobExecutionException("While executing memberships", e);
         }
     }
-
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/4810fb79/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
----------------------------------------------------------------------
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
index 02ac42c..fd262b5 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
@@ -26,6 +26,7 @@ import java.util.Map;
 import java.util.Set;
 import org.apache.commons.collections4.IterableUtils;
 import org.apache.commons.collections4.Predicate;
+import org.apache.syncope.common.lib.patch.AnyPatch;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.types.ConnConfProperty;
@@ -34,7 +35,6 @@ import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
 import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
-import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.identityconnectors.framework.common.objects.Attribute;
 import org.identityconnectors.framework.common.objects.ConnectorObject;
 import org.identityconnectors.framework.common.objects.ObjectClass;
@@ -45,7 +45,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.apache.syncope.core.persistence.api.entity.task.PullTask;
+import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.provisioning.java.job.SetUMembershipsJob;
+import org.springframework.transaction.annotation.Transactional;
 
 /**
  * Simple action for pulling LDAP groups memberships to Syncope group 
memberships, when the same resource is
@@ -61,15 +63,14 @@ public class LDAPMembershipPullActions extends 
SchedulingPullActions {
     protected AnyTypeDAO anyTypeDAO;
 
     @Autowired
-    protected UserDAO userDAO;
-
-    @Autowired
     protected GroupDAO groupDAO;
 
     @Autowired
     private PullUtils pullUtils;
 
-    protected final Map<String, Set<String>> memberships = new HashMap<>();
+    protected final Map<String, Set<String>> membershipsBefore = new 
HashMap<>();
+
+    protected final Map<String, Set<String>> membershipsAfter = new 
HashMap<>();
 
     /**
      * Allows easy subclassing for the ConnId AD connector bundle.
@@ -127,30 +128,39 @@ public class LDAPMembershipPullActions extends 
SchedulingPullActions {
     }
 
     /**
-     * Pull Syncope memberships with the situation read on the external 
resource's group.
+     * Keep track of members of the group being updated before actual update 
takes place.
+     * This is not needed on
+     * <ul>
+     * <li>{@link #beforeProvision} because the pulling group does not exist 
yet on Syncope</li>
+     * <li>{@link #beforeDelete} because group delete cascades as membership 
removal for all users involved</li>
+     * </ul>
      *
-     * @param profile pull profile
-     * @param delta representing the pullong group
-     * @param groupTO group after modification performed by the handler
-     * @throws JobExecutionException if anything goes wrong
+     * {@inheritDoc}
      */
-    protected void populateMemberships(
-            final ProvisioningProfile<?, ?> profile, final SyncDelta delta, 
final GroupTO groupTO)
-            throws JobExecutionException {
+    @Transactional(readOnly = true)
+    @Override
+    public <P extends AnyPatch> void beforeUpdate(
+            final ProvisioningProfile<?, ?> profile,
+            final SyncDelta delta,
+            final EntityTO entity,
+            final P anyPatch) throws JobExecutionException {
 
-        Connector connector = profile.getConnector();
-        for (Object membValue : getMembAttrValues(delta, connector)) {
-            Set<String> memb = memberships.get(membValue.toString());
+        if (!(entity instanceof GroupTO)) {
+            super.beforeUpdate(profile, delta, entity, anyPatch);
+        }
+
+        for (UMembership uMembership : 
groupDAO.findUMemberships(groupDAO.find(entity.getKey()))) {
+            Set<String> memb = 
membershipsBefore.get(uMembership.getLeftEnd().getKey());
             if (memb == null) {
                 memb = new HashSet<>();
-                memberships.put(membValue.toString(), memb);
+                membershipsBefore.put(uMembership.getLeftEnd().getKey(), memb);
             }
-            memb.add(groupTO.getKey());
+            memb.add(entity.getKey());
         }
     }
 
     /**
-     * Pull membership at group pull time (because PullJob first pulls users 
then groups).
+     * Keep track of members of the group being updated after actual update 
took place.
      * {@inheritDoc}
      */
     @Override
@@ -169,29 +179,32 @@ public class LDAPMembershipPullActions extends 
SchedulingPullActions {
                 || 
profile.getTask().getResource().getProvision(anyTypeDAO.findUser()).getMapping()
 == null) {
 
             super.after(profile, delta, entity, result);
-        } else {
-            populateMemberships(profile, delta, (GroupTO) entity);
         }
-    }
 
-    @Override
-    public void afterAll(final ProvisioningProfile<?, ?> profile) throws 
JobExecutionException {
-        Map<String, Set<String>> resolvedMemberships = new HashMap<>();
-        for (Map.Entry<String, Set<String>> entry : 
this.memberships.entrySet()) {
+        for (Object membValue : getMembAttrValues(delta, 
profile.getConnector())) {
             String userKey = pullUtils.match(
                     anyTypeDAO.findUser(),
-                    entry.getKey(),
+                    membValue.toString(),
                     profile.getTask().getResource(),
                     profile.getConnector());
             if (userKey == null) {
-                LOG.warn("Could not find matching user for {}", 
entry.getKey());
+                LOG.warn("Could not find matching user for {}", membValue);
             } else {
-                resolvedMemberships.put(userKey, entry.getValue());
+                Set<String> memb = membershipsAfter.get(userKey);
+                if (memb == null) {
+                    memb = new HashSet<>();
+                    membershipsAfter.put(userKey, memb);
+                }
+                memb.add(entity.getKey());
             }
         }
+    }
 
+    @Override
+    public void afterAll(final ProvisioningProfile<?, ?> profile) throws 
JobExecutionException {
         Map<String, Object> jobMap = new HashMap<>();
-        jobMap.put(SetUMembershipsJob.MEMBERSHIPS_KEY, resolvedMemberships);
+        jobMap.put(SetUMembershipsJob.MEMBERSHIPS_BEFORE_KEY, 
membershipsBefore);
+        jobMap.put(SetUMembershipsJob.MEMBERSHIPS_AFTER_KEY, membershipsAfter);
         schedule(SetUMembershipsJob.class, jobMap);
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/4810fb79/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
----------------------------------------------------------------------
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
index 140e056..5be82dc 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
@@ -393,19 +393,18 @@ public class PullTaskITCase extends AbstractTaskITCase {
         assertEquals(matchingUsers.getResult().iterator().next().getKey(), 
groupTO.getUserOwner());
         assertNull(groupTO.getGroupOwner());
         // SYNCOPE-1343, set value title to null on LDAP
-        ConnObjectTO connObject =
-                resourceService.readConnObject(RESOURCE_NAME_LDAP, 
AnyTypeKind.USER.name(),
-                        matchingUsers.getResult().get(0).getKey());
-        assertNotNull(connObject);
-        assertEquals("odd", connObject.getAttr("title").getValues().get(0));
-        AttrTO userDn = connObject.getAttr(Name.NAME);
+        ConnObjectTO userConnObject = resourceService.readConnObject(
+                RESOURCE_NAME_LDAP, AnyTypeKind.USER.name(), 
matchingUsers.getResult().get(0).getKey());
+        assertNotNull(userConnObject);
+        assertEquals("odd", 
userConnObject.getAttr("title").getValues().get(0));
+        AttrTO userDn = userConnObject.getAttr(Name.NAME);
         updateLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD,
                 userDn.getValues().get(0), Pair.of("title", (String) null));
 
         // SYNCOPE-317
         execProvisioningTask(taskService, TaskType.PULL, 
"1e419ca4-ea81-4493-a14f-28b90113686d", 50, false);
 
-        // 4. verify that LDAP group membership is propagated as Syncope 
membership
+        // 4. verify that LDAP group membership is pulled as Syncope membership
         int i = 0;
         int maxit = 50;
         PagedResult<UserTO> members;
@@ -427,13 +426,43 @@ public class PullTaskITCase extends AbstractTaskITCase {
         }
         assertEquals(1, members.getResult().size());
 
-        // SYNCOPE-1343, verify that the title attribte has been reset
+        // SYNCOPE-1343, verify that the title attribute has been reset
         matchingUsers = userService.search(
                 new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
                         
fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo("pullFromLDAP").
                                 query()).
                         build());
         assertNull(matchingUsers.getResult().get(0).getPlainAttr("title"));
+
+        // SYNCOPE-1356 remove group membership from LDAP, pull and check in 
Syncope
+        ConnObjectTO groupConnObject = resourceService.readConnObject(
+                RESOURCE_NAME_LDAP, AnyTypeKind.GROUP.name(), 
matchingGroups.getResult().get(0).getKey());
+        assertNotNull(groupConnObject);
+        AttrTO groupDn = groupConnObject.getAttr(Name.NAME);
+        updateLdapRemoteObject(RESOURCE_LDAP_ADMIN_DN, RESOURCE_LDAP_ADMIN_PWD,
+                groupDn.getValues().get(0), Pair.of("uniquemember", 
"uid=admin,ou=system"));
+
+        execProvisioningTask(taskService, TaskType.PULL, 
"1e419ca4-ea81-4493-a14f-28b90113686d", 50, false);
+
+        i = 0;
+        maxit = 50;
+        do {
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+            }
+
+            members = userService.search(new 
AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
+                    
fiql(SyncopeClient.getUserSearchConditionBuilder().inGroups(groupTO.getKey()).query()).
+                    build());
+            assertNotNull(members);
+
+            i++;
+        } while (!members.getResult().isEmpty() && i < maxit);
+        if (i == maxit) {
+            fail("Timeout while checking for memberships of " + 
groupTO.getName());
+        }
+        assertEquals(0, members.getResult().size());
     }
 
     @Test

Reply via email to