Author: andreapatricelli Date: Tue Jun 10 09:45:54 2014 New Revision: 1601588
URL: http://svn.apache.org/r1601588 Log: [SYNCOPE-458] improved membership virtual attribute management Modified: syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java Modified: syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java URL: http://svn.apache.org/viewvc/syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java (original) +++ syncope/trunk/console/src/main/java/org/apache/syncope/console/rest/SchemaRestClient.java Tue Jun 10 09:45:54 2014 @@ -140,16 +140,16 @@ public class SchemaRestClient extends Ba userDerSchemasNames.add(schemaTO.getName()); } } catch (SyncopeClientException e) { - LOG.error("While getting all user derived schema names", e); + LOG.error("While getting all {} derived schema names", type, e); } return userDerSchemasNames; } /** - * Get derived schemas. + * Get virtual schemas. * - * @return List of derived schemas. + * @return List of virtual schemas. */ @SuppressWarnings("unchecked") public List<VirSchemaTO> getVirSchemas(final AttributableType type) { @@ -158,7 +158,7 @@ public class SchemaRestClient extends Ba try { userVirSchemas = getService(SchemaService.class).list(type, SchemaType.VIRTUAL); } catch (SyncopeClientException e) { - LOG.error("While getting all user derived schemas", e); + LOG.error("While getting all {} virtual schemas", type, e); } return userVirSchemas; Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/connid/ConnObjectUtil.java Tue Jun 10 09:45:54 2014 @@ -550,7 +550,15 @@ public class ConnObjectUtil { final VirAttrCacheValue toBeCached = new VirAttrCacheValue(); - for (ExternalResource resource : getTargetResource(virAttr, type, attrUtil)) { + // SYNCOPE-458 if virattr owner is a Membership, owner must become user involved in membership because + // membership mapping is contained in user mapping + final AbstractAttributable realOwner = owner instanceof Membership ? ((Membership) owner).getSyncopeUser() + : owner; + + final Set<ExternalResource> targetResources = owner instanceof Membership ? getTargetResource(virAttr, type, + attrUtil, realOwner.getResources()) : getTargetResource(virAttr, type, attrUtil); + + for (ExternalResource resource : targetResources) { LOG.debug("Search values into {}", resource.getName()); try { final List<AbstractMappingItem> mappings = attrUtil.getMappingItems(resource, MappingPurpose.BOTH); @@ -564,7 +572,7 @@ public class ConnObjectUtil { final String accountId = attrUtil.getAccountIdItem(resource) == null ? null : MappingUtil.getAccountIdValue( - owner, resource, attrUtil.getAccountIdItem(resource)); + realOwner, resource, attrUtil.getAccountIdItem(resource)); if (StringUtils.isBlank(accountId)) { throw new IllegalArgumentException("No AccountId found for " + resource.getName()); @@ -575,7 +583,7 @@ public class ConnObjectUtil { final OperationOptions oo = connector.getOperationOptions(MappingUtil.getMatchingMappingItems(mappings, type)); - connectorObject = connector.getObject(fromAttributable(owner), new Uid(accountId), oo); + connectorObject = connector.getObject(fromAttributable(realOwner), new Uid(accountId), oo); externalResources.put(resource.getName(), connectorObject); } @@ -617,9 +625,9 @@ public class ConnObjectUtil { } } - virAttrCache.put(attrUtil.getType(), owner.getId(), schemaName, toBeCached); + virAttrCache.put(attrUtil.getType(), owner.getId(), schemaName, toBeCached); + } } - } private Set<ExternalResource> getTargetResource( final AbstractVirAttr attr, final IntMappingType type, final AttributableUtil attrUtil) { @@ -638,6 +646,23 @@ public class ConnObjectUtil { return resources; } + private Set<ExternalResource> getTargetResource(final AbstractVirAttr attr, final IntMappingType type, + final AttributableUtil attrUtil, final Set<ExternalResource> ownerResources) { + + final Set<ExternalResource> resources = new HashSet<ExternalResource>(); + + for (ExternalResource res : ownerResources) { + if (!MappingUtil.getMatchingMappingItems( + attrUtil.getMappingItems(res, MappingPurpose.BOTH), + attr.getSchema().getName(), type).isEmpty()) { + + resources.add(res); + } + } + + return resources; + } + private void fillFromTemplate(final AbstractAttributableTO attributableTO, final AbstractAttributableTO template) { Map<String, AttributeTO> currentAttrMap = attributableTO.getAttrMap(); for (AttributeTO templateAttr : template.getAttrs()) { @@ -657,7 +682,7 @@ public class ConnObjectUtil { } currentAttrMap = attributableTO.getVirAttrMap(); - for (AttributeTO templateVirAttr : template.getDerAttrs()) { + for (AttributeTO templateVirAttr : template.getVirAttrs()) { if (templateVirAttr.getValues() != null && !templateVirAttr.getValues().isEmpty() && (!currentAttrMap.containsKey(templateVirAttr.getSchema()) || currentAttrMap.get(templateVirAttr.getSchema()).getValues().isEmpty())) { Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/persistence/beans/membership/MVirAttr.java Tue Jun 10 09:45:54 2014 @@ -18,8 +18,6 @@ */ package org.apache.syncope.core.persistence.beans.membership; -import java.util.Collections; -import java.util.List; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; @@ -69,24 +67,4 @@ public class MVirAttr extends AbstractVi public <T extends AbstractVirSchema> T getSchema() { return template == null ? null : (T) template.getSchema(); } - - @Override - public List<String> getValues() { - return Collections.emptyList(); - } - - @Override - public boolean addValue(final String value) { - return false; - } - - @Override - public boolean removeValue(final String value) { - return false; - } - - @Override - public void setValues(final List<String> values) { - // do nothing - } } Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/propagation/impl/PropagationManager.java Tue Jun 10 09:45:54 2014 @@ -27,8 +27,10 @@ import java.util.List; import java.util.Map; import java.util.Set; import org.apache.syncope.common.mod.AttributeMod; +import org.apache.syncope.common.mod.MembershipMod; import org.apache.syncope.common.mod.UserMod; import org.apache.syncope.common.to.AttributeTO; +import org.apache.syncope.common.to.MembershipTO; import org.apache.syncope.common.types.AttributableType; import org.apache.syncope.common.types.MappingPurpose; import org.apache.syncope.common.types.ResourceOperation; @@ -38,6 +40,7 @@ import org.apache.syncope.core.persisten import org.apache.syncope.core.persistence.beans.AbstractVirAttr; import org.apache.syncope.core.persistence.beans.ExternalResource; import org.apache.syncope.core.persistence.beans.PropagationTask; +import org.apache.syncope.core.persistence.beans.membership.Membership; import org.apache.syncope.core.persistence.beans.role.SyncopeRole; import org.apache.syncope.core.persistence.beans.user.SyncopeUser; import org.apache.syncope.core.persistence.dao.NotFoundException; @@ -101,15 +104,16 @@ public class PropagationManager { * @param wfResult user to be propagated (and info associated), as per result from workflow * @param password to be set * @param vAttrs virtual attributes to be set + * @param membershipTOs user memberships * @return list of propagation tasks * @throws NotFoundException if user is not found * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user */ public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult, - final String password, final List<AttributeTO> vAttrs) + final String password, final List<AttributeTO> vAttrs, final List<MembershipTO> membershipTOs) throws NotFoundException, UnauthorizedRoleException { - return getUserCreateTaskIds(wfResult, password, vAttrs, null); + return getUserCreateTaskIds(wfResult, password, vAttrs, null, membershipTOs); } /** @@ -119,17 +123,30 @@ public class PropagationManager { * @param password to be set * @param vAttrs virtual attributes to be set * @param noPropResourceNames external resources not to be considered for propagation + * @param membershipTOs user memberships * @return list of propagation tasks * @throws NotFoundException if user is not found * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given user */ public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult, - final String password, final Collection<AttributeTO> vAttrs, final Set<String> noPropResourceNames) + final String password, final Collection<AttributeTO> vAttrs, + final Set<String> noPropResourceNames, final List<MembershipTO> membershipTOs) throws NotFoundException, UnauthorizedRoleException { SyncopeUser user = userDataBinder.getUserFromId(wfResult.getResult().getKey()); if (vAttrs != null && !vAttrs.isEmpty()) { userDataBinder.fillVirtual(user, vAttrs, AttributableUtil.getInstance(AttributableType.USER)); + + } + for (Membership membership : user.getMemberships()) { + MembershipTO membershipTO; + if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) { + membershipTO = findMembershipTO(membership, membershipTOs); + if (membershipTO != null) { + userDataBinder.fillVirtual(membership, membershipTO.getVirAttrs(), AttributableUtil.getInstance( + AttributableType.MEMBERSHIP)); + } + } } return getCreateTaskIds(user, password, wfResult.getResult().getValue(), wfResult.getPropByRes(), noPropResourceNames); @@ -185,7 +202,7 @@ public class PropagationManager { propByRes.get(ResourceOperation.CREATE).removeAll(noPropResourceNames); } - return createTasks(attributable, password, true, null, null, enable, false, propByRes); + return createTasks(attributable, password, true, null, null, null, null, enable, false, propByRes); } /** @@ -209,7 +226,8 @@ public class PropagationManager { Collections.<String>emptySet(), // no virtual attributes to be managed Collections.<AttributeMod>emptySet(), // no virtual attributes to be managed null, // no propagation by resources - noPropResourceNames); + noPropResourceNames, + Collections.<MembershipMod>emptySet()); } /** @@ -234,7 +252,8 @@ public class PropagationManager { wfResult.getResult().getKey().getVirAttrsToRemove(), wfResult.getResult().getKey().getVirAttrsToUpdate(), wfResult.getPropByRes(), - noPropResourceNames); + noPropResourceNames, + wfResult.getResult().getKey().getMembershipsToAdd()); } public List<PropagationTask> getUserUpdateTaskIds(final WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult) { @@ -310,13 +329,15 @@ public class PropagationManager { SyncopeRole role = roleDataBinder.getRoleFromId(wfResult.getResult()); return getUpdateTaskIds(role, null, false, null, - vAttrsToBeRemoved, vAttrsToBeUpdated, wfResult.getPropByRes(), noPropResourceNames); + vAttrsToBeRemoved, vAttrsToBeUpdated, wfResult.getPropByRes(), noPropResourceNames, + Collections.<MembershipMod>emptySet()); } protected List<PropagationTask> getUpdateTaskIds(final AbstractAttributable attributable, final String password, final boolean changePwd, final Boolean enable, final Set<String> vAttrsToBeRemoved, final Set<AttributeMod> vAttrsToBeUpdated, - final PropagationByResource propByRes, final Collection<String> noPropResourceNames) + final PropagationByResource propByRes, final Collection<String> noPropResourceNames, + final Set<MembershipMod> membershipsToAdd) throws NotFoundException { AbstractAttributableDataBinder binder = attributable instanceof SyncopeUser @@ -328,6 +349,24 @@ public class PropagationManager { ? Collections.<AttributeMod>emptySet() : vAttrsToBeUpdated, AttributableUtil.getInstance(attributable)); + // SYNCOPE-458 fill membership virtual attributes + if (attributable instanceof SyncopeUser) { + final SyncopeUser user = (SyncopeUser) attributable; + for (Membership membership : user.getMemberships()) { + if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) { + final MembershipMod membershipMod = findMembershipMod(membership, membershipsToAdd); + if (membershipMod != null) { + binder.fillVirtual(membership, membershipMod.getVirAttrsToRemove() == null + ? Collections.<String>emptySet() + : membershipMod.getVirAttrsToRemove(), + membershipMod.getVirAttrsToUpdate() == null ? Collections.<AttributeMod>emptySet() + : membershipMod.getVirAttrsToUpdate(), AttributableUtil.getInstance( + AttributableType.MEMBERSHIP)); + } + } + } + } + if (propByRes == null || propByRes.isEmpty()) { localPropByRes.addAll(ResourceOperation.UPDATE, attributable.getResourceNames()); } else { @@ -346,8 +385,23 @@ public class PropagationManager { } } + // SYNCOPE-458 fill membership virtual attributes to be updated map + Map<String, AttributeMod> membVAttrsToBeUpdatedMap = new HashMap<String, AttributeMod>(); + for (MembershipMod membershipMod : membershipsToAdd) { + for (AttributeMod attrMod : membershipMod.getVirAttrsToUpdate()) { + membVAttrsToBeUpdatedMap.put(attrMod.getSchema(), attrMod); + } + } + + // SYNCOPE-458 fill membership virtual attributes to be removed set + final Set<String> membVAttrsToBeRemoved = new HashSet<String>(); + for (MembershipMod membershipMod : membershipsToAdd) { + membVAttrsToBeRemoved.addAll(membershipMod.getVirAttrsToRemove()); + } + return createTasks(attributable, password, changePwd, - vAttrsToBeRemoved, vAttrsToBeUpdatedMap, enable, false, localPropByRes); + vAttrsToBeRemoved, vAttrsToBeUpdatedMap, membVAttrsToBeRemoved, membVAttrsToBeUpdatedMap, enable, false, + localPropByRes); } /** @@ -429,7 +483,7 @@ public class PropagationManager { */ public List<PropagationTask> getUserDeleteTaskIds(final WorkflowResult<Long> wfResult) { SyncopeUser user = userDataBinder.getUserFromId(wfResult.getResult()); - return createTasks(user, null, false, null, null, false, true, wfResult.getPropByRes()); + return createTasks(user, null, false, null, null, null, null, false, true, wfResult.getPropByRes()); } /** @@ -512,7 +566,7 @@ public class PropagationManager { if (noPropResourceNames != null && !noPropResourceNames.isEmpty()) { propByRes.get(ResourceOperation.DELETE).removeAll(noPropResourceNames); } - return createTasks(attributable, null, false, null, null, false, true, propByRes); + return createTasks(attributable, null, false, null, null, null, null, false, true, propByRes); } /** @@ -524,6 +578,8 @@ public class PropagationManager { * @param changePwd whether password should be included for propagation attributes or not * @param vAttrsToBeRemoved virtual attributes to be removed * @param vAttrsToBeUpdated virtual attributes to be added + * @param membVAttrsToBeRemoved membership virtual attributes to be removed + * @param membVAttrsToBeUpdatedMap membership virtual attributes to be added * @param enable whether user must be enabled or not * @param deleteOnResource whether user / role must be deleted anyway from external resource or not * @param propByRes operation to be performed per resource @@ -532,8 +588,8 @@ public class PropagationManager { protected <T extends AbstractAttributable> List<PropagationTask> createTasks(final T subject, final String password, final boolean changePwd, final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated, - final Boolean enable, final boolean deleteOnResource, - final PropagationByResource propByRes) { + final Set<String> membVAttrsToBeRemoved, final Map<String, AttributeMod> membVAttrsToBeUpdatedMap, + final Boolean enable, final boolean deleteOnResource, final PropagationByResource propByRes) { LOG.debug("Provisioning subject {}:\n{}", subject, propByRes); @@ -583,7 +639,8 @@ public class PropagationManager { task.setOldAccountId(propByRes.getOldAccountId(resource.getName())); Map.Entry<String, Set<Attribute>> preparedAttrs = MappingUtil.prepareAttributes(attrUtil, subject, - password, changePwd, vAttrsToBeRemoved, vAttrsToBeUpdated, enable, resource); + password, changePwd, vAttrsToBeRemoved, vAttrsToBeUpdated, membVAttrsToBeRemoved, + membVAttrsToBeUpdatedMap, enable, resource); task.setAccountId(preparedAttrs.getKey()); // Check if any of mandatory attributes (in the mapping) is missing or not received any value: @@ -621,4 +678,24 @@ public class PropagationManager { return tasks; } + + private MembershipTO findMembershipTO(final Membership membership, final List<MembershipTO> memberships) { + for (MembershipTO membershipTO : memberships) { + if (membershipTO.getRoleId() == membership.getSyncopeRole().getId()) { + return membershipTO; + } + } + LOG.error("No MembershipTO found for membership {}", membership); + return null; + } + + private MembershipMod findMembershipMod(final Membership membership, final Set<MembershipMod> membershipMods) { + for (MembershipMod membershipMod : membershipMods) { + if (membershipMod.getRole() == membership.getSyncopeRole().getId()) { + return membershipMod; + } + } + LOG.error("No MembershipMod found for membership {}", membership); + return null; + } } Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/controller/UserController.java Tue Jun 10 09:45:54 2014 @@ -38,6 +38,7 @@ import org.apache.syncope.common.to.User import org.apache.syncope.common.types.AttributableType; import org.apache.syncope.common.types.ClientExceptionType; import org.apache.syncope.common.SyncopeClientException; +import org.apache.syncope.common.mod.MembershipMod; import org.apache.syncope.core.persistence.beans.PropagationTask; import org.apache.syncope.core.persistence.beans.role.SyncopeRole; import org.apache.syncope.core.persistence.beans.user.SyncopeUser; @@ -207,7 +208,7 @@ public class UserController extends Abst WorkflowResult<Map.Entry<Long, Boolean>> created = uwfAdapter.create(actual); List<PropagationTask> tasks = propagationManager.getUserCreateTaskIds( - created, actual.getPassword(), actual.getVirAttrs()); + created, actual.getPassword(), actual.getVirAttrs(), actual.getMemberships()); PropagationReporter propagationReporter = ApplicationContextProvider.getApplicationContext(). getBean(PropagationReporter.class); try { Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/rest/data/UserDataBinder.java Tue Jun 10 09:45:54 2014 @@ -104,7 +104,7 @@ public class UserDataBinder extends Abst return user; } - + @Transactional(readOnly = true) public Set<String> getResourceNamesForUserId(final Long userId) { return getUserFromId(userId).getResourceNames(); @@ -412,6 +412,9 @@ public class UserDataBinder extends Abst membershipTO.setRoleId(membership.getSyncopeRole().getId()); membershipTO.setRoleName(membership.getSyncopeRole().getName()); + // SYNCOPE-458 retrieve also membership virtual attributes + connObjectUtil.retrieveVirAttrValues(membership, AttributableUtil.getInstance(AttributableType.MEMBERSHIP)); + fillTO(membershipTO, membership.getAttrs(), membership.getDerAttrs(), membership.getVirAttrs(), membership.getResources()); Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopePushResultHandler.java Tue Jun 10 09:45:54 2014 @@ -117,6 +117,8 @@ public class SyncopePushResultHandler ex true, // propagate password (if required) null, // no vir attrs to be removed null, // propagate current vir attr values + null, // no membership vir attrs to be removed + null, // propagate current membership vir attr values enabled, // propagate status (suspended or not) if required getSyncTask().getResource()); // target external resource Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/sync/impl/SyncopeSyncResultHandler.java Tue Jun 10 09:45:54 2014 @@ -362,7 +362,7 @@ public class SyncopeSyncResultHandler ex final List<ConnectorObject> found = connector.search(objectClass, new EqualsFilter(new Name(name)), connector.getOperationOptions( - attrUtil.getMappingItems(syncTask.getResource(), MappingPurpose.SYNCHRONIZATION))); + attrUtil.getMappingItems(syncTask.getResource(), MappingPurpose.SYNCHRONIZATION))); if (found.isEmpty()) { LOG.debug("No {} found on {} with __NAME__ {}", objectClass, syncTask.getResource(), name); @@ -441,7 +441,7 @@ public class SyncopeSyncResultHandler ex List<PropagationTask> tasks = propagationManager.getUserCreateTaskIds(created, ((UserTO) actual).getPassword(), actual.getVirAttrs(), - Collections.singleton(syncTask.getResource().getName())); + Collections.singleton(syncTask.getResource().getName()), ((UserTO) actual).getMemberships()); taskExecutor.execute(tasks); Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/util/AttributableUtil.java Tue Jun 10 09:45:54 2014 @@ -190,17 +190,17 @@ public final class AttributableUtil { if (resource != null) { switch (type) { - case USER: - if (resource.getUmapping() != null) { - result = resource.getUmapping().getAccountIdItem(); - } - break; case ROLE: if (resource.getRmapping() != null) { result = resource.getRmapping().getAccountIdItem(); } break; case MEMBERSHIP: + case USER: + if (resource.getUmapping() != null) { + result = resource.getUmapping().getAccountIdItem(); + } + break; default: } } @@ -215,17 +215,17 @@ public final class AttributableUtil { if (resource != null) { switch (type) { - case USER: - if (resource.getUmapping() != null) { - items = resource.getUmapping().getItems(); - } - break; case ROLE: if (resource.getRmapping() != null) { items = resource.getRmapping().getItems(); } break; case MEMBERSHIP: + case USER: + if (resource.getUmapping() != null) { + items = resource.getUmapping().getItems(); + } + break; default: } } @@ -265,6 +265,7 @@ public final class AttributableUtil { } break; default: + LOG.error("You requested not existing purpose {}", purpose); } return result; Modified: syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java (original) +++ syncope/trunk/core/src/main/java/org/apache/syncope/core/util/MappingUtil.java Tue Jun 10 09:45:54 2014 @@ -49,6 +49,7 @@ import org.apache.syncope.core.persisten import org.apache.syncope.core.persistence.beans.membership.MDerSchema; import org.apache.syncope.core.persistence.beans.membership.MSchema; import org.apache.syncope.core.persistence.beans.membership.MVirSchema; +import org.apache.syncope.core.persistence.beans.membership.Membership; import org.apache.syncope.core.persistence.beans.role.RAttrValue; import org.apache.syncope.core.persistence.beans.role.RDerSchema; import org.apache.syncope.core.persistence.beans.role.RSchema; @@ -130,6 +131,8 @@ public final class MappingUtil { * @param changePwd whether password should be included for propagation attributes or not * @param vAttrsToBeRemoved virtual attributes to be removed * @param vAttrsToBeUpdated virtual attributes to be added + * @param membVAttrsToBeRemoved membership virtual attributes to be removed + * @param membVAttrsToBeUpdated membership virtual attributes to be added * @param enable whether user must be enabled or not * @param resource target resource * @return account link + prepared attributes @@ -137,6 +140,7 @@ public final class MappingUtil { public static <T extends AbstractAttributable> Map.Entry<String, Set<Attribute>> prepareAttributes( final AttributableUtil attrUtil, final T subject, final String password, final boolean changePwd, final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated, + final Set<String> membVAttrsToBeRemoved, final Map<String, AttributeMod> membVAttrsToBeUpdated, final Boolean enable, final ExternalResource resource) { LOG.debug("Preparing resource attributes for {} on resource {} with attributes {}", @@ -162,8 +166,21 @@ public final class MappingUtil { virAttrCache.expire(attrUtil.getType(), subject.getId(), mapping.getIntAttrName()); } + // SYNCOPE-458 expire cache also for membership virtual schemas + if (attrUtil.getType() == AttributableType.USER && mapping.getIntMappingType() + == IntMappingType.MembershipVirtualSchema && (subject instanceof SyncopeUser)) { + final SyncopeUser user = (SyncopeUser) subject; + for (Membership membership : user.getMemberships()) { + LOG.debug("Expire entry cache {}-{} for membership {}", subject.getId(), mapping. + getIntAttrName(), membership); + virAttrCache.expire(AttributableType.MEMBERSHIP, membership.getId(), mapping. + getIntAttrName()); + } + } + Map.Entry<String, Attribute> preparedAttribute = prepareAttribute( - resource, mapping, subject, password, passwordGenerator, vAttrsToBeRemoved, vAttrsToBeUpdated); + resource, mapping, subject, password, passwordGenerator, vAttrsToBeRemoved, vAttrsToBeUpdated, + membVAttrsToBeRemoved, membVAttrsToBeUpdated); if (preparedAttribute != null && preparedAttribute.getKey() != null) { accountId = preparedAttribute.getKey(); @@ -220,7 +237,8 @@ public final class MappingUtil { private static <T extends AbstractAttributable> Map.Entry<String, Attribute> prepareAttribute( final ExternalResource resource, final AbstractMappingItem mapItem, final T subject, final String password, final PasswordGenerator passwordGenerator, - final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated) { + final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated, + final Set<String> membVAttrsToBeRemoved, final Map<String, AttributeMod> membVAttrsToBeUpdated) { final List<AbstractAttributable> attributables = new ArrayList<AbstractAttributable>(); @@ -256,7 +274,8 @@ public final class MappingUtil { } List<AbstractAttrValue> values = getIntValues( - resource, mapItem, attributables, vAttrsToBeRemoved, vAttrsToBeUpdated); + resource, mapItem, attributables, vAttrsToBeRemoved, vAttrsToBeUpdated, membVAttrsToBeRemoved, + membVAttrsToBeUpdated); AbstractNormalSchema schema = null; boolean readOnlyVirSchema = false; @@ -415,7 +434,8 @@ public final class MappingUtil { Map.Entry<String, Attribute> preparedAttr = prepareAttribute( resource, attrUtil.getAccountIdItem(resource), subject, null, null, - Collections.<String>emptySet(), Collections.<String, AttributeMod>emptyMap()); + Collections.<String>emptySet(), Collections.<String, AttributeMod>emptyMap(), Collections. + <String>emptySet(), Collections.<String, AttributeMod>emptyMap()); String accountId = preparedAttr.getKey(); final Name roleOwnerName = evaluateNAME(subject, resource, accountId); @@ -430,11 +450,14 @@ public final class MappingUtil { * @param attributables list of attributables * @param vAttrsToBeRemoved virtual attributes to be removed * @param vAttrsToBeUpdated virtual attributes to be added + * @param membVAttrsToBeRemoved membership virtual attributes to be removed + * @param membVAttrsToBeUpdated membership virtual attributes to be added * @return attribute values. */ public static List<AbstractAttrValue> getIntValues(final ExternalResource resource, final AbstractMappingItem mappingItem, final List<AbstractAttributable> attributables, - final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated) { + final Set<String> vAttrsToBeRemoved, final Map<String, AttributeMod> vAttrsToBeUpdated, + final Set<String> membVAttrsToBeRemoved, final Map<String, AttributeMod> membVAttrsToBeUpdated) { LOG.debug("Get attributes for '{}' and mapping type '{}'", attributables, mappingItem.getIntMappingType()); @@ -465,7 +488,6 @@ public final class MappingUtil { case UserVirtualSchema: case RoleVirtualSchema: - case MembershipVirtualSchema: for (AbstractAttributable attributable : attributables) { AbstractVirAttr virAttr = attributable.getVirAttr(mappingItem.getIntAttrName()); if (virAttr != null) { @@ -489,10 +511,44 @@ public final class MappingUtil { } } - LOG.debug("Retrieved virtual attribute {}" + LOG.debug("Retrieved {} virtual attribute {}" + + "\n* IntAttrName {}" + + "\n* IntMappingType {}" + + "\n* Attribute values {}", + attributable.getClass().getSimpleName(), + virAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values); + } + break; + + case MembershipVirtualSchema: + for (AbstractAttributable attributable : attributables) { + AbstractVirAttr virAttr = attributable.getVirAttr(mappingItem.getIntAttrName()); + if (virAttr != null) { + if (membVAttrsToBeRemoved != null && membVAttrsToBeUpdated != null) { + if (membVAttrsToBeUpdated.containsKey(mappingItem.getIntAttrName())) { + virAttr.setValues( + membVAttrsToBeUpdated.get(mappingItem.getIntAttrName()).getValuesToBeAdded()); + } else if (membVAttrsToBeRemoved.contains(mappingItem.getIntAttrName())) { + virAttr.getValues().clear(); + } else { + throw new IllegalArgumentException("Don't need to update membership virtual attribute '" + + mappingItem.getIntAttrName() + "'"); + } + } + if (virAttr.getValues() != null) { + for (String value : virAttr.getValues()) { + attrValue = new UAttrValue(); + attrValue.setStringValue(value); + values.add(attrValue); + } + } + } + + LOG.debug("Retrieved {} virtual attribute {}" + "\n* IntAttrName {}" + "\n* IntMappingType {}" + "\n* Attribute values {}", + attributable.getClass().getSimpleName(), virAttr, mappingItem.getIntAttrName(), mappingItem.getIntMappingType(), values); } break; @@ -580,6 +636,7 @@ public final class MappingUtil { * Get accountId internal value. * * @param attributable attributable + * @param resource external resource * @param accountIdItem accountid mapping item * @return accountId internal value */ @@ -587,7 +644,7 @@ public final class MappingUtil { final AbstractMappingItem accountIdItem) { List<AbstractAttrValue> values = getIntValues(resource, accountIdItem, - Collections.<AbstractAttributable>singletonList(attributable), null, null); + Collections.<AbstractAttributable>singletonList(attributable), null, null, null, null); return values == null || values.isEmpty() ? null : values.get(0).getValueAsString(); Modified: syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java URL: http://svn.apache.org/viewvc/syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java?rev=1601588&r1=1601587&r2=1601588&view=diff ============================================================================== --- syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java (original) +++ syncope/trunk/core/src/test/java/org/apache/syncope/core/rest/VirAttrTestITCase.java Tue Jun 10 09:45:54 2014 @@ -27,6 +27,7 @@ import static org.junit.Assert.assertTru import org.apache.commons.lang3.SerializationUtils; import java.util.Map; import org.apache.syncope.common.mod.AttributeMod; +import org.apache.syncope.common.mod.MembershipMod; import org.apache.syncope.common.mod.StatusMod; import org.apache.syncope.common.mod.UserMod; import org.apache.syncope.common.services.ResourceService; @@ -555,4 +556,169 @@ public class VirAttrTestITCase extends A userTO = updateUser(userMod); assertNotNull(userTO.getVirAttrMap().get("virtualdata")); } + + @Test + public void issueSYNCOPE458() { + // ------------------------------------------- + // Create a role ad-hoc + // ------------------------------------------- + final String roleName = "issueSYNCOPE458-Role-" + getUUIDString(); + RoleTO roleTO = new RoleTO(); + roleTO.setName(roleName); + roleTO.setParent(2L); + roleTO.setInheritTemplates(true); + roleTO = createRole(roleTO); + // ------------------------------------------- + + // ------------------------------------------- + // Update resource-db-virattr mapping adding new membership virtual schema mapping + // ------------------------------------------- + ResourceTO resourceDBVirAttr = resourceService.read(RESOURCE_NAME_DBVIRATTR); + assertNotNull(resourceDBVirAttr); + + final MappingTO resourceUMapping = resourceDBVirAttr.getUmapping(); + + MappingItemTO item = new MappingItemTO(); + item.setIntAttrName("mvirtualdata"); + item.setIntMappingType(IntMappingType.MembershipVirtualSchema); + item.setExtAttrName("EMAIL"); + item.setPurpose(MappingPurpose.BOTH); + + resourceUMapping.addItem(item); + + resourceDBVirAttr.setUmapping(resourceUMapping); + + resourceService.update(RESOURCE_NAME_DBVIRATTR, resourceDBVirAttr); + // ------------------------------------------- + + // ------------------------------------------- + // Create new user + // ------------------------------------------- + UserTO userTO = getUniqueSampleTO("[email protected]"); + userTO.getResources().clear(); + userTO.getResources().add(RESOURCE_NAME_DBVIRATTR); + userTO.getVirAttrs().clear(); + userTO.getDerAttrs().clear(); + userTO.getMemberships().clear(); + + // add membership, with virtual attribute populated, to user + MembershipTO membership = new MembershipTO(); + membership.setRoleId(roleTO.getId()); + membership.getVirAttrs().add(attributeTO("mvirtualdata", "[email protected]")); + userTO.getMemberships().add(membership); + + //propagate user + userTO = createUser(userTO); + assertEquals(1, userTO.getPropagationStatusTOs().size()); + assertTrue(userTO.getPropagationStatusTOs().get(0).getStatus().isSuccessful()); + // ------------------------------------------- + + // 1. check if membership has virtual attribute populated + assertNotNull(userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata")); + assertEquals("[email protected]", + userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata").getValues().get(0)); + // ------------------------------------------- + + // 2. update membership virtual attribute + MembershipMod membershipMod = new MembershipMod(); + membershipMod.setRole(roleTO.getId()); + membershipMod.getVirAttrsToUpdate().add(attributeMod("mvirtualdata", "[email protected]")); + + UserMod userMod = new UserMod(); + userMod.setId(userTO.getId()); + userMod.getMembershipsToAdd().add(membershipMod); + userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getId()); + + userTO = updateUser(userMod); + assertNotNull(userTO); + // 3. check again after update if membership has virtual attribute populated with new value + assertNotNull(userTO.getMemberships().get(0).getVirAttrMap().get("mvirtualdata")); + assertEquals("[email protected]", userTO.getMemberships().get(0).getVirAttrMap().get( + "mvirtualdata").getValues().get(0)); + + // ---------------------------------------- + // force cache expiring without any modification + // ---------------------------------------- + String jdbcURL = null; + ConnInstanceTO connInstanceBean = connectorService.readByResource(RESOURCE_NAME_DBVIRATTR); + for (ConnConfProperty prop : connInstanceBean.getConfiguration()) { + if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) { + jdbcURL = prop.getValues().iterator().next().toString(); + prop.getValues().clear(); + prop.getValues().add("jdbc:h2:tcp://localhost:9092/xxx"); + } + } + + connectorService.update(connInstanceBean.getId(), connInstanceBean); + + membershipMod = new MembershipMod(); + membershipMod.setRole(roleTO.getId()); + membershipMod.getVirAttrsToUpdate().add(attributeMod("mvirtualdata", "[email protected]")); + + userMod = new UserMod(); + userMod.setId(userTO.getId()); + userMod.getMembershipsToAdd().add(membershipMod); + userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getId()); + + userTO = updateUser(userMod); + assertNotNull(userTO); + // ---------------------------------- + + // change attribute value directly on resource + final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource); + + String value = jdbcTemplate.queryForObject( + "SELECT EMAIL FROM testsync WHERE ID=?", String.class, userTO.getId()); + assertEquals("[email protected]", value); + + jdbcTemplate.update("UPDATE testsync set EMAIL='[email protected]' WHERE ID=?", userTO. + getId()); + + value = jdbcTemplate.queryForObject("SELECT EMAIL FROM testsync WHERE ID=?", String.class, userTO.getId()); + assertEquals("[email protected]", value); + // ---------------------------------------- + + // ---------------------------------------- + // restore connector + // ---------------------------------------- + for (ConnConfProperty prop : connInstanceBean.getConfiguration()) { + if ("jdbcUrlTemplate".equals(prop.getSchema().getName())) { + prop.getValues().clear(); + prop.getValues().add(jdbcURL); + } + } + connectorService.update(connInstanceBean.getId(), connInstanceBean); + // ---------------------------------------- + + userTO = userService.read(userTO.getId()); + assertNotNull(userTO); + // 4. check virtual attribute synchronization after direct update on resource + assertEquals("[email protected]", userTO.getMemberships().get(0).getVirAttrMap().get( + "mvirtualdata").getValues().get(0)); + + // 5. remove membership virtual attribute + membershipMod = new MembershipMod(); + membershipMod.setRole(roleTO.getId()); + membershipMod.getVirAttrsToRemove().add("mvirtualdata"); + + userMod = new UserMod(); + userMod.setId(userTO.getId()); + userMod.getMembershipsToAdd().add(membershipMod); + userMod.getMembershipsToRemove().add(userTO.getMemberships().iterator().next().getId()); + + userTO = updateUser(userMod); + assertNotNull(userTO); + // check again after update if membership hasn't any virtual attribute + assertTrue(userTO.getMemberships().get(0).getVirAttrMap().isEmpty()); + + // ------------------------------------------- + // Delete role ad-hoc and restore resource mapping + // ------------------------------------------- + roleService.delete(roleTO.getId()); + + resourceUMapping.removeItem(item); + resourceDBVirAttr.setUmapping(resourceUMapping); + resourceService.update(RESOURCE_NAME_DBVIRATTR, resourceDBVirAttr); + // ------------------------------------------- + } }
