Author: ilgrosso Date: Mon Dec 24 09:56:52 2012 New Revision: 1425615 URL: http://svn.apache.org/viewvc?rev=1425615&view=rev Log: [SYNCOPE-26] Added sample (but working) LDAPMembershipSyncActions
Added: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/LDAPMembershipSyncActions.java (with props) Modified: syncope/trunk/archetype/src/main/resources/archetype-resources/core/src/test/resources/content.xml syncope/trunk/build-tools/src/main/resources/content.ldif syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/SyncActions.java syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/ConnectorFacadeProxy.java syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/LDAPMembershipPropagationActions.java syncope/trunk/core/src/main/java/org/apache/syncope/core/quartz/SampleJob.java syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/DefaultSyncActions.java syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncJob.java syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncopeSyncResultHandler.java syncope/trunk/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/SyncopeUserQueryImpl.java syncope/trunk/core/src/test/java/org/apache/syncope/core/quartz/TestSyncActions.java syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java syncope/trunk/core/src/test/resources/content.xml Modified: syncope/trunk/archetype/src/main/resources/archetype-resources/core/src/test/resources/content.xml URL: http://svn.apache.org/viewvc/syncope/trunk/archetype/src/main/resources/archetype-resources/core/src/test/resources/content.xml?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/archetype/src/main/resources/archetype-resources/core/src/test/resources/content.xml (original) +++ syncope/trunk/archetype/src/main/resources/archetype-resources/core/src/test/resources/content.xml Mon Dec 24 09:56:52 2012 @@ -542,7 +542,7 @@ under the License. <UMapping id="11" resource_name="resource-ldap" accountlink="'uid=' + username + ',ou=people,o=isp'"/> <UMappingItem id="311" accountid="1" password="0" mapping_id="11" - extAttrName="__NAME__" intAttrName="Username" intMappingType="Username" + extAttrName="__UID__" intAttrName="Username" intMappingType="Username" mandatoryCondition="true"/> <UMappingItem id="312" accountid="0" password="1" mapping_id="11" extAttrName="__PASSWORD__" intAttrName="Password" intMappingType="Password" @@ -551,7 +551,7 @@ under the License. extAttrName="sn" intAttrName="surname" intMappingType="UserSchema" mandatoryCondition="true"/> <UMappingItem id="314" accountid="0" password="0" mapping_id="11" - extAttrName="cn" intAttrName="firstname" intMappingType="UserSchema" + extAttrName="cn" intAttrName="fullname" intMappingType="UserSchema" mandatoryCondition="true"/> <UMappingItem id="315" accountid="0" password="0" mapping_id="11" extAttrName="mail" intAttrName="email" intMappingType="UserSchema" @@ -562,6 +562,9 @@ under the License. <UMappingItem id="317" accountid="0" password="0" mapping_id="11" extAttrName="postalAddress" intAttrName="postalAddress" intMappingType="MembershipSchema" mandatoryCondition="false"/> + <UMappingItem id="318" accountid="0" password="0" mapping_id="11" + extAttrName="mail" intAttrName="userId" intMappingType="UserSchema" + mandatoryCondition="false"/> <RMapping id="1" resource_name="resource-ldap" accountlink="'cn=' + name + ',ou=groups,o=isp'"/> <RMappingItem id="1" accountid="1" password="0" mapping_id="1" @@ -626,8 +629,9 @@ under the License. fullReconciliation="1" performCreate="1" performDelete="1" performUpdate="1" syncStatus="0" jobClassName="org.apache.syncope.core.sync.SyncJob"/> <Task DTYPE="SyncTask" id="11" name="LDAP Sync Task" resource_name="resource-ldap" - fullReconciliation="1" performCreate="1" performDelete="1" performUpdate="1" syncStatus="0" - jobClassName="org.apache.syncope.core.sync.SyncJob"/> + fullReconciliation="1" performCreate="1" performDelete="1" performUpdate="1" syncStatus="0" + actionsClassName="org.apache.syncope.core.sync.LDAPMembershipSyncActions" + jobClassName="org.apache.syncope.core.sync.SyncJob"/> <NotificationTask_recipients notificationtask_id="8" address="recipi...@prova.org"/> Modified: syncope/trunk/build-tools/src/main/resources/content.ldif URL: http://svn.apache.org/viewvc/syncope/trunk/build-tools/src/main/resources/content.ldif?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/build-tools/src/main/resources/content.ldif (original) +++ syncope/trunk/build-tools/src/main/resources/content.ldif Mon Dec 24 09:56:52 2012 @@ -29,3 +29,16 @@ objectClass: groupOfUniqueNames objectClass: top cn: testLDAPGroup uniqueMember: uid=admin,ou=system +uniqueMember: uid=syncFromLDAP,ou=People,o=isp + +DN: uid=syncFromLDAP,ou=People,o=isp +objectClass: organizationalPerson +objectClass: person +objectClass: inetOrgPerson +objectClass: top +cn: Sync from LDAP +description: Active +mail: syncfroml...@syncope.apache.org +sn: Surname +uid: syncFromLDAP +userpassword:: cGFzc3dvcmQxMjM= Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/SyncActions.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/SyncActions.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/SyncActions.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/SyncActions.java Mon Dec 24 09:56:52 2012 @@ -21,6 +21,7 @@ package org.apache.syncope.core.persiste import java.util.List; import org.apache.syncope.client.mod.AbstractAttributableMod; import org.apache.syncope.client.to.AbstractAttributableTO; +import org.apache.syncope.core.sync.SyncopeSyncResultHandler; import org.identityconnectors.framework.common.objects.SyncDelta; import org.quartz.JobExecutionException; @@ -32,62 +33,67 @@ public interface SyncActions { /** * Action to be executed before to start the synchronization task execution. * - * @param task synchronization task to be executed. + * @param handler synchronization handler being executed. * @throws JobExecutionException in case of generic failure. */ - void beforeAll(final SyncTask task) throws JobExecutionException; + void beforeAll(final SyncopeSyncResultHandler handler) throws JobExecutionException; /** * Action to be executed before to create a synchronized user locally. * + * @param handler synchronization handler being executed. * @param delta retrieved synchronization information * @param subject user / role to be created * @return synchronization information used for user status evaluation and to be passed to the 'after' method. * @throws JobExecutionException in case of generic failure */ - <T extends AbstractAttributableTO> SyncDelta beforeCreate(final SyncDelta delta, final T subject) - throws JobExecutionException; + <T extends AbstractAttributableTO> SyncDelta beforeCreate(final SyncopeSyncResultHandler handler, + final SyncDelta delta, final T subject) throws JobExecutionException; /** * Action to be executed before to update a synchronized user locally. * + * @param handler synchronization handler being executed. * @param delta retrieved synchronization information * @param subject local user / role information * @param subjectMod modification * @return synchronization information used for logging and to be passed to the 'after' method. * @throws JobExecutionException in case of generic failure. */ - <T extends AbstractAttributableTO, K extends AbstractAttributableMod> SyncDelta beforeUpdate(final SyncDelta delta, - final T subject, final K subjectMod) throws JobExecutionException; + <T extends AbstractAttributableTO, K extends AbstractAttributableMod> SyncDelta beforeUpdate( + final SyncopeSyncResultHandler handler, final SyncDelta delta, final T subject, final K subjectMod) + throws JobExecutionException; /** * Action to be executed before to delete a synchronized user locally. * + * @param handler synchronization handler being executed. * @param delta retrieved synchronization information * @param subject lcao user / role to be deleted * @return synchronization information used for logging and to be passed to the 'after' method. * @throws JobExecutionException in case of generic failure */ - <T extends AbstractAttributableTO> SyncDelta beforeDelete(final SyncDelta delta, final T subject) - throws JobExecutionException; + <T extends AbstractAttributableTO> SyncDelta beforeDelete(final SyncopeSyncResultHandler handler, + final SyncDelta delta, final T subject) throws JobExecutionException; /** * Action to be executed after each local user synchronization. * + * @param handler synchronization handler being executed. * @param delta retrieved synchronization information (may be modified by 'beforeCreate/beforeUpdate/beforeDelete') * @param subject synchronized local user / role * @param result global synchronization results at the current synchronization step * @throws JobExecutionException in case of generic failure */ - <T extends AbstractAttributableTO> void after(final SyncDelta delta, final T subject, final SyncResult result) - throws JobExecutionException; + <T extends AbstractAttributableTO> void after(final SyncopeSyncResultHandler handler, final SyncDelta delta, + final T subject, final SyncResult result) throws JobExecutionException; /** * Action to be executed after the synchronization task completion. * - * @param task executed synchronization task + * @param handler synchronization handler being executed. * @param results synchronization result * @throws JobExecutionException in case of generic failure */ - void afterAll(final SyncTask task, final List<SyncResult> results) throws JobExecutionException; + void afterAll(final SyncopeSyncResultHandler handler, final List<SyncResult> results) throws JobExecutionException; } Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/ConnectorFacadeProxy.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/ConnectorFacadeProxy.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/ConnectorFacadeProxy.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/ConnectorFacadeProxy.java Mon Dec 24 09:56:52 2012 @@ -20,6 +20,7 @@ package org.apache.syncope.core.propagat import java.io.File; import java.net.URI; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -57,6 +58,7 @@ import org.identityconnectors.framework. import org.identityconnectors.framework.common.objects.SyncResultsHandler; import org.identityconnectors.framework.common.objects.SyncToken; import org.identityconnectors.framework.common.objects.Uid; +import org.identityconnectors.framework.common.objects.filter.Filter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.ClassUtils; @@ -352,6 +354,27 @@ public class ConnectorFacadeProxy { return result; } + public List<ConnectorObject> search(final ObjectClass objectClass, final Filter filter, + final OperationOptions options) { + + final List<ConnectorObject> result = new ArrayList<ConnectorObject>(); + + if (activeConnInstance.getCapabilities().contains(ConnectorCapability.SEARCH)) { + connector.search(objectClass, filter, new ResultsHandler() { + + @Override + public boolean handle(final ConnectorObject obj) { + return result.add(obj); + } + }, options); + } else { + LOG.info("Search was attempted, although the connector only has these capabilities: {}. No action.", + activeConnInstance.getCapabilities()); + } + + return result; + } + /** * Get remote object used by the propagation manager in order to choose for a create (object doesn't exist) or an * update (object exists). Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/LDAPMembershipPropagationActions.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/LDAPMembershipPropagationActions.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/LDAPMembershipPropagationActions.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/LDAPMembershipPropagationActions.java Mon Dec 24 09:56:52 2012 @@ -43,21 +43,27 @@ import org.springframework.transaction.a /** * Simple action for propagating role memberships to LDAP groups, when the same resource is configured for both users * and roles. + * + * @see org.apache.syncope.core.sync.LDAPMembershipSyncActions */ public class LDAPMembershipPropagationActions extends DefaultPropagationActions { - private static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipPropagationActions.class); - - /** - * Allows easy subclassing for the ConnId AD connector bundle. - */ - protected static final String GROUP_MEMBERSHIP_ATTR = "ldapGroups"; + protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipPropagationActions.class); @Autowired - private UserDAO userDAO; + protected UserDAO userDAO; @Autowired - private JexlUtil jexlUtil; + protected JexlUtil jexlUtil; + + /** + * Allows easy subclassing for the ConnId AD connector bundle. + * + * @return the name of the attribute used to keep track of group memberships + */ + protected String getGroupMembershipAttrName() { + return "ldapGroups"; + } @Transactional(readOnly = true) @Override @@ -95,7 +101,7 @@ public class LDAPMembershipPropagationAc if (!roleAccountLinks.isEmpty()) { Set<Attribute> attributes = new HashSet<Attribute>(task.getAttributes()); - attributes.add(AttributeBuilder.build(GROUP_MEMBERSHIP_ATTR, roleAccountLinks)); + attributes.add(AttributeBuilder.build(getGroupMembershipAttrName(), roleAccountLinks)); task.setAttributes(attributes); } } else { Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/quartz/SampleJob.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/quartz/SampleJob.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/quartz/SampleJob.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/quartz/SampleJob.java Mon Dec 24 09:56:52 2012 @@ -18,9 +18,9 @@ */ package org.apache.syncope.core.quartz; -import org.quartz.JobExecutionException; import org.apache.syncope.core.persistence.beans.SchedTask; import org.apache.syncope.core.persistence.beans.TaskExec; +import org.quartz.JobExecutionException; /** * Sample implementation for execution a scheduled task. Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/DefaultSyncActions.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/DefaultSyncActions.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/DefaultSyncActions.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/DefaultSyncActions.java Mon Dec 24 09:56:52 2012 @@ -23,7 +23,6 @@ import org.apache.syncope.client.mod.Abs import org.apache.syncope.client.to.AbstractAttributableTO; import org.apache.syncope.core.persistence.beans.SyncActions; import org.apache.syncope.core.persistence.beans.SyncResult; -import org.apache.syncope.core.persistence.beans.SyncTask; import org.identityconnectors.framework.common.objects.SyncDelta; import org.quartz.JobExecutionException; @@ -33,36 +32,38 @@ import org.quartz.JobExecutionException; public class DefaultSyncActions implements SyncActions { @Override - public void beforeAll(final SyncTask task) throws JobExecutionException { + public void beforeAll(final SyncopeSyncResultHandler handler) throws JobExecutionException { } @Override - public <T extends AbstractAttributableTO> SyncDelta beforeCreate(final SyncDelta delta, final T subject) - throws JobExecutionException { + public <T extends AbstractAttributableTO> SyncDelta beforeCreate(final SyncopeSyncResultHandler handler, + final SyncDelta delta, final T subject) throws JobExecutionException { return delta; } @Override public <T extends AbstractAttributableTO, K extends AbstractAttributableMod> SyncDelta beforeUpdate( - final SyncDelta delta, final T subject, final K subjectMod) throws JobExecutionException { + final SyncopeSyncResultHandler handler, final SyncDelta delta, final T subject, final K subjectMod) + throws JobExecutionException { return delta; } @Override - public <T extends AbstractAttributableTO> SyncDelta beforeDelete(final SyncDelta delta, final T subject) - throws JobExecutionException { + public <T extends AbstractAttributableTO> SyncDelta beforeDelete( + final SyncopeSyncResultHandler handler, final SyncDelta delta, final T subject) throws JobExecutionException { return delta; } @Override - public <T extends AbstractAttributableTO> void after(final SyncDelta delta, final T subject, - final SyncResult result) throws JobExecutionException { + public <T extends AbstractAttributableTO> void after(final SyncopeSyncResultHandler handler, + final SyncDelta delta, final T subject, final SyncResult result) throws JobExecutionException { } @Override - public void afterAll(final SyncTask task, final List<SyncResult> results) throws JobExecutionException { + public void afterAll(final SyncopeSyncResultHandler handler, final List<SyncResult> results) + throws JobExecutionException { } } Added: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/LDAPMembershipSyncActions.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/LDAPMembershipSyncActions.java?rev=1425615&view=auto ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/LDAPMembershipSyncActions.java (added) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/LDAPMembershipSyncActions.java Mon Dec 24 09:56:52 2012 @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.core.sync; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.syncope.client.mod.AbstractAttributableMod; +import org.apache.syncope.client.mod.MembershipMod; +import org.apache.syncope.client.mod.UserMod; +import org.apache.syncope.client.to.AbstractAttributableTO; +import org.apache.syncope.client.to.RoleTO; +import org.apache.syncope.core.notification.NotificationManager; +import org.apache.syncope.core.persistence.beans.ExternalResource; +import org.apache.syncope.core.persistence.beans.PropagationTask; +import org.apache.syncope.core.persistence.beans.SyncResult; +import org.apache.syncope.core.persistence.beans.SyncTask; +import org.apache.syncope.core.persistence.beans.membership.Membership; +import org.apache.syncope.core.persistence.beans.role.SyncopeRole; +import org.apache.syncope.core.persistence.dao.RoleDAO; +import org.apache.syncope.core.propagation.ConnectorFacadeProxy; +import org.apache.syncope.core.propagation.ConnectorFactory; +import org.apache.syncope.core.propagation.PropagationException; +import org.apache.syncope.core.propagation.PropagationManager; +import org.apache.syncope.core.propagation.PropagationTaskExecutor; +import org.apache.syncope.core.util.AttributableUtil; +import org.apache.syncope.core.workflow.WorkflowResult; +import org.apache.syncope.core.workflow.user.UserWorkflowAdapter; +import org.apache.syncope.types.AttributableType; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.Name; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationOptionsBuilder; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.identityconnectors.framework.common.objects.filter.EqualsFilter; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +/** + * Simple action for synchronizing LDAP groups memberships to Syncope role memberships, when the same resource is + * configured for both users and roles. + * + * @see org.apache.syncope.core.propagation.LDAPMembershipPropagationActions + */ +public class LDAPMembershipSyncActions extends DefaultSyncActions { + + protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipSyncActions.class); + + @Autowired + protected RoleDAO roleDAO; + + @Autowired + protected ConnectorFactory connInstanceLoader; + + @Autowired + protected UserWorkflowAdapter uwfAdapter; + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + private PropagationTaskExecutor taskExecutor; + + @Autowired + private NotificationManager notificationManager; + + protected Map<Long, Long> membersBeforeRoleUpdate = Collections.<Long, Long>emptyMap(); + + /** + * Allows easy subclassing for the ConnId AD connector bundle. + * + * @return the name of the attribute used to keep track of group memberships + */ + protected String getGroupMembershipAttrName() { + return "uniquemember"; + } + + /** + * Keep track of members of the role being updated <b>before</b> actual update takes place. This is not needed on + * <ul> <li>beforeCreate() - because the synchronizing role does not exist yet on Syncope</li> <li>beforeDelete() - + * because role delete cascades as membership removal for all users involved</li> </ul> + */ + @Transactional(readOnly = true) + @Override + public <T extends AbstractAttributableTO, K extends AbstractAttributableMod> SyncDelta beforeUpdate( + final SyncopeSyncResultHandler handler, final SyncDelta delta, final T subject, final K subjectMod) + throws JobExecutionException { + + if (subject instanceof RoleTO) { + // search for all users assigned to given role + SyncopeRole role = roleDAO.find(subject.getId()); + if (role != null) { + List<Membership> membs = roleDAO.findMemberships(role); + // save memberships before role update takes place + membersBeforeRoleUpdate = new HashMap<Long, Long>(membs.size()); + for (Membership memb : membs) { + membersBeforeRoleUpdate.put(memb.getSyncopeUser().getId(), memb.getId()); + } + } + } + + return super.beforeUpdate(handler, delta, subject, subjectMod); + } + + /** + * Build UserMod for adding membership to given user, for given role. + * + * @param userId user to be assigned membership to given role + * @param roleTO role for adding membership + * @return UserMod for user update + */ + protected UserMod getUserMod(final Long userId, final RoleTO roleTO) { + UserMod userMod = new UserMod(); + // no actual modification takes place when user has already the role assigned + if (membersBeforeRoleUpdate.containsKey(userId)) { + membersBeforeRoleUpdate.remove(userId); + } else { + userMod.setId(userId); + + MembershipMod membershipMod = new MembershipMod(); + membershipMod.setRole(roleTO.getId()); + userMod.addMembershipToBeAdded(membershipMod); + } + + return userMod; + } + + /** + * Read values of attribute returned by getGroupMembershipAttrName(); if not present in the given delta, perform an + * additioanl read on the underlying connector. + * + * @param delta representing the synchronizing role + * @param connector associated to the current resource + * @return value of attribute returned by getGroupMembershipAttrName() + * @see getGroupMembershipAttrName() + */ + protected List<Object> getMembAttrValues(final SyncDelta delta, final ConnectorFacadeProxy connector) { + List<Object> result = Collections.<Object>emptyList(); + + // first, try to read the configured attribute from delta, returned by the ongoing synchronization + Attribute membAttr = delta.getObject().getAttributeByName(getGroupMembershipAttrName()); + // if not found, perform an additional read on the underlying connector for the same connector object + if (membAttr == null) { + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(getGroupMembershipAttrName()); + membAttr = connector.getObjectAttribute( + ObjectClass.GROUP, delta.getUid(), oob.build(), getGroupMembershipAttrName()); + } + if (membAttr != null && membAttr.getValue() != null) { + result = membAttr.getValue(); + } + + return result; + } + + /** + * Perform actual modifications (i.e. membership add / remove) for the given role on the given resource. + * + * @param userMod modifications to perform on the user + * @param resourceName resource to be propagated for changes + */ + protected void userUpdate(final UserMod userMod, final String resourceName) { + if (userMod.getId() == 0) { + return; + } + + try { + WorkflowResult<Map.Entry<Long, Boolean>> updated = uwfAdapter.update(userMod); + + List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds(updated, + userMod.getPassword(), userMod.getVirtualAttributesToBeRemoved(), + userMod.getVirtualAttributesToBeUpdated(), + Collections.singleton(resourceName)); + + taskExecutor.execute(tasks); + + notificationManager.createTasks(updated.getResult().getKey(), updated.getPerformedTasks()); + } catch (PropagationException e) { + LOG.error("Could not propagate {}", userMod, e); + } catch (Exception e) { + LOG.error("Could not perform update {}", userMod, e); + } + } + + /** + * Synchronize Syncope memberships with the situation read on the external resource's group. + * + * @param handler syncope sync result handler + * @param delta representing the synchronizing role + * @param roleTO role after modification performed by the handler + * @throws JobExecutionException if anything goes wrong + */ + protected void synchronizeMemberships(final SyncopeSyncResultHandler handler, final SyncDelta delta, + final RoleTO roleTO) throws JobExecutionException { + + final SyncTask task = handler.getSyncTask(); + final ExternalResource resource = task.getResource(); + + ConnectorFacadeProxy connector; + try { + connector = connInstanceLoader.getConnector(resource); + } catch (Exception e) { + final String msg = String.format("Connector instance bean for resource %s and connInstance %s not found", + resource, resource.getConnector()); + + throw new JobExecutionException(msg, e); + } + + for (Object membValue : getMembAttrValues(delta, connector)) { + + final List<ConnectorObject> found = connector.search(ObjectClass.ACCOUNT, + new EqualsFilter(new Name(membValue.toString())), + connector.getOperationOptions(resource.getUmapping().getItems())); + + if (found.isEmpty()) { + LOG.debug("No account found on {} with __NAME__ {}", resource, membValue.toString()); + } else { + if (found.size() > 1) { + LOG.warn("More than one account found on {} with __NAME__ {} - taking first only", + resource, membValue.toString()); + } + + ConnectorObject externalAccount = found.iterator().next(); + final List<Long> userIds = handler.findExisting(externalAccount.getUid().getUidValue(), + externalAccount, AttributableUtil.getInstance(AttributableType.USER)); + if (userIds.isEmpty()) { + LOG.debug("No matching user found for {}, aborting", externalAccount); + } else { + if (userIds.size() > 1) { + LOG.warn("More than one user found {} - taking first only", userIds); + } + + UserMod userMod = getUserMod(userIds.iterator().next(), roleTO); + userUpdate(userMod, resource.getName()); + } + } + } + + // finally remove any residual membership that was present before role update but not any more + for (Map.Entry<Long, Long> member : membersBeforeRoleUpdate.entrySet()) { + UserMod userMod = new UserMod(); + userMod.setId(member.getKey()); + userMod.addMembershipToBeRemoved(member.getValue()); + userUpdate(userMod, resource.getName()); + } + } + + /** + * Synchronize membership at role synchronization time (because SyncJob first synchronize users then roles). + */ + @Override + public <T extends AbstractAttributableTO> void after(final SyncopeSyncResultHandler handler, final SyncDelta delta, + final T subject, final SyncResult result) throws JobExecutionException { + + if (!(subject instanceof RoleTO) || handler.getSyncTask().getResource().getUmapping() == null) { + super.after(handler, delta, subject, result); + } else { + synchronizeMemberships(handler, delta, (RoleTO) subject); + } + } +} Propchange: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/LDAPMembershipSyncActions.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/LDAPMembershipSyncActions.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL Propchange: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/LDAPMembershipSyncActions.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncJob.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncJob.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncJob.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncJob.java Mon Dec 24 09:56:52 2012 @@ -355,7 +355,7 @@ public class SyncJob extends AbstractTas handler.setResults(results); handler.setSyncTask(syncTask); - actions.beforeAll(syncTask); + actions.beforeAll(handler); try { if (syncTask.isFullReconciliation()) { if (uMapping != null) { @@ -390,7 +390,7 @@ public class SyncJob extends AbstractTas } catch (Exception e) { throw new JobExecutionException("While syncing on connector", e); } - actions.afterAll(syncTask, results); + actions.afterAll(handler, results); final String result = createReport(results, syncTask.getResource().getSyncTraceLevel(), dryRun); Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncopeSyncResultHandler.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncopeSyncResultHandler.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncopeSyncResultHandler.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/SyncopeSyncResultHandler.java Mon Dec 24 09:56:52 2012 @@ -70,6 +70,7 @@ import org.apache.syncope.types.Resource import org.apache.syncope.types.SyncPolicySpec; import org.identityconnectors.framework.common.objects.Attribute; import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.ConnectorObject; import org.identityconnectors.framework.common.objects.OperationalAttributes; import org.identityconnectors.framework.common.objects.SyncDelta; import org.identityconnectors.framework.common.objects.SyncDeltaType; @@ -177,22 +178,42 @@ public class SyncopeSyncResultHandler im private boolean dryRun; + public SyncActions getActions() { + return actions; + } + public void setActions(final SyncActions actions) { this.actions = actions; } + public Collection<SyncResult> getResults() { + return results; + } + public void setResults(final Collection<SyncResult> results) { this.results = results; } + public SyncTask getSyncTask() { + return syncTask; + } + public void setSyncTask(final SyncTask syncTask) { this.syncTask = syncTask; } + public ConflictResolutionAction getResAct() { + return resAct; + } + public void setResAct(final ConflictResolutionAction resAct) { this.resAct = resAct; } + public boolean isDryRun() { + return dryRun; + } + public void setDryRun(final boolean dryRun) { this.dryRun = dryRun; } @@ -283,7 +304,7 @@ public class SyncopeSyncResultHandler im return result; } - private List<Long> findByAttributableSearch(final SyncDelta delta, final SyncPolicySpec policySpec, + private List<Long> findByAttributableSearch(final ConnectorObject connObj, final SyncPolicySpec policySpec, final AttributableUtil attrUtil) { final List<Long> result = new ArrayList<Long>(); @@ -293,7 +314,7 @@ public class SyncopeSyncResultHandler im final Map<String, Attribute> extValues = new HashMap<String, Attribute>(); for (AbstractMappingItem item : attrUtil.getMappingItems(syncTask.getResource())) { - extValues.put(item.getIntAttrName(), delta.getObject().getAttributeByName(item.getExtAttrName())); + extValues.put(item.getIntAttrName(), connObj.getAttributeByName(item.getExtAttrName())); } // search for user/role by attribute(s) specified in the policy @@ -352,15 +373,12 @@ public class SyncopeSyncResultHandler im /** * Find users / roles based on mapped uid value (or previous uid value, if updated). * - * @param delta sync delta + * @param uid for finding by account id + * @param connObj for finding by attribute value * @param attrUtil attributable util * @return list of matching users / roles */ - protected List<Long> findExisting(final SyncDelta delta, final AttributableUtil attrUtil) { - final String uid = delta.getPreviousUid() == null - ? delta.getUid().getUidValue() - : delta.getPreviousUid().getUidValue(); - + public List<Long> findExisting(final String uid, final ConnectorObject connObj, final AttributableUtil attrUtil) { SyncPolicySpec policySpec = null; if (syncTask.getResource().getSyncPolicy() != null) { policySpec = (SyncPolicySpec) syncTask.getResource().getSyncPolicy().getSpecification(); @@ -368,7 +386,7 @@ public class SyncopeSyncResultHandler im return policySpec == null || attrUtil.getAltSearchSchemas(policySpec).isEmpty() ? findByAccountIdItem(uid, attrUtil) - : findByAttributableSearch(delta, policySpec, attrUtil); + : findByAttributableSearch(connObj, policySpec, attrUtil); } protected List<SyncResult> create(SyncDelta delta, final AttributableUtil attrUtil, @@ -385,7 +403,7 @@ public class SyncopeSyncResultHandler im AbstractAttributableTO subjectTO = connObjectUtil.getAttributableTO(delta.getObject(), syncTask, attrUtil); - delta = actions.beforeCreate(delta, subjectTO); + delta = actions.beforeCreate(this, delta, subjectTO); if (dryRun) { result.setId(0L); @@ -458,7 +476,7 @@ public class SyncopeSyncResultHandler im } } - actions.after(delta, subjectTO, result); + actions.after(this, delta, subjectTO, result); return Collections.singletonList(result); } @@ -486,7 +504,7 @@ public class SyncopeSyncResultHandler im try { final AbstractAttributableMod mod = connObjectUtil.getAttributableMod( id, delta.getObject(), subjectTO, syncTask, attrUtil); - delta = actions.beforeUpdate(delta, subjectTO, mod); + delta = actions.beforeUpdate(this, delta, subjectTO, mod); result.setStatus(SyncResult.Status.SUCCESS); result.setId(mod.getId()); @@ -532,7 +550,7 @@ public class SyncopeSyncResultHandler im LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e); } - actions.after(delta, subjectTO, result); + actions.after(this, delta, subjectTO, result); updResults.add(result); } catch (NotFoundException e) { LOG.error("Could not find {} {}", attrUtil.getType(), id, e); @@ -561,7 +579,7 @@ public class SyncopeSyncResultHandler im AbstractAttributableTO subjectTO = AttributableType.USER == attrUtil.getType() ? userDataBinder.getUserTO(id) : roleDataBinder.getRoleTO(id); - delta = actions.beforeDelete(delta, subjectTO); + delta = actions.beforeDelete(this, delta, subjectTO); final SyncResult result = new SyncResult(); result.setId(id); @@ -604,7 +622,7 @@ public class SyncopeSyncResultHandler im } } - actions.after(delta, subjectTO, result); + actions.after(this, delta, subjectTO, result); delResults.add(result); } catch (NotFoundException e) { LOG.error("Could not find {} {}", attrUtil.getType(), id, e); @@ -631,7 +649,10 @@ public class SyncopeSyncResultHandler im AttributableUtil attrUtil = AttributableUtil.getInstance(delta.getObject().getObjectClass()); - final List<Long> subjects = findExisting(delta, attrUtil); + final String uid = delta.getPreviousUid() == null + ? delta.getUid().getUidValue() + : delta.getPreviousUid().getUidValue(); + final List<Long> subjects = findExisting(uid, delta.getObject(), attrUtil); if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) { if (subjects.isEmpty()) { Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/SyncopeUserQueryImpl.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/SyncopeUserQueryImpl.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/SyncopeUserQueryImpl.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/workflow/user/activiti/SyncopeUserQueryImpl.java Mon Dec 24 09:56:52 2012 @@ -48,7 +48,6 @@ public class SyncopeUserQueryImpl implem private List<User> result; public SyncopeUserQueryImpl(final UserDAO userDAO, final RoleDAO roleDAO, final EntitlementDAO entitlementDAO) { - this.userDAO = userDAO; this.roleDAO = roleDAO; this.entitlementDAO = entitlementDAO; @@ -133,20 +132,22 @@ public class SyncopeUserQueryImpl implem return new UserEntity(syncopeUser.getUsername()); } - private void execute(int page, int itemsPerPage) { + private void execute(final int page, final int itemsPerPage) { if (username != null) { SyncopeUser user = userDAO.find(username); - if (user != null) { + if (user == null) { + result = Collections.<User>emptyList(); + } else { if (memberOf == null || user.getRoleIds().contains(memberOf)) { result = Collections.singletonList(fromSyncopeUser(user)); } - } else { - result = Collections.emptyList(); } } if (memberOf != null) { SyncopeRole role = roleDAO.find(memberOf); - if (role != null) { + if (role == null) { + result = Collections.<User>emptyList(); + } else { result = new ArrayList<User>(); List<Membership> memberships = roleDAO.findMemberships(role); User user; @@ -156,8 +157,6 @@ public class SyncopeUserQueryImpl implem result.add(user); } } - } else { - result = Collections.emptyList(); } } // THIS CAN BE *VERY* DANGEROUS @@ -210,7 +209,7 @@ public class SyncopeUserQueryImpl implem } @Override - public UserQuery potentialStarter(String string) { + public UserQuery potentialStarter(final String string) { throw new UnsupportedOperationException(); } } Modified: syncope/trunk/core/src/test/java/org/apache/syncope/core/quartz/TestSyncActions.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/test/java/org/apache/syncope/core/quartz/TestSyncActions.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/test/java/org/apache/syncope/core/quartz/TestSyncActions.java (original) +++ syncope/trunk/core/src/test/java/org/apache/syncope/core/quartz/TestSyncActions.java Mon Dec 24 09:56:52 2012 @@ -24,6 +24,7 @@ import org.apache.syncope.client.mod.Att import org.apache.syncope.client.to.AbstractAttributableTO; import org.apache.syncope.client.to.AttributeTO; import org.apache.syncope.core.sync.DefaultSyncActions; +import org.apache.syncope.core.sync.SyncopeSyncResultHandler; import org.identityconnectors.framework.common.objects.SyncDelta; import org.quartz.JobExecutionException; @@ -32,8 +33,8 @@ public class TestSyncActions extends Def private int counter = 0; @Override - public <T extends AbstractAttributableTO> SyncDelta beforeCreate(final SyncDelta delta, final T subject) - throws JobExecutionException { + public <T extends AbstractAttributableTO> SyncDelta beforeCreate(final SyncopeSyncResultHandler handler, + final SyncDelta delta, final T subject) throws JobExecutionException { AttributeTO attrTO = null; for (int i = 0; i < subject.getAttributes().size(); i++) { @@ -53,7 +54,8 @@ public class TestSyncActions extends Def @Override public <T extends AbstractAttributableTO, K extends AbstractAttributableMod> SyncDelta beforeUpdate( - final SyncDelta delta, final T subject, final K subjectMod) throws JobExecutionException { + final SyncopeSyncResultHandler handler, final SyncDelta delta, final T subject, final K subjectMod) + throws JobExecutionException { subjectMod.addAttributeToBeRemoved("fullname"); Modified: syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java (original) +++ syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/TaskTestITCase.java Mon Dec 24 09:56:52 2012 @@ -362,10 +362,12 @@ public class TaskTestITCase extends Abst TaskExecTO execution = execTask(SyncTaskTO.class, actual.getId(), 20, false); + // 1. verify execution status final String status = execution.getStatus(); assertNotNull(status); assertTrue(PropagationTaskExecStatus.valueOf(status).isSuccessful()); + // 2. verify that synchronized role is found, with expected attributes final AttributableCond rolenameLeafCond = new AttributableCond(AttributableCond.Type.EQ); rolenameLeafCond.setSchema("name"); rolenameLeafCond.setExpression("testLDAPGroup"); @@ -379,6 +381,14 @@ public class TaskTestITCase extends Abst assertEquals("testLDAPGroup", roleTO.getName()); assertEquals(8L, roleTO.getParent()); assertEquals("true", roleTO.getAttributeMap().get("show").getValues().get(0)); + + // 3. verify that LDAP group membership is propagated as Syncope role membership + final MembershipCond membershipCond = new MembershipCond(); + membershipCond.setRoleId(roleTO.getId()); + final List<UserTO> members = Arrays.asList(restTemplate.postForObject(BASE_URL + "user/search", + NodeCond.getLeafCond(membershipCond), UserTO[].class)); + assertNotNull(members); + assertEquals(1, members.size()); } @Test Modified: syncope/trunk/core/src/test/resources/content.xml URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/test/resources/content.xml?rev=1425615&r1=1425614&r2=1425615&view=diff ============================================================================== --- syncope/trunk/core/src/test/resources/content.xml (original) +++ syncope/trunk/core/src/test/resources/content.xml Mon Dec 24 09:56:52 2012 @@ -542,7 +542,7 @@ under the License. <UMapping id="11" resource_name="resource-ldap" accountlink="'uid=' + username + ',ou=people,o=isp'"/> <UMappingItem id="311" accountid="1" password="0" mapping_id="11" - extAttrName="__NAME__" intAttrName="Username" intMappingType="Username" + extAttrName="__UID__" intAttrName="Username" intMappingType="Username" mandatoryCondition="true"/> <UMappingItem id="312" accountid="0" password="1" mapping_id="11" extAttrName="__PASSWORD__" intAttrName="Password" intMappingType="Password" @@ -551,7 +551,7 @@ under the License. extAttrName="sn" intAttrName="surname" intMappingType="UserSchema" mandatoryCondition="true"/> <UMappingItem id="314" accountid="0" password="0" mapping_id="11" - extAttrName="cn" intAttrName="firstname" intMappingType="UserSchema" + extAttrName="cn" intAttrName="fullname" intMappingType="UserSchema" mandatoryCondition="true"/> <UMappingItem id="315" accountid="0" password="0" mapping_id="11" extAttrName="mail" intAttrName="email" intMappingType="UserSchema" @@ -562,6 +562,9 @@ under the License. <UMappingItem id="317" accountid="0" password="0" mapping_id="11" extAttrName="postalAddress" intAttrName="postalAddress" intMappingType="MembershipSchema" mandatoryCondition="false"/> + <UMappingItem id="318" accountid="0" password="0" mapping_id="11" + extAttrName="mail" intAttrName="userId" intMappingType="UserSchema" + mandatoryCondition="false"/> <RMapping id="1" resource_name="resource-ldap" accountlink="'cn=' + name + ',ou=groups,o=isp'"/> <RMappingItem id="1" accountid="1" password="0" mapping_id="1" @@ -626,8 +629,9 @@ under the License. fullReconciliation="1" performCreate="1" performDelete="1" performUpdate="1" syncStatus="0" jobClassName="org.apache.syncope.core.sync.SyncJob"/> <Task DTYPE="SyncTask" id="11" name="LDAP Sync Task" resource_name="resource-ldap" - fullReconciliation="1" performCreate="1" performDelete="1" performUpdate="1" syncStatus="0" - jobClassName="org.apache.syncope.core.sync.SyncJob"/> + fullReconciliation="1" performCreate="1" performDelete="1" performUpdate="1" syncStatus="0" + actionsClassName="org.apache.syncope.core.sync.LDAPMembershipSyncActions" + jobClassName="org.apache.syncope.core.sync.SyncJob"/> <NotificationTask_recipients notificationtask_id="8" address="recipi...@prova.org"/>