/* * Copyright (c) 2010-2017 Evolveum * * Licensed 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 com.evolveum.midpoint.model.impl.lens.projector; import com.evolveum.midpoint.common.filter.Filter; import com.evolveum.midpoint.common.filter.FilterManager; import com.evolveum.midpoint.common.refinery.PropertyLimitations; import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.common.expression.ItemDeltaItem; import com.evolveum.midpoint.model.common.expression.Source; import com.evolveum.midpoint.model.common.expression.StringPolicyResolver; import com.evolveum.midpoint.model.common.mapping.Mapping; import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.lens.*; import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialPolicyEvaluator; import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.crypto.EncryptionException; import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Processor that takes changes from accounts and synchronization deltas and updates user attributes if necessary * (by creating secondary user object delta {@link ObjectDelta}). * * @author lazyman * @author Radovan Semancik */ @Component public class InboundProcessor { private static final String PROCESS_INBOUND_HANDLING = InboundProcessor.class.getName() + ".processInbound"; private static final Trace LOGGER = TraceManager.getTrace(InboundProcessor.class); @Autowired private PrismContext prismContext; @Autowired private FilterManager<Filter> filterManager; @Autowired private MappingFactory mappingFactory; @Autowired private ContextLoader contextLoader; @Autowired private CredentialsProcessor credentialsProcessor; @Autowired private MappingEvaluator mappingEvaluator; @Autowired private Protector protector; <O extends ObjectType> void processInbound(LensContext<O> context, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, CommunicationException, SecurityViolationException { LensFocusContext<O> focusContext = context.getFocusContext(); if (focusContext == null) { LOGGER.trace("Skipping inbound because there is no focus"); return; } if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { // We can do this only for focus types. LOGGER.trace("Skipping inbound because {} is not focal type", focusContext.getObjectTypeClass()); return; } processInboundFocal((LensContext<? extends FocusType>)context, task, now, result); } private <F extends FocusType> void processInboundFocal(LensContext<F> context, Task task, XMLGregorianCalendar now, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, CommunicationException, SecurityViolationException { LensFocusContext<F> focusContext = context.getFocusContext(); if (focusContext == null) { LOGGER.trace("Skipping inbound processing because focus is null"); return; } if (focusContext.isDelete()) { LOGGER.trace("Skipping inbound processing because focus is being deleted"); return; } ObjectDelta<F> userSecondaryDelta = focusContext.getProjectionWaveSecondaryDelta(); if (userSecondaryDelta != null && ChangeType.DELETE.equals(userSecondaryDelta.getChangeType())) { //we don't need to do inbound if we are deleting this user return; } OperationResult subResult = result.createMinorSubresult(PROCESS_INBOUND_HANDLING); try { for (LensProjectionContext projectionContext : context.getProjectionContexts()) { if (projectionContext.isThombstone()) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping processing of inbound expressions for projection {} because is is thombstone", projectionContext.getHumanReadableName()); } continue; } if (!projectionContext.isCanProject()){ if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping processing of inbound expressions for projection {}: there is a limit to propagate changes only from resource {}", projectionContext.getHumanReadableName(), context.getTriggeredResourceOid()); } continue; } ObjectDelta<ShadowType> aPrioriDelta = getAPrioriDelta(context, projectionContext); if (!projectionContext.isDoReconciliation() && aPrioriDelta == null && !LensUtil.hasDependentContext(context, projectionContext) && !projectionContext.isFullShadow()) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping processing of inbound expressions for projection {}: no full shadow, no reconciliation, no a priori delta and no dependent context", projectionContext.getHumanReadableName()); } continue; } RefinedObjectClassDefinition rOcDef = projectionContext.getCompositeObjectClassDefinition(); if (rOcDef == null) { LOGGER.error("Definition for projection {} not found in the context, but it " + "should be there, dumping context:\n{}", projectionContext.getHumanReadableName(), context.debugDump()); throw new IllegalStateException("Definition for projection " + projectionContext.getHumanReadableName() + " not found in the context, but it should be there"); } processInboundExpressionsForProjection(context, projectionContext, rOcDef, aPrioriDelta, task, now, subResult); } } finally { subResult.computeStatus(); } } private boolean isDeleteAccountDelta(LensProjectionContext accountContext) throws SchemaException { if (accountContext.getSyncDelta() != null && ChangeType.DELETE == accountContext.getSyncDelta().getChangeType()){ return true; } if (accountContext.getDelta() != null && ChangeType.DELETE == accountContext.getDelta().getChangeType()){ return true; } return false; } private <F extends FocusType> void processInboundExpressionsForProjection(LensContext<F> context, LensProjectionContext projContext, RefinedObjectClassDefinition accountDefinition, ObjectDelta<ShadowType> aPrioriDelta, Task task, XMLGregorianCalendar now, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, CommunicationException, SecurityViolationException { if (aPrioriDelta == null && projContext.getObjectCurrent() == null) { LOGGER.trace("Nothing to process in inbound, both a priori delta and current account were null."); return; } PrismObject<ShadowType> accountCurrent = projContext.getObjectCurrent(); PrismObject<ShadowType> accountNew = projContext.getObjectNew(); if (hasAnyStrongMapping(accountDefinition) && !projContext.isFullShadow() && !projContext.isThombstone()) { LOGGER.trace("There are strong inbound mapping, but the shadow hasn't be fully loaded yet. Trying to load full shadow now."); accountCurrent = loadProjection(context, projContext, task, result, accountCurrent); if (projContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { return; } } for (QName accountAttributeName : accountDefinition.getNamesOfAttributesWithInboundExpressions()) { final PropertyDelta<?> attributeAPrioriDelta; if (aPrioriDelta != null) { attributeAPrioriDelta = aPrioriDelta.findPropertyDelta(new ItemPath(SchemaConstants.C_ATTRIBUTES), accountAttributeName); if (attributeAPrioriDelta == null && !projContext.isFullShadow() && !LensUtil.hasDependentContext(context, projContext)) { LOGGER.trace("Skipping inbound for {} in {}: Not a full shadow and account a priori delta exists, but doesn't have change for processed property.", accountAttributeName, projContext.getResourceShadowDiscriminator()); continue; } } else { attributeAPrioriDelta = null; } RefinedAttributeDefinition attrDef = accountDefinition.findAttributeDefinition(accountAttributeName); if (attrDef.isIgnored(LayerType.MODEL)) { LOGGER.trace("Skipping inbound for attribute {} in {} because the attribute is ignored", PrettyPrinter.prettyPrint(accountAttributeName), projContext.getResourceShadowDiscriminator()); continue; } List<MappingType> inboundMappingTypes = attrDef.getInboundMappingTypes(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Processing inbound for {} in {}; ({} mappings)", PrettyPrinter.prettyPrint(accountAttributeName), projContext.getResourceShadowDiscriminator(), inboundMappingTypes.size()); } if (!inboundMappingTypes.isEmpty()) { PropertyLimitations limitations = attrDef.getLimitations(LayerType.MODEL); if (limitations != null) { PropertyAccessType access = limitations.getAccess(); if (access != null) { if (access.isRead() == null || !access.isRead()) { LOGGER.warn("Inbound mapping for non-readable attribute {} in {}, skipping", accountAttributeName, projContext.getHumanReadableName()); continue; } } } for (MappingType inboundMappingType : inboundMappingTypes) { // There are two processing options: // // * If we have a delta as an input we will proceed in relative mode, applying mappings on the delta. // This usually happens when a delta comes from a sync notification or if there is a primary projection delta. // // * if we do NOT have a delta then we will proceed in absolute mode. In that mode we will apply the // mappings to the absolute projection state that we got from provisioning. This is a kind of "inbound reconciliation". // // TODO what if there is a priori delta for a given attribute (e.g. ADD one) and // we want to reconcile also the existing attribute value? This probably would not work. if (inboundMappingType.getStrength() == MappingStrengthType.STRONG) { LOGGER.trace("There is an inbound mapping with strength == STRONG, trying to load full account now."); if (!projContext.isFullShadow() && !projContext.isDelete()) { accountCurrent = loadProjection(context, projContext, task, result, accountCurrent); if (projContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { return; } } } if (attributeAPrioriDelta == null && !projContext.isFullShadow() && !LensUtil.hasDependentContext(context, projContext)) { LOGGER.trace("Skipping inbound for {} in {}: Not a full shadow and account a priori delta exists, but doesn't have change for processed property.", accountAttributeName, projContext.getResourceShadowDiscriminator()); continue; } PrismObject<F> focus; if (context.getFocusContext().getObjectCurrent() != null) { focus = context.getFocusContext().getObjectCurrent(); } else { focus = context.getFocusContext().getObjectNew(); } ItemDelta focusItemDelta = null; if (attributeAPrioriDelta != null) { LOGGER.trace("Processing inbound from a priori delta: {}", aPrioriDelta); focusItemDelta = evaluateInboundMapping(context, inboundMappingType, accountAttributeName, null, attributeAPrioriDelta, focus, accountNew, projContext.getResource(), task, result); } else if (accountCurrent != null) { if (!projContext.isFullShadow()) { LOGGER.warn("Attempted to execute inbound expression on account shadow {} WITHOUT full account. Trying to load the account now.", projContext.getOid()); // todo change to trace level eventually accountCurrent = loadProjection(context, projContext, task, result, accountCurrent); if (projContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { return; } if (!projContext.isFullShadow()) { if (projContext.getResourceShadowDiscriminator().getOrder() > 0) { // higher-order context. It is OK not to load this LOGGER.trace("Skipped load of higher-order account with shadow OID {} skipping inbound processing on it", projContext.getOid()); return; } // TODO: is it good to mark as broken? what is // the resorce is down?? if there is no // assignment and the account was added directly // it can cause that the account will be // unlinked from the user FIXME LOGGER.warn("Couldn't load account with shadow OID {}, setting context as broken and skipping inbound processing on it", projContext.getOid()); projContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); return; } } PrismProperty<?> oldAccountProperty = accountCurrent.findProperty(new ItemPath(ShadowType.F_ATTRIBUTES, accountAttributeName)); LOGGER.trace("Processing inbound from account sync absolute state (currentAccount): {}", oldAccountProperty); focusItemDelta = evaluateInboundMapping(context, inboundMappingType, accountAttributeName, oldAccountProperty, null, focus, accountNew, projContext.getResource(), task, result); } if (focusItemDelta != null && !focusItemDelta.isEmpty()) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Created delta (from inbound expression for {} on {})\n{}", accountAttributeName, projContext.getResource(), focusItemDelta.debugDump(1)); } context.getFocusContext().swallowToProjectionWaveSecondaryDelta(focusItemDelta); context.recomputeFocus(); } else { LOGGER.trace("Created delta (from inbound expression for {} on {}) was null or empty.", accountAttributeName, projContext.getResource()); } } } } if (isDeleteAccountDelta(projContext)) { // we don't need to do inbound if account was deleted return; } processSpecialPropertyInbound(accountDefinition.getPasswordInbound(), SchemaConstants.PATH_PASSWORD_VALUE, context.getFocusContext().getObjectNew(), projContext, accountDefinition, context, task, now, result); processSpecialPropertyInbound(accountDefinition.getActivationBidirectionalMappingType(ActivationType.F_ADMINISTRATIVE_STATUS), SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, context.getFocusContext().getObjectNew(), projContext, accountDefinition, context, task, now, result); processSpecialPropertyInbound(accountDefinition.getActivationBidirectionalMappingType(ActivationType.F_VALID_FROM), SchemaConstants.PATH_ACTIVATION_VALID_FROM, context.getFocusContext().getObjectNew(), projContext, accountDefinition, context, task, now, result); processSpecialPropertyInbound(accountDefinition.getActivationBidirectionalMappingType(ActivationType.F_VALID_TO), SchemaConstants.PATH_ACTIVATION_VALID_TO, context.getFocusContext().getObjectNew(), projContext, accountDefinition, context, task, now, result); } private <F extends FocusType> PrismObject<ShadowType> loadProjection(LensContext<F> context, LensProjectionContext projContext, Task task, OperationResult result, PrismObject<ShadowType> accountCurrent) throws SchemaException { try { contextLoader.loadFullShadow(context, projContext, task, result); accountCurrent = projContext.getObjectCurrent(); } catch (ObjectNotFoundException |SecurityViolationException |CommunicationException |ConfigurationException e) { LOGGER.warn("Couldn't load account with shadow OID {} because of {}, setting context as broken and skipping inbound processing on it", projContext.getOid(), e.getMessage()); projContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); } return accountCurrent; } private boolean hasAnyStrongMapping(RefinedObjectClassDefinition objectDefinition) { for (QName attributeName : objectDefinition.getNamesOfAttributesWithInboundExpressions()) { RefinedAttributeDefinition<?> attributeDefinition = objectDefinition.findAttributeDefinition(attributeName); for (MappingType inboundMapping : attributeDefinition.getInboundMappingTypes()){ if (inboundMapping.getStrength() == MappingStrengthType.STRONG) { return true; } } } return false; } /** * A priori delta is a delta that was executed in a previous "step". That means it is either delta from a previous * wave or a sync delta (in wave 0). */ private <F extends ObjectType> ObjectDelta<ShadowType> getAPrioriDelta(LensContext<F> context, LensProjectionContext accountContext) throws SchemaException { int wave = context.getProjectionWave(); if (wave == 0) { return accountContext.getSyncDelta(); } if (wave == accountContext.getWave() + 1) { // If this resource was processed in a previous wave .... // Normally, we take executed delta. However, there are situations (like preview changes - i.e. projector without execution), // when there is no executed delta. In that case we take standard primary + secondary delta. // TODO is this really correct? Think if the following can happen: // - NOT previewing // - no executed deltas but // - existing primary/secondary delta. List<LensObjectDeltaOperation<ShadowType>> executed = accountContext.getExecutedDeltas(); if (executed != null && !executed.isEmpty()) { return executed.get(executed.size()-1).getObjectDelta(); } else { return accountContext.getDelta(); } } return null; } private <F extends ObjectType> boolean checkWeakSkip(Mapping<?,?> inbound, PrismObject<F> newUser) throws SchemaException { if (inbound.getStrength() != MappingStrengthType.WEAK) { return false; } if (newUser == null) { return false; } PrismProperty<?> property = newUser.findProperty(inbound.getOutputPath()); if (property != null && !property.isEmpty()) { return true; } return false; } private <A, F extends FocusType, V extends PrismValue,D extends ItemDefinition> ItemDelta<V,D> evaluateInboundMapping(final LensContext<F> context, MappingType inboundMappingType, QName accountAttributeName, PrismProperty<A> oldAccountProperty, PropertyDelta<A> attributeAPrioriDelta, PrismObject<F> focusNew, PrismObject<ShadowType> account, ResourceType resource, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, ConfigurationException { if (oldAccountProperty != null && oldAccountProperty.hasRaw()) { throw new SystemException("Property "+oldAccountProperty+" has raw parsing state, such property cannot be used in inbound expressions"); } Mapping.Builder<V,D> builder = mappingFactory.createMappingBuilder(inboundMappingType, "inbound expression for "+accountAttributeName+" in "+resource); if (!builder.isApplicableToChannel(context.getChannel())) { return null; } Source<PrismPropertyValue<A>,PrismPropertyDefinition<A>> defaultSource = new Source<>(oldAccountProperty, attributeAPrioriDelta, null, ExpressionConstants.VAR_INPUT); defaultSource.recompute(); Mapping<V,D> mapping = builder.defaultSource(defaultSource) .targetContext(LensUtil.getFocusDefinition(context)) .addVariableDefinition(ExpressionConstants.VAR_USER, focusNew) .addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusNew) .addVariableDefinition(ExpressionConstants.VAR_ACCOUNT, account) .addVariableDefinition(ExpressionConstants.VAR_SHADOW, account) .addVariableDefinition(ExpressionConstants.VAR_RESOURCE, resource) .stringPolicyResolver(createStringPolicyResolver(context, task, result)) .originType(OriginType.INBOUND) .originObject(resource) .build(); if (checkWeakSkip(mapping, focusNew)) { LOGGER.trace("Skipping because of mapping is weak and focus property has already a value"); return null; } ItemPath targetFocusItemPath = mapping.getOutputPath(); if (ItemPath.isNullOrEmpty(targetFocusItemPath)) { throw new ConfigurationException("Empty target path in "+mapping.getContextDescription()); } Item targetFocusItem = null; if (focusNew != null) { targetFocusItem = focusNew.findItem(targetFocusItemPath); } PrismObjectDefinition<F> focusDefinition = context.getFocusContext().getObjectDefinition(); ItemDefinition targetItemDef = focusDefinition.findItemDefinition(targetFocusItemPath); if (targetItemDef == null) { throw new SchemaException("No definition for focus property "+targetFocusItemPath+", cannot process inbound expression in "+resource); } final ItemDelta outputFocusItemDelta = targetItemDef.createEmptyDelta(targetFocusItemPath); mappingEvaluator.evaluateMapping(mapping, context, task, result); PrismValueDeltaSetTriple<V> triple = mapping.getOutputTriple(); // Meaning of the resulting triple: // values in PLUS set will be added (valuesToAdd in delta) // values in MINUS set will be removed (valuesToDelete in delta) // values in ZERO set will be compared with existing values in user property // the differences will be added to delta if (LOGGER.isTraceEnabled()) { LOGGER.trace("Inbound mapping for {} returned triple:\n{}", accountAttributeName, triple == null ? "null" : triple.debugDump()); } if (triple != null) { if (triple.hasPlusSet()) { boolean alreadyReplaced = false; for (V value : triple.getPlusSet()) { if (targetFocusItem != null && targetFocusItem.hasRealValue(value)) { continue; } //if property is not multi value replace existing attribute if (targetFocusItem != null && !targetFocusItem.getDefinition().isMultiValue() && !targetFocusItem.isEmpty()) { Collection<V> replace = new ArrayList<V>(); replace.add((V) value.clone()); outputFocusItemDelta.setValuesToReplace(replace); if (alreadyReplaced) { LOGGER.warn("Multiple values for a single-valued property {}; duplicate value = {}", targetFocusItem, value); } else { alreadyReplaced = true; } } else { outputFocusItemDelta.addValueToAdd(value.clone()); } } } if (triple.hasMinusSet()) { LOGGER.trace("Checking account sync property delta values to delete"); for (V value : triple.getMinusSet()) { if (targetFocusItem == null || targetFocusItem.hasRealValue(value)) { if (!outputFocusItemDelta.isReplace()) { // This is not needed if we are going to replace. In fact it might cause an error. outputFocusItemDelta.addValueToDelete(value); } } } } Item shouldBeItem = targetItemDef.instantiate(); shouldBeItem.addAll(PrismValue.cloneCollection(triple.getZeroSet())); shouldBeItem.addAll(PrismValue.cloneCollection(triple.getPlusSet())); if (targetFocusItem != null) { ItemDelta diffDelta = targetFocusItem.diff(shouldBeItem); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Comparing focus item:\n{}\nto should be item:\n{}\ndiff:\n{} ", DebugUtil.debugDump(targetFocusItem, 1), DebugUtil.debugDump(shouldBeItem, 1), DebugUtil.debugDump(diffDelta, 1)); } if (diffDelta != null) { if (mapping.isTolerant() == Boolean.TRUE) { // this is probably not correct, as the default for inbounds should be TRUE if (diffDelta.isReplace()) { if (diffDelta.getValuesToReplace().isEmpty()) { diffDelta.resetValuesToReplace(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Removing empty replace part of the diff delta because mapping is tolerant:\n{}", diffDelta.debugDump()); } } else { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Making sure that the replace part of the diff contains old values delta because mapping is tolerant:\n{}", diffDelta.debugDump()); } for (Object shouldBeValueObj: shouldBeItem.getValues()) { PrismValue shouldBeValue = (PrismValue)shouldBeValueObj; if (!PrismValue.containsRealValue(diffDelta.getValuesToReplace(), shouldBeValue)) { diffDelta.addValueToReplace(shouldBeValue.clone()); } } } } else { diffDelta.resetValuesToDelete(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Removing delete part of the diff delta because mapping is tolerant:\n{}", diffDelta.debugDump()); } } } diffDelta.setElementName(ItemPath.getName(targetFocusItemPath.last())); diffDelta.setParentPath(targetFocusItemPath.allExceptLast()); outputFocusItemDelta.merge(diffDelta); } } else { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Adding user property because inbound say so (account doesn't contain that value):\n{}", shouldBeItem.getValues()); } //if user property doesn't exist we have to add it (as delta), because inbound say so outputFocusItemDelta.addValuesToAdd(shouldBeItem.getClonedValues()); } } else { // triple == null // the mapping is not applicable. Nothing to do. } // if no changes were generated return null return outputFocusItemDelta.isEmpty() ? null : outputFocusItemDelta; } private <F extends ObjectType> StringPolicyResolver createStringPolicyResolver(final LensContext<F> context, final Task task, final OperationResult result) { StringPolicyResolver stringPolicyResolver = new StringPolicyResolver() { private ItemPath outputPath; private ItemDefinition outputDefinition; @Override public void setOutputPath(ItemPath outputPath) { this.outputPath = outputPath; } @Override public void setOutputDefinition(ItemDefinition outputDefinition) { this.outputDefinition = outputDefinition; } @Override public StringPolicyType resolve() { if (!outputDefinition.getName().equals(PasswordType.F_VALUE)) { return null; } ValuePolicyType passwordPolicy = credentialsProcessor.determinePasswordPolicy(context.getFocusContext(), task, result); if (passwordPolicy == null) { return null; } return passwordPolicy.getStringPolicy(); } }; return stringPolicyResolver; } private <T> PrismPropertyValue<T> filterValue(PrismPropertyValue<T> propertyValue, List<ValueFilterType> filters) { PrismPropertyValue<T> filteredValue = propertyValue.clone(); filteredValue.setOriginType(OriginType.INBOUND); if (filters == null || filters.isEmpty()) { return filteredValue; } for (ValueFilterType filter : filters) { Filter filterInstance = filterManager.getFilterInstance(filter.getType(), filter.getAny()); filterInstance.apply(filteredValue); } return filteredValue; } /** * Processing for special (fixed-schema) properties such as credentials and activation. * @throws ObjectNotFoundException */ private <F extends FocusType> void processSpecialPropertyInbound(ResourceBidirectionalMappingType biMappingType, ItemPath sourcePath, PrismObject<F> newUser, LensProjectionContext accContext, RefinedObjectClassDefinition accountDefinition, LensContext<F> context, Task task, XMLGregorianCalendar now, OperationResult opResult) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { if (biMappingType == null) { return; } processSpecialPropertyInbound(biMappingType.getInbound(), sourcePath, newUser, accContext, accountDefinition, context, task, now, opResult); } // private void processSpecialPropertyInbound(MappingType inboundMappingType, ItemPath sourcePath, // PrismObject<UserType> newUser, LensProjectionContext<ShadowType> accContext, // RefinedObjectClassDefinition accountDefinition, LensContext<UserType,ShadowType> context, // OperationResult opResult) throws SchemaException { // if (inboundMappingType == null) { // return; // } // Collection<MappingType> inboundMappingTypes = new ArrayList<MappingType>(1); // inboundMappingTypes.add(inboundMappingType); // processSpecialPropertyInbound(inboundMappingTypes, sourcePath, newUser, accContext, accountDefinition, context, opResult); // } /** * Processing for special (fixed-schema) properties such as credentials and activation. * @throws ObjectNotFoundException * @throws ExpressionEvaluationException */ private <F extends FocusType> void processSpecialPropertyInbound(Collection<MappingType> inboundMappingTypes, final ItemPath sourcePath, final PrismObject<F> newUser, final LensProjectionContext accContext, RefinedObjectClassDefinition accountDefinition, final LensContext<F> context, Task task, XMLGregorianCalendar now, OperationResult opResult) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { if (inboundMappingTypes == null || inboundMappingTypes.isEmpty() || newUser == null || !accContext.isFullShadow()) { return; } ObjectDelta<F> userPrimaryDelta = context.getFocusContext().getPrimaryDelta(); PropertyDelta primaryPropDelta = null; if (userPrimaryDelta != null) { primaryPropDelta = userPrimaryDelta.findPropertyDelta(sourcePath); if (primaryPropDelta != null && primaryPropDelta.isReplace()) { // Replace primary delta overrides any inbound return; } } ObjectDelta<F> userSecondaryDelta = context.getFocusContext().getProjectionWaveSecondaryDelta(); if (userSecondaryDelta != null) { PropertyDelta<?> delta = userSecondaryDelta.findPropertyDelta(sourcePath); if (delta != null) { //remove delta if exists, it will be handled by inbound userSecondaryDelta.getModifications().remove(delta); } } MappingInitializer initializer = (builder) -> { if (accContext.getObjectNew() == null) { accContext.recompute(); if (accContext.getObjectNew() == null) { // Still null? something must be really wrong here. String message = "Recomputing account " + accContext.getResourceShadowDiscriminator() + " results in null new account. Something must be really broken."; LOGGER.error(message); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Account context:\n{}", accContext.debugDump()); } throw new SystemException(message); } } ObjectDelta<ShadowType> aPrioriShadowDelta = getAPrioriDelta(context, accContext); ItemDelta<PrismPropertyValue<?>,PrismPropertyDefinition<?>> specialAttributeDelta = null; if (aPrioriShadowDelta != null){ specialAttributeDelta = aPrioriShadowDelta.findItemDelta(sourcePath); } ItemDeltaItem<PrismPropertyValue<?>,PrismPropertyDefinition<?>> sourceIdi = accContext.getObjectDeltaObject().findIdi(sourcePath); if (specialAttributeDelta == null){ specialAttributeDelta = sourceIdi.getDelta(); } Source<PrismPropertyValue<?>,PrismPropertyDefinition<?>> source = new Source<>(sourceIdi.getItemOld(), specialAttributeDelta, sourceIdi.getItemOld(), ExpressionConstants.VAR_INPUT); builder = builder.defaultSource(source) .addVariableDefinition(ExpressionConstants.VAR_USER, newUser) .addVariableDefinition(ExpressionConstants.VAR_FOCUS, newUser); PrismObject<ShadowType> accountNew = accContext.getObjectNew(); builder = builder.addVariableDefinition(ExpressionConstants.VAR_ACCOUNT, accountNew) .addVariableDefinition(ExpressionConstants.VAR_SHADOW, accountNew) .addVariableDefinition(ExpressionConstants.VAR_RESOURCE, accContext.getResource()) .stringPolicyResolver(createStringPolicyResolver(context, task, opResult)) .originType(OriginType.INBOUND) .originObject(accContext.getResource()); return builder; }; MappingOutputProcessor<PrismValue> processor = (mappingOutputPath, outputStruct) -> { PrismValueDeltaSetTriple<PrismValue> outputTriple = outputStruct.getOutputTriple(); if (outputTriple == null){ LOGGER.trace("Mapping for property {} evaluated to null. Skipping inboud processing for that property.", sourcePath); return false; } ObjectDelta<F> userSecondaryDeltaInt = context.getFocusContext().getProjectionWaveSecondaryDelta(); if (userSecondaryDeltaInt != null) { PropertyDelta<?> delta = userSecondaryDeltaInt.findPropertyDelta(sourcePath); if (delta != null) { //remove delta if exists, it will be handled by inbound userSecondaryDeltaInt.getModifications().remove(delta); } } PrismObjectDefinition<F> focusDefinition = context.getFocusContext().getObjectDefinition(); PrismProperty result = focusDefinition.findPropertyDefinition(sourcePath).instantiate(); result.addAll(PrismValue.cloneCollection(outputTriple.getNonNegativeValues())); PrismProperty targetPropertyNew = newUser.findOrCreateProperty(sourcePath); PropertyDelta<?> delta; if (ProtectedStringType.COMPLEX_TYPE.equals(targetPropertyNew.getDefinition().getTypeName())) { // We have to compare this in a special way. The cipherdata may be different due to a different // IV, but the value may still be the same ProtectedStringType resultValue = (ProtectedStringType) result.getRealValue(); ProtectedStringType targetPropertyNewValue = (ProtectedStringType) targetPropertyNew.getRealValue(); try { if (protector.compare(resultValue, targetPropertyNewValue)) { delta = null; } else { delta = targetPropertyNew.diff(result); } } catch (EncryptionException e) { throw new SystemException(e.getMessage(), e); } } else { delta = targetPropertyNew.diff(result); } if (LOGGER.isTraceEnabled()) { LOGGER.trace("targetPropertyNew:\n{}\ndelta:\n{}", targetPropertyNew.debugDump(1), DebugUtil.debugDump(delta, 1)); } if (delta != null && !delta.isEmpty()) { delta.setParentPath(sourcePath.allExceptLast()); if (!context.getFocusContext().alreadyHasDelta(delta)){ context.getFocusContext().swallowToProjectionWaveSecondaryDelta(delta); } } return false; }; MappingEvaluatorParams<PrismValue, ItemDefinition, F, F> params = new MappingEvaluatorParams<>(); params.setMappingTypes(inboundMappingTypes); params.setMappingDesc("inbound mapping for " + sourcePath + " in " + accContext.getResource()); params.setNow(now); params.setInitializer(initializer); params.setProcessor(processor); params.setAPrioriTargetObject(newUser); params.setAPrioriTargetDelta(userPrimaryDelta); params.setTargetContext(context.getFocusContext()); params.setDefaultTargetItemPath(sourcePath); params.setEvaluateCurrent(true); params.setContext(context); params.setHasFullTargetObject(true); mappingEvaluator.evaluateMappingSetProjection(params, task, opResult); // MutableBoolean strongMappingWasUsed = new MutableBoolean(); // PrismValueDeltaSetTriple<? extends PrismPropertyValue<?>> outputTriple = mappingEvaluatorHelper.evaluateMappingSetProjection( // inboundMappingTypes, "inbound mapping for " + sourcePath + " in " + accContext.getResource(), now, initializer, targetPropertyNew, primaryPropDelta, newUser, true, strongMappingWasUsed, context, accContext, task, opResult); } // private Collection<Mapping> getMappingApplicableToChannel( // Collection<MappingType> inboundMappingTypes, String description, String channelUri) { // Collection<Mapping> inboundMappings = new ArrayList<Mapping>(); // for (MappingType inboundMappingType : inboundMappingTypes){ // Mapping<PrismPropertyValue<?>,PrismPropertyDefinition<?>> mapping = mappingFactory.createMapping(inboundMappingType, // description); // // if (mapping.isApplicableToChannel(channelUri)){ // inboundMappings.add(mapping); // } // } // // return inboundMappings; // } }