/** * Copyright (c) 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 java.util.*; import java.util.stream.Collectors; import javax.xml.namespace.QName; import com.evolveum.midpoint.model.api.context.*; import com.evolveum.midpoint.model.common.expression.ExpressionUtil; import com.evolveum.midpoint.model.common.expression.ObjectDeltaObject; 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.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ModificationTypeType; import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; /** * @author semancik * */ @Component public class PolicyRuleProcessor { private static final Trace LOGGER = TraceManager.getTrace(PolicyRuleProcessor.class); @Autowired private PrismContext prismContext; @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; @Autowired private MappingFactory mappingFactory; @Autowired private MappingEvaluator mappingEvaluator; private static final QName CONDITION_OUTPUT_NAME = new QName(SchemaConstants.NS_C, "condition"); /** * Evaluate the policies (policy rules, but also the legacy policies). Trigger the rules. * But do not enforce anything and do not make any context changes. */ public <F extends FocusType> void processPolicies(LensContext<F> context, DeltaSetTriple<EvaluatedAssignmentImpl<F>> evaluatedAssignmentTriple, OperationResult result) throws PolicyViolationException, SchemaException { checkAssignmentRules(context, evaluatedAssignmentTriple, result); checkExclusionsLegacy(context, evaluatedAssignmentTriple.getPlusSet(), evaluatedAssignmentTriple.getNonNegativeValues()); // in policy based situations, the comparison is not symmetric checkExclusionsRuleBased(context, evaluatedAssignmentTriple.getPlusSet(), evaluatedAssignmentTriple.getPlusSet()); checkExclusionsRuleBased(context, evaluatedAssignmentTriple.getPlusSet(), evaluatedAssignmentTriple.getZeroSet()); checkExclusionsRuleBased(context, evaluatedAssignmentTriple.getZeroSet(), evaluatedAssignmentTriple.getPlusSet()); checkExclusionsRuleBased(context, evaluatedAssignmentTriple.getZeroSet(), evaluatedAssignmentTriple.getZeroSet()); checkAssigneeConstraints(context, evaluatedAssignmentTriple, result); checkSecondaryConstraints(context, evaluatedAssignmentTriple, result); } private <F extends FocusType> void checkAssignmentRules(LensContext<F> context, DeltaSetTriple<EvaluatedAssignmentImpl<F>> evaluatedAssignmentTriple, OperationResult result) throws PolicyViolationException, SchemaException { checkAssignmentRules(context, evaluatedAssignmentTriple.getPlusSet(), PlusMinusZero.PLUS, result); checkAssignmentRules(context, evaluatedAssignmentTriple.getMinusSet(), PlusMinusZero.MINUS, result); } private <F extends FocusType> void checkAssignmentRules(LensContext<F> context, Collection<EvaluatedAssignmentImpl<F>> evaluatedAssignmentSet, PlusMinusZero whichSet, OperationResult result) throws PolicyViolationException, SchemaException { for (EvaluatedAssignmentImpl<F> evaluatedAssignment: evaluatedAssignmentSet) { Collection<EvaluatedPolicyRule> policyRules = evaluatedAssignment.getThisTargetPolicyRules(); for (EvaluatedPolicyRule policyRule: policyRules) { PolicyConstraintsType policyConstraints = policyRule.getPolicyConstraints(); if (policyConstraints == null) { continue; } for (AssignmentPolicyConstraintType assignmentConstraint: policyConstraints.getAssignment()) { if (matchesOperation(assignmentConstraint, whichSet)) { List<QName> relationsToCheck = assignmentConstraint.getRelation().isEmpty() ? Collections.singletonList(null) : assignmentConstraint.getRelation(); for (QName constraintRelation : relationsToCheck) { if (MiscSchemaUtil.compareRelation(constraintRelation, evaluatedAssignment.getRelation())) { EvaluatedPolicyRuleTrigger trigger = new EvaluatedPolicyRuleTrigger<>( PolicyConstraintKindType.ASSIGNMENT, assignmentConstraint, "Assignment of " + evaluatedAssignment.getTarget()); evaluatedAssignment.triggerConstraint(policyRule, trigger); } } } } } } } private boolean matchesOperation(AssignmentPolicyConstraintType constraint, PlusMinusZero whichSet) { List<ModificationTypeType> operations = constraint.getOperation(); if (operations.isEmpty()) { return true; } switch (whichSet) { case PLUS: return operations.contains(ModificationTypeType.ADD); case MINUS: return operations.contains(ModificationTypeType.DELETE); case ZERO: return operations.contains(ModificationTypeType.REPLACE); default: throw new IllegalArgumentException("whichSet: " + whichSet); } } private <F extends FocusType> void checkExclusionsLegacy(LensContext<F> context, Collection<EvaluatedAssignmentImpl<F>> assignmentsA, Collection<EvaluatedAssignmentImpl<F>> assignmentsB) throws PolicyViolationException { for (EvaluatedAssignmentImpl<F> assignmentA: assignmentsA) { for (EvaluatedAssignmentImpl<F> assignmentB: assignmentsB) { if (assignmentA == assignmentB) { continue; // Same thing, this cannot exclude itself } for (EvaluatedAssignmentTargetImpl eRoleA : assignmentA.getRoles().getAllValues()) { if (eRoleA.appliesToFocus()) { for (EvaluatedAssignmentTargetImpl eRoleB : assignmentB.getRoles().getAllValues()) { if (eRoleB.appliesToFocus()) { checkExclusionLegacy(assignmentA, assignmentB, eRoleA, eRoleB); } } } } } } } private <F extends FocusType> void checkExclusionLegacy(EvaluatedAssignmentImpl<F> assignmentA, EvaluatedAssignmentImpl<F> assignmentB, EvaluatedAssignmentTargetImpl roleA, EvaluatedAssignmentTargetImpl roleB) throws PolicyViolationException { checkExclusionOneWayLegacy(assignmentA, assignmentB, roleA, roleB); checkExclusionOneWayLegacy(assignmentB, assignmentA, roleB, roleA); } private <F extends FocusType> void checkExclusionOneWayLegacy(EvaluatedAssignmentImpl<F> assignmentA, EvaluatedAssignmentImpl<F> assignmentB, EvaluatedAssignmentTargetImpl roleA, EvaluatedAssignmentTargetImpl roleB) throws PolicyViolationException { for (ExclusionPolicyConstraintType exclusionA : roleA.getExclusions()) { checkAndTriggerExclusionConstraintViolationLegacy(assignmentA, assignmentB, roleA, roleB, exclusionA); } } private <F extends FocusType> void checkAndTriggerExclusionConstraintViolationLegacy(EvaluatedAssignmentImpl<F> assignmentA, @NotNull EvaluatedAssignmentImpl<F> assignmentB, EvaluatedAssignmentTargetImpl roleA, EvaluatedAssignmentTargetImpl roleB, ExclusionPolicyConstraintType constraint) throws PolicyViolationException { ObjectReferenceType targetRef = constraint.getTargetRef(); if (roleB.getOid().equals(targetRef.getOid())) { EvaluatedExclusionTrigger trigger = new EvaluatedExclusionTrigger( constraint, "Violation of SoD policy: " + roleA.getTarget() + " excludes " + roleB.getTarget() + ", they cannot be assigned at the same time", assignmentB, roleA.getTarget() != null ? roleA.getTarget().asObjectable() : null, roleB.getTarget() != null ? roleB.getTarget().asObjectable() : null, roleA.getAssignmentPath(), roleB.getAssignmentPath()); assignmentA.triggerConstraint(null, trigger); } } private <F extends FocusType> void checkExclusionsRuleBased(LensContext<F> context, Collection<EvaluatedAssignmentImpl<F>> assignmentsA, Collection<EvaluatedAssignmentImpl<F>> assignmentsB) throws PolicyViolationException { for (EvaluatedAssignmentImpl<F> assignmentA : assignmentsA) { checkExclusionsRuleBased(context, assignmentA, assignmentsB); } } private <F extends FocusType> void checkExclusionsRuleBased(LensContext<F> context, EvaluatedAssignmentImpl<F> assignmentA, Collection<EvaluatedAssignmentImpl<F>> assignmentsB) throws PolicyViolationException { // We consider all policy rules, i.e. also from induced targets. (It is not possible to collect local // rules for individual targets in the chain - rules are computed only for directly evaluated assignments.) for (EvaluatedPolicyRule policyRule : assignmentA.getAllTargetsPolicyRules()) { if (policyRule.getPolicyConstraints() == null || policyRule.getPolicyConstraints().getExclusion().isEmpty()) { continue; } // In order to avoid false positives, we consider all targets from the current assignment as "allowed" Set<String> allowedTargetOids = assignmentA.getNonNegativeTargets().stream() .filter(t -> t.appliesToFocus()) .map(t -> t.getOid()) .collect(Collectors.toSet()); for (EvaluatedAssignmentImpl<F> assignmentB : assignmentsB) { for (EvaluatedAssignmentTargetImpl targetB : assignmentB.getNonNegativeTargets()) { if (!targetB.appliesToFocus() || allowedTargetOids.contains(targetB.getOid())) { continue; } for (ExclusionPolicyConstraintType exclusionConstraint : policyRule.getPolicyConstraints().getExclusion()) { if (excludes(exclusionConstraint, targetB)) { triggerExclusionConstraintViolation(assignmentA, assignmentB, targetB, exclusionConstraint, policyRule); } } } } } } private boolean excludes(ExclusionPolicyConstraintType constraint, EvaluatedAssignmentTargetImpl target) { if (constraint.getTargetRef() == null || target.getOid() == null) { return false; // shouldn't occur } else { return target.getOid().equals(constraint.getTargetRef().getOid()); } // We could speculate about resolving targets at runtime, but that's inefficient. More appropriate is // to specify an expression, and evaluate (already resolved) target with regards to this expression. } private <F extends FocusType> void triggerExclusionConstraintViolation(EvaluatedAssignmentImpl<F> assignmentA, @NotNull EvaluatedAssignmentImpl<F> assignmentB, EvaluatedAssignmentTargetImpl targetB, ExclusionPolicyConstraintType constraint, EvaluatedPolicyRule policyRule) throws PolicyViolationException { AssignmentPath pathA = policyRule.getAssignmentPath(); AssignmentPath pathB = targetB.getAssignmentPath(); String infoA = computeAssignmentInfo(pathA, assignmentA.getTarget()); String infoB = computeAssignmentInfo(pathB, targetB.getTarget()); ObjectType objectA = getConflictingObject(pathA, assignmentA.getTarget()); ObjectType objectB = getConflictingObject(pathB, targetB.getTarget()); EvaluatedExclusionTrigger trigger = new EvaluatedExclusionTrigger( constraint, "Violation of SoD policy: " + infoA + " excludes " + infoB + ", they cannot be assigned at the same time", assignmentB, objectA, objectB, pathA, pathB); assignmentA.triggerConstraint(policyRule, trigger); } private ObjectType getConflictingObject(AssignmentPath path, PrismObject<?> defaultObject) { if (path == null) { return ObjectTypeUtil.toObjectable(defaultObject); } List<ObjectType> objects = path.getFirstOrderChain(); return objects.isEmpty() ? ObjectTypeUtil.toObjectable(defaultObject) : objects.get(objects.size()-1); } private String computeAssignmentInfo(AssignmentPath path, PrismObject<?> defaultObject) { if (path == null) { return String.valueOf(defaultObject); // shouldn't occur } List<ObjectType> objects = path.getFirstOrderChain(); if (objects.isEmpty()) { // shouldn't occur return String.valueOf(defaultObject); } ObjectType last = objects.get(objects.size()-1); StringBuilder sb = new StringBuilder(); sb.append(last); if (objects.size() > 1) { sb.append(objects.stream() .map(o -> PolyString.getOrig(o.getName())) .collect(Collectors.joining("->", " (", ")"))); } return sb.toString(); } private <F extends FocusType> void checkAssigneeConstraints(LensContext<F> context, DeltaSetTriple<EvaluatedAssignmentImpl<F>> evaluatedAssignmentTriple, OperationResult result) throws PolicyViolationException, SchemaException { for (EvaluatedAssignmentImpl<F> assignment: evaluatedAssignmentTriple.union()) { if (evaluatedAssignmentTriple.presentInPlusSet(assignment)) { if (!assignment.isPresentInCurrentObject()) { checkAssigneeConstraints(context, assignment, PlusMinusZero.PLUS, result); // only really new assignments } } else if (evaluatedAssignmentTriple.presentInZeroSet(assignment)) { // No need to check anything here. Maintain status quo. } else { if (assignment.isPresentInCurrentObject()) { checkAssigneeConstraints(context, assignment, PlusMinusZero.MINUS, result); // only assignments that are really deleted } } } } private <F extends FocusType> void checkAssigneeConstraints(LensContext<F> context, EvaluatedAssignment<F> assignment, PlusMinusZero plusMinus, OperationResult result) throws PolicyViolationException, SchemaException { PrismObject<?> target = assignment.getTarget(); if (target == null || !(target.asObjectable() instanceof AbstractRoleType)) { return; } AbstractRoleType targetRole = (AbstractRoleType) target.asObjectable(); QName relation = ObjectTypeUtil.normalizeRelation(assignment.getRelation()); Collection<EvaluatedPolicyRule> policyRules = assignment.getThisTargetPolicyRules(); for (EvaluatedPolicyRule policyRule: policyRules) { PolicyConstraintsType policyConstraints = policyRule.getPolicyConstraints(); if (policyConstraints == null) { continue; } List<MultiplicityPolicyConstraintType> relevantMinAssignees = getForRelation(policyConstraints.getMinAssignees(), relation); List<MultiplicityPolicyConstraintType> relevantMaxAssignees = getForRelation(policyConstraints.getMaxAssignees(), relation); if (relevantMinAssignees.isEmpty() && relevantMaxAssignees.isEmpty()) { continue; } String focusOid = null; if (context.getFocusContext() != null) { focusOid = context.getFocusContext().getOid(); } int currentAssigneesExceptMyself = getNumberOfAssigneesExceptMyself(targetRole, focusOid, relation, result); for (MultiplicityPolicyConstraintType constraint: relevantMinAssignees) { Integer requiredMultiplicity = XsdTypeMapper.multiplicityToInteger(constraint.getMultiplicity()); if (requiredMultiplicity <= 0) { continue; // unbounded or 0 } // Complain only if the situation is getting worse if (currentAssigneesExceptMyself < requiredMultiplicity && plusMinus == PlusMinusZero.MINUS) { EvaluatedPolicyRuleTrigger<MultiplicityPolicyConstraintType> trigger = new EvaluatedPolicyRuleTrigger<>(PolicyConstraintKindType.MIN_ASSIGNEES, constraint, target+" requires at least " + requiredMultiplicity + " assignees with the relation of '" + relation.getLocalPart() + "'. The operation would result in "+currentAssigneesExceptMyself+" assignees."); assignment.triggerConstraint(policyRule, trigger); } } for (MultiplicityPolicyConstraintType constraint: relevantMaxAssignees) { Integer requiredMultiplicity = XsdTypeMapper.multiplicityToInteger(constraint.getMultiplicity()); if (requiredMultiplicity < 0) { continue; // unbounded } // Complain only if the situation is getting worse if (currentAssigneesExceptMyself >= requiredMultiplicity && plusMinus == PlusMinusZero.PLUS) { EvaluatedPolicyRuleTrigger<MultiplicityPolicyConstraintType> trigger = new EvaluatedPolicyRuleTrigger<>(PolicyConstraintKindType.MAX_ASSIGNEES, constraint, target + " requires at most " + requiredMultiplicity + " assignees with the relation of '" + relation.getLocalPart() + "'. The operation would result in " + (currentAssigneesExceptMyself+1) + " assignees."); assignment.triggerConstraint(policyRule, trigger); } } } } private List<MultiplicityPolicyConstraintType> getForRelation(List<MultiplicityPolicyConstraintType> all, QName relation) { return all.stream() .filter(c -> containsRelation(c, relation)) .collect(Collectors.toList()); } private boolean containsRelation(MultiplicityPolicyConstraintType constraint, QName relation) { return getConstraintRelations(constraint).stream() .anyMatch(constraintRelation -> ObjectTypeUtil.relationMatches(constraintRelation, relation)); } private List<QName> getConstraintRelations(MultiplicityPolicyConstraintType constraint) { return !constraint.getRelation().isEmpty() ? constraint.getRelation() : Collections.singletonList(SchemaConstants.ORG_DEFAULT); } /** * Returns numbers of assignees with the given relation name. */ private int getNumberOfAssigneesExceptMyself(AbstractRoleType target, String selfOid, QName relation, OperationResult result) throws SchemaException { S_AtomicFilterExit q = QueryBuilder.queryFor(FocusType.class, prismContext) .item(FocusType.F_ASSIGNMENT, AssignmentType.F_TARGET_REF).ref(target.getOid()); if (selfOid != null) { q = q.and().not().id(selfOid); } ObjectQuery query = q.build(); List<PrismObject<FocusType>> assignees = repositoryService.searchObjects(FocusType.class, query, null, result); int count = 0; assignee: for (PrismObject<FocusType> assignee : assignees) { for (AssignmentType assignment : assignee.asObjectable().getAssignment()) { if (assignment.getTargetRef() != null && ObjectTypeUtil.relationsEquivalent(relation, assignment.getTargetRef().getRelation())) { count++; continue assignee; } } } return count; } public <F extends FocusType> boolean processPruning(LensContext<F> context, DeltaSetTriple<EvaluatedAssignmentImpl<F>> evaluatedAssignmentTriple, OperationResult result) throws PolicyViolationException, SchemaException { Collection<EvaluatedAssignmentImpl<F>> plusSet = evaluatedAssignmentTriple.getPlusSet(); if (plusSet == null) { return false; } boolean needToReevaluateAssignments = false; for (EvaluatedAssignmentImpl<F> plusAssignment: plusSet) { for (EvaluatedPolicyRule targetPolicyRule: plusAssignment.getAllTargetsPolicyRules()) { for (EvaluatedPolicyRuleTrigger trigger: targetPolicyRule.getTriggers()) { if (!(trigger instanceof EvaluatedExclusionTrigger)) { continue; } EvaluatedExclusionTrigger exclTrigger = (EvaluatedExclusionTrigger) trigger; PolicyActionsType actions = targetPolicyRule.getActions(); if (actions == null || actions.getPrune() == null) { continue; } EvaluatedAssignment<FocusType> conflictingAssignment = exclTrigger.getConflictingAssignment(); if (conflictingAssignment == null) { throw new SystemException("Added assignment "+plusAssignment +", the exclusion prune rule was triggered but there is no conflicting assignment in the trigger"); } LOGGER.debug("Pruning assignment {} because it conflicts with added assignment {}", conflictingAssignment, plusAssignment); PrismContainerValue<AssignmentType> assignmentValueToRemove = conflictingAssignment.getAssignmentType().asPrismContainerValue().clone(); PrismObjectDefinition<F> focusDef = context.getFocusContext().getObjectDefinition(); ContainerDelta<AssignmentType> assignmentDelta = ContainerDelta.createDelta(FocusType.F_ASSIGNMENT, focusDef); assignmentDelta.addValuesToDelete(assignmentValueToRemove); context.getFocusContext().swallowToSecondaryDelta(assignmentDelta); needToReevaluateAssignments = true; } } } return needToReevaluateAssignments; } private <F extends FocusType> void checkSecondaryConstraints(LensContext<F> context, DeltaSetTriple<EvaluatedAssignmentImpl<F>> evaluatedAssignmentTriple, OperationResult result) throws SchemaException, PolicyViolationException { checkSecondaryConstraints(context, evaluatedAssignmentTriple.getPlusSet(), result); checkSecondaryConstraints(context, evaluatedAssignmentTriple.getZeroSet(), result); checkSecondaryConstraints(context, evaluatedAssignmentTriple.getMinusSet(), result); } private <F extends FocusType> void checkSecondaryConstraints(LensContext<F> context, Collection<EvaluatedAssignmentImpl<F>> evaluatedAssignmentSet, OperationResult result) throws PolicyViolationException, SchemaException { for (EvaluatedAssignmentImpl<F> evaluatedAssignment : evaluatedAssignmentSet) { checkSecondaryConstraints(evaluatedAssignment, result); } } private <F extends FocusType> void checkSecondaryConstraints(EvaluatedAssignmentImpl<F> evaluatedAssignment, OperationResult result) throws PolicyViolationException, SchemaException { // Single pass only (for the time being) // We consider only directly attached "situation" policy rules. In the future, we might configure this. // So, if someone wants to report (forward) triggers from a target, he must ensure that a particular // "situation" constraint is present directly on it. for (EvaluatedPolicyRule policyRule: evaluatedAssignment.getThisTargetPolicyRules()) { if (policyRule.getPolicyConstraints() == null) { continue; } for (PolicySituationPolicyConstraintType situationConstraint : policyRule.getPolicyConstraints().getSituation()) { Collection<EvaluatedPolicyRule> sourceRules = selectTriggeredRules(evaluatedAssignment, situationConstraint.getSituation()); if (sourceRules.isEmpty()) { continue; } String message = sourceRules.stream() .flatMap(r -> r.getTriggers().stream().map(EvaluatedPolicyRuleTrigger::getMessage)) .distinct() .collect(Collectors.joining("; ")); EvaluatedSituationTrigger trigger = new EvaluatedSituationTrigger(situationConstraint, message, sourceRules); evaluatedAssignment.triggerConstraint(policyRule, trigger); } } } private <F extends FocusType> Collection<EvaluatedPolicyRule> selectTriggeredRules( EvaluatedAssignmentImpl<F> evaluatedAssignment, List<String> situations) { // We consider all rules here, i.e. also those that are triggered on targets induced by this one. // Decision whether to trigger such rules lies on "primary" policy constraints. (E.g. approvals would // not trigger, whereas exclusions probably would.) Overall, our responsibility is simply to collect // all triggered rules. return evaluatedAssignment.getAllTargetsPolicyRules().stream() .filter(r -> !r.getTriggers().isEmpty() && situations.contains(r.getPolicySituation())) .collect(Collectors.toList()); } @NotNull private <F extends FocusType> List<ItemDelta<?, ?>> getAssignmentModificationDelta( EvaluatedAssignmentImpl<F> evaluatedAssignment, List<EvaluatedPolicyRuleTriggerType> triggers) throws SchemaException { Long id = evaluatedAssignment.getAssignmentType().getId(); if (id == null) { throw new IllegalArgumentException("Assignment with no ID: " + evaluatedAssignment); } List<ItemDelta<?, ?>> deltas = new ArrayList<>(); Set<String> currentSituations = new HashSet<>(evaluatedAssignment.getAssignmentType().getPolicySituation()); Set<String> newSituations = new HashSet<>(evaluatedAssignment.getPolicySituations()); CollectionUtils.addIgnoreNull(deltas, createSituationDelta( new ItemPath(FocusType.F_ASSIGNMENT, id, AssignmentType.F_POLICY_SITUATION), currentSituations, newSituations)); Set<EvaluatedPolicyRuleTriggerType> currentTriggers = new HashSet<>(evaluatedAssignment.getAssignmentType().getTrigger()); Set<EvaluatedPolicyRuleTriggerType> newTriggers = new HashSet<>(triggers); CollectionUtils.addIgnoreNull(deltas, createTriggerDelta( new ItemPath(FocusType.F_ASSIGNMENT, id, AssignmentType.F_TRIGGER), currentTriggers, newTriggers)); return deltas; } private <F extends FocusType> boolean shouldSituationBeUpdated(EvaluatedAssignment<F> evaluatedAssignment, List<EvaluatedPolicyRuleTriggerType> triggers) { Set<String> currentSituations = new HashSet<>(evaluatedAssignment.getAssignmentType().getPolicySituation()); Set<EvaluatedPolicyRuleTriggerType> currentTriggers = new HashSet<>(evaluatedAssignment.getAssignmentType().getTrigger()); // if the current situations different from the ones in the old assignment => update // (provided that the situations in the assignment were _not_ changed directly via a delta!!!) TODO check this if (!currentSituations.equals(new HashSet<>(evaluatedAssignment.getPolicySituations()))) { LOGGER.trace("computed policy situations are different from the current ones"); return true; } if (!currentTriggers.equals(new HashSet<>(triggers))) { LOGGER.trace("computed policy rules triggers are different from the current ones"); return true; } return false; } public <F extends FocusType> void storeFocusPolicySituation(LensContext<F> context, Task task, OperationResult result) throws SchemaException { LensFocusContext<F> focusContext = context.getFocusContext(); if (focusContext == null) { return; } Set<String> currentSituations = focusContext.getObjectCurrent() != null ? new HashSet<>(focusContext.getObjectCurrent().asObjectable().getPolicySituation()) : Collections.emptySet(); Set<String> newSituations = new HashSet<>(focusContext.getPolicySituations()); PropertyDelta<String> situationsDelta = createSituationDelta( new ItemPath(FocusType.F_POLICY_SITUATION), currentSituations, newSituations); if (situationsDelta != null) { focusContext.swallowToProjectionWaveSecondaryDelta(situationsDelta); } } @Nullable private PropertyDelta<String> createSituationDelta(ItemPath path, Set<String> currentSituations, Set<String> newSituations) throws SchemaException { if (newSituations.equals(currentSituations)) { return null; } @SuppressWarnings({ "unchecked", "raw" }) PropertyDelta<String> situationsDelta = (PropertyDelta<String>) DeltaBuilder.deltaFor(FocusType.class, prismContext) .item(path) .add(CollectionUtils.subtract(newSituations, currentSituations).toArray()) .delete(CollectionUtils.subtract(currentSituations, newSituations).toArray()) .asItemDelta(); situationsDelta.setEstimatedOldValues(PrismPropertyValue.wrap(currentSituations)); return situationsDelta; } private PropertyDelta<EvaluatedPolicyRuleTriggerType> createTriggerDelta(ItemPath path, Set<EvaluatedPolicyRuleTriggerType> currentTriggers, Set<EvaluatedPolicyRuleTriggerType> newTriggers) throws SchemaException { if (newTriggers.equals(currentTriggers)) { return null; } @SuppressWarnings({ "unchecked", "raw" }) PropertyDelta<EvaluatedPolicyRuleTriggerType> triggersDelta = (PropertyDelta<EvaluatedPolicyRuleTriggerType>) DeltaBuilder.deltaFor(FocusType.class, prismContext) .item(path) .replace(newTriggers.toArray()) // TODO or add + delete? .asItemDelta(); triggersDelta.setEstimatedOldValues(PrismPropertyValue.wrap(currentTriggers)); return triggersDelta; } public <F extends FocusType> void addGlobalPoliciesToAssignments(LensContext<F> context, DeltaSetTriple<EvaluatedAssignmentImpl<F>> evaluatedAssignmentTriple, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { PrismObject<SystemConfigurationType> systemConfiguration = context.getSystemConfiguration(); if (systemConfiguration == null) { return; } // We need to consider object before modification here. LensFocusContext<F> focusContext = context.getFocusContext(); PrismObject<F> focus = focusContext.getObjectCurrent(); if (focus == null) { focus = focusContext.getObjectNew(); // only if it does not exist, let's try the new one } for (GlobalPolicyRuleType globalPolicyRule: systemConfiguration.asObjectable().getGlobalPolicyRule()) { ObjectSelectorType focusSelector = globalPolicyRule.getFocusSelector(); if (!repositoryService.selectorMatches(focusSelector, focus, LOGGER, "Global policy rule "+globalPolicyRule.getName()+" focus selector: ")) { continue; } for (EvaluatedAssignmentImpl<F> evaluatedAssignment : evaluatedAssignmentTriple.getAllValues()) { for (EvaluatedAssignmentTargetImpl target : evaluatedAssignment.getRoles().getNonNegativeValues()) { if (!repositoryService.selectorMatches(globalPolicyRule.getTargetSelector(), target.getTarget(), LOGGER, "Global policy rule "+globalPolicyRule.getName()+" target selector: ")) { continue; } if (!isRuleConditionTrue(globalPolicyRule, focus, evaluatedAssignment, context, task, result)) { LOGGER.trace("Skipping global policy rule because the condition evaluated to false: {}", globalPolicyRule); continue; } EvaluatedPolicyRule evaluatedRule = new EvaluatedPolicyRuleImpl(globalPolicyRule, target.getAssignmentPath() != null ? target.getAssignmentPath().clone() : null); if (target.getAssignmentPath() != null && target.getAssignmentPath().size() == 1) { evaluatedAssignment.addThisTargetPolicyRule(evaluatedRule); } else { evaluatedAssignment.addOtherTargetPolicyRule(evaluatedRule); } } } } } private <F extends FocusType> boolean isRuleConditionTrue(GlobalPolicyRuleType globalPolicyRule, PrismObject<F> focus, EvaluatedAssignmentImpl<F> evaluatedAssignment, LensContext<F> context, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { MappingType condition = globalPolicyRule.getCondition(); if (condition == null) { return true; } Mapping.Builder<PrismPropertyValue<Boolean>, PrismPropertyDefinition<Boolean>> builder = mappingFactory .createMappingBuilder(); ObjectDeltaObject<F> focusOdo = new ObjectDeltaObject<>(focus, null, focus); builder = builder.mappingType(condition) .contextDescription("condition in global policy rule " + globalPolicyRule.getName()) .sourceContext(focusOdo) .defaultTargetDefinition( new PrismPropertyDefinitionImpl<>(CONDITION_OUTPUT_NAME, DOMUtil.XSD_BOOLEAN, prismContext)) .addVariableDefinition(ExpressionConstants.VAR_USER, focusOdo) .addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusOdo) .addVariableDefinition(ExpressionConstants.VAR_TARGET, evaluatedAssignment.getTarget()) .addVariableDefinition(ExpressionConstants.VAR_ASSIGNMENT, evaluatedAssignment) // TODO: ok? .rootNode(focusOdo); Mapping<PrismPropertyValue<Boolean>, PrismPropertyDefinition<Boolean>> mapping = builder.build(); mappingEvaluator.evaluateMapping(mapping, context, task, result); PrismValueDeltaSetTriple<PrismPropertyValue<Boolean>> conditionTriple = mapping.getOutputTriple(); return conditionTriple != null && ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); // TODO: null -> true (in the method) - ok? } public <F extends FocusType, T extends FocusType> void applyAssignmentSituationOnAdd(LensContext<F> context, PrismObject<T> objectToAdd) throws SchemaException { if (context.getEvaluatedAssignmentTriple() == null) { return; } T focus = objectToAdd.asObjectable(); for (EvaluatedAssignmentImpl<?> evaluatedAssignment : context.getEvaluatedAssignmentTriple().getNonNegativeValues()) { LOGGER.trace("Applying assignment situation on object ADD for {}", evaluatedAssignment); List<EvaluatedPolicyRuleTriggerType> triggers = getTriggers(evaluatedAssignment); if (!shouldSituationBeUpdated(evaluatedAssignment, triggers)) { continue; } AssignmentType assignment = evaluatedAssignment.getAssignmentType(); if (assignment.getId() != null) { ItemDelta.applyTo(getAssignmentModificationDelta(evaluatedAssignment, triggers), objectToAdd); } else { int i = focus.getAssignment().indexOf(assignment); if (i < 0) { throw new IllegalStateException("Assignment to be replaced not found in an object to add: " + assignment + " / " + objectToAdd); } copyPolicyData(focus.getAssignment().get(i), evaluatedAssignment, triggers); } } } private List<EvaluatedPolicyRuleTriggerType> getTriggers(EvaluatedAssignmentImpl<?> evaluatedAssignment) { List<EvaluatedPolicyRuleTriggerType> rv = new ArrayList<>(); for (EvaluatedPolicyRule policyRule : evaluatedAssignment.getAllTargetsPolicyRules()) { for (EvaluatedPolicyRuleTrigger<?> trigger : policyRule.getTriggers()) { EvaluatedPolicyRuleTriggerType triggerType = trigger.toEvaluatedPolicyRuleTriggerType(policyRule).clone(); simplifyTrigger(triggerType); rv.add(triggerType); } } return rv; } private void simplifyTrigger(EvaluatedPolicyRuleTriggerType trigger) { deleteAssignments(trigger.getAssignmentPath()); if (trigger instanceof EvaluatedExclusionTriggerType) { EvaluatedExclusionTriggerType exclusionTrigger = (EvaluatedExclusionTriggerType) trigger; deleteAssignments(exclusionTrigger.getConflictingObjectPath()); exclusionTrigger.setConflictingAssignment(null); } else if (trigger instanceof EvaluatedSituationTriggerType) { for (EvaluatedPolicyRuleType sourceRule : ((EvaluatedSituationTriggerType) trigger).getSourceRule()) { for (EvaluatedPolicyRuleTriggerType sourceTrigger : sourceRule.getTrigger()) { simplifyTrigger(sourceTrigger); } } } } private void deleteAssignments(AssignmentPathType path) { if (path == null) { return; } for (AssignmentPathSegmentType segment : path.getSegment()) { segment.setAssignment(null); } } public <F extends FocusType, T extends ObjectType> ObjectDelta<T> applyAssignmentSituationOnModify(LensContext<F> context, ObjectDelta<T> objectDelta) throws SchemaException { if (context.getEvaluatedAssignmentTriple() == null) { return objectDelta; } for (EvaluatedAssignmentImpl<?> evaluatedAssignment : context.getEvaluatedAssignmentTriple().getNonNegativeValues()) { LOGGER.trace("Applying assignment situation on object MODIFY for {}", evaluatedAssignment); List<EvaluatedPolicyRuleTriggerType> triggers = getTriggers(evaluatedAssignment); if (!shouldSituationBeUpdated(evaluatedAssignment, triggers)) { continue; } AssignmentType assignment = evaluatedAssignment.getAssignmentType(); if (assignment.getId() != null) { objectDelta = swallow(context, objectDelta, getAssignmentModificationDelta(evaluatedAssignment, triggers)); } else { if (objectDelta == null) { throw new IllegalStateException("No object delta!"); } ContainerDelta<Containerable> assignmentDelta = objectDelta.findContainerDelta(FocusType.F_ASSIGNMENT); if (assignmentDelta == null) { throw new IllegalStateException("Unnumbered assignment (" + assignment + ") couldn't be found in object delta (no assignment modification): " + objectDelta); } PrismContainerValue<AssignmentType> assignmentInDelta = assignmentDelta.findValueToAddOrReplace(assignment.asPrismContainerValue()); if (assignmentInDelta == null) { throw new IllegalStateException("Unnumbered assignment (" + assignment + ") couldn't be found in object delta (no corresponding assignment value): " + objectDelta); } copyPolicyData(assignmentInDelta.asContainerable(), evaluatedAssignment, triggers); } } return objectDelta; } private <T extends ObjectType, F extends FocusType> ObjectDelta<T> swallow(LensContext<F> context, ObjectDelta<T> objectDelta, List<ItemDelta<?,?>> deltas) throws SchemaException { if (deltas.isEmpty()) { return objectDelta; } if (objectDelta == null) { if (context.getFocusClass() == null) { throw new IllegalStateException("No focus class in " + context); } if (context.getFocusContext() == null) { throw new IllegalStateException("No focus context in " + context); } objectDelta = (ObjectDelta) new ObjectDelta<F>(context.getFocusClass(), ChangeType.MODIFY, prismContext); objectDelta.setOid(context.getFocusContext().getOid()); } objectDelta.swallow(deltas); return objectDelta; } private void copyPolicyData(AssignmentType targetAssignment, EvaluatedAssignmentImpl<?> evaluatedAssignment, List<EvaluatedPolicyRuleTriggerType> triggers) { targetAssignment.getPolicySituation().clear(); targetAssignment.getPolicySituation().addAll(evaluatedAssignment.getPolicySituations()); targetAssignment.getTrigger().clear(); targetAssignment.getTrigger().addAll(triggers); } public <O extends ObjectType> ObjectDelta<O> applyAssignmentSituation(LensContext<O> context, ObjectDelta<O> focusDelta) throws SchemaException { if (context.getFocusClass() == null || !FocusType.class.isAssignableFrom(context.getFocusClass())) { return focusDelta; } LensContext<? extends FocusType> contextOfFocus = (LensContext<FocusType>) context; if (focusDelta != null && focusDelta.isAdd()) { applyAssignmentSituationOnAdd(contextOfFocus, (PrismObject<? extends FocusType>) focusDelta.getObjectToAdd()); return focusDelta; } else if (focusDelta == null || focusDelta.isModify()) { return applyAssignmentSituationOnModify(contextOfFocus, focusDelta); } else { return focusDelta; } } }