http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java new file mode 100644 index 0000000..fc1b00c --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPPasswordPropagationActions.java @@ -0,0 +1,125 @@ +/* + * 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.provisioning.java.propagation; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import org.apache.syncope.common.lib.types.AttributableType; +import org.apache.syncope.common.lib.types.CipherAlgorithm; +import org.apache.syncope.common.lib.types.ConnConfProperty; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.ConnInstance; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.identityconnectors.common.security.GuardedString; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeBuilder; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.OperationalAttributes; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.crypto.codec.Hex; +import org.springframework.transaction.annotation.Transactional; + +/** + * Propagate a non-cleartext password out to a resource, if the PropagationManager has not already + * added a password. The CipherAlgorithm associated with the password must match the password + * hash algorithm property of the LDAP Connector. + */ +public class LDAPPasswordPropagationActions extends DefaultPropagationActions { + + private static final String CLEARTEXT = "CLEARTEXT"; + + @Autowired + private UserDAO userDAO; + + @Transactional(readOnly = true) + @Override + public void before(final PropagationTask task, final ConnectorObject beforeObj) { + super.before(task, beforeObj); + + if (AttributableType.USER == task.getSubjectType()) { + User user = userDAO.find(task.getSubjectKey()); + + if (user != null && user.getPassword() != null) { + Attribute missing = AttributeUtil.find( + PropagationTaskExecutor.MANDATORY_MISSING_ATTR_NAME, + task.getAttributes()); + + ConnInstance connInstance = task.getResource().getConnector(); + String cipherAlgorithm = getCipherAlgorithm(connInstance); + if (missing != null && missing.getValue() != null && missing.getValue().size() == 1 + && missing.getValue().get(0).equals(OperationalAttributes.PASSWORD_NAME) + && cipherAlgorithmMatches(getCipherAlgorithm(connInstance), user.getCipherAlgorithm())) { + + String password = user.getPassword().toLowerCase(); + byte[] decodedPassword = Hex.decode(password); + byte[] base64EncodedPassword = Base64.encode(decodedPassword); + + String cipherPlusPassword = + ("{" + cipherAlgorithm.toLowerCase() + "}" + new String(base64EncodedPassword)); + + Attribute passwordAttribute = AttributeBuilder.buildPassword( + new GuardedString(cipherPlusPassword.toCharArray())); + + Set<Attribute> attributes = new HashSet<Attribute>(task.getAttributes()); + attributes.add(passwordAttribute); + attributes.remove(missing); + + task.setAttributes(attributes); + } + } + } + } + + private String getCipherAlgorithm(ConnInstance connInstance) { + String cipherAlgorithm = CLEARTEXT; + for (Iterator<ConnConfProperty> propertyIterator = connInstance.getConfiguration().iterator(); + propertyIterator.hasNext();) { + + ConnConfProperty property = propertyIterator.next(); + if ("passwordHashAlgorithm".equals(property.getSchema().getName()) + && property.getValues() != null && !property.getValues().isEmpty()) { + return (String) property.getValues().get(0); + } + } + return cipherAlgorithm; + } + + private boolean cipherAlgorithmMatches(String connectorAlgorithm, CipherAlgorithm userAlgorithm) { + if (userAlgorithm == null) { + return false; + } + + if (connectorAlgorithm.equals(userAlgorithm.name())) { + return true; + } + + // Special check for "SHA" (user sync'd from LDAP) + if ("SHA".equals(connectorAlgorithm) && "SHA1".equals(userAlgorithm.name())) { + return true; + } + + return false; + } + +}
http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java new file mode 100644 index 0000000..320de88 --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PriorityPropagationTaskExecutor.java @@ -0,0 +1,119 @@ +/* + * 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.provisioning.java.propagation; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditElements.Result; +import org.apache.syncope.common.lib.types.PropagationTaskExecStatus; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.persistence.api.entity.task.TaskExec; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; +import org.springframework.stereotype.Component; + +@Component +public class PriorityPropagationTaskExecutor extends AbstractPropagationTaskExecutor { + + /** + * Sort the given collection by looking at related ExternalResource's priority, then execute. + * {@inheritDoc} + */ + @Override + public void execute(final Collection<PropagationTask> tasks, final PropagationReporter reporter) { + final List<PropagationTask> prioritizedTasks = new ArrayList<>(tasks); + Collections.sort(prioritizedTasks, new PriorityComparator()); + + Result result = Result.SUCCESS; + + try { + for (PropagationTask task : prioritizedTasks) { + LOG.debug("Execution started for {}", task); + + TaskExec execution = execute(task, reporter); + + LOG.debug("Execution finished for {}, {}", task, execution); + + // Propagation is interrupted as soon as the result of the + // communication with a primary resource is in error + PropagationTaskExecStatus execStatus; + try { + execStatus = PropagationTaskExecStatus.valueOf(execution.getStatus()); + } catch (IllegalArgumentException e) { + LOG.error("Unexpected execution status found {}", execution.getStatus()); + execStatus = PropagationTaskExecStatus.FAILURE; + } + if (task.getResource().isPropagationPrimary() && !execStatus.isSuccessful()) { + result = Result.FAILURE; + throw new PropagationException(task.getResource().getKey(), execution.getMessage()); + } + } + } finally { + notificationManager.createTasks( + AuditElements.EventCategoryType.PROPAGATION, + null, + null, + null, + result, + reporter == null ? null : reporter.getStatuses(), + tasks); + + auditManager.audit( + AuditElements.EventCategoryType.PROPAGATION, + null, + null, + null, + result, + reporter == null ? null : reporter.getStatuses(), + tasks); + } + } + + /** + * Compare propagation tasks according to related ExternalResource's priority. + * + * @see PropagationTask + * @see org.apache.syncope.core.persistence.beans.ExternalResource#propagationPriority + */ + protected static class PriorityComparator implements Comparator<PropagationTask>, Serializable { + + private static final long serialVersionUID = -1969355670784448878L; + + @Override + public int compare(final PropagationTask task1, final PropagationTask task2) { + int prop1 = task1.getResource().getPropagationPriority() == null + ? Integer.MIN_VALUE + : task1.getResource().getPropagationPriority(); + int prop2 = task2.getResource().getPropagationPriority() == null + ? Integer.MIN_VALUE + : task2.getResource().getPropagationPriority(); + + return prop1 > prop2 + ? 1 + : prop1 == prop2 + ? 0 + : -1; + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java new file mode 100644 index 0000000..500dfa8 --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/PropagationManagerImpl.java @@ -0,0 +1,772 @@ +/* + * 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.provisioning.java.propagation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.syncope.common.lib.mod.AttrMod; +import org.apache.syncope.common.lib.mod.MembershipMod; +import org.apache.syncope.common.lib.mod.UserMod; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.MembershipTO; +import org.apache.syncope.common.lib.types.AttributableType; +import org.apache.syncope.common.lib.types.MappingPurpose; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; +import org.apache.syncope.core.persistence.api.dao.NotFoundException; +import org.apache.syncope.core.persistence.api.dao.RoleDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.AttributableUtil; +import org.apache.syncope.core.persistence.api.entity.AttributableUtilFactory; +import org.apache.syncope.core.persistence.api.entity.EntityFactory; +import org.apache.syncope.core.persistence.api.entity.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.MappingItem; +import org.apache.syncope.core.persistence.api.entity.Subject; +import org.apache.syncope.core.persistence.api.entity.VirAttr; +import org.apache.syncope.core.persistence.api.entity.membership.Membership; +import org.apache.syncope.core.persistence.api.entity.role.Role; +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.apache.syncope.core.provisioning.java.VirAttrHandler; +import org.apache.syncope.core.misc.security.UnauthorizedRoleException; +import org.apache.syncope.core.misc.ConnObjectUtil; +import org.apache.syncope.core.misc.MappingUtil; +import org.apache.syncope.core.misc.jexl.JexlUtil; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.AttributeBuilder; +import org.identityconnectors.framework.common.objects.AttributeUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +/** + * Manage the data propagation to external resources. + */ +@Transactional(rollbackFor = { Throwable.class }) +public class PropagationManagerImpl implements PropagationManager { + + /** + * Logger. + */ + protected static final Logger LOG = LoggerFactory.getLogger(PropagationManager.class); + + /** + * User DAO. + */ + @Autowired + protected UserDAO userDAO; + + /** + * Role DAO. + */ + @Autowired + protected RoleDAO roleDAO; + + /** + * Resource DAO. + */ + @Autowired + protected ExternalResourceDAO resourceDAO; + + @Autowired + protected EntityFactory entityFactory; + + /** + * ConnObjectUtil. + */ + @Autowired + protected ConnObjectUtil connObjectUtil; + + @Autowired + protected AttributableUtilFactory attrUtilFactory; + + @Autowired + protected VirAttrHandler virAttrHandler; + + /** + * Create the user on every associated resource. + * + * @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 + */ + @Override + public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult, + final String password, final List<AttrTO> vAttrs, final List<MembershipTO> membershipTOs) + throws NotFoundException, UnauthorizedRoleException { + + return getUserCreateTaskIds(wfResult, password, vAttrs, null, membershipTOs); + } + + /** + * Create the user on every associated resource. + * + * @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 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 + */ + @Override + public List<PropagationTask> getUserCreateTaskIds(final WorkflowResult<Map.Entry<Long, Boolean>> wfResult, + final String password, final Collection<AttrTO> vAttrs, + final Set<String> noPropResourceNames, final List<MembershipTO> membershipTOs) + throws NotFoundException, UnauthorizedRoleException { + + return getUserCreateTaskIds( + wfResult.getResult().getKey(), + wfResult.getResult().getValue(), + wfResult.getPropByRes(), + password, + vAttrs, + membershipTOs, + noPropResourceNames); + } + + @Override + public List<PropagationTask> getUserCreateTaskIds( + final Long key, + final Boolean enabled, + final PropagationByResource propByRes, + final String password, + final Collection<AttrTO> vAttrs, + final Collection<MembershipTO> membershipTOs, + final Collection<String> noPropResourceNames) + throws NotFoundException, UnauthorizedRoleException { + + User user = userDAO.authFetch(key); + if (vAttrs != null && !vAttrs.isEmpty()) { + virAttrHandler.fillVirtual(user, vAttrs, attrUtilFactory.getInstance(AttributableType.USER)); + + } + for (Membership membership : user.getMemberships()) { + MembershipTO membershipTO; + if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) { + membershipTO = findMembershipTO(membership, membershipTOs); + if (membershipTO != null) { + virAttrHandler.fillVirtual(membership, + membershipTO.getVirAttrs(), attrUtilFactory.getInstance(AttributableType.MEMBERSHIP)); + } + } + } + return getCreateTaskIds(user, password, enabled, propByRes, noPropResourceNames); + } + + /** + * Create the role on every associated resource. + * + * @param wfResult user to be propagated (and info associated), as per result from workflow + * @param vAttrs virtual attributes to be set + * @return list of propagation tasks + * @throws NotFoundException if role is not found + * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role + */ + @Override + public List<PropagationTask> getRoleCreateTaskIds(final WorkflowResult<Long> wfResult, final List<AttrTO> vAttrs) + throws NotFoundException, UnauthorizedRoleException { + + return getRoleCreateTaskIds(wfResult, vAttrs, null); + } + + /** + * Create the role on every associated resource. + * + * @param wfResult role to be propagated (and info associated), as per result from workflow + * @param vAttrs virtual attributes to be set + * @param noPropResourceNames external resources performing not to be considered for propagation + * @return list of propagation tasks + * @throws NotFoundException if role is not found + * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role + */ + @Override + public List<PropagationTask> getRoleCreateTaskIds( + final WorkflowResult<Long> wfResult, + final Collection<AttrTO> vAttrs, + final Collection<String> noPropResourceNames) + throws NotFoundException, UnauthorizedRoleException { + + return getRoleCreateTaskIds(wfResult.getResult(), vAttrs, wfResult.getPropByRes(), noPropResourceNames); + } + + /** + * Create the role on every associated resource. + * + * @param key role key + * @param vAttrs virtual attributes to be set + * @param propByRes operation to be performed per resource + * @param noPropResourceNames external resources performing not to be considered for propagation + * @return list of propagation tasks + * @throws NotFoundException if role is not found + * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role + */ + @Override + public List<PropagationTask> getRoleCreateTaskIds( + final Long key, + final Collection<AttrTO> vAttrs, + final PropagationByResource propByRes, + final Collection<String> noPropResourceNames) + throws NotFoundException, UnauthorizedRoleException { + + Role role = roleDAO.authFetch(key); + if (vAttrs != null && !vAttrs.isEmpty()) { + virAttrHandler.fillVirtual(role, vAttrs, attrUtilFactory.getInstance(AttributableType.ROLE)); + } + + return getCreateTaskIds(role, null, null, propByRes, noPropResourceNames); + } + + protected List<PropagationTask> getCreateTaskIds(final Subject<?, ?, ?> subject, + final String password, final Boolean enable, + final PropagationByResource propByRes, + final Collection<String> noPropResourceNames) { + + if (propByRes == null || propByRes.isEmpty()) { + return Collections.<PropagationTask>emptyList(); + } + + if (noPropResourceNames != null) { + propByRes.get(ResourceOperation.CREATE).removeAll(noPropResourceNames); + } + + return createTasks(subject, password, true, null, null, null, null, enable, false, propByRes); + } + + /** + * Performs update on each resource associated to the user excluding the specified into 'resourceNames' parameter. + * + * @param user to be propagated + * @param enable whether user must be enabled or not + * @param noPropResourceNames external resource names not to be considered for propagation + * @return list of propagation tasks + * @throws NotFoundException if user is not found + */ + @Override + public List<PropagationTask> getUserUpdateTaskIds(final User user, final Boolean enable, + final Set<String> noPropResourceNames) throws NotFoundException { + + return getUpdateTaskIds( + user, // user to be updated on external resources + null, // no password + false, + enable, // status to be propagated + Collections.<String>emptySet(), // no virtual attributes to be managed + Collections.<AttrMod>emptySet(), // no virtual attributes to be managed + null, // no propagation by resources + noPropResourceNames, + Collections.<MembershipMod>emptySet()); + } + + /** + * Performs update on each resource associated to the user. + * + * @param wfResult user to be propagated (and info associated), as per result from workflow + * @param changePwd whether password should be included for propagation attributes or not + * @param noPropResourceNames external resources not to be considered for propagation + * @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 + */ + @Override + public List<PropagationTask> getUserUpdateTaskIds(final WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult, + final boolean changePwd, final Collection<String> noPropResourceNames) + throws NotFoundException, UnauthorizedRoleException { + + User user = userDAO.authFetch(wfResult.getResult().getKey().getKey()); + return getUpdateTaskIds(user, + wfResult.getResult().getKey().getPassword(), + changePwd, + wfResult.getResult().getValue(), + wfResult.getResult().getKey().getVirAttrsToRemove(), + wfResult.getResult().getKey().getVirAttrsToUpdate(), + wfResult.getPropByRes(), + noPropResourceNames, + wfResult.getResult().getKey().getMembershipsToAdd()); + } + + @Override + public List<PropagationTask> getUserUpdateTaskIds(final WorkflowResult<Map.Entry<UserMod, Boolean>> wfResult) { + UserMod userMod = wfResult.getResult().getKey(); + + // Propagate password update only to requested resources + List<PropagationTask> tasks = new ArrayList<>(); + if (userMod.getPwdPropRequest() == null) { + // a. no specific password propagation request: generate propagation tasks for any resource associated + tasks = getUserUpdateTaskIds(wfResult, true, null); + } else { + // b. generate the propagation task list in two phases: first the ones containing password, + // the the rest (with no password) + final PropagationByResource origPropByRes = new PropagationByResource(); + origPropByRes.merge(wfResult.getPropByRes()); + + Set<String> pwdResourceNames = new HashSet<>(userMod.getPwdPropRequest().getResourceNames()); + Set<String> currentResourceNames = userDAO.authFetch(userMod.getKey()).getResourceNames(); + pwdResourceNames.retainAll(currentResourceNames); + PropagationByResource pwdPropByRes = new PropagationByResource(); + pwdPropByRes.addAll(ResourceOperation.UPDATE, pwdResourceNames); + if (!pwdPropByRes.isEmpty()) { + Set<String> toBeExcluded = new HashSet<>(currentResourceNames); + toBeExcluded.addAll(userMod.getResourcesToAdd()); + toBeExcluded.removeAll(pwdResourceNames); + tasks.addAll(getUserUpdateTaskIds(wfResult, true, toBeExcluded)); + } + + final PropagationByResource nonPwdPropByRes = new PropagationByResource(); + nonPwdPropByRes.merge(origPropByRes); + nonPwdPropByRes.removeAll(pwdResourceNames); + nonPwdPropByRes.purge(); + if (!nonPwdPropByRes.isEmpty()) { + tasks.addAll(getUserUpdateTaskIds(wfResult, false, pwdResourceNames)); + } + } + + return tasks; + } + + /** + * Performs update on each resource associated to the role. + * + * @param wfResult role to be propagated (and info associated), as per result from workflow + * @param vAttrsToBeRemoved virtual attributes to be removed + * @param vAttrsToBeUpdated virtual attributes to be added + * @return list of propagation tasks + * @throws NotFoundException if role is not found + * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role + */ + @Override + public List<PropagationTask> getRoleUpdateTaskIds(final WorkflowResult<Long> wfResult, + final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated) + throws NotFoundException, UnauthorizedRoleException { + + return getRoleUpdateTaskIds(wfResult, vAttrsToBeRemoved, vAttrsToBeUpdated, null); + } + + /** + * Performs update on each resource associated to the role. + * + * @param wfResult role to be propagated (and info associated), as per result from workflow + * @param vAttrsToBeRemoved virtual attributes to be removed + * @param vAttrsToBeUpdated virtual attributes to be added + * @param noPropResourceNames external resource names not to be considered for propagation + * @return list of propagation tasks + * @throws NotFoundException if role is not found + * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role + */ + public List<PropagationTask> getRoleUpdateTaskIds(final WorkflowResult<Long> wfResult, + final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated, + final Set<String> noPropResourceNames) + throws NotFoundException, UnauthorizedRoleException { + + Role role = roleDAO.authFetch(wfResult.getResult()); + return getUpdateTaskIds(role, null, false, null, + vAttrsToBeRemoved, vAttrsToBeUpdated, wfResult.getPropByRes(), noPropResourceNames, + Collections.<MembershipMod>emptySet()); + } + + @Override + public List<PropagationTask> getUpdateTaskIds(final Subject<?, ?, ?> subject, + final String password, final boolean changePwd, final Boolean enable, + final Set<String> vAttrsToBeRemoved, final Set<AttrMod> vAttrsToBeUpdated, + final PropagationByResource propByRes, final Collection<String> noPropResourceNames, + final Set<MembershipMod> membershipsToAdd) + throws NotFoundException { + + PropagationByResource localPropByRes = virAttrHandler.fillVirtual(subject, vAttrsToBeRemoved == null + ? Collections.<String>emptySet() + : vAttrsToBeRemoved, vAttrsToBeUpdated == null + ? Collections.<AttrMod>emptySet() + : vAttrsToBeUpdated, attrUtilFactory.getInstance(subject)); + + // SYNCOPE-458 fill membership virtual attributes + if (subject instanceof User) { + final User user = (User) subject; + for (Membership membership : user.getMemberships()) { + if (membership.getVirAttrs() != null && !membership.getVirAttrs().isEmpty()) { + final MembershipMod membershipMod = findMembershipMod(membership, membershipsToAdd); + if (membershipMod != null) { + virAttrHandler.fillVirtual(membership, membershipMod.getVirAttrsToRemove() == null + ? Collections.<String>emptySet() + : membershipMod.getVirAttrsToRemove(), + membershipMod.getVirAttrsToUpdate() == null ? Collections.<AttrMod>emptySet() + : membershipMod.getVirAttrsToUpdate(), attrUtilFactory.getInstance( + AttributableType.MEMBERSHIP)); + } + } + } + } + + if (propByRes == null || propByRes.isEmpty()) { + localPropByRes.addAll(ResourceOperation.UPDATE, subject.getResourceNames()); + } else { + localPropByRes.merge(propByRes); + } + + if (noPropResourceNames != null) { + localPropByRes.removeAll(noPropResourceNames); + } + + Map<String, AttrMod> vAttrsToBeUpdatedMap = null; + if (vAttrsToBeUpdated != null) { + vAttrsToBeUpdatedMap = new HashMap<>(); + for (AttrMod attrMod : vAttrsToBeUpdated) { + vAttrsToBeUpdatedMap.put(attrMod.getSchema(), attrMod); + } + } + + // SYNCOPE-458 fill membership virtual attributes to be updated map + Map<String, AttrMod> membVAttrsToBeUpdatedMap = new HashMap<>(); + for (MembershipMod membershipMod : membershipsToAdd) { + for (AttrMod attrMod : membershipMod.getVirAttrsToUpdate()) { + membVAttrsToBeUpdatedMap.put(attrMod.getSchema(), attrMod); + } + } + + // SYNCOPE-458 fill membership virtual attributes to be removed set + final Set<String> membVAttrsToBeRemoved = new HashSet<>(); + for (MembershipMod membershipMod : membershipsToAdd) { + membVAttrsToBeRemoved.addAll(membershipMod.getVirAttrsToRemove()); + } + + return createTasks(subject, password, changePwd, + vAttrsToBeRemoved, vAttrsToBeUpdatedMap, membVAttrsToBeRemoved, membVAttrsToBeUpdatedMap, enable, false, + localPropByRes); + } + + /** + * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for + * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if + * the creation fails onto a mandatory resource. + * + * @param userKey to be deleted + * @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 + */ + @Override + public List<PropagationTask> getUserDeleteTaskIds(final Long userKey) + throws NotFoundException, UnauthorizedRoleException { + + return getUserDeleteTaskIds(userKey, Collections.<String>emptySet()); + } + + /** + * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for + * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if + * the creation fails onto a mandatory resource. + * + * @param userKey to be deleted + * @param noPropResourceName name of external resource not to be considered for propagation + * @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 + */ + @Override + public List<PropagationTask> getUserDeleteTaskIds(final Long userKey, final String noPropResourceName) + throws NotFoundException, UnauthorizedRoleException { + return getUserDeleteTaskIds(userKey, Collections.<String>singleton(noPropResourceName)); + } + + /** + * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for + * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if + * the creation fails onto a mandatory resource. + * + * @param userKey to be deleted + * @param noPropResourceNames name of external resources not to be considered for propagation + * @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 + */ + @Override + public List<PropagationTask> getUserDeleteTaskIds(final Long userKey, final Collection<String> noPropResourceNames) + throws NotFoundException, UnauthorizedRoleException { + + User user = userDAO.authFetch(userKey); + return getDeleteTaskIds(user, user.getResourceNames(), noPropResourceNames); + } + + /** + * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for + * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if + * the creation fails onto a mandatory resource. + * + * @param userKey to be deleted + * @param resourceNames resource from which user is to be deleted + * @param noPropResourceNames name of external resources not to be considered for propagation + * @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 + */ + @Override + public List<PropagationTask> getUserDeleteTaskIds( + final Long userKey, final Set<String> resourceNames, final Collection<String> noPropResourceNames) + throws NotFoundException, UnauthorizedRoleException { + + User user = userDAO.authFetch(userKey); + return getDeleteTaskIds(user, resourceNames, noPropResourceNames); + } + + /** + * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for + * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if + * the creation fails onto a mandatory resource. + * + * @param wfResult user to be propagated (and info associated), as per result from workflow + * @return list of propagation tasks + */ + @Override + public List<PropagationTask> getUserDeleteTaskIds(final WorkflowResult<Long> wfResult) { + User user = userDAO.authFetch(wfResult.getResult()); + return createTasks(user, null, false, null, null, null, null, false, true, wfResult.getPropByRes()); + } + + /** + * Perform delete on each resource associated to the role. It is possible to ask for a mandatory provisioning for + * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if + * the creation fails onto a mandatory resource. + * + * @param roleKey to be deleted + * @return list of propagation tasks + * @throws NotFoundException if role is not found + * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role + */ + @Override + public List<PropagationTask> getRoleDeleteTaskIds(final Long roleKey) + throws NotFoundException, UnauthorizedRoleException { + + return getRoleDeleteTaskIds(roleKey, Collections.<String>emptySet()); + } + + /** + * Perform delete on each resource associated to the role. It is possible to ask for a mandatory provisioning for + * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if + * the creation fails onto a mandatory resource. + * + * @param roleKey to be deleted + * @param noPropResourceName name of external resource not to be considered for propagation + * @return list of propagation tasks + * @throws NotFoundException if role is not found + * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role + */ + @Override + public List<PropagationTask> getRoleDeleteTaskIds(final Long roleKey, final String noPropResourceName) + throws NotFoundException, UnauthorizedRoleException { + + return getRoleDeleteTaskIds(roleKey, Collections.<String>singleton(noPropResourceName)); + } + + /** + * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for + * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if + * the creation fails onto a mandatory resource. + * + * @param roleKey to be deleted + * @param noPropResourceNames name of external resources not to be considered for propagation + * @return list of propagation tasks + * @throws NotFoundException if role is not found + * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role + */ + @Override + public List<PropagationTask> getRoleDeleteTaskIds(final Long roleKey, final Collection<String> noPropResourceNames) + throws NotFoundException, UnauthorizedRoleException { + + Role role = roleDAO.authFetch(roleKey); + return getDeleteTaskIds(role, role.getResourceNames(), noPropResourceNames); + } + + /** + * Perform delete on each resource associated to the user. It is possible to ask for a mandatory provisioning for + * some resources specifying a set of resource names. Exceptions won't be ignored and the process will be stopped if + * the creation fails onto a mandatory resource. + * + * @param roleKey to be deleted + * @param resourceNames resource from which role is to be deleted + * @param noPropResourceNames name of external resources not to be considered for propagation + * @return list of propagation tasks + * @throws NotFoundException if role is not found + * @throws UnauthorizedRoleException if caller doesn't own enough entitlements to administer the given role + */ + @Override + public List<PropagationTask> getRoleDeleteTaskIds( + final Long roleKey, final Set<String> resourceNames, final Collection<String> noPropResourceNames) + throws NotFoundException, UnauthorizedRoleException { + + Role role = roleDAO.authFetch(roleKey); + return getDeleteTaskIds(role, resourceNames, noPropResourceNames); + } + + protected List<PropagationTask> getDeleteTaskIds( + final Subject<?, ?, ?> subject, + final Set<String> resourceNames, + final Collection<String> noPropResourceNames) { + + final PropagationByResource propByRes = new PropagationByResource(); + propByRes.set(ResourceOperation.DELETE, resourceNames); + if (noPropResourceNames != null && !noPropResourceNames.isEmpty()) { + propByRes.get(ResourceOperation.DELETE).removeAll(noPropResourceNames); + } + return createTasks(subject, null, false, null, null, null, null, false, true, propByRes); + } + + /** + * Create propagation tasks. + * + * @param subject user / role to be provisioned + * @param password cleartext password to be provisioned + * @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 + * @return list of propagation tasks created + */ + protected List<PropagationTask> createTasks(final Subject<?, ?, ?> subject, + final String password, final boolean changePwd, + final Set<String> vAttrsToBeRemoved, final Map<String, AttrMod> vAttrsToBeUpdated, + final Set<String> membVAttrsToBeRemoved, final Map<String, AttrMod> membVAttrsToBeUpdatedMap, + final Boolean enable, final boolean deleteOnResource, final PropagationByResource propByRes) { + + LOG.debug("Provisioning subject {}:\n{}", subject, propByRes); + + final AttributableUtil attrUtil = attrUtilFactory.getInstance(subject); + + if (!propByRes.get(ResourceOperation.CREATE).isEmpty() + && vAttrsToBeRemoved != null && vAttrsToBeUpdated != null) { + + connObjectUtil.retrieveVirAttrValues(subject, attrUtil); + + // update vAttrsToBeUpdated as well + for (VirAttr virAttr : subject.getVirAttrs()) { + final String schema = virAttr.getSchema().getKey(); + + final AttrMod attributeMod = new AttrMod(); + attributeMod.setSchema(schema); + attributeMod.getValuesToBeAdded().addAll(virAttr.getValues()); + + vAttrsToBeUpdated.put(schema, attributeMod); + } + } + + // Avoid duplicates - see javadoc + propByRes.purge(); + LOG.debug("After purge: {}", propByRes); + + final List<PropagationTask> tasks = new ArrayList<>(); + + for (ResourceOperation operation : ResourceOperation.values()) { + for (String resourceName : propByRes.get(operation)) { + final ExternalResource resource = resourceDAO.find(resourceName); + if (resource == null) { + LOG.error("Invalid resource name specified: {}, ignoring...", resourceName); + } else if (attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION).isEmpty()) { + LOG.warn("Requesting propagation for {} but no propagation mapping provided for {}", + attrUtil.getType(), resource); + } else { + PropagationTask task = entityFactory.newEntity(PropagationTask.class); + task.setResource(resource); + task.setObjectClassName(connObjectUtil.fromSubject(subject).getObjectClassValue()); + task.setSubjectType(attrUtil.getType()); + if (!deleteOnResource) { + task.setSubjectKey(subject.getKey()); + } + task.setPropagationOperation(operation); + task.setPropagationMode(resource.getPropagationMode()); + task.setOldAccountId(propByRes.getOldAccountId(resource.getKey())); + + Map.Entry<String, Set<Attribute>> preparedAttrs = MappingUtil.prepareAttributes(attrUtil, subject, + 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: + // if so, add special attributes that will be evaluated by PropagationTaskExecutor + List<String> mandatoryMissing = new ArrayList<>(); + List<String> mandatoryNullOrEmpty = new ArrayList<>(); + for (MappingItem item : attrUtil.getMappingItems(resource, MappingPurpose.PROPAGATION)) { + if (!item.isAccountid() + && JexlUtil.evaluateMandatoryCondition(item.getMandatoryCondition(), subject)) { + + Attribute attr = AttributeUtil.find(item.getExtAttrName(), preparedAttrs.getValue()); + if (attr == null) { + mandatoryMissing.add(item.getExtAttrName()); + } else if (attr.getValue() == null || attr.getValue().isEmpty()) { + mandatoryNullOrEmpty.add(item.getExtAttrName()); + } + } + } + if (!mandatoryMissing.isEmpty()) { + preparedAttrs.getValue().add(AttributeBuilder.build( + PropagationTaskExecutor.MANDATORY_MISSING_ATTR_NAME, mandatoryMissing)); + } + if (!mandatoryNullOrEmpty.isEmpty()) { + preparedAttrs.getValue().add(AttributeBuilder.build( + PropagationTaskExecutor.MANDATORY_NULL_OR_EMPTY_ATTR_NAME, mandatoryNullOrEmpty)); + } + + task.setAttributes(preparedAttrs.getValue()); + tasks.add(task); + + LOG.debug("PropagationTask created: {}", task); + } + } + } + + return tasks; + } + + protected MembershipTO findMembershipTO(final Membership membership, final Collection<MembershipTO> memberships) { + for (MembershipTO membershipTO : memberships) { + if (membershipTO.getRoleId() == membership.getRole().getKey()) { + return membershipTO; + } + } + LOG.error("No MembershipTO found for membership {}", membership); + return null; + } + + protected MembershipMod findMembershipMod(final Membership membership, final Set<MembershipMod> membershipMods) { + for (MembershipMod membershipMod : membershipMods) { + if (membershipMod.getRole() == membership.getRole().getKey()) { + return membershipMod; + } + } + LOG.error("No MembershipMod found for membership {}", membership); + return null; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractProvisioningJob.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractProvisioningJob.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractProvisioningJob.java new file mode 100644 index 0000000..ea03a12 --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractProvisioningJob.java @@ -0,0 +1,375 @@ +/* + * 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.provisioning.java.sync; + +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.apache.syncope.common.lib.types.TraceLevel; +import org.apache.syncope.core.persistence.api.dao.EntitlementDAO; +import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO; +import org.apache.syncope.core.persistence.api.dao.PolicyDAO; +import org.apache.syncope.core.persistence.api.entity.Entitlement; +import org.apache.syncope.core.persistence.api.entity.role.RMapping; +import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask; +import org.apache.syncope.core.persistence.api.entity.task.PushTask; +import org.apache.syncope.core.persistence.api.entity.task.SyncTask; +import org.apache.syncope.core.persistence.api.entity.task.TaskExec; +import org.apache.syncope.core.persistence.api.entity.user.UMapping; +import org.apache.syncope.core.provisioning.api.Connector; +import org.apache.syncope.core.provisioning.api.ConnectorFactory; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningActions; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.apache.syncope.core.provisioning.java.job.AbstractTaskJob; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; + +/** + * Job for executing synchronization tasks. + * + * @see AbstractTaskJob + * @see SyncTask + * @see PushTask + */ +public abstract class AbstractProvisioningJob<T extends ProvisioningTask, A extends ProvisioningActions> + extends AbstractTaskJob { + + /** + * ConnInstance loader. + */ + @Autowired + protected ConnectorFactory connFactory; + + /** + * Resource DAO. + */ + @Autowired + protected ExternalResourceDAO resourceDAO; + + /** + * Entitlement DAO. + */ + @Autowired + protected EntitlementDAO entitlementDAO; + + /** + * Policy DAO. + */ + @Autowired + protected PolicyDAO policyDAO; + + /** + * SyncJob actions. + */ + protected List<A> actions; + + public void setActions(final List<A> actions) { + this.actions = actions; + } + + /** + * Create a textual report of the synchronization, based on the trace level. + * + * @param provResults Sync results + * @param syncTraceLevel Sync trace level + * @param dryRun dry run? + * @return report as string + */ + protected String createReport(final Collection<ProvisioningResult> provResults, final TraceLevel syncTraceLevel, + final boolean dryRun) { + + if (syncTraceLevel == TraceLevel.NONE) { + return null; + } + + StringBuilder report = new StringBuilder(); + + if (dryRun) { + report.append("==>Dry run only, no modifications were made<==\n\n"); + } + + List<ProvisioningResult> uSuccCreate = new ArrayList<>(); + List<ProvisioningResult> uFailCreate = new ArrayList<>(); + List<ProvisioningResult> uSuccUpdate = new ArrayList<>(); + List<ProvisioningResult> uFailUpdate = new ArrayList<>(); + List<ProvisioningResult> uSuccDelete = new ArrayList<>(); + List<ProvisioningResult> uFailDelete = new ArrayList<>(); + List<ProvisioningResult> rSuccCreate = new ArrayList<>(); + List<ProvisioningResult> rFailCreate = new ArrayList<>(); + List<ProvisioningResult> rSuccUpdate = new ArrayList<>(); + List<ProvisioningResult> rFailUpdate = new ArrayList<>(); + List<ProvisioningResult> rSuccDelete = new ArrayList<>(); + List<ProvisioningResult> rFailDelete = new ArrayList<>(); + + for (ProvisioningResult provResult : provResults) { + switch (provResult.getStatus()) { + case SUCCESS: + switch (provResult.getOperation()) { + case CREATE: + switch (provResult.getSubjectType()) { + case USER: + uSuccCreate.add(provResult); + break; + + case ROLE: + rSuccCreate.add(provResult); + break; + + default: + } + break; + + case UPDATE: + switch (provResult.getSubjectType()) { + case USER: + uSuccUpdate.add(provResult); + break; + + case ROLE: + rSuccUpdate.add(provResult); + break; + + default: + } + break; + + case DELETE: + switch (provResult.getSubjectType()) { + case USER: + uSuccDelete.add(provResult); + break; + + case ROLE: + rSuccDelete.add(provResult); + break; + + default: + } + break; + + default: + } + break; + + case FAILURE: + switch (provResult.getOperation()) { + case CREATE: + switch (provResult.getSubjectType()) { + case USER: + uFailCreate.add(provResult); + break; + + case ROLE: + rFailCreate.add(provResult); + break; + + default: + } + break; + + case UPDATE: + switch (provResult.getSubjectType()) { + case USER: + uFailUpdate.add(provResult); + break; + + case ROLE: + rFailUpdate.add(provResult); + break; + + default: + } + break; + + case DELETE: + switch (provResult.getSubjectType()) { + case USER: + uFailDelete.add(provResult); + break; + + case ROLE: + rFailDelete.add(provResult); + break; + + default: + } + break; + + default: + } + break; + + default: + } + } + + // Summary, also to be included for FAILURE and ALL, so create it anyway. + report.append("Users "). + append("[created/failures]: ").append(uSuccCreate.size()).append('/').append(uFailCreate.size()). + append(' '). + append("[updated/failures]: ").append(uSuccUpdate.size()).append('/').append(uFailUpdate.size()). + append(' '). + append("[deleted/failures]: ").append(uSuccDelete.size()).append('/').append(uFailDelete.size()). + append('\n'); + report.append("Roles "). + append("[created/failures]: ").append(rSuccCreate.size()).append('/').append(rFailCreate.size()). + append(' '). + append("[updated/failures]: ").append(rSuccUpdate.size()).append('/').append(rFailUpdate.size()). + append(' '). + append("[deleted/failures]: ").append(rSuccDelete.size()).append('/').append(rFailDelete.size()); + + // Failures + if (syncTraceLevel == TraceLevel.FAILURES || syncTraceLevel == TraceLevel.ALL) { + if (!uFailCreate.isEmpty()) { + report.append("\n\nUsers failed to create: "); + report.append(ProvisioningResult.produceReport(uFailCreate, syncTraceLevel)); + } + if (!uFailUpdate.isEmpty()) { + report.append("\nUsers failed to update: "); + report.append(ProvisioningResult.produceReport(uFailUpdate, syncTraceLevel)); + } + if (!uFailDelete.isEmpty()) { + report.append("\nUsers failed to delete: "); + report.append(ProvisioningResult.produceReport(uFailDelete, syncTraceLevel)); + } + + if (!rFailCreate.isEmpty()) { + report.append("\n\nRoles failed to create: "); + report.append(ProvisioningResult.produceReport(rFailCreate, syncTraceLevel)); + } + if (!rFailUpdate.isEmpty()) { + report.append("\nRoles failed to update: "); + report.append(ProvisioningResult.produceReport(rFailUpdate, syncTraceLevel)); + } + if (!rFailDelete.isEmpty()) { + report.append("\nRoles failed to delete: "); + report.append(ProvisioningResult.produceReport(rFailDelete, syncTraceLevel)); + } + } + + // Succeeded, only if on 'ALL' level + if (syncTraceLevel == TraceLevel.ALL) { + report.append("\n\nUsers created:\n") + .append(ProvisioningResult.produceReport(uSuccCreate, syncTraceLevel)) + .append("\nUsers updated:\n") + .append(ProvisioningResult.produceReport(uSuccUpdate, syncTraceLevel)) + .append("\nUsers deleted:\n") + .append(ProvisioningResult.produceReport(uSuccDelete, syncTraceLevel)); + report.append("\n\nRoles created:\n") + .append(ProvisioningResult.produceReport(rSuccCreate, syncTraceLevel)) + .append("\nRoles updated:\n") + .append(ProvisioningResult.produceReport(rSuccUpdate, syncTraceLevel)) + .append("\nRoles deleted:\n") + .append(ProvisioningResult.produceReport(rSuccDelete, syncTraceLevel)); + } + + return report.toString(); + } + + @Override + protected String doExecute(final boolean dryRun) throws JobExecutionException { + // PRE: grant all authorities (i.e. setup the SecurityContextHolder) + final List<GrantedAuthority> authorities = new ArrayList<>(); + + for (Entitlement entitlement : entitlementDAO.findAll()) { + authorities.add(new SimpleGrantedAuthority(entitlement.getKey())); + } + + final UserDetails userDetails = new User("admin", "FAKE_PASSWORD", true, true, true, true, authorities); + + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken(userDetails, "FAKE_PASSWORD", authorities)); + + try { + final Class<T> clazz = getTaskClassReference(); + if (!clazz.isAssignableFrom(task.getClass())) { + throw new JobExecutionException("Task " + taskId + " isn't a SyncTask"); + } + + final T syncTask = clazz.cast(this.task); + + final Connector connector; + try { + connector = connFactory.getConnector(syncTask.getResource()); + } catch (Exception e) { + final String msg = String. + format("Connector instance bean for resource %s and connInstance %s not found", + syncTask.getResource(), syncTask.getResource().getConnector()); + + throw new JobExecutionException(msg, e); + } + + final UMapping uMapping = syncTask.getResource().getUmapping(); + if (uMapping != null && uMapping.getAccountIdItem() == null) { + throw new JobExecutionException( + "Invalid user account id mapping for resource " + syncTask.getResource()); + } + final RMapping rMapping = syncTask.getResource().getRmapping(); + if (rMapping != null && rMapping.getAccountIdItem() == null) { + throw new JobExecutionException( + "Invalid role account id mapping for resource " + syncTask.getResource()); + } + if (uMapping == null && rMapping == null) { + return "No mapping configured for both users and roles: aborting..."; + } + + return executeWithSecurityContext( + syncTask, + connector, + uMapping, + rMapping, + dryRun); + } catch (Throwable t) { + LOG.error("While executing provisioning job {}", getClass().getName(), t); + throw t; + } finally { + // POST: clean up the SecurityContextHolder + SecurityContextHolder.clearContext(); + } + } + + protected abstract String executeWithSecurityContext( + final T task, + final Connector connector, + final UMapping uMapping, + final RMapping rMapping, + final boolean dryRun) throws JobExecutionException; + + @Override + protected boolean hasToBeRegistered(final TaskExec execution) { + final ProvisioningTask provTask = (ProvisioningTask) task; + + // True if either failed and failures have to be registered, or if ALL has to be registered. + return (Status.valueOf(execution.getStatus()) == Status.FAILURE + && provTask.getResource().getSyncTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal()) + || provTask.getResource().getSyncTraceLevel().ordinal() >= TraceLevel.SUMMARY.ordinal(); + } + + @SuppressWarnings("unchecked") + private Class<T> getTaskClassReference() { + return (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/d30c8526/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java new file mode 100644 index 0000000..5161c74 --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractPushResultHandler.java @@ -0,0 +1,374 @@ +/* + * 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.provisioning.java.sync; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.syncope.common.lib.mod.AttrMod; +import org.apache.syncope.common.lib.mod.MembershipMod; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.types.AttributableType; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditElements.Result; +import org.apache.syncope.common.lib.types.IntMappingType; +import org.apache.syncope.common.lib.types.MatchingRule; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.common.lib.types.UnmatchingRule; +import org.apache.syncope.core.persistence.api.entity.AttributableUtil; +import org.apache.syncope.core.persistence.api.entity.Mapping; +import org.apache.syncope.core.persistence.api.entity.MappingItem; +import org.apache.syncope.core.persistence.api.entity.Subject; +import org.apache.syncope.core.persistence.api.entity.VirAttr; +import org.apache.syncope.core.persistence.api.entity.membership.Membership; +import org.apache.syncope.core.persistence.api.entity.task.PushTask; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.apache.syncope.core.provisioning.api.sync.PushActions; +import org.apache.syncope.core.misc.MappingUtil; +import org.apache.syncope.core.provisioning.api.sync.SyncopePushResultHandler; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.quartz.JobExecutionException; +import org.springframework.transaction.annotation.Transactional; + +public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHandler<PushTask, PushActions> + implements SyncopePushResultHandler { + + protected abstract String getName(final Subject<?, ?, ?> subject); + + protected abstract Mapping<?> getMapping(); + + protected abstract AbstractSubjectTO getSubjectTO(final long key); + + protected abstract Subject<?, ?, ?> getSubject(final long key); + + protected abstract Subject<?, ?, ?> deprovision(final Subject<?, ?, ?> sbj); + + protected abstract Subject<?, ?, ?> provision(final Subject<?, ?, ?> sbj, final Boolean enabled); + + protected abstract Subject<?, ?, ?> link(final Subject<?, ?, ?> sbj, final Boolean unlink); + + protected abstract Subject<?, ?, ?> unassign(final Subject<?, ?, ?> sbj); + + protected abstract Subject<?, ?, ?> assign(final Subject<?, ?, ?> sbj, Boolean enabled); + + protected abstract ConnectorObject getRemoteObject(final String accountId); + + @Transactional + @Override + public boolean handle(final long subjectId) { + try { + doHandle(subjectId); + return true; + } catch (JobExecutionException e) { + LOG.error("Synchronization failed", e); + return false; + } + } + + protected final void doHandle(final long subjectId) + throws JobExecutionException { + + final Subject<?, ?, ?> subject = getSubject(subjectId); + + final AttributableUtil attrUtil = attrUtilFactory.getInstance(subject); + + final ProvisioningResult result = new ProvisioningResult(); + profile.getResults().add(result); + + result.setId(subject.getKey()); + result.setSubjectType(attrUtil.getType()); + result.setName(getName(subject)); + + final Boolean enabled = subject instanceof User && profile.getTask().isSyncStatus() + ? ((User) subject).isSuspended() ? Boolean.FALSE : Boolean.TRUE + : null; + + LOG.debug("Propagating {} with key {} towards {}", + attrUtil.getType(), subject.getKey(), profile.getTask().getResource()); + + Object output = null; + Result resultStatus = null; + ConnectorObject beforeObj = null; + String operation = null; + + // Try to read remote object (user / group) BEFORE any actual operation + final String accountId = MappingUtil.getAccountIdValue( + subject, profile.getTask().getResource(), getMapping().getAccountIdItem()); + + beforeObj = getRemoteObject(accountId); + + Boolean status = profile.getTask().isSyncStatus() ? enabled : null; + + if (profile.isDryRun()) { + if (beforeObj == null) { + result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule())); + } else { + result.setOperation(getResourceOperation(profile.getTask().getMatchingRule())); + } + result.setStatus(ProvisioningResult.Status.SUCCESS); + } else { + try { + if (beforeObj == null) { + operation = profile.getTask().getUnmatchingRule().name().toLowerCase(); + result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule())); + + switch (profile.getTask().getUnmatchingRule()) { + case ASSIGN: + for (PushActions action : profile.getActions()) { + action.beforeAssign(this.getProfile(), subject); + } + + if (!profile.getTask().isPerformCreate()) { + LOG.debug("PushTask not configured for create"); + } else { + assign(subject, status); + } + + break; + case PROVISION: + for (PushActions action : profile.getActions()) { + action.beforeProvision(this.getProfile(), subject); + } + + if (!profile.getTask().isPerformCreate()) { + LOG.debug("PushTask not configured for create"); + } else { + provision(subject, status); + } + + break; + case UNLINK: + for (PushActions action : profile.getActions()) { + action.beforeUnlink(this.getProfile(), subject); + } + + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("PushTask not configured for update"); + } else { + link(subject, true); + } + + break; + default: + // do nothing + } + + } else { + operation = profile.getTask().getMatchingRule().name().toLowerCase(); + result.setOperation(getResourceOperation(profile.getTask().getMatchingRule())); + + switch (profile.getTask().getMatchingRule()) { + case UPDATE: + for (PushActions action : profile.getActions()) { + action.beforeUpdate(this.getProfile(), subject); + } + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("PushTask not configured for update"); + } else { + update(subject, status); + } + + break; + case DEPROVISION: + for (PushActions action : profile.getActions()) { + action.beforeDeprovision(this.getProfile(), subject); + } + + if (!profile.getTask().isPerformDelete()) { + LOG.debug("PushTask not configured for delete"); + } else { + deprovision(subject); + } + + break; + case UNASSIGN: + for (PushActions action : profile.getActions()) { + action.beforeUnassign(this.getProfile(), subject); + } + + if (!profile.getTask().isPerformDelete()) { + LOG.debug("PushTask not configured for delete"); + } else { + unassign(subject); + } + + break; + case LINK: + for (PushActions action : profile.getActions()) { + action.beforeLink(this.getProfile(), subject); + } + + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("PushTask not configured for update"); + } else { + link(subject, false); + } + + break; + case UNLINK: + for (PushActions action : profile.getActions()) { + action.beforeUnlink(this.getProfile(), subject); + } + + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("PushTask not configured for update"); + } else { + link(subject, true); + } + + break; + default: + // do nothing + } + } + + for (PushActions action : profile.getActions()) { + action.after(this.getProfile(), subject, result); + } + + result.setStatus(ProvisioningResult.Status.SUCCESS); + resultStatus = AuditElements.Result.SUCCESS; + output = getRemoteObject(accountId); + } catch (Exception e) { + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage(ExceptionUtils.getRootCauseMessage(e)); + resultStatus = AuditElements.Result.FAILURE; + output = e; + + LOG.warn("Error pushing {} towards {}", subject, profile.getTask().getResource(), e); + throw new JobExecutionException(e); + } finally { + notificationManager.createTasks( + AuditElements.EventCategoryType.PUSH, + AttributableType.USER.name().toLowerCase(), + profile.getTask().getResource().getKey(), + operation, + resultStatus, + beforeObj, + output, + subject); + auditManager.audit( + AuditElements.EventCategoryType.PUSH, + AttributableType.USER.name().toLowerCase(), + profile.getTask().getResource().getKey(), + operation, + resultStatus, + beforeObj, + output, + subject); + } + } + } + + private ResourceOperation getResourceOperation(final UnmatchingRule rule) { + switch (rule) { + case ASSIGN: + case PROVISION: + return ResourceOperation.CREATE; + default: + return ResourceOperation.NONE; + } + } + + private ResourceOperation getResourceOperation(final MatchingRule rule) { + switch (rule) { + case UPDATE: + return ResourceOperation.UPDATE; + case DEPROVISION: + case UNASSIGN: + return ResourceOperation.DELETE; + default: + return ResourceOperation.NONE; + } + } + + protected Subject<?, ?, ?> update(final Subject<?, ?, ?> sbj, final Boolean enabled) { + + final Set<MembershipMod> membsToAdd = new HashSet<>(); + final Set<String> vattrToBeRemoved = new HashSet<>(); + final Set<String> membVattrToBeRemoved = new HashSet<>(); + final Set<AttrMod> vattrToBeUpdated = new HashSet<>(); + + // Search for all mapped vattrs + final Mapping<?> umapping = getMapping(); + for (MappingItem mappingItem : umapping.getItems()) { + if (mappingItem.getIntMappingType() == IntMappingType.UserVirtualSchema) { + vattrToBeRemoved.add(mappingItem.getIntAttrName()); + } else if (mappingItem.getIntMappingType() == IntMappingType.MembershipVirtualSchema) { + membVattrToBeRemoved.add(mappingItem.getIntAttrName()); + } + } + + // Search for all user's vattrs and: + // 1. add mapped vattrs not owned by the user to the set of vattrs to be removed + // 2. add all vattrs owned by the user to the set of vattrs to be update + for (VirAttr vattr : sbj.getVirAttrs()) { + vattrToBeRemoved.remove(vattr.getSchema().getKey()); + final AttrMod mod = new AttrMod(); + mod.setSchema(vattr.getSchema().getKey()); + mod.getValuesToBeAdded().addAll(vattr.getValues()); + vattrToBeUpdated.add(mod); + } + + final boolean changepwd; + + if (sbj instanceof User) { + changepwd = true; + + // Search for memberships + for (Membership membership : User.class.cast(sbj).getMemberships()) { + final MembershipMod membershipMod = new MembershipMod(); + membershipMod.setKey(membership.getKey()); + membershipMod.setRole(membership.getRole().getKey()); + + for (VirAttr vattr : membership.getVirAttrs()) { + membVattrToBeRemoved.remove(vattr.getSchema().getKey()); + final AttrMod mod = new AttrMod(); + mod.setSchema(vattr.getSchema().getKey()); + mod.getValuesToBeAdded().addAll(vattr.getValues()); + membershipMod.getVirAttrsToUpdate().add(mod); + } + + membsToAdd.add(membershipMod); + } + + if (!membsToAdd.isEmpty()) { + membsToAdd.iterator().next().getVirAttrsToRemove().addAll(membVattrToBeRemoved); + } + } else { + changepwd = false; + } + + final List<String> noPropResources = new ArrayList<>(sbj.getResourceNames()); + noPropResources.remove(profile.getTask().getResource().getKey()); + + final PropagationByResource propByRes = new PropagationByResource(); + propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getUpdateTaskIds( + sbj, null, changepwd, enabled, vattrToBeRemoved, vattrToBeUpdated, propByRes, noPropResources, + membsToAdd)); + + return userDAO.authFetch(sbj.getKey()); + } +}
