blob: 6195b309ad0ee29d4420f764a3eafa0ace2dfc98 [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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.to.RealmTO;
import org.apache.syncope.common.lib.types.AuditElements;
import org.apache.syncope.common.lib.types.AuditElements.Result;
import org.apache.syncope.common.lib.types.MatchingRule;
import org.apache.syncope.common.lib.types.ExecStatus;
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.Realm;
import org.apache.syncope.core.persistence.api.entity.resource.Item;
import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
import org.apache.syncope.core.persistence.api.entity.task.PushTask;
import org.apache.syncope.core.provisioning.api.MappingManager;
import org.apache.syncope.core.provisioning.api.TimeoutException;
import org.apache.syncope.core.provisioning.api.event.AfterHandlingEvent;
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.RealmPushResultHandler;
import org.apache.syncope.core.provisioning.java.job.AfterHandlingJob;
import org.apache.syncope.core.provisioning.java.propagation.DefaultPropagationReporter;
import org.apache.syncope.core.provisioning.java.utils.MappingUtils;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class DefaultRealmPushResultHandler
extends AbstractRealmResultHandler<PushTask, PushActions>
implements RealmPushResultHandler {
@Autowired
private MappingManager mappingManager;
@Autowired
private SchedulerFactoryBean scheduler;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public boolean handle(final String realmKey) {
Realm realm = null;
try {
realm = realmDAO.find(realmKey);
doHandle(realm);
return true;
} catch (IgnoreProvisionException e) {
ProvisioningReport result = new ProvisioningReport();
result.setOperation(ResourceOperation.NONE);
result.setAnyType(realm == null ? null : SyncopeConstants.REALM_ANYTYPE);
result.setStatus(ProvisioningReport.Status.IGNORE);
result.setKey(realmKey);
profile.getResults().add(result);
LOG.warn("Ignoring during push", e);
return true;
} catch (JobExecutionException e) {
LOG.error("Push failed", e);
return false;
}
}
private static void reportPropagation(final ProvisioningReport result, final PropagationReporter reporter) {
if (!reporter.getStatuses().isEmpty()) {
result.setStatus(toProvisioningReportStatus(reporter.getStatuses().get(0).getStatus()));
result.setMessage(reporter.getStatuses().get(0).getFailureReason());
}
}
private Realm update(final RealmTO realmTO, final ConnectorObject beforeObj, final ProvisioningReport result) {
Realm realm = realmDAO.findByFullPath(realmTO.getFullPath());
PropagationByResource<String> propByRes = binder.update(realm, realmTO);
realm = realmDAO.save(realm);
List<PropagationTaskInfo> taskInfos = propagationManager.createTasks(realm, propByRes, null);
if (!taskInfos.isEmpty()) {
taskInfos.get(0).setBeforeObj(Optional.ofNullable(beforeObj));
PropagationReporter reporter = new DefaultPropagationReporter();
taskExecutor.execute(taskInfos.get(0), reporter, securityProperties.getAdminUser());
reportPropagation(result, reporter);
}
return realm;
}
private void deprovision(final Realm realm, final ConnectorObject beforeObj, final ProvisioningReport result) {
List<String> noPropResources = new ArrayList<>(realm.getResourceKeys());
noPropResources.remove(profile.getTask().getResource().getKey());
PropagationByResource<String> propByRes = new PropagationByResource<>();
propByRes.addAll(ResourceOperation.DELETE, realm.getResourceKeys());
List<PropagationTaskInfo> taskInfos = propagationManager.createTasks(realm, propByRes, noPropResources);
if (!taskInfos.isEmpty()) {
taskInfos.get(0).setBeforeObj(Optional.ofNullable(beforeObj));
PropagationReporter reporter = new DefaultPropagationReporter();
taskExecutor.execute(taskInfos.get(0), reporter, securityProperties.getAdminUser());
reportPropagation(result, reporter);
}
}
private void provision(final Realm realm, final ProvisioningReport result) {
List<String> noPropResources = new ArrayList<>(realm.getResourceKeys());
noPropResources.remove(profile.getTask().getResource().getKey());
PropagationByResource<String> propByRes = new PropagationByResource<>();
propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey());
PropagationReporter reporter = taskExecutor.execute(
propagationManager.createTasks(realm, propByRes, noPropResources),
false, securityProperties.getAdminUser());
reportPropagation(result, reporter);
}
private void link(final Realm realm, final boolean unlink, final ProvisioningReport result) {
RealmTO realmTO = binder.getRealmTO(realm, true);
if (unlink) {
realmTO.getResources().remove(profile.getTask().getResource().getKey());
} else {
realmTO.getResources().add(profile.getTask().getResource().getKey());
}
update(realmTO, null, result);
}
private void unassign(final Realm realm, final ConnectorObject beforeObj, final ProvisioningReport result) {
RealmTO realmTO = binder.getRealmTO(realm, true);
realmTO.getResources().remove(profile.getTask().getResource().getKey());
deprovision(update(realmTO, beforeObj, result), beforeObj, result);
}
private void assign(final Realm realm, final ProvisioningReport result) {
RealmTO realmTO = binder.getRealmTO(realm, true);
realmTO.getResources().add(profile.getTask().getResource().getKey());
provision(update(realmTO, null, result), result);
}
protected ConnectorObject getRemoteObject(
final ObjectClass objectClass,
final String connObjectKey,
final String connObjectKeyValue,
final boolean ignoreCaseMatch,
final Stream<? extends Item> mapItems) {
ConnectorObject obj = null;
try {
obj = profile.getConnector().getObject(
objectClass,
AttributeBuilder.build(connObjectKey, connObjectKeyValue),
ignoreCaseMatch,
MappingUtils.buildOperationOptions(mapItems));
} catch (TimeoutException toe) {
LOG.debug("Request timeout", toe);
throw toe;
} catch (RuntimeException ignore) {
LOG.debug("While resolving {}", connObjectKeyValue, ignore);
}
return obj;
}
private void doHandle(final Realm realm) throws JobExecutionException {
ProvisioningReport result = new ProvisioningReport();
profile.getResults().add(result);
result.setKey(realm.getKey());
result.setAnyType(SyncopeConstants.REALM_ANYTYPE);
result.setName(realm.getFullPath());
LOG.debug("Propagating Realm with key {} towards {}", realm.getKey(), profile.getTask().getResource());
Object output = null;
Result resultStatus = null;
// Try to read remote object BEFORE any actual operation
OrgUnit orgUnit = profile.getTask().getResource().getOrgUnit();
Optional<? extends OrgUnitItem> connObjectKey = orgUnit.getConnObjectKeyItem();
Optional<String> connObjecKeyValue = mappingManager.getConnObjectKeyValue(realm, orgUnit);
ConnectorObject beforeObj = null;
if (connObjectKey.isPresent() && connObjecKeyValue.isPresent()) {
beforeObj = getRemoteObject(
orgUnit.getObjectClass(),
connObjectKey.get().getExtAttrName(),
connObjecKeyValue.get(),
orgUnit.isIgnoreCaseMatch(),
orgUnit.getItems().stream());
} else {
LOG.debug("OrgUnitItem {} or its value {} are null", connObjectKey, connObjecKeyValue);
}
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 {
String operation = beforeObj == null
? UnmatchingRule.toEventName(profile.getTask().getUnmatchingRule())
: MatchingRule.toEventName(profile.getTask().getMatchingRule());
boolean notificationsAvailable = notificationManager.notificationsAvailable(
AuditElements.EventCategoryType.PUSH,
SyncopeConstants.REALM_ANYTYPE.toLowerCase(),
profile.getTask().getResource().getKey(),
operation);
boolean auditRequested = auditManager.auditRequested(
AuthContextUtils.getUsername(),
AuditElements.EventCategoryType.PUSH,
SyncopeConstants.REALM_ANYTYPE.toLowerCase(),
profile.getTask().getResource().getKey(),
operation);
try {
if (beforeObj == null) {
result.setOperation(toResourceOperation(profile.getTask().getUnmatchingRule()));
switch (profile.getTask().getUnmatchingRule()) {
case ASSIGN:
for (PushActions action : profile.getActions()) {
action.beforeAssign(profile, realm);
}
if (!profile.getTask().isPerformCreate()) {
LOG.debug("PushTask not configured for create");
result.setStatus(ProvisioningReport.Status.IGNORE);
} else {
assign(realm, result);
}
break;
case PROVISION:
for (PushActions action : profile.getActions()) {
action.beforeProvision(profile, realm);
}
if (!profile.getTask().isPerformCreate()) {
LOG.debug("PushTask not configured for create");
result.setStatus(ProvisioningReport.Status.IGNORE);
} else {
provision(realm, result);
}
break;
case UNLINK:
for (PushActions action : profile.getActions()) {
action.beforeUnlink(profile, realm);
}
if (!profile.getTask().isPerformUpdate()) {
LOG.debug("PushTask not configured for update");
result.setStatus(ProvisioningReport.Status.IGNORE);
} else {
link(realm, true, result);
}
break;
case IGNORE:
LOG.debug("Ignored any: {}", realm);
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, realm);
}
if (!profile.getTask().isPerformUpdate()) {
LOG.debug("PushTask not configured for update");
result.setStatus(ProvisioningReport.Status.IGNORE);
} else {
update(binder.getRealmTO(realm, true), beforeObj, result);
}
break;
case DEPROVISION:
for (PushActions action : profile.getActions()) {
action.beforeDeprovision(profile, realm);
}
if (!profile.getTask().isPerformDelete()) {
LOG.debug("PushTask not configured for delete");
result.setStatus(ProvisioningReport.Status.IGNORE);
} else {
deprovision(realm, beforeObj, result);
}
break;
case UNASSIGN:
for (PushActions action : profile.getActions()) {
action.beforeUnassign(profile, realm);
}
if (!profile.getTask().isPerformDelete()) {
LOG.debug("PushTask not configured for delete");
result.setStatus(ProvisioningReport.Status.IGNORE);
} else {
unassign(realm, beforeObj, result);
}
break;
case LINK:
for (PushActions action : profile.getActions()) {
action.beforeLink(profile, realm);
}
if (!profile.getTask().isPerformUpdate()) {
LOG.debug("PushTask not configured for update");
result.setStatus(ProvisioningReport.Status.IGNORE);
} else {
link(realm, false, result);
}
break;
case UNLINK:
for (PushActions action : profile.getActions()) {
action.beforeUnlink(profile, realm);
}
if (!profile.getTask().isPerformUpdate()) {
LOG.debug("PushTask not configured for update");
result.setStatus(ProvisioningReport.Status.IGNORE);
} else {
link(realm, true, result);
}
break;
case IGNORE:
LOG.debug("Ignored any: {}", realm);
result.setStatus(ProvisioningReport.Status.IGNORE);
break;
default:
// do nothing
}
}
for (PushActions action : profile.getActions()) {
action.after(profile, realm, result);
}
if (result.getStatus() == null) {
result.setStatus(ProvisioningReport.Status.SUCCESS);
}
if (notificationsAvailable || auditRequested) {
resultStatus = AuditElements.Result.SUCCESS;
if (connObjectKey.isPresent() && connObjecKeyValue.isPresent()) {
output = getRemoteObject(
orgUnit.getObjectClass(),
connObjectKey.get().getExtAttrName(),
connObjecKeyValue.get(),
orgUnit.isIgnoreCaseMatch(),
orgUnit.getItems().stream());
}
}
} catch (IgnoreProvisionException e) {
throw e;
} catch (Exception e) {
result.setStatus(ProvisioningReport.Status.FAILURE);
result.setMessage(ExceptionUtils.getRootCauseMessage(e));
if (notificationsAvailable || auditRequested) {
resultStatus = AuditElements.Result.FAILURE;
output = e;
}
LOG.warn("Error pushing {} towards {}", realm, profile.getTask().getResource(), e);
for (PushActions action : profile.getActions()) {
action.onError(profile, realm, result, e);
}
throw new JobExecutionException(e);
} finally {
if (notificationsAvailable || auditRequested) {
Map<String, Object> jobMap = new HashMap<>();
jobMap.put(AfterHandlingEvent.JOBMAP_KEY, new AfterHandlingEvent(
AuthContextUtils.getWho(),
AuditElements.EventCategoryType.PUSH,
SyncopeConstants.REALM_ANYTYPE.toLowerCase(),
profile.getTask().getResource().getKey(),
operation,
resultStatus,
beforeObj,
output,
realm));
AfterHandlingJob.schedule(scheduler, jobMap);
}
}
}
}
private static ResourceOperation toResourceOperation(final UnmatchingRule rule) {
switch (rule) {
case ASSIGN:
case PROVISION:
return ResourceOperation.CREATE;
default:
return ResourceOperation.NONE;
}
}
private static ResourceOperation toResourceOperation(final MatchingRule rule) {
switch (rule) {
case UPDATE:
return ResourceOperation.UPDATE;
case DEPROVISION:
case UNASSIGN:
return ResourceOperation.DELETE;
default:
return ResourceOperation.NONE;
}
}
private static ProvisioningReport.Status toProvisioningReportStatus(final ExecStatus status) {
switch (status) {
case FAILURE:
return ProvisioningReport.Status.FAILURE;
case SUCCESS:
return ProvisioningReport.Status.SUCCESS;
case CREATED:
case NOT_ATTEMPTED:
default:
return ProvisioningReport.Status.IGNORE;
}
}
}