blob: 6827ce0b4b1164ca4742742566afff41698b33a3 [file] [log] [blame]
/*
* 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,
securityProperties.getAdminUser());
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, securityProperties.getAdminUser());
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, securityProperties.getAdminUser());
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, securityProperties.getAdminUser());
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, securityProperties.getAdminUser());
reportPropagation(result, reporter);
}
}
}