/* * 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 static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; import java.util.Collection; import java.util.List; import javax.xml.datatype.XMLGregorianCalendar; import com.evolveum.midpoint.util.exception.NoFocusNameSchemaException; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; import org.apache.commons.lang.BooleanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.evolveum.midpoint.common.ActivationComputer; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; import com.evolveum.midpoint.model.common.expression.ExpressionFactory; import com.evolveum.midpoint.model.common.expression.ExpressionVariables; import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.ModelObjectResolver; import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.EvaluatedPolicyRuleImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.impl.lens.OperationalDataManager; import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; import com.evolveum.midpoint.model.impl.util.Utils; import com.evolveum.midpoint.prism.ComplexTypeDefinition; import com.evolveum.midpoint.prism.OriginType; import com.evolveum.midpoint.prism.PrismContainerDefinition; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismObjectDefinition; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.delta.DeltaSetTriple; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.OidUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractCredentialType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.GlobalPolicyRuleType; import com.evolveum.midpoint.xml.ns._public.common.common_3.IterationSpecificationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LockoutStatusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ModificationPolicyConstraintType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectPolicyConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSelectorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateMappingEvaluationPhaseType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingOptionsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyConstraintKindType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyConstraintsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PropertyConstraintType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.TimeIntervalStatusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; /** * Processor to handle everything about focus: values, assignments, etc. * * @author Radovan Semancik * */ @Component public class FocusProcessor { private static final Trace LOGGER = TraceManager.getTrace(FocusProcessor.class); private PrismContainerDefinition<ActivationType> activationDefinition; private PrismPropertyDefinition<Integer> failedLoginsDefinition; @Autowired private InboundProcessor inboundProcessor; @Autowired private AssignmentProcessor assignmentProcessor; @Autowired private ObjectTemplateProcessor objectTemplateProcessor; @Autowired private MappingFactory mappingFactory; @Autowired private PrismContext prismContext; @Autowired private CredentialsProcessor credentialsProcessor; @Autowired private ModelObjectResolver modelObjectResolver; @Autowired private ActivationComputer activationComputer; @Autowired private ExpressionFactory expressionFactory; @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; @Autowired private MappingEvaluator mappingHelper; @Autowired private OperationalDataManager metadataManager; @Autowired private PolicyRuleProcessor policyRuleProcessor; <O extends ObjectType, F extends FocusType> void processFocus(LensContext<O> context, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException { LensFocusContext<O> focusContext = context.getFocusContext(); if (focusContext == null) { return; } if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { // We can do this only for FocusType objects. return; } processFocusFocus((LensContext<F>)context, activityDescription, now, task, result); } private <F extends FocusType> void processFocusFocus(LensContext<F> context, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException { LensFocusContext<F> focusContext = context.getFocusContext(); ObjectTemplateType objectTemplate = context.getFocusTemplate(); PartialProcessingOptionsType partialProcessingOptions = context.getPartialProcessingOptions(); boolean resetOnRename = true; // This is fixed now. TODO: make it configurable int maxIterations = 0; IterationSpecificationType iterationSpecificationType = null; if (objectTemplate != null) { iterationSpecificationType = objectTemplate.getIteration(); maxIterations = LensUtil.determineMaxIterations(iterationSpecificationType); } int iteration = focusContext.getIteration(); String iterationToken = focusContext.getIterationToken(); boolean wasResetIterationCounter = false; PrismObject<F> focusCurrent = focusContext.getObjectCurrent(); if (focusCurrent != null && iterationToken == null) { Integer focusIteration = focusCurrent.asObjectable().getIteration(); if (focusIteration != null) { iteration = focusIteration; } iterationToken = focusCurrent.asObjectable().getIterationToken(); } while (true) { ObjectPolicyConfigurationType objectPolicyConfigurationType = focusContext.getObjectPolicyConfigurationType(); applyObjectPolicyConstraints(focusContext, objectPolicyConfigurationType); ExpressionVariables variablesPreIteration = Utils.getDefaultExpressionVariables(focusContext.getObjectNew(), null, null, null, context.getSystemConfiguration(), focusContext); if (iterationToken == null) { iterationToken = LensUtil.formatIterationToken(context, focusContext, iterationSpecificationType, iteration, expressionFactory, variablesPreIteration, task, result); } // We have to remember the token and iteration in the context. // The context can be recomputed several times. But we always want // to use the same iterationToken if possible. If there is a random // part in the iterationToken expression that we need to avoid recomputing // the token otherwise the value can change all the time (even for the same inputs). // Storing the token in the secondary delta is not enough because secondary deltas can be dropped // if the context is re-projected. focusContext.setIteration(iteration); focusContext.setIterationToken(iterationToken); LOGGER.trace("Focus {} processing, iteration {}, token '{}'", focusContext.getHumanReadableName(), iteration, iterationToken); String conflictMessage; if (!LensUtil.evaluateIterationCondition(context, focusContext, iterationSpecificationType, iteration, iterationToken, true, expressionFactory, variablesPreIteration, task, result)) { conflictMessage = "pre-iteration condition was false"; LOGGER.debug("Skipping iteration {}, token '{}' for {} because the pre-iteration condition was false", iteration, iterationToken, focusContext.getHumanReadableName()); } else { // INBOUND if (consistencyChecks) context.checkConsistence(); LensUtil.partialExecute("inbound", () -> { // Loop through the account changes, apply inbound expressions inboundProcessor.processInbound(context, now, task, result); if (consistencyChecks) context.checkConsistence(); context.recomputeFocus(); LensUtil.traceContext(LOGGER, activityDescription, "inbound", false, context, false); if (consistencyChecks) context.checkConsistence(); }, partialProcessingOptions::getInbound); // ACTIVATION LensUtil.partialExecute("focusActivation", () -> processActivation(context, now, result), partialProcessingOptions::getFocusActivation); // OBJECT TEMPLATE (before assignments) LensUtil.partialExecute("objectTemplateBeforeAssignments", () -> objectTemplateProcessor.processTemplate(context, ObjectTemplateMappingEvaluationPhaseType.BEFORE_ASSIGNMENTS, now, task, result), partialProcessingOptions::getObjectTemplateBeforeAssignments); // process activation again. Object template might have changed it. context.recomputeFocus(); LensUtil.partialExecute("focusActivation", () -> processActivation(context, now, result), partialProcessingOptions::getFocusActivation); // ASSIGNMENTS LensUtil.partialExecute("assignments", () -> assignmentProcessor.processAssignmentsProjections(context, now, task, result), partialProcessingOptions::getAssignments); LensUtil.partialExecute("assignmentsOrg", () -> assignmentProcessor.processOrgAssignments(context, result), partialProcessingOptions::getAssignmentsOrg); LensUtil.partialExecute("assignmentsMembershipAndDelegate", () -> assignmentProcessor.processMembershipAndDelegatedRefs(context, result), partialProcessingOptions::getAssignmentsMembershipAndDelegate); context.recompute(); LensUtil.partialExecute("assignmentsConflicts", () -> assignmentProcessor.checkForAssignmentConflicts(context, result), partialProcessingOptions::getAssignmentsConflicts); // OBJECT TEMPLATE (after assignments) LensUtil.partialExecute("objectTemplateAfterAssignments", () -> objectTemplateProcessor.processTemplate(context, ObjectTemplateMappingEvaluationPhaseType.AFTER_ASSIGNMENTS, now, task, result), partialProcessingOptions::getObjectTemplateBeforeAssignments); context.recompute(); // process activation again. Second pass through object template might have changed it. context.recomputeFocus(); LensUtil.partialExecute("focusActivation", () -> processActivation(context, now, result), partialProcessingOptions::getFocusActivation); // CREDENTIALS (including PASSWORD POLICY) LensUtil.partialExecute("focusCredentials", () -> credentialsProcessor.processFocusCredentials(context, now, task, result), partialProcessingOptions::getFocusCredentials); // We need to evaluate this as a last step. We need to make sure we have all the // focus deltas so we can properly trigger the rules. LensUtil.partialExecute("focusPolicyRules", () -> evaluateFocusPolicyRules(context, activityDescription, now, task, result), partialProcessingOptions::getFocusPolicyRules); // Processing done, check for success if (resetOnRename && !wasResetIterationCounter && willResetIterationCounter(focusContext)) { // Make sure this happens only the very first time during the first recompute. // Otherwise it will always change the token (especially if the token expression has a random part) // hence the focusContext.getIterationToken() == null wasResetIterationCounter = true; if (iteration != 0) { iteration = 0; iterationToken = null; LOGGER.trace("Resetting iteration counter and token because rename was detected"); cleanupContext(focusContext); continue; } } PrismObject<F> previewObjectNew = focusContext.getObjectNew(); if (previewObjectNew == null) { // this must be delete } else { // Explicitly check for name. The checker would check for this also. But checking it here // will produce better error message PolyStringType objectName = previewObjectNew.asObjectable().getName(); if (objectName == null || objectName.getOrig().isEmpty()) { throw new NoFocusNameSchemaException("No name in new object "+objectName+" as produced by template "+objectTemplate+ " in iteration "+iteration+", we cannot process an object without a name"); } } // Check if iteration constraints are OK FocusConstraintsChecker<F> checker = new FocusConstraintsChecker<>(); checker.setPrismContext(prismContext); checker.setContext(context); checker.setRepositoryService(cacheRepositoryService); checker.check(previewObjectNew, result); if (checker.isSatisfiesConstraints()) { LOGGER.trace("Current focus satisfies uniqueness constraints. Iteration {}, token '{}'", iteration, iterationToken); ExpressionVariables variablesPostIteration = Utils.getDefaultExpressionVariables(focusContext.getObjectNew(), null, null, null, context.getSystemConfiguration(), focusContext); if (LensUtil.evaluateIterationCondition(context, focusContext, iterationSpecificationType, iteration, iterationToken, false, expressionFactory, variablesPostIteration, task, result)) { // stop the iterations break; } else { conflictMessage = "post-iteration condition was false"; LOGGER.debug("Skipping iteration {}, token '{}' for {} because the post-iteration condition was false", iteration, iterationToken, focusContext.getHumanReadableName()); } } else { LOGGER.trace("Current focus does not satisfy constraints. Conflicting object: {}; iteration={}, maxIterations={}", checker.getConflictingObject(), iteration, maxIterations); conflictMessage = checker.getMessages(); } if (!wasResetIterationCounter) { wasResetIterationCounter = true; if (iteration != 0) { iterationToken = null; iteration = 0; LOGGER.trace("Resetting iteration counter and token after conflict"); cleanupContext(focusContext); continue; } } } // Next iteration iteration++; iterationToken = null; LensUtil.checkMaxIterations(iteration, maxIterations, conflictMessage, focusContext.getHumanReadableName()); cleanupContext(focusContext); } addIterationTokenDeltas(focusContext, iteration, iterationToken); if (consistencyChecks) context.checkConsistence(); } private <F extends FocusType> void evaluateFocusPolicyRules(LensContext<F> context, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws PolicyViolationException, SchemaException { triggerAssignmentFocusPolicyRules(context, activityDescription, now, task, result); triggerGlobalRules(context); } // TODO: should we really do this? Focus policy rules (e.g. forbidden modifications) are irrelevant in this situation, // TODO: i.e. if we are assigning the object into some other object [med] private <F extends FocusType> void triggerAssignmentFocusPolicyRules(LensContext<F> context, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws PolicyViolationException, SchemaException { LensFocusContext<F> focusContext = context.getFocusContext(); DeltaSetTriple<EvaluatedAssignmentImpl<?>> evaluatedAssignmentTriple = context.getEvaluatedAssignmentTriple(); if (evaluatedAssignmentTriple == null) { return; } for (EvaluatedAssignmentImpl<?> evaluatedAssignment: evaluatedAssignmentTriple.getNonNegativeValues()) { Collection<EvaluatedPolicyRule> policyRules = evaluatedAssignment.getFocusPolicyRules(); for (EvaluatedPolicyRule policyRule: policyRules) { triggerRule(focusContext, policyRule); } } } private <F extends FocusType> void triggerGlobalRules(LensContext<F> context) throws SchemaException, PolicyViolationException { PrismObject<SystemConfigurationType> systemConfiguration = context.getSystemConfiguration(); if (systemConfiguration == null) { return; } LensFocusContext<F> focusContext = context.getFocusContext(); // We need to consider object before modification here. We need to prohibit the modification, so we // cannot look at modified object. PrismObject<F> focus = focusContext.getObjectCurrent(); if (focus == null) { focus = focusContext.getObjectNew(); } for (GlobalPolicyRuleType globalPolicyRule: systemConfiguration.asObjectable().getGlobalPolicyRule()) { ObjectSelectorType focusSelector = globalPolicyRule.getFocusSelector(); if (cacheRepositoryService.selectorMatches(focusSelector, focus, LOGGER, "Global policy rule "+globalPolicyRule.getName()+": ")) { EvaluatedPolicyRule evaluatedRule = new EvaluatedPolicyRuleImpl(globalPolicyRule, null); triggerRule(focusContext, evaluatedRule); } } } private <F extends FocusType> void triggerRule(LensFocusContext<F> focusContext, EvaluatedPolicyRule policyRule) throws PolicyViolationException, SchemaException { PolicyConstraintsType policyConstraints = policyRule.getPolicyConstraints(); if (policyConstraints == null) { return; } for (ModificationPolicyConstraintType modificationConstraintType: policyConstraints.getModification()) { focusContext.addPolicyRule(policyRule); if (modificationConstraintMatches(focusContext, policyRule, modificationConstraintType)) { EvaluatedPolicyRuleTrigger<?> trigger = new EvaluatedPolicyRuleTrigger<>(PolicyConstraintKindType.MODIFICATION, modificationConstraintType, "Focus "+focusContext.getHumanReadableName()+" was modified"); focusContext.triggerConstraint(policyRule, trigger); } } } private <F extends FocusType> boolean modificationConstraintMatches(LensFocusContext<F> focusContext, EvaluatedPolicyRule policyRule, ModificationPolicyConstraintType modificationConstraintType) throws SchemaException { if (!operationMatches(focusContext, modificationConstraintType.getOperation())) { LOGGER.trace("Rule {} operation not applicable", policyRule.getName()); return false; } if (modificationConstraintType.getItem().isEmpty()) { return focusContext.hasAnyDelta(); } ObjectDelta<?> summaryDelta = ObjectDelta.union(focusContext.getPrimaryDelta(), focusContext.getSecondaryDelta()); if (summaryDelta == null) { return false; } for (ItemPathType path : modificationConstraintType.getItem()) { if (!pathMatches(focusContext.getObjectOld(), summaryDelta, path.getItemPath())) { return false; } } return true; } private <F extends FocusType> boolean pathMatches(PrismObject<F> objectOld, ObjectDelta<?> delta, ItemPath path) throws SchemaException { if (delta.isAdd()) { return delta.getObjectToAdd().containsItem(path, false); } else if (delta.isDelete()) { return objectOld != null && objectOld.containsItem(path, false); } else { return delta.findItemDelta(path) != null; } } private <F extends FocusType> boolean operationMatches(LensFocusContext<F> focusContext, List<ChangeTypeType> operations) { if (operations.isEmpty()) { return true; } for (ChangeTypeType operation: operations) { if (focusContext.operationMatches(operation)) { return true; } } return false; } private <F extends FocusType> void applyObjectPolicyConstraints(LensFocusContext<F> focusContext, ObjectPolicyConfigurationType objectPolicyConfigurationType) throws SchemaException { if (objectPolicyConfigurationType == null) { return; } final PrismObject<F> focusNew = focusContext.getObjectNew(); if (focusNew == null) { // This is delete. Nothing to do. return; } for (PropertyConstraintType propertyConstraintType: objectPolicyConfigurationType.getPropertyConstraint()) { ItemPath itemPath = propertyConstraintType.getPath().getItemPath(); if (BooleanUtils.isTrue(propertyConstraintType.isOidBound())) { PrismProperty<Object> prop = focusNew.findProperty(itemPath); if (prop == null || prop.isEmpty()) { String newValue = focusNew.getOid(); if (newValue == null) { newValue = OidUtil.generateOid(); } LOGGER.trace("Generating new OID-bound value for {}: {}", itemPath, newValue); PrismObjectDefinition<F> focusDefinition = focusContext.getObjectDefinition(); PrismPropertyDefinition<Object> propDef = focusDefinition.findPropertyDefinition(itemPath); if (propDef == null) { throw new SchemaException("No definition for property "+itemPath+" in "+focusDefinition+" as specified in object policy"); } PropertyDelta<Object> propDelta = propDef.createEmptyDelta(itemPath); if (String.class.isAssignableFrom(propDef.getTypeClass())) { propDelta.setValueToReplace(new PrismPropertyValue<Object>(newValue, OriginType.USER_POLICY, null)); } else if (PolyString.class.isAssignableFrom(propDef.getTypeClass())) { propDelta.setValueToReplace(new PrismPropertyValue<Object>(new PolyString(newValue), OriginType.USER_POLICY, null)); } else { throw new SchemaException("Unsupported type "+propDef.getTypeName()+" for property "+itemPath+" in "+focusDefinition+" as specified in object policy, only string and polystring properties are supported for OID-bound mode"); } focusContext.swallowToSecondaryDelta(propDelta); focusContext.recompute(); } } } // Deprecated if (BooleanUtils.isTrue(objectPolicyConfigurationType.isOidNameBoundMode())) { // Generate the name now - unless it is already present PolyStringType focusNewName = focusNew.asObjectable().getName(); if (focusNewName == null) { String newValue = focusNew.getOid(); if (newValue == null) { newValue = OidUtil.generateOid(); } LOGGER.trace("Generating new name (bound to OID): {}", newValue); PrismObjectDefinition<F> focusDefinition = focusContext.getObjectDefinition(); PrismPropertyDefinition<PolyString> focusNameDef = focusDefinition.findPropertyDefinition(FocusType.F_NAME); PropertyDelta<PolyString> nameDelta = focusNameDef.createEmptyDelta(new ItemPath(FocusType.F_NAME)); nameDelta.setValueToReplace(new PrismPropertyValue<PolyString>(new PolyString(newValue), OriginType.USER_POLICY, null)); focusContext.swallowToSecondaryDelta(nameDelta); focusContext.recompute(); } } } private <F extends FocusType> boolean willResetIterationCounter(LensFocusContext<F> focusContext) throws SchemaException { ObjectDelta<F> focusDelta = focusContext.getDelta(); if (focusDelta == null) { return false; } if (focusContext.isAdd() || focusContext.isDelete()) { return false; } if (focusDelta.findPropertyDelta(FocusType.F_ITERATION) != null) { // there was a reset already in previous projector runs return false; } // Check for rename PropertyDelta<Object> nameDelta = focusDelta.findPropertyDelta(new ItemPath(FocusType.F_NAME)); return nameDelta != null; } /** * Remove the intermediate results of values processing such as secondary deltas. */ private <F extends FocusType> void cleanupContext(LensFocusContext<F> focusContext) throws SchemaException { // We must NOT clean up activation computation. This has happened before, it will not happen again // and it does not depend on iteration LOGGER.trace("Cleaning up focus context"); focusContext.setProjectionWaveSecondaryDelta(null); focusContext.clearIntermediateResults(); focusContext.recompute(); } private <F extends FocusType> void processActivation(LensContext<F> context, XMLGregorianCalendar now, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { LensFocusContext<F> focusContext = context.getFocusContext(); if (focusContext.isDelete()) { LOGGER.trace("Skipping processing of focus activation: focus delete"); return; } processActivationAdministrativeAndValidity(focusContext, now, result); if (focusContext.canRepresent(UserType.class)) { processActivationLockout((LensFocusContext<UserType>) focusContext, now, result); } } private <F extends FocusType> void processActivationAdministrativeAndValidity(LensFocusContext<F> focusContext, XMLGregorianCalendar now, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { TimeIntervalStatusType validityStatusNew = null; TimeIntervalStatusType validityStatusCurrent = null; XMLGregorianCalendar validityChangeTimestamp = null; String lifecycleStateNew = null; String lifecycleStateCurrent = null; ActivationType activationNew = null; ActivationType activationCurrent = null; PrismObject<F> focusNew = focusContext.getObjectNew(); if (focusNew != null) { F focusTypeNew = focusNew.asObjectable(); activationNew = focusTypeNew.getActivation(); if (activationNew != null) { validityStatusNew = activationComputer.getValidityStatus(activationNew, now); validityChangeTimestamp = activationNew.getValidityChangeTimestamp(); } lifecycleStateNew = focusTypeNew.getLifecycleState(); } PrismObject<F> focusCurrent = focusContext.getObjectCurrent(); if (focusCurrent != null) { F focusCurrentType = focusCurrent.asObjectable(); activationCurrent = focusCurrentType.getActivation(); if (activationCurrent != null) { validityStatusCurrent = activationComputer.getValidityStatus(activationCurrent, validityChangeTimestamp); } lifecycleStateCurrent = focusCurrentType.getLifecycleState(); } if (validityStatusCurrent == validityStatusNew) { // No change, (almost) no work if (validityStatusNew != null && activationNew.getValidityStatus() == null) { // There was no validity change. But the status is not recorded. So let's record it so it can be used in searches. recordValidityDelta(focusContext, validityStatusNew, now); } else { LOGGER.trace("Skipping validity processing because there was no change ({} -> {})", validityStatusCurrent, validityStatusNew); } } else { LOGGER.trace("Validity change {} -> {}", validityStatusCurrent, validityStatusNew); recordValidityDelta(focusContext, validityStatusNew, now); } ActivationStatusType effectiveStatusNew = activationComputer.getEffectiveStatus(lifecycleStateNew, activationNew, validityStatusNew); ActivationStatusType effectiveStatusCurrent = activationComputer.getEffectiveStatus(lifecycleStateCurrent, activationCurrent, validityStatusCurrent); if (effectiveStatusCurrent == effectiveStatusNew) { // No change, (almost) no work if (effectiveStatusNew != null && (activationNew == null || activationNew.getEffectiveStatus() == null)) { // There was no effective status change. But the status is not recorded. So let's record it so it can be used in searches. recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); } else { if (focusContext.getPrimaryDelta() != null && focusContext.getPrimaryDelta().hasItemDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS)) { LOGGER.trace("Forcing effective status delta even though there was no change ({} -> {}) because there is explicit administrativeStatus delta", effectiveStatusCurrent, effectiveStatusNew); // We need this to force the change down to the projections later in the activation processor // some of the mappings will use effectiveStatus as a source, therefore there has to be a delta for the mapping to work correctly recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); } else { LOGGER.trace("Skipping effective status processing because there was no change ({} -> {})", effectiveStatusCurrent, effectiveStatusNew); } } } else { LOGGER.trace("Effective status change {} -> {}", effectiveStatusCurrent, effectiveStatusNew); recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); } } private <F extends FocusType> void processActivationLockout(LensFocusContext<UserType> focusContext, XMLGregorianCalendar now, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { ObjectDelta<UserType> focusPrimaryDelta = focusContext.getPrimaryDelta(); if (focusPrimaryDelta != null) { PropertyDelta<LockoutStatusType> lockoutStatusDelta = focusContext.getPrimaryDelta().findPropertyDelta(SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS); if (lockoutStatusDelta != null) { if (lockoutStatusDelta.isAdd()) { for (PrismPropertyValue<LockoutStatusType> pval: lockoutStatusDelta.getValuesToAdd()) { if (pval.getValue() == LockoutStatusType.LOCKED) { throw new SchemaException("Lockout status cannot be changed to LOCKED value"); } } } else if (lockoutStatusDelta.isReplace()) { for (PrismPropertyValue<LockoutStatusType> pval: lockoutStatusDelta.getValuesToReplace()) { if (pval.getValue() == LockoutStatusType.LOCKED) { throw new SchemaException("Lockout status cannot be changed to LOCKED value"); } } } } } ActivationType activationNew = null; ActivationType activationCurrent = null; LockoutStatusType lockoutStatusNew = null; LockoutStatusType lockoutStatusCurrent = null; PrismObject<UserType> focusNew = focusContext.getObjectNew(); if (focusNew != null) { activationNew = focusNew.asObjectable().getActivation(); if (activationNew != null) { lockoutStatusNew = activationNew.getLockoutStatus(); } } PrismObject<UserType> focusCurrent = focusContext.getObjectCurrent(); if (focusCurrent != null) { activationCurrent = focusCurrent.asObjectable().getActivation(); if (activationCurrent != null) { lockoutStatusCurrent = activationCurrent.getLockoutStatus(); } } if (lockoutStatusNew == lockoutStatusCurrent) { // No change, (almost) no work LOGGER.trace("Skipping lockout processing because there was no change ({} -> {})", lockoutStatusCurrent, lockoutStatusNew); return; } LOGGER.trace("Lockout change {} -> {}", lockoutStatusCurrent, lockoutStatusNew); if (lockoutStatusNew == LockoutStatusType.NORMAL) { CredentialsType credentialsTypeNew = focusNew.asObjectable().getCredentials(); if (credentialsTypeNew != null) { resetFailedLogins(focusContext, credentialsTypeNew.getPassword(), SchemaConstants.PATH_CREDENTIALS_PASSWORD_FAILED_LOGINS); resetFailedLogins(focusContext, credentialsTypeNew.getNonce(), SchemaConstants.PATH_CREDENTIALS_NONCE_FAILED_LOGINS); resetFailedLogins(focusContext, credentialsTypeNew.getSecurityQuestions(), SchemaConstants.PATH_CREDENTIALS_SECURITY_QUESTIONS_FAILED_LOGINS); } if (activationNew != null && activationNew.getLockoutExpirationTimestamp() != null) { PrismContainerDefinition<ActivationType> activationDefinition = getActivationDefinition(); PrismPropertyDefinition<XMLGregorianCalendar> lockoutExpirationTimestampDef = activationDefinition.findPropertyDefinition(ActivationType.F_LOCKOUT_EXPIRATION_TIMESTAMP); PropertyDelta<XMLGregorianCalendar> lockoutExpirationTimestampDelta = lockoutExpirationTimestampDef.createEmptyDelta(new ItemPath(UserType.F_ACTIVATION, ActivationType.F_LOCKOUT_EXPIRATION_TIMESTAMP)); lockoutExpirationTimestampDelta.setValueToReplace(); focusContext.swallowToProjectionWaveSecondaryDelta(lockoutExpirationTimestampDelta); } } } private void resetFailedLogins(LensFocusContext<UserType> focusContext, AbstractCredentialType credentialTypeNew, ItemPath path) throws SchemaException{ if (credentialTypeNew != null) { Integer failedLogins = credentialTypeNew.getFailedLogins(); if (failedLogins != null && failedLogins != 0) { PrismPropertyDefinition<Integer> failedLoginsDef = getFailedLoginsDefinition(); PropertyDelta<Integer> failedLoginsDelta = failedLoginsDef.createEmptyDelta(path); failedLoginsDelta.setValueToReplace(new PrismPropertyValue<Integer>(0, OriginType.USER_POLICY, null)); focusContext.swallowToProjectionWaveSecondaryDelta(failedLoginsDelta); } } } private <F extends ObjectType> void recordValidityDelta(LensFocusContext<F> focusContext, TimeIntervalStatusType validityStatusNew, XMLGregorianCalendar now) throws SchemaException { PrismContainerDefinition<ActivationType> activationDefinition = getActivationDefinition(); PrismPropertyDefinition<TimeIntervalStatusType> validityStatusDef = activationDefinition.findPropertyDefinition(ActivationType.F_VALIDITY_STATUS); PropertyDelta<TimeIntervalStatusType> validityStatusDelta = validityStatusDef.createEmptyDelta(new ItemPath(UserType.F_ACTIVATION, ActivationType.F_VALIDITY_STATUS)); if (validityStatusNew == null) { validityStatusDelta.setValueToReplace(); } else { validityStatusDelta.setValueToReplace(new PrismPropertyValue<>(validityStatusNew, OriginType.USER_POLICY, null)); } focusContext.swallowToProjectionWaveSecondaryDelta(validityStatusDelta); PrismPropertyDefinition<XMLGregorianCalendar> validityChangeTimestampDef = activationDefinition.findPropertyDefinition(ActivationType.F_VALIDITY_CHANGE_TIMESTAMP); PropertyDelta<XMLGregorianCalendar> validityChangeTimestampDelta = validityChangeTimestampDef.createEmptyDelta(new ItemPath(UserType.F_ACTIVATION, ActivationType.F_VALIDITY_CHANGE_TIMESTAMP)); validityChangeTimestampDelta.setValueToReplace(new PrismPropertyValue<>(now, OriginType.USER_POLICY, null)); focusContext.swallowToProjectionWaveSecondaryDelta(validityChangeTimestampDelta); } private <F extends ObjectType> void recordEffectiveStatusDelta(LensFocusContext<F> focusContext, ActivationStatusType effectiveStatusNew, XMLGregorianCalendar now) throws SchemaException { PrismContainerDefinition<ActivationType> activationDefinition = getActivationDefinition(); // We always want explicit delta for effective status even if there is no real change // we want to propagate enable/disable events to all the resources, even if we are enabling // already enabled user (some resources may be disabled) // This may produce duplicate delta, but that does not matter too much. The duplicate delta // will be filtered out later. PrismPropertyDefinition<ActivationStatusType> effectiveStatusDef = activationDefinition.findPropertyDefinition(ActivationType.F_EFFECTIVE_STATUS); PropertyDelta<ActivationStatusType> effectiveStatusDelta = effectiveStatusDef.createEmptyDelta(new ItemPath(UserType.F_ACTIVATION, ActivationType.F_EFFECTIVE_STATUS)); effectiveStatusDelta.setValueToReplace(new PrismPropertyValue<ActivationStatusType>(effectiveStatusNew, OriginType.USER_POLICY, null)); if (!focusContext.alreadyHasDelta(effectiveStatusDelta)){ focusContext.swallowToProjectionWaveSecondaryDelta(effectiveStatusDelta); } // It is not enough to check alreadyHasDelta(). The change may happen in previous waves // and the secondary delta may no longer be here. When it comes to disableTimestamp we even // cannot rely on natural filtering of already executed deltas as the timestamp here may // be off by several milliseconds. So explicitly check for the change here. PrismObject<F> objectCurrent = focusContext.getObjectCurrent(); if (objectCurrent != null) { PrismProperty<ActivationStatusType> effectiveStatusPropCurrent = objectCurrent.findProperty(SchemaConstants.PATH_ACTIVATION_EFFECTIVE_STATUS); if (effectiveStatusPropCurrent != null && effectiveStatusNew.equals(effectiveStatusPropCurrent.getRealValue())) { LOGGER.trace("Skipping setting disableTimestamp because there was no change"); return; } } PropertyDelta<XMLGregorianCalendar> timestampDelta = LensUtil.createActivationTimestampDelta(effectiveStatusNew, now, activationDefinition, OriginType.USER_POLICY); if (!focusContext.alreadyHasDelta(timestampDelta)) { focusContext.swallowToProjectionWaveSecondaryDelta(timestampDelta); } } private PrismContainerDefinition<ActivationType> getActivationDefinition() { if (activationDefinition == null) { ComplexTypeDefinition focusDefinition = prismContext.getSchemaRegistry().findComplexTypeDefinition(FocusType.COMPLEX_TYPE); activationDefinition = focusDefinition.findContainerDefinition(FocusType.F_ACTIVATION); } return activationDefinition; } private PrismPropertyDefinition<Integer> getFailedLoginsDefinition() { if (failedLoginsDefinition == null) { PrismObjectDefinition<UserType> userDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(UserType.class); failedLoginsDefinition = userDef.findPropertyDefinition(SchemaConstants.PATH_CREDENTIALS_PASSWORD_FAILED_LOGINS); } return failedLoginsDefinition; } /** * Adds deltas for iteration and iterationToken to the focus if needed. */ private <F extends FocusType> void addIterationTokenDeltas(LensFocusContext<F> focusContext, int iteration, String iterationToken) throws SchemaException { PrismObject<F> objectCurrent = focusContext.getObjectCurrent(); if (objectCurrent != null) { Integer iterationOld = objectCurrent.asObjectable().getIteration(); String iterationTokenOld = objectCurrent.asObjectable().getIterationToken(); if (iterationOld != null && iterationOld == iteration && iterationTokenOld != null && iterationTokenOld.equals(iterationToken)) { // Already stored return; } } PrismObjectDefinition<F> objDef = focusContext.getObjectDefinition(); PrismPropertyValue<Integer> iterationVal = new PrismPropertyValue<Integer>(iteration); iterationVal.setOriginType(OriginType.USER_POLICY); PropertyDelta<Integer> iterationDelta = PropertyDelta.createReplaceDelta(objDef, FocusType.F_ITERATION, iterationVal); focusContext.swallowToSecondaryDelta(iterationDelta); PrismPropertyValue<String> iterationTokenVal = new PrismPropertyValue<String>(iterationToken); iterationTokenVal.setOriginType(OriginType.USER_POLICY); PropertyDelta<String> iterationTokenDelta = PropertyDelta.createReplaceDelta(objDef, FocusType.F_ITERATION_TOKEN, iterationTokenVal); focusContext.swallowToSecondaryDelta(iterationTokenDelta); } }