http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java new file mode 100644 index 0000000..e2570eb --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java @@ -0,0 +1,434 @@ +/* + * 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.Collection; +import java.util.List; +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.patch.StringPatchItem; +import org.apache.syncope.common.lib.to.AnyTO; +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.PatchOperation; +import org.apache.syncope.common.lib.types.PropagationByResource; +import org.apache.syncope.common.lib.types.ResourceOperation; +import org.apache.syncope.common.lib.types.UnmatchingRule; +import org.apache.syncope.core.persistence.api.entity.task.PushTask; +import org.apache.syncope.core.persistence.api.entity.user.User; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport; +import org.apache.syncope.core.provisioning.api.pushpull.PushActions; +import org.apache.syncope.core.provisioning.java.MappingManagerImpl; +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.anyobject.AnyObject; +import org.apache.syncope.core.persistence.api.entity.group.Group; +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem; +import org.apache.syncope.core.persistence.api.entity.resource.Provision; +import org.apache.syncope.core.provisioning.api.MappingManager; +import org.apache.syncope.core.provisioning.api.TimeoutException; +import org.apache.syncope.core.provisioning.api.pushpull.IgnoreProvisionException; +import org.apache.syncope.core.provisioning.api.pushpull.SyncopePushResultHandler; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.identityconnectors.framework.common.objects.ObjectClass; +import org.identityconnectors.framework.common.objects.Uid; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHandler<PushTask, PushActions> + implements SyncopePushResultHandler { + + @Autowired + protected MappingManager mappingManager; + + protected abstract String getName(Any<?> any); + + protected void deprovision(final Any<?> any) { + AnyTO before = getAnyTO(any.getKey()); + + List<String> noPropResources = new ArrayList<>(before.getResources()); + noPropResources.remove(profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getDeleteTasks( + any.getType().getKind(), + any.getKey(), + null, + noPropResources)); + } + + protected void provision(final Any<?> any, final Boolean enabled) { + AnyTO before = getAnyTO(any.getKey()); + + List<String> noPropResources = new ArrayList<>(before.getResources()); + noPropResources.remove(profile.getTask().getResource().getKey()); + + PropagationByResource propByRes = new PropagationByResource(); + propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getCreateTasks( + any.getType().getKind(), + any.getKey(), + propByRes, + before.getVirAttrs(), + noPropResources)); + } + + @SuppressWarnings("unchecked") + protected void link(final Any<?> any, final Boolean unlink) { + AnyPatch patch = newPatch(any.getKey()); + patch.getResources().add(new StringPatchItem.Builder(). + operation(unlink ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE). + value(profile.getTask().getResource().getKey()).build()); + + update(patch); + } + + @SuppressWarnings("unchecked") + protected void unassign(final Any<?> any) { + AnyPatch patch = newPatch(any.getKey()); + patch.getResources().add(new StringPatchItem.Builder(). + operation(PatchOperation.DELETE). + value(profile.getTask().getResource().getKey()).build()); + + update(patch); + + deprovision(any); + } + + protected void assign(final Any<?> any, final Boolean enabled) { + AnyPatch patch = newPatch(any.getKey()); + patch.getResources().add(new StringPatchItem.Builder(). + operation(PatchOperation.ADD_REPLACE). + value(profile.getTask().getResource().getKey()).build()); + + update(patch); + + provision(any, enabled); + } + + protected ConnectorObject getRemoteObject(final String connObjectKey, final ObjectClass objectClass) { + ConnectorObject obj = null; + try { + Uid uid = new Uid(connObjectKey); + + obj = profile.getConnector().getObject(objectClass, + uid, + MappingManagerImpl.buildOperationOptions(IteratorUtils.<MappingItem>emptyIterator())); + } catch (TimeoutException toe) { + LOG.debug("Request timeout", toe); + throw toe; + } catch (RuntimeException ignore) { + LOG.debug("While resolving {}", connObjectKey, ignore); + } + + return obj; + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + @Override + public boolean handle(final long anyKey) { + Any<?> any = null; + try { + any = getAny(anyKey); + doHandle(any); + return true; + } catch (IgnoreProvisionException e) { + ProvisioningReport result = new ProvisioningReport(); + result.setOperation(ResourceOperation.NONE); + result.setAnyType(any == null ? null : any.getType().getKey()); + result.setStatus(ProvisioningReport.Status.IGNORE); + result.setKey(anyKey); + profile.getResults().add(result); + + LOG.warn("Ignoring during push", e); + return true; + } catch (JobExecutionException e) { + LOG.error("Push failed", e); + return false; + } + } + + protected final void doHandle(final Any<?> any) throws JobExecutionException { + AnyUtils anyUtils = anyUtilsFactory.getInstance(any); + + ProvisioningReport result = new ProvisioningReport(); + profile.getResults().add(result); + + result.setKey(any.getKey()); + result.setAnyType(any.getType().getKey()); + result.setName(getName(any)); + + Boolean enabled = any instanceof User && profile.getTask().isPullStatus() + ? ((User) any).isSuspended() ? Boolean.FALSE : Boolean.TRUE + : null; + + LOG.debug("Propagating {} with key {} towards {}", + anyUtils.getAnyTypeKind(), any.getKey(), profile.getTask().getResource()); + + Object output = null; + Result resultStatus = null; + String operation = null; + + // Try to read remote object BEFORE any actual operation + Provision provision = profile.getTask().getResource().getProvision(any.getType()); + String connObjecKey = mappingManager.getConnObjectKeyValue(any, provision); + + ConnectorObject beforeObj = getRemoteObject(connObjecKey, provision.getObjectClass()); + + Boolean status = profile.getTask().isPullStatus() ? enabled : null; + + if (profile.isDryRun()) { + if (beforeObj == null) { + result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule())); + } else { + result.setOperation(getResourceOperation(profile.getTask().getMatchingRule())); + } + result.setStatus(ProvisioningReport.Status.SUCCESS); + } else { + try { + if (beforeObj == null) { + operation = UnmatchingRule.toEventName(profile.getTask().getUnmatchingRule()); + result.setOperation(getResourceOperation(profile.getTask().getUnmatchingRule())); + + switch (profile.getTask().getUnmatchingRule()) { + case ASSIGN: + for (PushActions action : profile.getActions()) { + action.beforeAssign(this.getProfile(), any); + } + + if (!profile.getTask().isPerformCreate()) { + LOG.debug("PushTask not configured for create"); + } else { + assign(any, status); + } + + break; + + case PROVISION: + for (PushActions action : profile.getActions()) { + action.beforeProvision(this.getProfile(), any); + } + + if (!profile.getTask().isPerformCreate()) { + LOG.debug("PushTask not configured for create"); + } else { + provision(any, status); + } + + break; + + case UNLINK: + for (PushActions action : profile.getActions()) { + action.beforeUnlink(this.getProfile(), any); + } + + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("PushTask not configured for update"); + } else { + link(any, true); + } + + break; + + case IGNORE: + LOG.debug("Ignored any: {}", any); + break; + default: + // do nothing + } + } else { + operation = MatchingRule.toEventName(profile.getTask().getMatchingRule()); + result.setOperation(getResourceOperation(profile.getTask().getMatchingRule())); + + switch (profile.getTask().getMatchingRule()) { + case UPDATE: + for (PushActions action : profile.getActions()) { + action.beforeUpdate(this.getProfile(), any); + } + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("PushTask not configured for update"); + } else { + update(any, status); + } + + break; + + case DEPROVISION: + for (PushActions action : profile.getActions()) { + action.beforeDeprovision(this.getProfile(), any); + } + + if (!profile.getTask().isPerformDelete()) { + LOG.debug("PushTask not configured for delete"); + } else { + deprovision(any); + } + + break; + + case UNASSIGN: + for (PushActions action : profile.getActions()) { + action.beforeUnassign(this.getProfile(), any); + } + + if (!profile.getTask().isPerformDelete()) { + LOG.debug("PushTask not configured for delete"); + } else { + unassign(any); + } + + break; + + case LINK: + for (PushActions action : profile.getActions()) { + action.beforeLink(this.getProfile(), any); + } + + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("PushTask not configured for update"); + } else { + link(any, false); + } + + break; + + case UNLINK: + for (PushActions action : profile.getActions()) { + action.beforeUnlink(this.getProfile(), any); + } + + if (!profile.getTask().isPerformUpdate()) { + LOG.debug("PushTask not configured for update"); + } else { + link(any, true); + } + + break; + + case IGNORE: + LOG.debug("Ignored any: {}", any); + break; + default: + // do nothing + } + } + + for (PushActions action : profile.getActions()) { + action.after(this.getProfile(), any, result); + } + + result.setStatus(ProvisioningReport.Status.SUCCESS); + resultStatus = AuditElements.Result.SUCCESS; + output = getRemoteObject(connObjecKey, provision.getObjectClass()); + } catch (IgnoreProvisionException e) { + throw e; + } catch (Exception e) { + result.setStatus(ProvisioningReport.Status.FAILURE); + result.setMessage(ExceptionUtils.getRootCauseMessage(e)); + resultStatus = AuditElements.Result.FAILURE; + output = e; + + LOG.warn("Error pushing {} towards {}", any, profile.getTask().getResource(), e); + + for (PushActions action : profile.getActions()) { + action.onError(this.getProfile(), any, result, e); + } + + throw new JobExecutionException(e); + } finally { + notificationManager.createTasks(AuditElements.EventCategoryType.PUSH, + any.getType().getKind().name().toLowerCase(), + profile.getTask().getResource().getKey(), + operation, + resultStatus, + beforeObj, + output, + any); + auditManager.audit(AuditElements.EventCategoryType.PUSH, + any.getType().getKind().name().toLowerCase(), + profile.getTask().getResource().getKey(), + operation, + resultStatus, + connObjectUtils.getConnObjectTO(beforeObj), + output instanceof ConnectorObject + ? connObjectUtils.getConnObjectTO((ConnectorObject) output) : output, + any); + } + } + } + + private ResourceOperation getResourceOperation(final UnmatchingRule rule) { + switch (rule) { + case ASSIGN: + case PROVISION: + return ResourceOperation.CREATE; + default: + return ResourceOperation.NONE; + } + } + + private ResourceOperation getResourceOperation(final MatchingRule rule) { + switch (rule) { + case UPDATE: + return ResourceOperation.UPDATE; + case DEPROVISION: + case UNASSIGN: + return ResourceOperation.DELETE; + default: + return ResourceOperation.NONE; + } + } + + protected Any<?> update(final Any<?> any, final Boolean enabled) { + boolean changepwd; + Collection<String> resourceNames; + if (any instanceof User) { + changepwd = true; + resourceNames = userDAO.findAllResourceNames((User) any); + } else if (any instanceof AnyObject) { + changepwd = false; + resourceNames = anyObjectDAO.findAllResourceNames((AnyObject) any); + } else { + changepwd = false; + resourceNames = ((Group) any).getResourceNames(); + } + + List<String> noPropResources = new ArrayList<>(resourceNames); + noPropResources.remove(profile.getTask().getResource().getKey()); + + PropagationByResource propByRes = new PropagationByResource(); + propByRes.add(ResourceOperation.CREATE, profile.getTask().getResource().getKey()); + + taskExecutor.execute(propagationManager.getUpdateTasks( + any.getType().getKind(), + any.getKey(), + changepwd, + null, + propByRes, + null, + noPropResources)); + + return getAny(any.getKey()); + } +}
http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractSyncopeResultHandler.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractSyncopeResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractSyncopeResultHandler.java new file mode 100644 index 0000000..37f67ff --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractSyncopeResultHandler.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.pushpull; + +import org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.task.ProvisioningTask; +import org.apache.syncope.core.provisioning.api.GroupProvisioningManager; +import org.apache.syncope.core.provisioning.api.data.GroupDataBinder; +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.pushpull.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.pushpull.SyncopeResultHandler; +import org.apache.syncope.core.provisioning.java.utils.ConnObjectUtils; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; +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.AnyUtilsFactory; +import org.apache.syncope.core.provisioning.api.AnyObjectProvisioningManager; +import org.apache.syncope.core.provisioning.api.AuditManager; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.data.AnyObjectDataBinder; +import org.apache.syncope.core.provisioning.api.notification.NotificationManager; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningActions; +import org.apache.syncope.core.workflow.api.AnyObjectWorkflowAdapter; +import org.apache.syncope.core.workflow.api.GroupWorkflowAdapter; +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> { + + protected static final Logger LOG = LoggerFactory.getLogger(SyncopeResultHandler.class); + + @Autowired + protected AnyObjectDAO anyObjectDAO; + + @Autowired + protected UserDAO userDAO; + + @Autowired + protected GroupDAO groupDAO; + + /** + * ConnectorObject utils. + */ + @Autowired + protected ConnObjectUtils connObjectUtils; + + /** + * Notification Manager. + */ + @Autowired + protected NotificationManager notificationManager; + + /** + * Audit Manager. + */ + @Autowired + protected AuditManager auditManager; + + /** + * Propagation manager. + */ + @Autowired + protected PropagationManager propagationManager; + + /** + * Task executor. + */ + @Autowired + protected PropagationTaskExecutor taskExecutor; + + protected AnyObjectWorkflowAdapter awfAdapter; + + /** + * User workflow adapter. + */ + @Autowired + protected UserWorkflowAdapter uwfAdapter; + + /** + * Group workflow adapter. + */ + @Autowired + protected GroupWorkflowAdapter gwfAdapter; + + @Autowired + protected AnyObjectDataBinder anyObjectDataBinder; + + @Autowired + protected UserDataBinder userDataBinder; + + @Autowired + protected GroupDataBinder groupDataBinder; + + @Autowired + protected AnyObjectProvisioningManager anyObjectProvisioningManager; + + @Autowired + protected UserProvisioningManager userProvisioningManager; + + @Autowired + protected GroupProvisioningManager groupProvisioningManager; + + @Autowired + protected AnyUtilsFactory anyUtilsFactory; + + /** + * Sync profile. + */ + protected ProvisioningProfile<T, A> profile; + + protected abstract AnyUtils getAnyUtils(); + + protected abstract AnyTO getAnyTO(long key); + + protected abstract Any<?> getAny(long key); + + protected abstract AnyPatch newPatch(final long key); + + protected abstract WorkflowResult<Long> update(AnyPatch patch); + + @Override + public void setProfile(final ProvisioningProfile<T, A> profile) { + this.profile = profile; + } + + @Override + public ProvisioningProfile<T, A> getProfile() { + return profile; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPullResultHandlerImpl.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPullResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPullResultHandlerImpl.java new file mode 100644 index 0000000..67c5731 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPullResultHandlerImpl.java @@ -0,0 +1,112 @@ +/* + * 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.Collections; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.patch.AnyObjectPatch; +import org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.PropagationStatus; +import org.apache.syncope.common.lib.to.AnyObjectTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.persistence.api.entity.AnyUtils; +import org.apache.syncope.core.provisioning.api.ProvisioningManager; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPullResultHandler; + +public class AnyObjectPullResultHandlerImpl extends AbstractPullResultHandler implements AnyObjectPullResultHandler { + + @Override + protected AnyUtils getAnyUtils() { + return anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT); + } + + @Override + protected String getName(final AnyTO anyTO) { + return StringUtils.EMPTY; + } + + @Override + protected ProvisioningManager<?, ?> getProvisioningManager() { + return anyObjectProvisioningManager; + } + + @Override + protected Any<?> getAny(final long key) { + try { + return anyObjectDAO.authFind(key); + } catch (Exception e) { + LOG.warn("Error retrieving anyObject {}", key, e); + return null; + } + } + + @Override + protected AnyTO getAnyTO(final long key) { + return anyObjectDataBinder.getAnyObjectTO(key); + } + + @Override + protected AnyPatch newPatch(final long key) { + AnyObjectPatch patch = new AnyObjectPatch(); + patch.setKey(key); + return patch; + } + + @Override + protected WorkflowResult<Long> update(final AnyPatch patch) { + return awfAdapter.update((AnyObjectPatch) patch); + } + + @Override + protected AnyTO doCreate(final AnyTO anyTO, final SyncDelta delta, final ProvisioningReport result) { + AnyObjectTO anyObjectTO = AnyObjectTO.class.cast(anyTO); + + Map.Entry<Long, List<PropagationStatus>> created = anyObjectProvisioningManager.create( + anyObjectTO, Collections.singleton(profile.getTask().getResource().getKey()), true); + + result.setKey(created.getKey()); + result.setName(getName(anyTO)); + + return getAnyTO(created.getKey()); + } + + @Override + protected AnyTO doUpdate( + final AnyTO before, + final AnyPatch anyPatch, + final SyncDelta delta, + final ProvisioningReport result) { + + AnyObjectPatch anyObjectPatch = AnyObjectPatch.class.cast(anyPatch); + + Map.Entry<Long, List<PropagationStatus>> updated = + anyObjectProvisioningManager.update(anyObjectPatch, true); + + AnyObjectTO after = anyObjectDataBinder.getAnyObjectTO(updated.getKey()); + result.setName(getName(after)); + return after; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPushResultHandlerImpl.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPushResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPushResultHandlerImpl.java new file mode 100644 index 0000000..ce5aebb --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AnyObjectPushResultHandlerImpl.java @@ -0,0 +1,70 @@ +/* + * 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 org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.patch.AnyObjectPatch; +import org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.persistence.api.entity.AnyUtils; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPushResultHandler; + +public class AnyObjectPushResultHandlerImpl extends AbstractPushResultHandler implements AnyObjectPushResultHandler { + + @Override + protected AnyUtils getAnyUtils() { + return anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT); + } + + @Override + protected String getName(final Any<?> any) { + return StringUtils.EMPTY; + } + + @Override + protected Any<?> getAny(final long key) { + try { + return anyObjectDAO.authFind(key); + } catch (Exception e) { + LOG.warn("Error retrieving anyObject {}", key, e); + return null; + } + } + + @Override + protected AnyTO getAnyTO(final long key) { + return anyObjectDataBinder.getAnyObjectTO(key); + } + + @Override + protected AnyPatch newPatch(final long key) { + AnyObjectPatch patch = new AnyObjectPatch(); + patch.setKey(key); + return patch; + } + + @Override + protected WorkflowResult<Long> update(final AnyPatch patch) { + return awfAdapter.update((AnyObjectPatch) patch); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DBPasswordPullActions.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DBPasswordPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DBPasswordPullActions.java new file mode 100644 index 0000000..2df5ace --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DBPasswordPullActions.java @@ -0,0 +1,142 @@ +/* + * 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 org.apache.commons.collections4.IterableUtils; +import org.apache.commons.collections4.Predicate; +import org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.patch.PasswordPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.AnyTO; +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.pushpull.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport; +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 {@link org.apache.syncope.core.provisioning.api.pushpull.PullActions} 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 DBPasswordPullActions extends DefaultPullActions { + + private static final Logger LOG = LoggerFactory.getLogger(DBPasswordPullActions.class); + + private static final String CLEARTEXT = "CLEARTEXT"; + + @Autowired + private UserDAO userDAO; + + private String encodedPassword; + + private CipherAlgorithm cipher; + + @Transactional(readOnly = true) + @Override + public <A extends AnyTO> SyncDelta beforeProvision( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final A any) throws JobExecutionException { + + if (any instanceof UserTO) { + String password = ((UserTO) any).getPassword(); + parseEncodedPassword(password, profile.getConnector()); + } + + return delta; + } + + @Transactional(readOnly = true) + @Override + public <A extends AnyTO, M extends AnyPatch> SyncDelta beforeUpdate( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final A any, + final M anyPatch) throws JobExecutionException { + + if (anyPatch instanceof UserPatch) { + PasswordPatch modPassword = ((UserPatch) anyPatch).getPassword(); + parseEncodedPassword(modPassword == null ? null : modPassword.getValue(), profile.getConnector()); + } + + return delta; + } + + private void parseEncodedPassword(final String password, final Connector connector) { + if (password != null) { + ConnInstance connInstance = connector.getConnInstance(); + + 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) { + ConnConfProperty cipherAlgorithm = + IterableUtils.find(connInstance.getConf(), new Predicate<ConnConfProperty>() { + + @Override + public boolean evaluate(final ConnConfProperty property) { + return "cipherAlgorithm".equals(property.getSchema().getName()) + && property.getValues() != null && !property.getValues().isEmpty(); + } + }); + + return cipherAlgorithm == null + ? CLEARTEXT + : (String) cipherAlgorithm.getValues().get(0); + } + + @Transactional(readOnly = true) + @Override + public <A extends AnyTO> void after( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final A any, + final ProvisioningReport result) throws JobExecutionException { + + if (any instanceof UserTO && encodedPassword != null && cipher != null) { + User syncopeUser = userDAO.find(any.getKey()); + if (syncopeUser != null) { + syncopeUser.setEncodedPassword(encodedPassword.toUpperCase(), cipher); + } + encodedPassword = null; + cipher = null; + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultPullActions.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultPullActions.java new file mode 100644 index 0000000..7a4cbe0 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultPullActions.java @@ -0,0 +1,121 @@ +/* + * 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 org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.quartz.JobExecutionException; +import org.apache.syncope.core.provisioning.api.pushpull.PullActions; + +/** + * Default (empty) implementation of {@link PullActions}. + */ +public abstract class DefaultPullActions implements PullActions { + + @Override + public void beforeAll(final ProvisioningProfile<?, ?> profile) throws JobExecutionException { + } + + @Override + public <A extends AnyTO, P extends AnyPatch> SyncDelta beforeUpdate( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final A any, + final P anyMod) throws JobExecutionException { + + return delta; + } + + @Override + public <A extends AnyTO> SyncDelta beforeDelete( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final A any) + throws JobExecutionException { + + return delta; + } + + @Override + public <A extends AnyTO> SyncDelta beforeAssign( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final A any) + throws JobExecutionException { + + return delta; + } + + @Override + public <A extends AnyTO> SyncDelta beforeProvision( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final A any) + throws JobExecutionException { + + return delta; + } + + @Override + public <A extends AnyTO> SyncDelta beforeLink( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final A any) + throws JobExecutionException { + + return delta; + } + + @Override + public <A extends AnyTO> SyncDelta beforeUnassign( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final A any) + throws JobExecutionException { + + return delta; + } + + @Override + public <A extends AnyTO> SyncDelta beforeDeprovision( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final A any) + throws JobExecutionException { + + return delta; + } + + @Override + public <A extends AnyTO> SyncDelta beforeUnlink( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final A any) + throws JobExecutionException { + + return delta; + } + + @Override + public void onError( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final ProvisioningReport result, + final Exception error) throws JobExecutionException { + } + + @Override + public <A extends AnyTO> void after( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final A any, + final ProvisioningReport result) + throws JobExecutionException { + } + + @Override + public void afterAll(final ProvisioningProfile<?, ?> profile) + throws JobExecutionException { + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultPushActions.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultPushActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultPushActions.java new file mode 100644 index 0000000..2e75870 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultPushActions.java @@ -0,0 +1,100 @@ +/* + * 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 org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.provisioning.api.pushpull.PushActions; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport; +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 <A extends Any<?>> A beforeAssign(final ProvisioningProfile<?, ?> profile, final A any) + throws JobExecutionException { + + return any; + } + + @Override + public <A extends Any<?>> A beforeProvision(final ProvisioningProfile<?, ?> profile, final A any) + throws JobExecutionException { + + return any; + } + + @Override + public <A extends Any<?>> A beforeLink(final ProvisioningProfile<?, ?> profile, final A any) + throws JobExecutionException { + + return any; + } + + @Override + public <A extends Any<?>> A beforeUnassign(final ProvisioningProfile<?, ?> profile, final A any) + throws JobExecutionException { + + return any; + } + + @Override + public <A extends Any<?>> A beforeDeprovision(final ProvisioningProfile<?, ?> profile, final A any) + throws JobExecutionException { + + return any; + } + + @Override + public <A extends Any<?>> A beforeUnlink(final ProvisioningProfile<?, ?> profile, final A any) + throws JobExecutionException { + + return any; + } + + @Override + public <A extends Any<?>> void onError( + final ProvisioningProfile<?, ?> profile, final A any, final ProvisioningReport result, + final Exception error) throws JobExecutionException { + + // do nothing + } + + @Override + public <A extends Any<?>> void after( + final ProvisioningProfile<?, ?> profile, final A any, final ProvisioningReport result) + throws JobExecutionException { + + // do nothing + } + + @Override + public void afterAll(final ProvisioningProfile<?, ?> profile) + throws JobExecutionException { + + // do nothing + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultReconciliationFilterBuilder.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultReconciliationFilterBuilder.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultReconciliationFilterBuilder.java new file mode 100644 index 0000000..c46c0fb --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultReconciliationFilterBuilder.java @@ -0,0 +1,38 @@ +/* + * 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 static org.identityconnectors.framework.impl.api.local.operations.FilteredResultsHandler.PassThroughFilter; + +import org.identityconnectors.framework.common.objects.filter.Filter; +import org.apache.syncope.core.provisioning.api.pushpull.ReconciliationFilterBuilder; + +/** + * Default (pass-through) implementation of {@link ReconciliationFilterBuilder}. + */ +public abstract class DefaultReconciliationFilterBuilder implements ReconciliationFilterBuilder { + + private static final PassThroughFilter PASS_THROUGH = new PassThroughFilter(); + + @Override + public Filter build() { + return PASS_THROUGH; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GroupPullResultHandlerImpl.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GroupPullResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GroupPullResultHandlerImpl.java new file mode 100644 index 0000000..b880b57 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GroupPullResultHandlerImpl.java @@ -0,0 +1,138 @@ +/* + * 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.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.patch.AttrPatch; +import org.apache.syncope.common.lib.patch.GroupPatch; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.PropagationStatus; +import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +import org.apache.syncope.common.lib.types.PatchOperation; +import org.apache.syncope.core.persistence.api.entity.Any; +import org.apache.syncope.core.persistence.api.entity.AnyUtils; +import org.apache.syncope.core.provisioning.api.ProvisioningManager; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport; +import org.identityconnectors.framework.common.objects.SyncDelta; +import org.apache.syncope.core.provisioning.api.pushpull.GroupPullResultHandler; + +public class GroupPullResultHandlerImpl extends AbstractPullResultHandler implements GroupPullResultHandler { + + protected final Map<Long, String> groupOwnerMap = new HashMap<>(); + + @Override + public Map<Long, String> getGroupOwnerMap() { + return this.groupOwnerMap; + } + + @Override + protected AnyUtils getAnyUtils() { + return anyUtilsFactory.getInstance(AnyTypeKind.GROUP); + } + + @Override + protected String getName(final AnyTO anyTO) { + return GroupTO.class.cast(anyTO).getName(); + } + + @Override + protected ProvisioningManager<?, ?> getProvisioningManager() { + return groupProvisioningManager; + } + + @Override + protected Any<?> getAny(final long key) { + try { + return groupDAO.authFind(key); + } catch (Exception e) { + LOG.warn("Error retrieving group {}", key, e); + return null; + } + } + + @Override + protected AnyTO getAnyTO(final long key) { + return groupDataBinder.getGroupTO(key); + } + + @Override + protected AnyPatch newPatch(final long key) { + GroupPatch patch = new GroupPatch(); + patch.setKey(key); + return patch; + } + + @Override + protected WorkflowResult<Long> update(final AnyPatch patch) { + return gwfAdapter.update((GroupPatch) patch); + } + + @Override + protected AnyTO doCreate(final AnyTO anyTO, final SyncDelta delta, final ProvisioningReport result) { + GroupTO groupTO = GroupTO.class.cast(anyTO); + + Map.Entry<Long, List<PropagationStatus>> created = groupProvisioningManager.create( + groupTO, + groupOwnerMap, + Collections.singleton(profile.getTask().getResource().getKey()), + true); + + result.setKey(created.getKey()); + result.setName(getName(anyTO)); + + return getAnyTO(created.getKey()); + } + + @Override + protected AnyTO doUpdate( + final AnyTO before, + final AnyPatch anyPatch, + final SyncDelta delta, + final ProvisioningReport result) { + + GroupPatch groupPatch = GroupPatch.class.cast(anyPatch); + + Map.Entry<Long, List<PropagationStatus>> updated = groupProvisioningManager.update(groupPatch, true); + + String groupOwner = null; + for (AttrPatch attrPatch : groupPatch.getPlainAttrs()) { + if (attrPatch.getOperation() == PatchOperation.ADD_REPLACE && attrPatch.getAttrTO() != null + && attrPatch.getAttrTO().getSchema().isEmpty() && !attrPatch.getAttrTO().getValues().isEmpty()) { + + groupOwner = attrPatch.getAttrTO().getValues().get(0); + } + } + if (groupOwner != null) { + groupOwnerMap.put(updated.getKey(), groupOwner); + } + + GroupTO after = groupDataBinder.getGroupTO(updated.getKey()); + + result.setName(getName(after)); + + return after; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GroupPushResultHandlerImpl.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GroupPushResultHandlerImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GroupPushResultHandlerImpl.java new file mode 100644 index 0000000..c572177 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/GroupPushResultHandlerImpl.java @@ -0,0 +1,70 @@ +/* + * 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 org.apache.syncope.common.lib.patch.GroupPatch; +import org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.types.AnyTypeKind; +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.group.Group; +import org.apache.syncope.core.provisioning.api.WorkflowResult; +import org.apache.syncope.core.provisioning.api.pushpull.GroupPushResultHandler; + +public class GroupPushResultHandlerImpl extends AbstractPushResultHandler implements GroupPushResultHandler { + + @Override + protected AnyUtils getAnyUtils() { + return anyUtilsFactory.getInstance(AnyTypeKind.GROUP); + } + + @Override + protected String getName(final Any<?> any) { + return Group.class.cast(any).getName(); + } + + @Override + protected Any<?> getAny(final long key) { + try { + return groupDAO.authFind(key); + } catch (Exception e) { + LOG.warn("Error retrieving group {}", key, e); + return null; + } + } + + @Override + protected AnyTO getAnyTO(final long key) { + return groupDataBinder.getGroupTO(key); + } + + @Override + protected AnyPatch newPatch(final long key) { + GroupPatch patch = new GroupPatch(); + patch.setKey(key); + return patch; + } + + @Override + protected WorkflowResult<Long> update(final AnyPatch patch) { + return gwfAdapter.update((GroupPatch) patch); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java new file mode 100644 index 0000000..7f7971c --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java @@ -0,0 +1,333 @@ +/* + * 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.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.collections4.IterableUtils; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.patch.MembershipPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.AnyTO; +import org.apache.syncope.common.lib.to.GroupTO; +import org.apache.syncope.common.lib.to.MembershipTO; +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.common.lib.types.PatchOperation; +import org.apache.syncope.core.persistence.api.dao.GroupDAO; +import org.apache.syncope.core.persistence.api.entity.group.Group; +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.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.pushpull.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport; +import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO; +import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource; +import org.apache.syncope.core.persistence.api.entity.user.UMembership; +import org.apache.syncope.core.provisioning.api.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.ConnectorObject; +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; +import org.apache.syncope.core.persistence.api.entity.task.PullTask; + +/** + * Simple action for pulling LDAP groups memberships to Syncope group memberships, when the same resource is + * configured for both users and groups. + * + * @see org.apache.syncope.core.provisioning.java.propagation.LDAPMembershipPropagationActions + */ +public class LDAPMembershipPullActions extends DefaultPullActions { + + protected static final Logger LOG = LoggerFactory.getLogger(LDAPMembershipPullActions.class); + + @Autowired + protected AnyTypeDAO anyTypeDAO; + + @Autowired + protected UserDAO userDAO; + + @Autowired + protected GroupDAO groupDAO; + + @Autowired + protected UserWorkflowAdapter uwfAdapter; + + @Autowired + protected PropagationManager propagationManager; + + @Autowired + private PropagationTaskExecutor taskExecutor; + + @Autowired + private NotificationManager notificationManager; + + @Autowired + private AuditManager auditManager; + + @Autowired + private PullUtils pullUtils; + + protected Map<Long, Long> membersBeforeGroupUpdate = 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) { + ConnConfProperty groupMembership = IterableUtils.find(connector.getConnInstance().getConf(), + new Predicate<ConnConfProperty>() { + + @Override + public boolean evaluate(final ConnConfProperty property) { + return "groupMemberAttribute".equals(property.getSchema().getName()) + && property.getValues() != null && !property.getValues().isEmpty(); + } + }); + + return groupMembership == null + ? "uniquemember" + : (String) groupMembership.getValues().get(0); + } + + /** + * Keep track of members of the group being updated <b>before</b> actual update takes place. This is not needed on + * <ul> <li>beforeProvision() - because the pulling group does not exist yet on Syncope</li> + * <li>beforeDelete() - because group delete cascades as membership removal for all users involved</li> </ul> + * + * {@inheritDoc} + */ + @Override + public <A extends AnyTO, M extends AnyPatch> SyncDelta beforeUpdate( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, final A any, final M anyPatch) throws JobExecutionException { + + if (any instanceof GroupTO) { + // search for all users assigned to given group + Group group = groupDAO.find(any.getKey()); + if (group != null) { + List<UMembership> membs = groupDAO.findUMemberships(group); + // save memberships before group update takes place + membersBeforeGroupUpdate = new HashMap<>(membs.size()); + for (UMembership memb : membs) { + membersBeforeGroupUpdate.put(memb.getLeftEnd().getKey(), memb.getKey()); + } + } + } + + return super.beforeUpdate(profile, delta, any, anyPatch); + } + + /** + * Build UserPatch for adding membership to given user, for given group. + * + * @param userKey user to be assigned membership to given group + * @param groupTO group for adding membership + * @return UserPatch for user update + */ + protected UserPatch getUserPatch(final Long userKey, final GroupTO groupTO) { + UserPatch userPatch = new UserPatch(); + // no actual modification takes place when user has already the group assigned + if (membersBeforeGroupUpdate.containsKey(userKey)) { + membersBeforeGroupUpdate.remove(userKey); + } else { + userPatch.setKey(userKey); + + userPatch.getMemberships().add( + new MembershipPatch.Builder(). + operation(PatchOperation.ADD_REPLACE). + membershipTO(new MembershipTO.Builder().group(groupTO.getKey(), null).build()). + build()); + } + + return userPatch; + } + + /** + * 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 pulling group + * @param connector associated to the current resource + * @return value of attribute returned by + * {@link #getGroupMembershipAttrName} + */ + 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 pull + 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) { + OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(groupMemberName); + ConnectorObject remoteObj = connector.getObject(ObjectClass.GROUP, delta.getUid(), oob.build()); + if (remoteObj == null) { + LOG.debug("Object for '{}' not found", delta.getUid().getUidValue()); + } else { + membAttr = remoteObj.getAttributeByName(groupMemberName); + } + } + if (membAttr != null && membAttr.getValue() != null) { + result = membAttr.getValue(); + } + + return result; + } + + /** + * Perform actual modifications (i.e. membership add / remove) for the given group on the given resource. + * + * @param userPatch modifications to perform on the user + * @param resourceName resource to be propagated for changes + */ + protected void userUpdate(final UserPatch userPatch, final String resourceName) { + if (userPatch.getKey() == 0) { + return; + } + + Result result; + + WorkflowResult<Pair<UserPatch, Boolean>> updated = null; + + try { + updated = uwfAdapter.update(userPatch); + + List<PropagationTask> tasks = propagationManager.getUserUpdateTasks( + updated, false, Collections.singleton(resourceName)); + + taskExecutor.execute(tasks); + result = Result.SUCCESS; + } catch (PropagationException e) { + result = Result.FAILURE; + LOG.error("Could not propagate {}", userPatch, e); + } catch (Exception e) { + result = Result.FAILURE; + LOG.error("Could not perform update {}", userPatch, e); + } + + notificationManager.createTasks(AuditElements.EventCategoryType.PULL, + this.getClass().getSimpleName(), + null, + "update", + result, + null, // searching for before object is too much expensive ... + updated == null ? null : updated.getResult().getKey(), + userPatch, + resourceName); + + auditManager.audit(AuditElements.EventCategoryType.PULL, + this.getClass().getSimpleName(), + null, + "update", + result, + null, // searching for before object is too much expensive ... + updated == null ? null : updated.getResult().getKey(), + userPatch, + resourceName); + } + + /** + * Pull Syncope memberships with the situation read on the external resource's group. + * + * @param profile pull profile + * @param delta representing the pullong group + * @param groupTO group after modification performed by the handler + * @throws JobExecutionException if anything goes wrong + */ + protected void pullMemberships( + final ProvisioningProfile<?, ?> profile, final SyncDelta delta, final GroupTO groupTO) + throws JobExecutionException { + + ProvisioningTask task = profile.getTask(); + ExternalResource resource = task.getResource(); + Connector connector = profile.getConnector(); + + for (Object membValue : getMembAttrValues(delta, connector)) { + Long userKey = pullUtils.findMatchingAnyKey( + anyTypeDAO.findUser(), + membValue.toString(), + profile.getTask().getResource(), + profile.getConnector()); + if (userKey != null) { + UserPatch userPatch = getUserPatch(userKey, groupTO); + userUpdate(userPatch, resource.getKey()); + } + } + + // finally remove any residual membership that was present before group update but not any more + for (Map.Entry<Long, Long> member : membersBeforeGroupUpdate.entrySet()) { + UserPatch userPatch = new UserPatch(); + userPatch.setKey(member.getKey()); + + userPatch.getMemberships().add( + new MembershipPatch.Builder(). + operation(PatchOperation.DELETE). + membershipTO(new MembershipTO.Builder().group(groupTO.getKey(), null).build()). + build()); + + userUpdate(userPatch, resource.getKey()); + } + } + + /** + * Pull membership at group pull time (because PullJob first pulls users then groups). + * {@inheritDoc} + */ + @Override + public <A extends AnyTO> void after( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final A any, + final ProvisioningReport result) throws JobExecutionException { + + if (!(profile.getTask() instanceof PullTask)) { + return; + } + + if (!(any instanceof GroupTO) + || profile.getTask().getResource().getProvision(anyTypeDAO.findUser()) == null + || profile.getTask().getResource().getProvision(anyTypeDAO.findUser()).getMapping() == null) { + + super.after(profile, delta, any, result); + } else { + pullMemberships(profile, delta, (GroupTO) any); + } + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java new file mode 100644 index 0000000..2d61396 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPPasswordPullActions.java @@ -0,0 +1,124 @@ +/* + * 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 org.apache.syncope.common.lib.patch.AnyPatch; +import org.apache.syncope.common.lib.patch.PasswordPatch; +import org.apache.syncope.common.lib.patch.UserPatch; +import org.apache.syncope.common.lib.to.AnyTO; +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.pushpull.ProvisioningProfile; +import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningReport; +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 {@link org.apache.syncope.core.provisioning.api.pushpull.PullActions} implementation which allows the ability to + * import passwords from an LDAP backend that are hashed. + */ +public class LDAPPasswordPullActions extends DefaultPullActions { + + protected static final Logger LOG = LoggerFactory.getLogger(LDAPPasswordPullActions.class); + + @Autowired + private UserDAO userDAO; + + private String encodedPassword; + + private CipherAlgorithm cipher; + + @Transactional(readOnly = true) + @Override + public <A extends AnyTO> SyncDelta beforeProvision( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final A any) throws JobExecutionException { + + if (any instanceof UserTO) { + String password = ((UserTO) any).getPassword(); + parseEncodedPassword(password); + } + + return delta; + } + + @Transactional(readOnly = true) + @Override + public <A extends AnyTO, M extends AnyPatch> SyncDelta beforeUpdate( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final A any, + final M anyPatch) throws JobExecutionException { + + if (anyPatch instanceof UserPatch) { + PasswordPatch modPassword = ((UserPatch) anyPatch).getPassword(); + parseEncodedPassword(modPassword == null ? null : modPassword.getValue()); + } + + return delta; + } + + private void parseEncodedPassword(final 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 <A extends AnyTO> void after( + final ProvisioningProfile<?, ?> profile, + final SyncDelta delta, + final A any, + final ProvisioningReport result) throws JobExecutionException { + + if (any instanceof UserTO && encodedPassword != null && cipher != null) { + User syncopeUser = userDAO.find(any.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/61a7fdd3/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PlainAttrsPullCorrelationRule.java ---------------------------------------------------------------------- diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PlainAttrsPullCorrelationRule.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PlainAttrsPullCorrelationRule.java new file mode 100644 index 0000000..d752cd5 --- /dev/null +++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PlainAttrsPullCorrelationRule.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.pushpull; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.syncope.core.provisioning.java.MappingManagerImpl; +import org.apache.syncope.core.persistence.api.dao.search.AnyCond; +import org.apache.syncope.core.persistence.api.dao.search.AttributeCond; +import org.apache.syncope.core.persistence.api.dao.search.SearchCond; +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem; +import org.apache.syncope.core.persistence.api.entity.resource.Provision; +import org.apache.syncope.core.provisioning.api.data.MappingItemTransformer; +import org.identityconnectors.framework.common.objects.Attribute; +import org.identityconnectors.framework.common.objects.ConnectorObject; +import org.apache.syncope.core.provisioning.api.pushpull.PullCorrelationRule; + +public class PlainAttrsPullCorrelationRule implements PullCorrelationRule { + + private final List<String> plainSchemaNames; + + private final Provision provision; + + public PlainAttrsPullCorrelationRule(final String[] plainSchemaNames, final Provision provision) { + this.plainSchemaNames = Arrays.asList(plainSchemaNames); + this.provision = provision; + } + + @Override + public SearchCond getSearchCond(final ConnectorObject connObj) { + Map<String, MappingItem> mappingItems = new HashMap<>(); + for (MappingItem item : MappingManagerImpl.getPullMappingItems(provision)) { + mappingItems.put(item.getIntAttrName(), item); + } + + // search for anys by attribute(s) specified in the policy + SearchCond searchCond = null; + + for (String schema : plainSchemaNames) { + Attribute attr = mappingItems.get(schema) == null + ? null + : connObj.getAttributeByName(mappingItems.get(schema).getExtAttrName()); + if (attr == null) { + throw new IllegalArgumentException( + "Connector object does not contains the attributes to perform the search: " + schema); + } + + List<Object> values = attr.getValue(); + for (MappingItemTransformer transformer + : MappingManagerImpl.getMappingItemTransformers(mappingItems.get(schema))) { + + values = transformer.beforePull(values); + } + + AttributeCond.Type type; + String expression = null; + + if (values == null || values.isEmpty() || (values.size() == 1 && values.get(0) == null)) { + type = AttributeCond.Type.ISNULL; + } else { + type = AttributeCond.Type.EQ; + expression = values.size() > 1 + ? values.toString() + : values.get(0).toString(); + } + + SearchCond nodeCond; + // users: just key or username can be selected + // groups: just key or name can be selected + // any objects: just key can be selected + if ("key".equalsIgnoreCase(schema) + || "username".equalsIgnoreCase(schema) || "name".equalsIgnoreCase(schema)) { + + AnyCond cond = new AnyCond(); + cond.setSchema(schema); + cond.setType(type); + cond.setExpression(expression); + + nodeCond = SearchCond.getLeafCond(cond); + } else { + AttributeCond cond = new AttributeCond(); + cond.setSchema(schema); + cond.setType(type); + cond.setExpression(expression); + + nodeCond = SearchCond.getLeafCond(cond); + } + + searchCond = searchCond == null + ? nodeCond + : SearchCond.getAndCond(searchCond, nodeCond); + } + + return searchCond; + } + +}
