| /* |
| * 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.pushpull; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Optional; |
| import java.util.stream.Collectors; |
| import org.apache.commons.lang3.BooleanUtils; |
| import org.apache.commons.lang3.exception.ExceptionUtils; |
| import org.apache.commons.lang3.tuple.Pair; |
| import org.apache.syncope.common.lib.request.AnyUR; |
| import org.apache.syncope.common.lib.request.UserUR; |
| import org.apache.syncope.common.lib.to.AnyTO; |
| import org.apache.syncope.common.lib.types.AnyTypeKind; |
| import org.apache.syncope.common.lib.types.MatchType; |
| import org.apache.syncope.common.lib.types.MatchingRule; |
| import org.apache.syncope.core.provisioning.api.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.Any; |
| import org.apache.syncope.core.persistence.api.entity.AnyUtils; |
| import org.apache.syncope.core.persistence.api.entity.Entity; |
| import org.apache.syncope.core.persistence.api.entity.resource.Provision; |
| import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount; |
| import org.apache.syncope.core.persistence.api.entity.user.User; |
| import org.apache.syncope.core.provisioning.api.UserWorkflowResult; |
| import org.apache.syncope.core.provisioning.api.WorkflowResult; |
| import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter; |
| import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskInfo; |
| import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException; |
| import org.apache.syncope.common.lib.to.ProvisioningReport; |
| import org.apache.syncope.core.provisioning.api.pushpull.PushActions; |
| import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler; |
| import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter; |
| import org.apache.syncope.core.provisioning.java.utils.MappingUtils; |
| import org.identityconnectors.framework.common.objects.ConnectorObject; |
| import org.quartz.JobExecutionException; |
| import org.springframework.transaction.annotation.Propagation; |
| import org.springframework.transaction.annotation.Transactional; |
| |
| public class DefaultUserPushResultHandler extends AbstractPushResultHandler implements UserPushResultHandler { |
| |
| @Override |
| protected AnyUtils getAnyUtils() { |
| return anyUtilsFactory.getInstance(AnyTypeKind.USER); |
| } |
| |
| @Override |
| protected String getName(final Any<?> any) { |
| return User.class.cast(any).getUsername(); |
| } |
| |
| @Override |
| protected AnyTO getAnyTO(final Any<?> any) { |
| return userDataBinder.getUserTO((User) any, true); |
| } |
| |
| @Override |
| protected void provision(final Any<?> any, final Boolean enabled, final ProvisioningReport result) { |
| AnyTO before = getAnyTO(any); |
| |
| List<String> noPropResources = new ArrayList<>(before.getResources()); |
| noPropResources.remove(profile.getTask().getResource().getKey()); |
| |
| PropagationByResource<String> propByRes = new PropagationByResource<>(); |
| propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey()); |
| |
| PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>(); |
| ((User) any).getLinkedAccounts(profile.getTask().getResource().getKey()). |
| forEach(account -> propByLinkedAccount.add( |
| ResourceOperation.CREATE, |
| Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()))); |
| |
| PropagationReporter reporter = taskExecutor.execute(propagationManager.getUserCreateTasks( |
| before.getKey(), |
| null, |
| enabled, |
| propByRes, |
| propByLinkedAccount, |
| before.getVirAttrs(), |
| noPropResources), |
| false, |
| adminUser); |
| reportPropagation(result, reporter); |
| } |
| |
| @Override |
| protected void update( |
| final Any<?> any, |
| final Boolean enable, |
| final ConnectorObject beforeObj, |
| final ProvisioningReport result) { |
| |
| List<String> ownedResources = getAnyUtils().getAllResources(any).stream(). |
| map(Entity::getKey).collect(Collectors.toList()); |
| |
| List<String> noPropResources = new ArrayList<>(ownedResources); |
| noPropResources.remove(profile.getTask().getResource().getKey()); |
| |
| PropagationByResource<String> propByRes = new PropagationByResource<>(); |
| propByRes.add(ResourceOperation.UPDATE, profile.getTask().getResource().getKey()); |
| propByRes.addOldConnObjectKey(profile.getTask().getResource().getKey(), beforeObj.getUid().getUidValue()); |
| |
| PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>(); |
| ((User) any).getLinkedAccounts(profile.getTask().getResource().getKey()). |
| forEach(account -> propByLinkedAccount.add( |
| ResourceOperation.UPDATE, |
| Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()))); |
| |
| List<PropagationTaskInfo> taskInfos = propagationManager.getUpdateTasks( |
| any.getType().getKind(), |
| any.getKey(), |
| true, |
| enable, |
| propByRes, |
| propByLinkedAccount, |
| null, |
| noPropResources); |
| if (!taskInfos.isEmpty()) { |
| taskInfos.get(0).setBeforeObj(Optional.of(beforeObj)); |
| PropagationReporter reporter = new DefaultPropagationReporter(); |
| taskExecutor.execute(taskInfos.get(0), reporter, adminUser); |
| reportPropagation(result, reporter); |
| } |
| } |
| |
| @Override |
| protected void deprovision(final Any<?> any, final ConnectorObject beforeObj, final ProvisioningReport result) { |
| AnyTO before = getAnyTO(any); |
| |
| List<String> noPropResources = new ArrayList<>(before.getResources()); |
| noPropResources.remove(profile.getTask().getResource().getKey()); |
| |
| PropagationByResource<String> propByRes = new PropagationByResource<>(); |
| propByRes.add(ResourceOperation.DELETE, profile.getTask().getResource().getKey()); |
| propByRes.addOldConnObjectKey(profile.getTask().getResource().getKey(), beforeObj.getUid().getUidValue()); |
| |
| PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>(); |
| ((User) any).getLinkedAccounts(profile.getTask().getResource().getKey()). |
| forEach(account -> propByLinkedAccount.add( |
| ResourceOperation.DELETE, |
| Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue()))); |
| |
| List<PropagationTaskInfo> taskInfos = propagationManager.getDeleteTasks( |
| any.getType().getKind(), |
| any.getKey(), |
| propByRes, |
| propByLinkedAccount, |
| noPropResources); |
| if (!taskInfos.isEmpty()) { |
| taskInfos.get(0).setBeforeObj(Optional.of(beforeObj)); |
| PropagationReporter reporter = new DefaultPropagationReporter(); |
| taskExecutor.execute(taskInfos.get(0), reporter, adminUser); |
| reportPropagation(result, reporter); |
| } |
| } |
| |
| @Override |
| protected WorkflowResult<? extends AnyUR> update(final AnyUR req) { |
| WorkflowResult<Pair<UserUR, Boolean>> update = |
| uwfAdapter.update((UserUR) req, profile.getExecutor(), getContext()); |
| return new WorkflowResult<>(update.getResult().getLeft(), update.getPropByRes(), update.getPerformedTasks()); |
| } |
| |
| @Transactional(propagation = Propagation.REQUIRES_NEW) |
| @Override |
| public boolean handle(final LinkedAccount account, final Provision provision) { |
| try { |
| doHandle(account, provision); |
| return true; |
| } catch (IgnoreProvisionException e) { |
| ProvisioningReport ignoreResult = profile.getResults().stream(). |
| filter(report -> account.getKey().equalsIgnoreCase(report.getKey())). |
| findFirst(). |
| orElse(null); |
| if (ignoreResult == null) { |
| ignoreResult = new ProvisioningReport(); |
| ignoreResult.setKey(account.getKey()); |
| ignoreResult.setAnyType(MatchType.LINKED_ACCOUNT.name()); |
| ignoreResult.setUidValue(account.getConnObjectKeyValue()); |
| |
| profile.getResults().add(ignoreResult); |
| } |
| |
| ignoreResult.setOperation(ResourceOperation.NONE); |
| ignoreResult.setStatus(ProvisioningReport.Status.IGNORE); |
| ignoreResult.setMessage(e.getMessage()); |
| |
| LOG.warn("Ignoring during push", e); |
| return true; |
| } catch (JobExecutionException e) { |
| LOG.error("Push failed", e); |
| return false; |
| } |
| } |
| |
| protected void doHandle(final LinkedAccount account, final Provision provision) throws JobExecutionException { |
| ProvisioningReport result = new ProvisioningReport(); |
| profile.getResults().add(result); |
| |
| result.setKey(account.getKey()); |
| result.setAnyType(MatchType.LINKED_ACCOUNT.name()); |
| result.setUidValue(account.getConnObjectKeyValue()); |
| result.setName(account.getConnObjectKeyValue()); |
| |
| LOG.debug("Pushing linked account {} towards {}", account.getKey(), profile.getTask().getResource()); |
| |
| // Try to read remote object BEFORE any actual operation |
| Optional<ConnectorObject> connObj = MappingUtils.getConnObjectKeyItem(provision). |
| flatMap(connObjectKeyItem -> outboundMatcher.matchByConnObjectKeyValue( |
| profile.getConnector(), |
| connObjectKeyItem, |
| account.getConnObjectKeyValue(), |
| provision, |
| Optional.empty(), |
| Optional.empty())); |
| LOG.debug("Match found for linked account {} as {}: {}", account, provision.getObjectClass(), connObj); |
| |
| ConnectorObject beforeObj = connObj.orElse(null); |
| |
| if (profile.isDryRun()) { |
| if (beforeObj == null) { |
| result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule())); |
| } else { |
| result.setOperation(toResourceOperation(profile.getTask().getMatchingRule())); |
| } |
| result.setStatus(ProvisioningReport.Status.SUCCESS); |
| } else { |
| Boolean enable = profile.getTask().isSyncStatus() |
| ? BooleanUtils.negate(account.isSuspended()) |
| : null; |
| try { |
| if (beforeObj == null) { |
| result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule())); |
| |
| switch (profile.getTask().getUnmatchingRule()) { |
| case ASSIGN: |
| case PROVISION: |
| for (PushActions action : profile.getActions()) { |
| if (profile.getTask().getUnmatchingRule() == UnmatchingRule.ASSIGN) { |
| action.beforeAssign(profile, account); |
| } else { |
| action.beforeProvision(profile, account); |
| } |
| } |
| |
| if (!profile.getTask().isPerformCreate()) { |
| LOG.debug("PushTask not configured for create"); |
| result.setStatus(ProvisioningReport.Status.IGNORE); |
| } else { |
| provision(account, enable, result); |
| } |
| break; |
| |
| case UNLINK: |
| LOG.warn("{} not applicable to linked accounts, ignoring", |
| profile.getTask().getUnmatchingRule()); |
| break; |
| |
| case IGNORE: |
| result.setStatus(ProvisioningReport.Status.IGNORE); |
| break; |
| |
| default: |
| // do nothing |
| } |
| } else { |
| result.setOperation(toResourceOperation(profile.getTask().getMatchingRule())); |
| |
| switch (profile.getTask().getMatchingRule()) { |
| case UPDATE: |
| for (PushActions action : profile.getActions()) { |
| action.beforeUpdate(profile, account); |
| } |
| if (!profile.getTask().isPerformUpdate()) { |
| LOG.debug("PushTask not configured for update"); |
| result.setStatus(ProvisioningReport.Status.IGNORE); |
| } else { |
| update(account, enable, beforeObj, ResourceOperation.UPDATE, result); |
| } |
| break; |
| |
| case UNASSIGN: |
| case DEPROVISION: |
| for (PushActions action : profile.getActions()) { |
| if (profile.getTask().getMatchingRule() == MatchingRule.UNASSIGN) { |
| action.beforeUnassign(profile, account); |
| } else { |
| action.beforeDeprovision(profile, account); |
| } |
| } |
| |
| if (!profile.getTask().isPerformDelete()) { |
| LOG.debug("PushTask not configured for delete"); |
| result.setStatus(ProvisioningReport.Status.IGNORE); |
| } else { |
| update(account, enable, beforeObj, ResourceOperation.DELETE, result); |
| } |
| break; |
| |
| case LINK: |
| case UNLINK: |
| LOG.warn("{} not applicable to linked accounts, ignoring", |
| profile.getTask().getMatchingRule()); |
| break; |
| |
| case IGNORE: |
| result.setStatus(ProvisioningReport.Status.IGNORE); |
| break; |
| |
| default: |
| // do nothing |
| } |
| } |
| |
| for (PushActions action : profile.getActions()) { |
| action.after(profile, account, result); |
| } |
| |
| if (result.getStatus() == null) { |
| result.setStatus(ProvisioningReport.Status.SUCCESS); |
| } |
| } catch (IgnoreProvisionException e) { |
| throw e; |
| } catch (Exception e) { |
| result.setStatus(ProvisioningReport.Status.FAILURE); |
| result.setMessage(ExceptionUtils.getRootCauseMessage(e)); |
| |
| LOG.warn("Error pushing linked account {} towards {}", account, profile.getTask().getResource(), e); |
| |
| for (PushActions action : profile.getActions()) { |
| action.onError(profile, account, result, e); |
| } |
| |
| throw new JobExecutionException(e); |
| } |
| } |
| } |
| |
| protected void provision( |
| final LinkedAccount account, |
| final Boolean enable, |
| final ProvisioningReport result) { |
| |
| PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>(); |
| propByLinkedAccount.add( |
| ResourceOperation.CREATE, |
| Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())); |
| |
| List<PropagationTaskInfo> taskInfos = propagationManager.getUserCreateTasks( |
| account.getOwner().getKey(), |
| null, |
| enable, |
| new PropagationByResource<>(), |
| propByLinkedAccount, |
| null, |
| null); |
| if (!taskInfos.isEmpty()) { |
| taskInfos.get(0).setBeforeObj(Optional.empty()); |
| PropagationReporter reporter = new DefaultPropagationReporter(); |
| taskExecutor.execute(taskInfos.get(0), reporter, adminUser); |
| reportPropagation(result, reporter); |
| } |
| } |
| |
| protected void update( |
| final LinkedAccount account, |
| final Boolean enable, |
| final ConnectorObject beforeObj, |
| final ResourceOperation operation, |
| final ProvisioningReport result) { |
| |
| UserUR req = new UserUR(); |
| req.setKey(account.getOwner().getKey()); |
| |
| PropagationByResource<Pair<String, String>> propByLinkedAccount = new PropagationByResource<>(); |
| propByLinkedAccount.add(operation, Pair.of(account.getResource().getKey(), account.getConnObjectKeyValue())); |
| |
| List<PropagationTaskInfo> taskInfos = propagationManager.getUserUpdateTasks( |
| new UserWorkflowResult<>( |
| Pair.of(req, enable), |
| new PropagationByResource<>(), |
| propByLinkedAccount, |
| "")); |
| if (!taskInfos.isEmpty()) { |
| taskInfos.get(0).setBeforeObj(Optional.empty()); |
| PropagationReporter reporter = new DefaultPropagationReporter(); |
| taskExecutor.execute(taskInfos.get(0), reporter, adminUser); |
| reportPropagation(result, reporter); |
| } |
| } |
| } |