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/AbstractSyncResultHandler.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java new file mode 100644 index 0000000..04ad24d --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncResultHandler.java @@ -0,0 +1,617 @@ +/* + * 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.Collections; +import java.util.List; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.syncope.common.lib.mod.AbstractSubjectMod; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditElements.Result; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.core.persistence.api.dao.NotFoundException; +import org.apache.syncope.core.persistence.api.entity.AttributableUtil; +import org.apache.syncope.core.persistence.api.entity.task.SyncTask; +import org.apache.syncope.core.provisioning.api.AttributableTransformer; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.sync.SyncActions; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.apache.syncope.core.misc.security.UnauthorizedRoleException; +import org.apache.syncope.core.provisioning.api.sync.SyncopeSyncResultHandler; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.identityconnectors.framework.common.objects.SyncDeltaType; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; + +public abstract class AbstractSyncResultHandler extends AbstractSyncopeResultHandler<SyncTask, SyncActions> + implements SyncopeSyncResultHandler { + + @Autowired + protected SyncUtilities syncUtilities; + + @Autowired + protected AttributableTransformer attrTransformer; + + protected abstract AttributableUtil getAttributableUtil(); + + protected abstract String getName(AbstractSubjectTO subjectTO); + + protected abstract AbstractSubjectTO getSubjectTO(long key); + + protected abstract AbstractSubjectMod getSubjectMod(AbstractSubjectTO subjectTO, SyncDelta delta); + + protected abstract AbstractSubjectTO create(AbstractSubjectTO subjectTO, SyncDelta _delta, ProvisioningResult result); + + protected abstract AbstractSubjectTO link(AbstractSubjectTO before, ProvisioningResult result, boolean unlink); + + protected abstract AbstractSubjectTO update(AbstractSubjectTO before, AbstractSubjectMod subjectMod, + SyncDelta delta, ProvisioningResult result); + + protected abstract void deprovision(Long key, boolean unlink); + + protected abstract void delete(Long key); + + @Override + public boolean handle(final SyncDelta delta) { + try { + doHandle(delta); + return true; + } catch (JobExecutionException e) { + LOG.error("Synchronization failed", e); + return false; + } + } + + protected List<ProvisioningResult> assign(final SyncDelta delta, final AttributableUtil attrUtil) + throws JobExecutionException { + if (!profile.getTask().isPerformCreate()) { + LOG.debug("SyncTask not configured for create"); + return Collections.<ProvisioningResult>emptyList(); + } + + final AbstractSubjectTO subjectTO = + connObjectUtil.getSubjectTO(delta.getObject(), profile.getTask(), attrUtil); + + subjectTO.getResources().add(profile.getTask().getResource().getKey()); + + final ProvisioningResult result = new ProvisioningResult(); + result.setOperation(ResourceOperation.CREATE); + result.setSubjectType(attrUtil.getType()); + result.setStatus(ProvisioningResult.Status.SUCCESS); + + // Attributable transformation (if configured) + AbstractSubjectTO transformed = attrTransformer.transform(subjectTO); + LOG.debug("Transformed: {}", transformed); + + result.setName(getName(transformed)); + + if (profile.isDryRun()) { + result.setId(0L); + } else { + SyncDelta _delta = delta; + for (SyncActions action : profile.getActions()) { + _delta = action.beforeAssign(this.getProfile(), _delta, transformed); + } + + create(transformed, _delta, attrUtil, "assign", result); + } + + return Collections.singletonList(result); + } + + protected List<ProvisioningResult> create(final SyncDelta delta, final AttributableUtil attrUtil) + throws JobExecutionException { + + if (!profile.getTask().isPerformCreate()) { + LOG.debug("SyncTask not configured for create"); + return Collections.<ProvisioningResult>emptyList(); + } + + final AbstractSubjectTO subjectTO = + connObjectUtil.getSubjectTO(delta.getObject(), profile.getTask(), attrUtil); + + // Attributable transformation (if configured) + AbstractSubjectTO transformed = attrTransformer.transform(subjectTO); + LOG.debug("Transformed: {}", transformed); + + final ProvisioningResult result = new ProvisioningResult(); + result.setOperation(ResourceOperation.CREATE); + result.setSubjectType(attrUtil.getType()); + result.setStatus(ProvisioningResult.Status.SUCCESS); + + result.setName(getName(transformed)); + + if (profile.isDryRun()) { + result.setId(0L); + } else { + SyncDelta _delta = delta; + for (SyncActions action : profile.getActions()) { + _delta = action.beforeProvision(this.getProfile(), _delta, transformed); + } + + create(transformed, _delta, attrUtil, "provision", result); + } + + return Collections.<ProvisioningResult>singletonList(result); + } + + private void create( + final AbstractSubjectTO subjectTO, + final SyncDelta delta, + final AttributableUtil attrUtil, + final String operation, + final ProvisioningResult result) + throws JobExecutionException { + + Object output; + Result resultStatus; + + try { + AbstractSubjectTO actual = create(subjectTO, delta, result); + result.setName(getName(actual)); + output = actual; + resultStatus = Result.SUCCESS; + + for (SyncActions action : profile.getActions()) { + action.after(this.getProfile(), delta, actual, result); + } + } catch (PropagationException e) { + // A propagation failure doesn't imply a synchronization failure. + // The propagation exception status will be reported into the propagation task execution. + LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e); + output = e; + resultStatus = Result.FAILURE; + } catch (Exception e) { + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage(ExceptionUtils.getRootCauseMessage(e)); + LOG.error("Could not create {} {} ", attrUtil.getType(), delta.getUid().getUidValue(), e); + output = e; + resultStatus = Result.FAILURE; + } + + audit(operation, resultStatus, null, output, delta); + } + + protected List<ProvisioningResult> update(SyncDelta delta, final List<Long> subjects, + final AttributableUtil attrUtil) + throws JobExecutionException { + + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("SyncTask not configured for update"); + return Collections.<ProvisioningResult>emptyList(); + } + + LOG.debug("About to update {}", subjects); + + List<ProvisioningResult> results = new ArrayList<>(); + + for (Long key : subjects) { + LOG.debug("About to update {}", key); + + final ProvisioningResult result = new ProvisioningResult(); + result.setOperation(ResourceOperation.UPDATE); + result.setSubjectType(attrUtil.getType()); + result.setStatus(ProvisioningResult.Status.SUCCESS); + result.setId(key); + + AbstractSubjectTO before = getSubjectTO(key); + if (before == null) { + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), key)); + } else { + result.setName(getName(before)); + } + + Result resultStatus; + Object output; + if (!profile.isDryRun()) { + if (before == null) { + resultStatus = Result.FAILURE; + output = null; + } else { + try { + final AbstractSubjectMod attributableMod = getSubjectMod(before, delta); + + // Attribute value transformation (if configured) + final AbstractSubjectMod actual = attrTransformer.transform(attributableMod); + LOG.debug("Transformed: {}", actual); + + for (SyncActions action : profile.getActions()) { + delta = action.beforeUpdate(this.getProfile(), delta, before, attributableMod); + } + + final AbstractSubjectTO updated = update(before, attributableMod, delta, result); + + for (SyncActions action : profile.getActions()) { + action.after(this.getProfile(), delta, updated, result); + } + + output = updated; + resultStatus = Result.SUCCESS; + result.setName(getName(updated)); + LOG.debug("{} {} successfully updated", attrUtil.getType(), key); + } catch (PropagationException e) { + // A propagation failure doesn't imply a synchronization failure. + // The propagation exception status will be reported into the propagation task execution. + LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e); + output = e; + resultStatus = Result.FAILURE; + } catch (Exception e) { + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage(ExceptionUtils.getRootCauseMessage(e)); + LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e); + output = e; + resultStatus = Result.FAILURE; + } + } + audit("update", resultStatus, before, output, delta); + } + results.add(result); + } + return results; + } + + protected List<ProvisioningResult> deprovision( + SyncDelta delta, + final List<Long> subjects, + final AttributableUtil attrUtil, + final boolean unlink) + throws JobExecutionException { + + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("SyncTask not configured for update"); + return Collections.<ProvisioningResult>emptyList(); + } + + LOG.debug("About to update {}", subjects); + + final List<ProvisioningResult> updResults = new ArrayList<>(); + + for (Long id : subjects) { + LOG.debug("About to unassign resource {}", id); + + Object output; + Result resultStatus; + + final ProvisioningResult result = new ProvisioningResult(); + result.setOperation(ResourceOperation.DELETE); + result.setSubjectType(attrUtil.getType()); + result.setStatus(ProvisioningResult.Status.SUCCESS); + result.setId(id); + + final AbstractSubjectTO before = getSubjectTO(id); + + if (before == null) { + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), id)); + } + + if (!profile.isDryRun()) { + if (before == null) { + resultStatus = Result.FAILURE; + output = null; + } else { + result.setName(getName(before)); + + try { + if (unlink) { + for (SyncActions action : profile.getActions()) { + action.beforeUnassign(this.getProfile(), delta, before); + } + } else { + for (SyncActions action : profile.getActions()) { + action.beforeDeprovision(this.getProfile(), delta, before); + } + } + + deprovision(id, unlink); + output = getSubjectTO(id); + + for (SyncActions action : profile.getActions()) { + action.after(this.getProfile(), delta, AbstractSubjectTO.class.cast(output), result); + } + + resultStatus = Result.SUCCESS; + LOG.debug("{} {} successfully updated", attrUtil.getType(), id); + } catch (PropagationException e) { + // A propagation failure doesn't imply a synchronization failure. + // The propagation exception status will be reported into the propagation task execution. + LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e); + output = e; + resultStatus = Result.FAILURE; + } catch (Exception e) { + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage(ExceptionUtils.getRootCauseMessage(e)); + LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e); + output = e; + resultStatus = Result.FAILURE; + } + } + audit(unlink ? "unassign" : "deprovision", resultStatus, before, output, delta); + } + updResults.add(result); + } + + return updResults; + } + + protected List<ProvisioningResult> link( + SyncDelta delta, + final List<Long> subjects, + final AttributableUtil attrUtil, + final boolean unlink) + throws JobExecutionException { + + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("SyncTask not configured for update"); + return Collections.<ProvisioningResult>emptyList(); + } + + LOG.debug("About to update {}", subjects); + + final List<ProvisioningResult> updResults = new ArrayList<>(); + + for (Long id : subjects) { + LOG.debug("About to unassign resource {}", id); + + Object output; + Result resultStatus; + + final ProvisioningResult result = new ProvisioningResult(); + result.setOperation(ResourceOperation.NONE); + result.setSubjectType(attrUtil.getType()); + result.setStatus(ProvisioningResult.Status.SUCCESS); + result.setId(id); + + final AbstractSubjectTO before = getSubjectTO(id); + + if (before == null) { + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage(String.format("Subject '%s(%d)' not found", attrUtil.getType().name(), id)); + } + + if (!profile.isDryRun()) { + if (before == null) { + resultStatus = Result.FAILURE; + output = null; + } else { + result.setName(getName(before)); + + try { + if (unlink) { + for (SyncActions action : profile.getActions()) { + action.beforeUnlink(this.getProfile(), delta, before); + } + } else { + for (SyncActions action : profile.getActions()) { + action.beforeLink(this.getProfile(), delta, before); + } + } + + output = link(before, result, unlink); + + for (SyncActions action : profile.getActions()) { + action.after(this.getProfile(), delta, AbstractSubjectTO.class.cast(output), result); + } + + resultStatus = Result.SUCCESS; + LOG.debug("{} {} successfully updated", attrUtil.getType(), id); + } catch (PropagationException e) { + // A propagation failure doesn't imply a synchronization failure. + // The propagation exception status will be reported into the propagation task execution. + LOG.error("Could not propagate {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e); + output = e; + resultStatus = Result.FAILURE; + } catch (Exception e) { + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage(ExceptionUtils.getRootCauseMessage(e)); + LOG.error("Could not update {} {}", attrUtil.getType(), delta.getUid().getUidValue(), e); + output = e; + resultStatus = Result.FAILURE; + } + } + audit(unlink ? "unlink" : "link", resultStatus, before, output, delta); + } + updResults.add(result); + } + + return updResults; + } + + protected List<ProvisioningResult> delete( + SyncDelta delta, final List<Long> subjects, final AttributableUtil attrUtil) + throws JobExecutionException { + + if (!profile.getTask().isPerformDelete()) { + LOG.debug("SyncTask not configured for delete"); + return Collections.<ProvisioningResult>emptyList(); + } + + LOG.debug("About to delete {}", subjects); + + List<ProvisioningResult> delResults = new ArrayList<>(); + + for (Long id : subjects) { + Object output; + Result resultStatus = Result.FAILURE; + + AbstractSubjectTO before = null; + final ProvisioningResult result = new ProvisioningResult(); + + try { + before = getSubjectTO(id); + + result.setId(id); + result.setName(getName(before)); + result.setOperation(ResourceOperation.DELETE); + result.setSubjectType(attrUtil.getType()); + result.setStatus(ProvisioningResult.Status.SUCCESS); + + if (!profile.isDryRun()) { + for (SyncActions action : profile.getActions()) { + delta = action.beforeDelete(this.getProfile(), delta, before); + } + + try { + delete(id); + output = null; + resultStatus = Result.SUCCESS; + } catch (Exception e) { + result.setStatus(ProvisioningResult.Status.FAILURE); + result.setMessage(ExceptionUtils.getRootCauseMessage(e)); + LOG.error("Could not delete {} {}", attrUtil.getType(), id, e); + output = e; + } + + for (SyncActions action : profile.getActions()) { + action.after(this.getProfile(), delta, before, result); + } + + audit("delete", resultStatus, before, output, delta); + } + + delResults.add(result); + + } catch (NotFoundException e) { + LOG.error("Could not find {} {}", attrUtil.getType(), id, e); + } catch (UnauthorizedRoleException e) { + LOG.error("Not allowed to read {} {}", attrUtil.getType(), id, e); + } catch (Exception e) { + LOG.error("Could not delete {} {}", attrUtil.getType(), id, e); + } + } + + return delResults; + } + + /** + * Look into SyncDelta and take necessary profile.getActions() (create / update / delete) on user(s)/role(s). + * + * @param delta returned by the underlying profile.getConnector() + * @throws JobExecutionException in case of synchronization failure. + */ + protected final void doHandle(final SyncDelta delta) + throws JobExecutionException { + + final AttributableUtil attrUtil = getAttributableUtil(); + + LOG.debug("Process {} for {} as {}", + delta.getDeltaType(), delta.getUid().getUidValue(), delta.getObject().getObjectClass()); + + final String uid = delta.getPreviousUid() == null + ? delta.getUid().getUidValue() + : delta.getPreviousUid().getUidValue(); + + try { + List<Long> subjectKeys = syncUtilities.findExisting( + uid, delta.getObject(), profile.getTask().getResource(), attrUtil); + + if (subjectKeys.size() > 1) { + switch (profile.getResAct()) { + case IGNORE: + throw new IllegalStateException("More than one match " + subjectKeys); + + case FIRSTMATCH: + subjectKeys = subjectKeys.subList(0, 1); + break; + + case LASTMATCH: + subjectKeys = subjectKeys.subList(subjectKeys.size() - 1, subjectKeys.size()); + break; + + default: + // keep subjectIds as is + } + } + + if (SyncDeltaType.CREATE_OR_UPDATE == delta.getDeltaType()) { + if (subjectKeys.isEmpty()) { + switch (profile.getTask().getUnmatchingRule()) { + case ASSIGN: + profile.getResults().addAll(assign(delta, attrUtil)); + break; + case PROVISION: + profile.getResults().addAll(create(delta, attrUtil)); + break; + default: + // do nothing + } + } else { + switch (profile.getTask().getMatchingRule()) { + case UPDATE: + profile.getResults().addAll(update(delta, subjectKeys, attrUtil)); + break; + case DEPROVISION: + profile.getResults().addAll(deprovision(delta, subjectKeys, attrUtil, false)); + break; + case UNASSIGN: + profile.getResults().addAll(deprovision(delta, subjectKeys, attrUtil, true)); + break; + case LINK: + profile.getResults().addAll(link(delta, subjectKeys, attrUtil, false)); + break; + case UNLINK: + profile.getResults().addAll(link(delta, subjectKeys, attrUtil, true)); + break; + default: + // do nothing + } + } + } else if (SyncDeltaType.DELETE == delta.getDeltaType()) { + if (subjectKeys.isEmpty()) { + LOG.debug("No match found for deletion"); + } else { + profile.getResults().addAll(delete(delta, subjectKeys, attrUtil)); + } + } + } catch (IllegalStateException | IllegalArgumentException e) { + LOG.warn(e.getMessage()); + } + } + + private void audit( + final String event, + final Result result, + final Object before, + final Object output, + final Object... input) { + + notificationManager.createTasks( + AuditElements.EventCategoryType.SYNCHRONIZATION, + getAttributableUtil().getType().name().toLowerCase(), + profile.getTask().getResource().getKey(), + event, + result, + before, + output, + input); + + auditManager.audit( + AuditElements.EventCategoryType.SYNCHRONIZATION, + getAttributableUtil().getType().name().toLowerCase(), + profile.getTask().getResource().getKey(), + event, + result, + before, + output, + input); + } +}
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/AbstractSyncopeResultHandler.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncopeResultHandler.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncopeResultHandler.java new file mode 100644 index 0000000..c65df96 --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/AbstractSyncopeResultHandler.java @@ -0,0 +1,126 @@ +/* + * 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 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.AttributableUtilFactory; +import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask; +import org.apache.syncope.core.provisioning.api.RoleProvisioningManager; +import org.apache.syncope.core.provisioning.api.data.RoleDataBinder; +import org.apache.syncope.core.provisioning.api.UserProvisioningManager; +import org.apache.syncope.core.provisioning.api.data.UserDataBinder; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningActions; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.sync.SyncopeResultHandler; +import org.apache.syncope.core.misc.AuditManager; +import org.apache.syncope.core.misc.ConnObjectUtil; +import org.apache.syncope.core.provisioning.api.notification.NotificationManager; +import org.apache.syncope.core.workflow.api.RoleWorkflowAdapter; +import org.apache.syncope.core.workflow.api.UserWorkflowAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +public abstract class AbstractSyncopeResultHandler<T extends ProvisioningTask, A extends ProvisioningActions> + implements SyncopeResultHandler<T, A> { + + /** + * Logger. + */ + protected static final Logger LOG = LoggerFactory.getLogger(AbstractSyncopeResultHandler.class); + + @Autowired + protected UserDAO userDAO; + + @Autowired + protected RoleDAO roleDAO; + + /** + * ConnectorObject util. + */ + @Autowired + protected ConnObjectUtil connObjectUtil; + + /** + * Notification Manager. + */ + @Autowired + protected NotificationManager notificationManager; + + /** + * Audit Manager. + */ + @Autowired + protected AuditManager auditManager; + + /** + * Propagation manager. + */ + @Autowired + protected PropagationManager propagationManager; + + /** + * Task executor. + */ + @Autowired + protected PropagationTaskExecutor taskExecutor; + + /** + * User workflow adapter. + */ + @Autowired + protected UserWorkflowAdapter uwfAdapter; + + /** + * Role workflow adapter. + */ + @Autowired + protected RoleWorkflowAdapter rwfAdapter; + + @Autowired + protected UserDataBinder userTransfer; + + @Autowired + protected RoleDataBinder roleTransfer; + + @Autowired + protected UserProvisioningManager userProvisioningManager; + + @Autowired + protected RoleProvisioningManager roleProvisioningManager; + + @Autowired + protected AttributableUtilFactory attrUtilFactory; + + /** + * Sync profile. + */ + protected ProvisioningProfile<T, A> profile; + + public void setProfile(final ProvisioningProfile<T, A> profile) { + this.profile = profile; + } + + public ProvisioningProfile<T, A> getProfile() { + return profile; + } +} 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/DBPasswordSyncActions.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DBPasswordSyncActions.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DBPasswordSyncActions.java new file mode 100644 index 0000000..75f2078 --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DBPasswordSyncActions.java @@ -0,0 +1,139 @@ +/* + * 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.Iterator; +import org.apache.syncope.common.lib.mod.AbstractSubjectMod; +import org.apache.syncope.common.lib.mod.UserMod; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.to.UserTO; +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.user.User; +import org.apache.syncope.core.provisioning.api.Connector; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +/** + * A SyncActions implementation which allows the ability to import passwords from a Database + * backend, where the passwords are hashed according to the password cipher algorithm property + * of the (DB) Connector and HEX-encoded. + */ +public class DBPasswordSyncActions extends DefaultSyncActions { + + private static final Logger LOG = LoggerFactory.getLogger(DBPasswordSyncActions.class); + + private static final String CLEARTEXT = "CLEARTEXT"; + + @Autowired + private UserDAO userDAO; + + private String encodedPassword; + + private CipherAlgorithm cipher; + + @Transactional(readOnly = true) + @Override + public <T extends AbstractSubjectTO> SyncDelta beforeProvision( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final T subject) throws JobExecutionException { + + if (subject instanceof UserTO) { + String password = ((UserTO) subject).getPassword(); + parseEncodedPassword(password, profile.getConnector()); + } + + return delta; + } + + @Transactional(readOnly = true) + @Override + public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final T subject, + final K subjectMod) throws JobExecutionException { + + if (subjectMod instanceof UserMod) { + String modPassword = ((UserMod) subjectMod).getPassword(); + parseEncodedPassword(modPassword, profile.getConnector()); + } + + return delta; + } + + private void parseEncodedPassword(final String password, final Connector connector) { + if (password != null) { + ConnInstance connInstance = connector.getActiveConnInstance(); + + String cipherAlgorithm = getCipherAlgorithm(connInstance); + if (!CLEARTEXT.equals(cipherAlgorithm)) { + try { + encodedPassword = password; + cipher = CipherAlgorithm.valueOf(cipherAlgorithm); + } catch (IllegalArgumentException e) { + LOG.error("Cipher algorithm not allowed: {}", cipherAlgorithm, e); + encodedPassword = null; + } + } + } + } + + private String getCipherAlgorithm(final ConnInstance connInstance) { + String cipherAlgorithm = CLEARTEXT; + for (Iterator<ConnConfProperty> propertyIterator = connInstance.getConfiguration().iterator(); + propertyIterator.hasNext();) { + + ConnConfProperty property = propertyIterator.next(); + if ("cipherAlgorithm".equals(property.getSchema().getName()) + && property.getValues() != null && !property.getValues().isEmpty()) { + + return (String) property.getValues().get(0); + } + } + return cipherAlgorithm; + } + + @Transactional(readOnly = true) + @Override + public <T extends AbstractSubjectTO> void after( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final T subject, + final ProvisioningResult result) throws JobExecutionException { + + if (subject instanceof UserTO && encodedPassword != null && cipher != null) { + User syncopeUser = userDAO.find(subject.getKey()); + if (syncopeUser != null) { + syncopeUser.setEncodedPassword(encodedPassword.toUpperCase(), cipher); + } + encodedPassword = null; + cipher = 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/DefaultPushActions.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DefaultPushActions.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DefaultPushActions.java new file mode 100644 index 0000000..4517f78 --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DefaultPushActions.java @@ -0,0 +1,88 @@ +/* + * 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 org.apache.syncope.core.persistence.api.entity.Subject; +import org.apache.syncope.core.provisioning.api.sync.PushActions; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.quartz.JobExecutionException; + +/** + * Default (empty) implementation of PushActions. + */ +public abstract class DefaultPushActions implements PushActions { + + @Override + public void beforeAll(final ProvisioningProfile<?, ?> profile) throws JobExecutionException { + } + + @Override + public <T extends Subject<?, ?, ?>> T beforeAssign(final ProvisioningProfile<?, ?> profile, final T subject) + throws JobExecutionException { + + return subject; + } + + @Override + public <T extends Subject<?, ?, ?>> T beforeProvision(final ProvisioningProfile<?, ?> profile, final T subject) + throws JobExecutionException { + + return subject; + } + + @Override + public <T extends Subject<?, ?, ?>> T beforeLink(final ProvisioningProfile<?, ?> profile, final T subject) + throws JobExecutionException { + + return subject; + } + + @Override + public <T extends Subject<?, ?, ?>> T beforeUnassign(final ProvisioningProfile<?, ?> profile, final T subject) + throws JobExecutionException { + + return subject; + } + + @Override + public <T extends Subject<?, ?, ?>> T beforeDeprovision(final ProvisioningProfile<?, ?> profile, final T subject) + throws JobExecutionException { + + return subject; + } + + @Override + public <T extends Subject<?, ?, ?>> T beforeUnlink(final ProvisioningProfile<?, ?> profile, final T subject) + throws JobExecutionException { + + return subject; + } + + @Override + public <T extends Subject<?, ?, ?>> void after( + final ProvisioningProfile<?, ?> profile, final T subject, final ProvisioningResult result) + throws JobExecutionException { + } + + @Override + public void afterAll(final ProvisioningProfile<?, ?> profile) + throws JobExecutionException { + } +} 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/DefaultSyncActions.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DefaultSyncActions.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DefaultSyncActions.java new file mode 100644 index 0000000..e29847d --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/DefaultSyncActions.java @@ -0,0 +1,115 @@ +/* + * 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 org.apache.syncope.common.lib.mod.AbstractSubjectMod; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.core.provisioning.api.sync.SyncActions; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.quartz.JobExecutionException; + +/** + * Default (empty) implementation of SyncActions. + */ +public abstract class DefaultSyncActions implements SyncActions { + + @Override + public void beforeAll(final ProvisioningProfile<?, ?> profile) throws JobExecutionException { + } + + @Override + public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final T subject, + final K subjectMod) throws JobExecutionException { + + return delta; + } + + @Override + public <T extends AbstractSubjectTO> SyncDelta beforeDelete( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject) + throws JobExecutionException { + + return delta; + } + + @Override + public <T extends AbstractSubjectTO> SyncDelta beforeAssign( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject) + throws JobExecutionException { + + return delta; + } + + @Override + public <T extends AbstractSubjectTO> SyncDelta beforeProvision( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject) + throws JobExecutionException { + + return delta; + } + + @Override + public <T extends AbstractSubjectTO> SyncDelta beforeLink( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject) + throws JobExecutionException { + + return delta; + } + + @Override + public <T extends AbstractSubjectTO> SyncDelta beforeUnassign( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject) + throws JobExecutionException { + + return delta; + } + + @Override + public <T extends AbstractSubjectTO> SyncDelta beforeDeprovision( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject) + throws JobExecutionException { + + return delta; + } + + @Override + public <T extends AbstractSubjectTO> SyncDelta beforeUnlink( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject) + throws JobExecutionException { + + return delta; + } + + @Override + public <T extends AbstractSubjectTO> void after( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final T subject, + final ProvisioningResult result) + throws JobExecutionException { + } + + @Override + public void afterAll(final ProvisioningProfile<?, ?> profile) + throws JobExecutionException { + } +} 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/LDAPMembershipSyncActions.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/LDAPMembershipSyncActions.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/LDAPMembershipSyncActions.java new file mode 100644 index 0000000..adc04ad --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/LDAPMembershipSyncActions.java @@ -0,0 +1,309 @@ +/* + * 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.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.apache.syncope.common.lib.mod.AbstractSubjectMod; +import org.apache.syncope.common.lib.mod.MembershipMod; +import org.apache.syncope.common.lib.mod.UserMod; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.to.RoleTO; +import org.apache.syncope.common.lib.types.AuditElements; +import org.apache.syncope.common.lib.types.AuditElements.Result; +import org.apache.syncope.common.lib.types.ConnConfProperty; +import org.apache.syncope.core.persistence.api.dao.RoleDAO; +import org.apache.syncope.core.persistence.api.entity.ConnInstance; +import org.apache.syncope.core.persistence.api.entity.ExternalResource; +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.task.ProvisioningTask; +import org.apache.syncope.core.persistence.api.entity.task.SyncTask; +import org.apache.syncope.core.provisioning.api.Connector; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.propagation.PropagationException; +import org.apache.syncope.core.provisioning.api.propagation.PropagationManager; +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.apache.syncope.core.misc.AuditManager; +import org.apache.syncope.core.provisioning.api.notification.NotificationManager; +import org.apache.syncope.core.workflow.api.UserWorkflowAdapter; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.OperationOptionsBuilder; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Simple action for synchronizing LDAP groups memberships to Syncope role memberships, when the same resource is + * configured for both users and roles. + * + * @see org.apache.syncope.core.propagation.impl.LDAPMembershipPropagationActions + */ +public class LDAPMembershipSyncActions extends DefaultSyncActions { + + protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipSyncActions.class); + + @Autowired + protected RoleDAO roleDAO; + + @Autowired + protected UserWorkflowAdapter uwfAdapter; + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + private PropagationTaskExecutor taskExecutor; + + @Autowired + private NotificationManager notificationManager; + + @Autowired + private AuditManager auditManager; + + @Autowired + private SyncUtilities syncUtilities; + + protected Map<Long, Long> membersBeforeRoleUpdate = Collections.<Long, Long>emptyMap(); + + /** + * Allows easy subclassing for the ConnId AD connector bundle. + * + * @param connector A Connector instance to query for the groupMemberAttribute property name + * @return the name of the attribute used to keep track of group memberships + */ + protected String getGroupMembershipAttrName(final Connector connector) { + ConnInstance connInstance = connector.getActiveConnInstance(); + Iterator<ConnConfProperty> propertyIterator = connInstance.getConfiguration().iterator(); + String groupMembershipName = "uniquemember"; + while (propertyIterator.hasNext()) { + ConnConfProperty property = propertyIterator.next(); + if ("groupMemberAttribute".equals(property.getSchema().getName()) + && property.getValues() != null && !property.getValues().isEmpty()) { + + groupMembershipName = (String) property.getValues().get(0); + break; + } + } + + return groupMembershipName; + } + + /** + * Keep track of members of the role being updated <b>before</b> actual update takes place. This is not needed on + * <ul> <li>beforeProvision() - because the synchronizing role does not exist yet on Syncope</li> <li>beforeDelete() + * - + * because role delete cascades as membership removal for all users involved</li> </ul> + * + * {@inheritDoc} + */ + @Override + public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, final T subject, final K subjectMod) throws JobExecutionException { + + if (subject instanceof RoleTO) { + // search for all users assigned to given role + Role role = roleDAO.find(subject.getKey()); + if (role != null) { + List<Membership> membs = roleDAO.findMemberships(role); + // save memberships before role update takes place + membersBeforeRoleUpdate = new HashMap<>(membs.size()); + for (Membership memb : membs) { + membersBeforeRoleUpdate.put(memb.getUser().getKey(), memb.getKey()); + } + } + } + + return super.beforeUpdate(profile, delta, subject, subjectMod); + } + + /** + * Build UserMod for adding membership to given user, for given role. + * + * @param userKey user to be assigned membership to given role + * @param roleTO role for adding membership + * @return UserMod for user update + */ + protected UserMod getUserMod(final Long userKey, final RoleTO roleTO) { + UserMod userMod = new UserMod(); + // no actual modification takes place when user has already the role assigned + if (membersBeforeRoleUpdate.containsKey(userKey)) { + membersBeforeRoleUpdate.remove(userKey); + } else { + userMod.setKey(userKey); + + MembershipMod membershipMod = new MembershipMod(); + membershipMod.setRole(roleTO.getKey()); + userMod.getMembershipsToAdd().add(membershipMod); + } + + return userMod; + } + + /** + * Read values of attribute returned by getGroupMembershipAttrName(); if not present in the given delta, perform an + * additional read on the underlying connector. + * + * @param delta representing the synchronizing role + * @param connector associated to the current resource + * @return value of attribute returned by + * {@link #getGroupMembershipAttrName(org.apache.syncope.core.propagation.Connector) } + */ + protected List<Object> getMembAttrValues(final SyncDelta delta, final Connector connector) { + List<Object> result = Collections.<Object>emptyList(); + String groupMemberName = getGroupMembershipAttrName(connector); + + // first, try to read the configured attribute from delta, returned by the ongoing synchronization + Attribute membAttr = delta.getObject().getAttributeByName(groupMemberName); + // if not found, perform an additional read on the underlying connector for the same connector object + if (membAttr == null) { + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(groupMemberName); + membAttr = connector.getObjectAttribute(ObjectClass.GROUP, delta.getUid(), oob.build(), groupMemberName); + } + if (membAttr != null && membAttr.getValue() != null) { + result = membAttr.getValue(); + } + + return result; + } + + /** + * Perform actual modifications (i.e. membership add / remove) for the given role on the given resource. + * + * @param userMod modifications to perform on the user + * @param resourceName resource to be propagated for changes + */ + protected void userUpdate(final UserMod userMod, final String resourceName) { + if (userMod.getKey() == 0) { + return; + } + + Result result; + + WorkflowResult<Map.Entry<UserMod, Boolean>> updated = null; + + try { + updated = uwfAdapter.update(userMod); + + List<PropagationTask> tasks = propagationManager.getUserUpdateTaskIds( + updated, false, Collections.singleton(resourceName)); + + taskExecutor.execute(tasks); + result = Result.SUCCESS; + } catch (PropagationException e) { + result = Result.FAILURE; + LOG.error("Could not propagate {}", userMod, e); + } catch (Exception e) { + result = Result.FAILURE; + LOG.error("Could not perform update {}", userMod, e); + } + + notificationManager.createTasks( + AuditElements.EventCategoryType.SYNCHRONIZATION, + this.getClass().getSimpleName(), + null, + "update", + result, + null, // searching for before object is too much expensive ... + updated == null ? null : updated.getResult().getKey(), + userMod, + resourceName); + + auditManager.audit( + AuditElements.EventCategoryType.SYNCHRONIZATION, + this.getClass().getSimpleName(), + null, + "update", + result, + null, // searching for before object is too much expensive ... + updated == null ? null : updated.getResult().getKey(), + userMod, + resourceName); + } + + /** + * Synchronize Syncope memberships with the situation read on the external resource's group. + * + * @param profile sync profile + * @param delta representing the synchronizing role + * @param roleTO role after modification performed by the handler + * @throws JobExecutionException if anything goes wrong + */ + protected void synchronizeMemberships( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final RoleTO roleTO) throws + JobExecutionException { + + final ProvisioningTask task = profile.getTask(); + final ExternalResource resource = task.getResource(); + final Connector connector = profile.getConnector(); + + for (Object membValue : getMembAttrValues(delta, connector)) { + Long userKey = syncUtilities.findMatchingAttributableKey( + ObjectClass.ACCOUNT, + membValue.toString(), + profile.getTask().getResource(), + profile.getConnector()); + if (userKey != null) { + UserMod userMod = getUserMod(userKey, roleTO); + userUpdate(userMod, resource.getKey()); + } + } + + // finally remove any residual membership that was present before role update but not any more + for (Map.Entry<Long, Long> member : membersBeforeRoleUpdate.entrySet()) { + UserMod userMod = new UserMod(); + userMod.setKey(member.getKey()); + userMod.getMembershipsToRemove().add(member.getValue()); + userUpdate(userMod, resource.getKey()); + } + } + + /** + * Synchronize membership at role synchronization time (because SyncJob first synchronize users then roles). + * {@inheritDoc} + */ + @Override + public <T extends AbstractSubjectTO> void after( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final T subject, + final ProvisioningResult result) throws JobExecutionException { + + if (!(profile.getTask() instanceof SyncTask)) { + return; + } + + if (!(subject instanceof RoleTO) || profile.getTask().getResource().getUmapping() == null) { + super.after(profile, delta, subject, result); + } else { + synchronizeMemberships(profile, delta, (RoleTO) subject); + } + } +} 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/LDAPPasswordSyncActions.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/LDAPPasswordSyncActions.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/LDAPPasswordSyncActions.java new file mode 100644 index 0000000..16cd796 --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/LDAPPasswordSyncActions.java @@ -0,0 +1,123 @@ +/* + * 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 org.apache.syncope.common.lib.mod.AbstractSubjectMod; +import org.apache.syncope.common.lib.mod.UserMod; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.CipherAlgorithm; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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; + +/** + * A SyncActions implementation which allows the ability to import passwords from an LDAP backend + * that are hashed. + */ +public class LDAPPasswordSyncActions extends DefaultSyncActions { + + protected static final Logger LOG = LoggerFactory.getLogger(LDAPPasswordSyncActions.class); + + @Autowired + private UserDAO userDAO; + + private String encodedPassword; + + private CipherAlgorithm cipher; + + @Transactional(readOnly = true) + @Override + public <T extends AbstractSubjectTO> SyncDelta beforeProvision( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final T subject) throws JobExecutionException { + + if (subject instanceof UserTO) { + String password = ((UserTO) subject).getPassword(); + parseEncodedPassword(password); + } + + return delta; + } + + @Transactional(readOnly = true) + @Override + public <T extends AbstractSubjectTO, K extends AbstractSubjectMod> SyncDelta beforeUpdate( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final T subject, + final K subjectMod) throws JobExecutionException { + + if (subjectMod instanceof UserMod) { + String modPassword = ((UserMod) subjectMod).getPassword(); + parseEncodedPassword(modPassword); + } + + return delta; + } + + private void parseEncodedPassword(String password) { + if (password != null && password.startsWith("{")) { + int closingBracketIndex = password.indexOf('}'); + String digest = password.substring(1, password.indexOf('}')); + if (digest != null) { + digest = digest.toUpperCase(); + } + try { + encodedPassword = password.substring(closingBracketIndex + 1); + cipher = CipherAlgorithm.valueOf(digest); + } catch (IllegalArgumentException e) { + LOG.error("Cipher algorithm not allowed: {}", digest, e); + encodedPassword = null; + } + } + } + + @Transactional(readOnly = true) + @Override + public <T extends AbstractSubjectTO> void after( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final T subject, + final ProvisioningResult result) throws JobExecutionException { + + if (subject instanceof UserTO && encodedPassword != null && cipher != null) { + User syncopeUser = userDAO.find(subject.getKey()); + if (syncopeUser != null) { + byte[] encodedPasswordBytes = Base64.decode(encodedPassword.getBytes()); + char[] encodedHex = Hex.encode(encodedPasswordBytes); + String encodedHexStr = new String(encodedHex).toUpperCase(); + + syncopeUser.setEncodedPassword(encodedHexStr, cipher); + } + encodedPassword = null; + cipher = 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/PushJobImpl.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/PushJobImpl.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/PushJobImpl.java new file mode 100644 index 0000000..90c2ffd --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/PushJobImpl.java @@ -0,0 +1,176 @@ +/* + * 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.Collections; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.types.SubjectType; +import org.apache.syncope.core.persistence.api.RoleEntitlementUtil; +import org.apache.syncope.core.persistence.api.dao.RoleDAO; +import org.apache.syncope.core.persistence.api.dao.SubjectSearchDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.entity.role.RMapping; +import org.apache.syncope.core.persistence.api.entity.role.Role; +import org.apache.syncope.core.persistence.api.entity.task.PushTask; +import org.apache.syncope.core.persistence.api.entity.user.UMapping; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.Connector; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.sync.PushActions; +import org.apache.syncope.core.misc.spring.ApplicationContextProvider; +import org.apache.syncope.core.misc.search.SearchCondConverter; +import org.apache.syncope.core.provisioning.api.job.PushJob; +import org.apache.syncope.core.provisioning.api.sync.RolePushResultHandler; +import org.apache.syncope.core.provisioning.api.sync.UserPushResultHandler; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.support.AbstractBeanDefinition; + +/** + * Job for executing synchronization (towards external resource) tasks. + * + * @see AbstractProvisioningJob + * @see PushTask + * @see PushActions + */ +public class PushJobImpl extends AbstractProvisioningJob<PushTask, PushActions> implements PushJob { + + /** + * User DAO. + */ + @Autowired + private UserDAO userDAO; + + /** + * Search DAO. + */ + @Autowired + private SubjectSearchDAO searchDAO; + + /** + * Role DAO. + */ + @Autowired + private RoleDAO roleDAO; + + private final int PAGE_SIZE = 1000; + + @Override + protected String executeWithSecurityContext( + final PushTask pushTask, + final Connector connector, + final UMapping uMapping, + final RMapping rMapping, + final boolean dryRun) throws JobExecutionException { + LOG.debug("Execute synchronization (push) with resource {}", pushTask.getResource()); + + final Set<Long> authorizations = RoleEntitlementUtil.getRoleKeys(entitlementDAO.findAll()); + + final ProvisioningProfile<PushTask, PushActions> profile = new ProvisioningProfile<>(connector, pushTask); + if (actions != null) { + profile.getActions().addAll(actions); + } + profile.setDryRun(dryRun); + profile.setResAct(null); + + final UserPushResultHandler uhandler = + (UserPushResultHandler) ApplicationContextProvider.getApplicationContext().getBeanFactory(). + createBean(UserPushResultHandlerImpl.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false); + uhandler.setProfile(profile); + + final RolePushResultHandler rhandler = + (RolePushResultHandler) ApplicationContextProvider.getApplicationContext().getBeanFactory(). + createBean(RolePushResultHandlerImpl.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false); + rhandler.setProfile(profile); + + if (actions != null && !profile.isDryRun()) { + for (PushActions action : actions) { + action.beforeAll(profile); + } + } + + if (uMapping != null) { + final int count = userDAO.count(authorizations); + for (int page = 1; page <= (count / PAGE_SIZE) + 1; page++) { + final List<User> localUsers = getUsers(authorizations, pushTask, page); + + for (User localUser : localUsers) { + try { + // user propagation + uhandler.handle(localUser.getKey()); + } catch (Exception e) { + LOG.warn("Failure pushing user '{}' on '{}'", localUser, pushTask.getResource(), e); + throw new JobExecutionException("While pushing users on connector", e); + } + } + } + } + + if (rMapping != null) { + final List<Role> localRoles = getRoles(authorizations, pushTask); + + for (Role localRole : localRoles) { + try { + // role propagation + rhandler.handle(localRole.getKey()); + } catch (Exception e) { + LOG.warn("Failure pushing role '{}' on '{}'", localRole, pushTask.getResource(), e); + throw new JobExecutionException("While pushing roles on connector", e); + } + } + } + + if (actions != null && !profile.isDryRun()) { + for (PushActions action : actions) { + action.afterAll(profile); + } + } + + final String result = createReport(profile.getResults(), pushTask.getResource().getSyncTraceLevel(), dryRun); + + LOG.debug("Sync result: {}", result); + + return result; + } + + private List<User> getUsers(final Set<Long> authorizations, final PushTask pushTask, final int page) { + final String filter = pushTask.getUserFilter(); + if (StringUtils.isBlank(filter)) { + return userDAO.findAll(authorizations, page, PAGE_SIZE); + } else { + return searchDAO.<User>search( + authorizations, SearchCondConverter.convert(filter), + Collections.<OrderByClause>emptyList(), SubjectType.USER); + } + } + + private List<Role> getRoles(final Set<Long> authorizations, final PushTask pushTask) { + final String filter = pushTask.getRoleFilter(); + if (StringUtils.isBlank(filter)) { + return roleDAO.findAll(); + } else { + return searchDAO.<Role>search( + authorizations, SearchCondConverter.convert(filter), + Collections.<OrderByClause>emptyList(), SubjectType.ROLE); + } + } +} 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/RolePushResultHandlerImpl.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RolePushResultHandlerImpl.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RolePushResultHandlerImpl.java new file mode 100644 index 0000000..d51b26f --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RolePushResultHandlerImpl.java @@ -0,0 +1,155 @@ +/* + * 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.Collections; +import java.util.List; +import org.apache.syncope.common.lib.mod.RoleMod; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.to.RoleTO; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +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.role.Role; +import org.apache.syncope.core.provisioning.api.TimeoutException; +import org.apache.syncope.core.provisioning.api.sync.RolePushResultHandler; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.Uid; + +public class RolePushResultHandlerImpl extends AbstractPushResultHandler implements RolePushResultHandler { + + @Override + protected Subject<?, ?, ?> deprovision(final Subject<?, ?, ?> sbj) { + final RoleTO before = roleTransfer.getRoleTO(Role.class.cast(sbj)); + + final List<String> noPropResources = new ArrayList<>(before.getResources()); + noPropResources.remove(profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getRoleDeleteTaskIds(before.getKey(), noPropResources)); + + return roleDAO.authFetch(before.getKey()); + } + + @Override + protected Subject<?, ?, ?> provision(final Subject<?, ?, ?> sbj, final Boolean enabled) { + final RoleTO before = roleTransfer.getRoleTO(Role.class.cast(sbj)); + + final List<String> noPropResources = new ArrayList<>(before.getResources()); + noPropResources.remove(profile.getTask().getResource().getKey()); + + final PropagationByResource propByRes = new PropagationByResource(); + propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getRoleCreateTaskIds( + before.getKey(), + Collections.unmodifiableCollection(before.getVirAttrs()), + propByRes, + noPropResources)); + + return roleDAO.authFetch(before.getKey()); + } + + @Override + protected Subject<?, ?, ?> link(final Subject<?, ?, ?> sbj, final Boolean unlink) { + final RoleMod roleMod = new RoleMod(); + roleMod.setKey(sbj.getKey()); + + if (unlink) { + roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey()); + } else { + roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey()); + } + + rwfAdapter.update(roleMod); + + return roleDAO.authFetch(sbj.getKey()); + } + + @Override + protected Subject<?, ?, ?> unassign(final Subject<?, ?, ?> sbj) { + final RoleMod roleMod = new RoleMod(); + roleMod.setKey(sbj.getKey()); + roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey()); + rwfAdapter.update(roleMod); + return deprovision(sbj); + } + + @Override + protected Subject<?, ?, ?> assign(final Subject<?, ?, ?> sbj, final Boolean enabled) { + final RoleMod roleMod = new RoleMod(); + roleMod.setKey(sbj.getKey()); + roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey()); + rwfAdapter.update(roleMod); + return provision(sbj, enabled); + } + + @Override + protected String getName(final Subject<?, ?, ?> subject) { + return Role.class.cast(subject).getName(); + } + + @Override + protected AbstractSubjectTO getSubjectTO(final long key) { + try { + return roleTransfer.getRoleTO(key); + } catch (Exception e) { + LOG.warn("Error retrieving user {}", key, e); + return null; + } + } + + @Override + protected Subject<?, ?, ?> getSubject(final long key) { + try { + return roleDAO.authFetch(key); + } catch (Exception e) { + LOG.warn("Error retrieving role {}", key, e); + return null; + } + } + + @Override + protected ConnectorObject getRemoteObject(final String accountId) { + ConnectorObject obj = null; + + try { + final Uid uid = new Uid(accountId); + + obj = profile.getConnector().getObject( + ObjectClass.GROUP, + uid, + profile.getConnector().getOperationOptions(Collections.<MappingItem>emptySet())); + } catch (TimeoutException toe) { + LOG.debug("Request timeout", toe); + throw toe; + } catch (RuntimeException ignore) { + LOG.debug("While resolving {}", accountId, ignore); + } + return obj; + } + + @Override + protected Mapping<?> getMapping() { + return profile.getTask().getResource().getRmapping(); + } +} 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/RoleSyncResultHandlerImpl.java ---------------------------------------------------------------------- diff --git a/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RoleSyncResultHandlerImpl.java b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RoleSyncResultHandlerImpl.java new file mode 100644 index 0000000..4bfbe70 --- /dev/null +++ b/syncope620/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/sync/RoleSyncResultHandlerImpl.java @@ -0,0 +1,169 @@ +/* + * 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.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.syncope.common.lib.mod.AbstractSubjectMod; +import org.apache.syncope.common.lib.mod.AttrMod; +import org.apache.syncope.common.lib.mod.RoleMod; +import org.apache.syncope.common.lib.mod.UserMod; +import org.apache.syncope.common.lib.to.AbstractSubjectTO; +import org.apache.syncope.common.lib.to.PropagationStatus; +import org.apache.syncope.common.lib.to.RoleTO; +import org.apache.syncope.common.lib.types.AttributableType; +import org.apache.syncope.core.persistence.api.entity.AttributableUtil; +import org.apache.syncope.core.provisioning.api.sync.ProvisioningResult; +import org.apache.syncope.core.provisioning.api.sync.RoleSyncResultHandler; +import org.identityconnectors.framework.common.objects.SyncDelta; + +public class RoleSyncResultHandlerImpl extends AbstractSyncResultHandler implements RoleSyncResultHandler { + + protected Map<Long, String> roleOwnerMap = new HashMap<>(); + + @Override + public Map<Long, String> getRoleOwnerMap() { + return this.roleOwnerMap; + } + + @Override + protected AttributableUtil getAttributableUtil() { + return attrUtilFactory.getInstance(AttributableType.ROLE); + } + + @Override + protected String getName(final AbstractSubjectTO subjectTO) { + return RoleTO.class.cast(subjectTO).getName(); + } + + @Override + protected AbstractSubjectTO getSubjectTO(final long key) { + try { + return roleTransfer.getRoleTO(key); + } catch (Exception e) { + LOG.warn("Error retrieving role {}", key, e); + return null; + } + } + + @Override + protected AbstractSubjectMod getSubjectMod( + final AbstractSubjectTO subjectTO, final SyncDelta delta) { + + return connObjectUtil.getAttributableMod( + subjectTO.getKey(), + delta.getObject(), + subjectTO, + profile.getTask(), + attrUtilFactory.getInstance(AttributableType.ROLE)); + } + + @Override + protected AbstractSubjectTO create( + final AbstractSubjectTO subjectTO, final SyncDelta _delta, final ProvisioningResult result) { + + RoleTO roleTO = RoleTO.class.cast(subjectTO); + + Map.Entry<Long, List<PropagationStatus>> created = roleProvisioningManager.create(roleTO, roleOwnerMap, + Collections.singleton(profile.getTask().getResource().getKey())); + + roleTO = roleTransfer.getRoleTO(created.getKey()); + + result.setId(created.getKey()); + result.setName(getName(subjectTO)); + + return roleTO; + } + + @Override + protected AbstractSubjectTO link( + final AbstractSubjectTO before, + final ProvisioningResult result, + final boolean unlink) { + + final RoleMod roleMod = new RoleMod(); + roleMod.setKey(before.getKey()); + + if (unlink) { + roleMod.getResourcesToRemove().add(profile.getTask().getResource().getKey()); + } else { + roleMod.getResourcesToAdd().add(profile.getTask().getResource().getKey()); + } + + return roleTransfer.getRoleTO(rwfAdapter.update(roleMod).getResult()); + } + + @Override + protected AbstractSubjectTO update( + final AbstractSubjectTO before, + final AbstractSubjectMod subjectMod, + final SyncDelta delta, + final ProvisioningResult result) { + + RoleMod roleMod = RoleMod.class.cast(subjectMod); + + Map.Entry<Long, List<PropagationStatus>> updated = roleProvisioningManager.update(roleMod); + + //moved after role provisioning manager + String roleOwner = null; + for (AttrMod attrMod : roleMod.getPlainAttrsToUpdate()) { + if (attrMod.getSchema().isEmpty()) { + roleOwner = attrMod.getValuesToBeAdded().iterator().next(); + } + } + if (roleOwner != null) { + roleOwnerMap.put(updated.getKey(), roleOwner); + } + + final RoleTO after = roleTransfer.getRoleTO(updated.getKey()); + + result.setName(getName(after)); + + return after; + } + + @Override + protected void deprovision(final Long id, final boolean unlink) { + + taskExecutor.execute( + propagationManager.getRoleDeleteTaskIds(id, profile.getTask().getResource().getKey())); + + if (unlink) { + final UserMod userMod = new UserMod(); + userMod.setKey(id); + userMod.getResourcesToRemove().add(profile.getTask().getResource().getKey()); + } + } + + @Override + protected void delete(final Long id) { + try { + taskExecutor.execute( + propagationManager.getRoleDeleteTaskIds(id, profile.getTask().getResource().getKey())); + } catch (Exception e) { + // A propagation failure doesn't imply a synchronization failure. + // The propagation exception status will be reported into the propagation task execution. + LOG.error("Could not propagate user " + id, e); + } + + roleProvisioningManager.delete(id); + } +}
