From 46d3938c32e2bbe00d22a7e2f610f0378fc5dec0 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Tue, 14 Mar 2017 00:33:58 +0100 Subject: [PATCH] Collecting policy rules even for non-member/non-deputy relations (to support MID-3799:1). --- .../model/api/context/EvaluationOrder.java | 3 + .../model/impl/lens/AssignmentEvaluator.java | 345 +++++++++--------- .../impl/lens/AssignmentPathSegmentImpl.java | 77 ++-- .../lens/EvaluatedAssignmentTargetImpl.java | 4 +- .../model/impl/lens/EvaluationOrderImpl.java | 9 + .../impl/lens/TestAssignmentProcessor2.java | 93 ++++- 6 files changed, 314 insertions(+), 217 deletions(-) diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluationOrder.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluationOrder.java index 6eedb5a3b9f..ccb573e2351 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluationOrder.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluationOrder.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.util.DebugDumpable; import javax.xml.namespace.QName; +import java.util.Collection; /** * @author semancik @@ -39,4 +40,6 @@ public interface EvaluationOrder extends DebugDumpable { int getMatchingRelationOrder(QName relation); String shortDump(); + + Collection getExtraRelations(); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java index 41e320575be..74ed86b4f59 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java @@ -22,7 +22,6 @@ import javax.xml.namespace.QName; import com.evolveum.midpoint.common.ActivationComputer; -import com.evolveum.midpoint.model.api.context.AssignmentPath; import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; import com.evolveum.midpoint.model.api.context.EvaluationOrder; @@ -209,6 +208,7 @@ public EvaluatedAssignmentImpl evaluate( AssignmentPathSegmentImpl segment = new AssignmentPathSegmentImpl(source, sourceDescription, assignmentIdi, true); segment.setEvaluationOrder(getInitialEvaluationOrder(assignmentIdi, ctx)); + segment.setEvaluationOrderForTarget(EvaluationOrderImpl.ZERO); segment.setValidityOverride(true); segment.setPathToSourceValid(true); segment.setProcessMembership(true); @@ -336,7 +336,7 @@ private boolean evaluateSegmentContent(AssignmentPathSegm if (segment.isMatchingOrder()) { collectPolicyRule(true, segment, ctx); } - if (segment.isMatchingOrderPlusOne()) { + if (segment.isMatchingOrderForTarget()) { collectPolicyRule(false, segment, ctx); } } @@ -585,9 +585,9 @@ private List> resolveTargetsFromFilter(Cla } } - private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, - PlusMinusZero mode, boolean isValid, FocusType targetType, QName relation, - EvaluationContext ctx) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { + private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, + FocusType targetType, QName relation, EvaluationContext ctx) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { assertSourceNotNull(segment.source, ctx.evalAssignment); assert ctx.assignmentPath.last() == segment; @@ -601,7 +601,7 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, checkRelationWithTarget(segment, targetType, relation); if (!LensUtil.isFocusValid(targetType, now, activationComputer)) { - LOGGER.trace("Skipping evaluation of " + targetType + " because it is not valid"); + LOGGER.trace("Skipping evaluation of {} because it is not valid", targetType); return; } @@ -627,148 +627,31 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, } EvaluatedAssignmentTargetImpl evalAssignmentTarget = new EvaluatedAssignmentTargetImpl( - targetType.asPrismObject(), segment.isMatchingOrder(), - ctx.assignmentPath.clone(), segment.getAssignment(), isValid); + targetType.asPrismObject(), + segment.isMatchingOrder(), // evaluateConstructions: exact meaning of this is to be revised + ctx.assignmentPath.clone(), + segment.getAssignment(), + isValid); ctx.evalAssignment.addRole(evalAssignmentTarget, mode); if ((isNonNegative(mode)) && segment.isProcessMembership()) { - PrismReferenceValue refVal = new PrismReferenceValue(); - refVal.setObject(targetType.asPrismObject()); - refVal.setTargetType(ObjectTypes.getObjectType(targetType.getClass()).getTypeQName()); - refVal.setRelation(relation); - refVal.setTargetName(targetType.getName().toPolyString()); - - if (ctx.assignmentPath.getSegments().stream().anyMatch(aps -> DeputyUtils.isDelegationAssignment(aps.getAssignment()))) { - LOGGER.trace("Adding target {} to delegationRef", targetType); - ctx.evalAssignment.addDelegationRefVal(refVal); - } else { - if (targetType instanceof AbstractRoleType) { - LOGGER.trace("Adding target {} to membershipRef", targetType); - ctx.evalAssignment.addMembershipRefVal(refVal); - } - } - - if (targetType instanceof OrgType) { - LOGGER.trace("Adding target {} to orgRef", targetType); - ctx.evalAssignment.addOrgRefVal(refVal); - } else { - LOGGER.trace("NOT adding target {} to orgRef: {}", targetType, ctx.assignmentPath); - } - } - - if (!DeputyUtils.isMembershipRelation(relation) && !DeputyUtils.isDelegationRelation(relation)) { - LOGGER.trace("Cutting evaluation of " + targetType + " because it is neither membership nor delegation relation ({})", relation); - return; - } - - EvaluationOrder evaluationOrder = segment.getEvaluationOrder(); - ObjectType orderOneObject; - - if (evaluationOrder.getSummaryOrder() == 1) { - orderOneObject = targetType; - } else { - AssignmentPathSegment last = ctx.assignmentPath.last(); - if (last != null && last.getSource() != null) { - orderOneObject = last.getSource(); - } else { - orderOneObject = targetType; - } + evaluateMembership(targetType, relation, ctx); } + // We continue evaluation even if the relation is non-membership and non-delegation. + // Computation of isMatchingOrder will ensure that we won't collect any unwanted content. + if (targetType instanceof AbstractRoleType) { for (AssignmentType roleInducement : ((AbstractRoleType)targetType).getInducement()) { - if (!isApplicableToFocusType(roleInducement.getFocusType(), (AbstractRoleType)targetType)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of inducement {} because the focusType does not match (specified: {}, actual: {})", - FocusTypeUtil.dumpAssignment(roleInducement), roleInducement.getFocusType(), targetType.getClass().getSimpleName()); - } - continue; - } - if (!isAllowedByLimitations(segment, roleInducement)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of inducement {} because it is limited", - FocusTypeUtil.dumpAssignment(roleInducement)); - } - continue; - } - ItemDeltaItem,PrismContainerDefinition> roleInducementIdi = new ItemDeltaItem<>(); - roleInducementIdi.setItemOld(LensUtil.createAssignmentSingleValueContainerClone(roleInducement)); - roleInducementIdi.recompute(); - String subSourceDescription = targetType+" in "+segment.sourceDescription; - AssignmentPathSegmentImpl subAssignmentPathSegment = new AssignmentPathSegmentImpl(targetType, subSourceDescription, roleInducementIdi, false); - - boolean newIsMatchingOrder = AssignmentPathSegmentImpl.computeMatchingOrder( - subAssignmentPathSegment.getAssignment(), evaluationOrder, 0); - boolean newIsMatchingOrderPlusOne = AssignmentPathSegmentImpl.computeMatchingOrder( - subAssignmentPathSegment.getAssignment(), evaluationOrder, 1); - - EvaluationOrder newEvaluationOrder; - if (roleInducement.getOrder() != null && roleInducement.getOrder() > 1) { - newEvaluationOrder = evaluationOrder.decrease(roleInducement.getOrder()-1); // TODO what about relations? - } else { - newEvaluationOrder = evaluationOrder; - } - // TODO undefined if intervals - subAssignmentPathSegment.setEvaluationOrder(newEvaluationOrder, newIsMatchingOrder, newIsMatchingOrderPlusOne); - - subAssignmentPathSegment.setOrderOneObject(orderOneObject); - subAssignmentPathSegment.setPathToSourceValid(isValid); - subAssignmentPathSegment.setProcessMembership(newIsMatchingOrder); - - // Originally we executed the following only if isMatchingOrder. However, sometimes we have to look even into - // inducements with non-matching order: for example because we need to extract target-related policy rules - // (these are stored with order of one less than orders for focus-related policy rules). - // - // We need to make sure NOT to extract anything other from such inducements. That's why we set e.g. - // processMembership attribute to false for these inducements. - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("orig EO({}): evaluate {} inducement({}) {}; new EO({})", - evaluationOrder.shortDump(), targetType, FocusTypeUtil.dumpInducementConstraints(roleInducement), - FocusTypeUtil.dumpAssignment(roleInducement), newEvaluationOrder.shortDump()); - } - assert !ctx.assignmentPath.isEmpty(); - evaluateFromSegment(subAssignmentPathSegment, mode, ctx); + evaluateInducement(segment, mode, isValid, ctx, targetType, roleInducement); } } - for (AssignmentType roleAssignment : targetType.getAssignment()) { - if (DeputyUtils.isDelegationRelation(relation)) { - // We have to handle assignments as though they were inducements here. - if (!isAllowedByLimitations(segment, roleAssignment)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of delegated assignment {} because it is limited in the delegation", - FocusTypeUtil.dumpAssignment(roleAssignment)); - } - continue; - } - } - QName subrelation = getRelation(roleAssignment); - EvaluationOrder newEvaluationOrder = evaluationOrder.advance(subrelation); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("orig EO({}): follow assignment {} {}; new EO({})", - evaluationOrder.shortDump(), targetType, FocusTypeUtil.dumpAssignment(roleAssignment), newEvaluationOrder); - } - ItemDeltaItem,PrismContainerDefinition> roleAssignmentIdi = new ItemDeltaItem<>(); - roleAssignmentIdi.setItemOld(LensUtil.createAssignmentSingleValueContainerClone(roleAssignment)); - roleAssignmentIdi.recompute(); - String subSourceDescription = targetType+" in "+segment.sourceDescription; - AssignmentPathSegmentImpl subAssignmentPathSegment = new AssignmentPathSegmentImpl(targetType, subSourceDescription, roleAssignmentIdi, true); - subAssignmentPathSegment.setEvaluationOrder(newEvaluationOrder); - subAssignmentPathSegment.setOrderOneObject(orderOneObject); - // TODO why??? this should depend on evaluation order - if (targetType instanceof AbstractRoleType) { - subAssignmentPathSegment.setProcessMembership(false); - } else { - // We want to process membership in case of deputy and similar user->user assignments - subAssignmentPathSegment.setProcessMembership(true); - } - subAssignmentPathSegment.setPathToSourceValid(isValid); - assert !ctx.assignmentPath.isEmpty(); - evaluateFromSegment(subAssignmentPathSegment, mode, ctx); + evaluateAssignment(segment, mode, isValid, ctx, targetType, relation, roleAssignment); } - - if (evaluationOrder.getSummaryOrder() == 1 && targetType instanceof AbstractRoleType && isNonNegative(mode)) { - + + boolean matchesOrder = AssignmentPathSegmentImpl.computeMatchingOrder(segment.getEvaluationOrder(), 1, Collections.emptyList()); + if (matchesOrder && targetType instanceof AbstractRoleType && isNonNegative(mode)) { for (AuthorizationType authorizationType: ((AbstractRoleType)targetType).getAuthorization()) { Authorization authorization = createAuthorization(authorizationType, targetType.toString()); ctx.evalAssignment.addAuthorization(authorization); @@ -776,16 +659,155 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, if (((AbstractRoleType)targetType).getAdminGuiConfiguration() != null) { ctx.evalAssignment.addAdminGuiConfiguration(((AbstractRoleType)targetType).getAdminGuiConfiguration()); } - PolicyConstraintsType policyConstraints = ((AbstractRoleType)targetType).getPolicyConstraints(); if (policyConstraints != null) { ctx.evalAssignment.addLegacyPolicyConstraints(policyConstraints, ctx.assignmentPath.clone(), targetType); } } - LOGGER.trace("Evaluating segment target DONE for {}", segment); } + // TODO revisit this + private ObjectType getOrderOneObject(AssignmentPathSegmentImpl segment) { + EvaluationOrder evaluationOrder = segment.getEvaluationOrder(); + if (evaluationOrder.getSummaryOrder() == 1) { + return segment.getTarget(); + } else { + if (segment.getSource() != null) { // should be always the case... + return segment.getSource(); + } else { + return segment.getTarget(); + } + } + } + + private void evaluateAssignment(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, + FocusType targetType, QName relation, AssignmentType roleAssignment) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { + + ObjectType orderOneObject = getOrderOneObject(segment); + + if (DeputyUtils.isDelegationRelation(relation)) { + // We have to handle assignments as though they were inducements here. + if (!isInducementAllowedByLimitations(segment, roleAssignment)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping application of delegated assignment {} because it is limited in the delegation", + FocusTypeUtil.dumpAssignment(roleAssignment)); + } + return; + } + } + QName nextRelation = getRelation(roleAssignment); + EvaluationOrder nextEvaluationOrder = segment.getEvaluationOrder().advance(nextRelation); + EvaluationOrder nextEvaluationOrderForTarget = segment.getEvaluationOrderForTarget().advance(nextRelation); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("orig EO({}): follow assignment {} {}; new EO({})", + segment.getEvaluationOrder().shortDump(), targetType, FocusTypeUtil.dumpAssignment(roleAssignment), nextEvaluationOrder); + } + ItemDeltaItem,PrismContainerDefinition> roleAssignmentIdi = new ItemDeltaItem<>(); + roleAssignmentIdi.setItemOld(LensUtil.createAssignmentSingleValueContainerClone(roleAssignment)); + roleAssignmentIdi.recompute(); + String nextSourceDescription = targetType+" in "+segment.sourceDescription; + AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(targetType, nextSourceDescription, roleAssignmentIdi, true); + nextSegment.setEvaluationOrder(nextEvaluationOrder); + nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTarget); + nextSegment.setOrderOneObject(orderOneObject); + // TODO why??? this should depend on evaluation order + if (targetType instanceof AbstractRoleType) { + nextSegment.setProcessMembership(false); // evaluation order of an assignment is probably too high (TODO but not in case of inducements going back into zero or negative orders!) + } else { + // We want to process membership in case of deputy and similar user->user assignments + nextSegment.setProcessMembership(true); + } + nextSegment.setPathToSourceValid(isValid); + assert !ctx.assignmentPath.isEmpty(); + evaluateFromSegment(nextSegment, mode, ctx); + } + + private void evaluateInducement(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, + FocusType targetType, AssignmentType inducement) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { + + ObjectType orderOneObject = getOrderOneObject(segment); + + if (!isInducementApplicableToFocusType(inducement.getFocusType(), (AbstractRoleType)targetType)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping application of inducement {} because the focusType does not match (specified: {}, actual: {})", + FocusTypeUtil.dumpAssignment(inducement), inducement.getFocusType(), targetType.getClass().getSimpleName()); + } + return; + } + if (!isInducementAllowedByLimitations(segment, inducement)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping application of inducement {} because it is limited", FocusTypeUtil.dumpAssignment(inducement)); + } + return; + } + ItemDeltaItem,PrismContainerDefinition> roleInducementIdi = new ItemDeltaItem<>(); + roleInducementIdi.setItemOld(LensUtil.createAssignmentSingleValueContainerClone(inducement)); + roleInducementIdi.recompute(); + String subSourceDescription = targetType+" in "+segment.sourceDescription; + AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(targetType, subSourceDescription, roleInducementIdi, false); + + boolean nextIsMatchingOrder = AssignmentPathSegmentImpl.computeMatchingOrder( + segment.getEvaluationOrder(), nextSegment.getAssignment()); + boolean nextIsMatchingOrderForTarget = AssignmentPathSegmentImpl.computeMatchingOrder( + segment.getEvaluationOrderForTarget(), nextSegment.getAssignment()); + + EvaluationOrder nextEvaluationOrder, nextEvaluationOrderForTarget; + if (inducement.getOrder() != null && inducement.getOrder() > 1) { + nextEvaluationOrder = segment.getEvaluationOrder().decrease(inducement.getOrder()-1); // TODO what about relations? + nextEvaluationOrderForTarget = segment.getEvaluationOrderForTarget().decrease(inducement.getOrder()-1); // TODO what about relations? + } else { + nextEvaluationOrder = segment.getEvaluationOrder(); + nextEvaluationOrderForTarget = segment.getEvaluationOrderForTarget(); + } + // TODO undefined if intervals + nextSegment.setEvaluationOrder(nextEvaluationOrder, nextIsMatchingOrder); + nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTarget, nextIsMatchingOrderForTarget); + + nextSegment.setOrderOneObject(orderOneObject); + nextSegment.setPathToSourceValid(isValid); + nextSegment.setProcessMembership(nextIsMatchingOrder); + + // Originally we executed the following only if isMatchingOrder. However, sometimes we have to look even into + // inducements with non-matching order: for example because we need to extract target-related policy rules + // (these are stored with order of one less than orders for focus-related policy rules). + // + // We need to make sure NOT to extract anything other from such inducements. That's why we set e.g. + // processMembership attribute to false for these inducements. + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("orig EO({}): evaluate {} inducement({}) {}; new EO({})", + segment.getEvaluationOrder().shortDump(), targetType, FocusTypeUtil.dumpInducementConstraints(inducement), + FocusTypeUtil.dumpAssignment(inducement), nextEvaluationOrder.shortDump()); + } + assert !ctx.assignmentPath.isEmpty(); + evaluateFromSegment(nextSegment, mode, ctx); + } + + private void evaluateMembership(FocusType targetType, QName relation, EvaluationContext ctx) { + PrismReferenceValue refVal = new PrismReferenceValue(); + refVal.setObject(targetType.asPrismObject()); + refVal.setTargetType(ObjectTypes.getObjectType(targetType.getClass()).getTypeQName()); + refVal.setRelation(relation); + refVal.setTargetName(targetType.getName().toPolyString()); + + if (ctx.assignmentPath.getSegments().stream().anyMatch(aps -> DeputyUtils.isDelegationAssignment(aps.getAssignment()))) { + LOGGER.trace("Adding target {} to delegationRef", targetType); + ctx.evalAssignment.addDelegationRefVal(refVal); + } else { + if (targetType instanceof AbstractRoleType) { + LOGGER.trace("Adding target {} to membershipRef", targetType); + ctx.evalAssignment.addMembershipRefVal(refVal); + } + } + + if (targetType instanceof OrgType) { + LOGGER.trace("Adding target {} to orgRef", targetType); + ctx.evalAssignment.addOrgRefVal(refVal); + } + } + private boolean isNonNegative(PlusMinusZero mode) { // mode == null is also considered negative, because it is a combination of PLUS and MINUS; // so the net result is that for both old and new state there exists an unsatisfied condition on the path. @@ -805,48 +827,23 @@ private void checkRelationWithTarget(AssignmentPathSegmentImpl segment, FocusTyp } } - private boolean containsOtherOrgs(AssignmentPath assignmentPath, FocusType thisOrg) { - for (AssignmentPathSegment segment: assignmentPath.getSegments()) { - ObjectType segmentTarget = segment.getTarget(); - if (segmentTarget != null) { - if (segmentTarget instanceof OrgType && !segmentTarget.getOid().equals(thisOrg.getOid())) { - return true; - } - } - } - return false; - } - - private boolean isApplicableToFocusType(QName focusType, AbstractRoleType roleType) throws SchemaException { - if (focusType == null) { + private boolean isInducementApplicableToFocusType(QName inducementFocusType, AbstractRoleType targetType) throws SchemaException { + if (inducementFocusType == null) { return true; } - - Class focusClass = prismContext.getSchemaRegistry().determineCompileTimeClass(focusType); - - if (focusClass == null){ - throw new SchemaException("Could not determine class for " + focusType); - } - - if (!focusClass.equals(lensContext.getFocusClass())) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping evaluation of {} because it is applicable only for {} and not for {}", - roleType, focusClass, lensContext.getFocusClass()); - } - return false; + Class inducementFocusClass = prismContext.getSchemaRegistry().determineCompileTimeClass(inducementFocusType); + if (inducementFocusClass == null){ + throw new SchemaException("Could not determine class for " + inducementFocusType); } - return true; + return !inducementFocusClass.equals(lensContext.getFocusClass()); } - private boolean isAllowedByLimitations(AssignmentPathSegment assignmentPathSegment, AssignmentType roleInducement) { - AssignmentSelectorType limitation = assignmentPathSegment.getAssignment().getLimitTargetContent(); - if (limitation == null) { - return true; - } - return FocusTypeUtil.selectorMatches(limitation, roleInducement); + private boolean isInducementAllowedByLimitations(AssignmentPathSegment segment, AssignmentType roleInducement) { + AssignmentSelectorType limitation = segment.getAssignment().getLimitTargetContent(); + return limitation == null || FocusTypeUtil.selectorMatches(limitation, roleInducement); } - private QName getTargetType(AssignmentPathSegment assignmentPathSegment){ + private QName getTargetType(AssignmentPathSegment assignmentPathSegment) { return assignmentPathSegment.getTarget().asPrismObject().getDefinition().getName(); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java index 019e4ba8499..799c0fed598 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java @@ -27,11 +27,15 @@ import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.QNameUtil; 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 org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + /** * Primary duty of this class is to be a part of assignment path. (This is what is visible through its interface, * AssignmentPathSegment.) However, it also serves as a place where auxiliary information about assignment evaluation @@ -153,7 +157,6 @@ public class AssignmentPathSegmentImpl implements AssignmentPathSegment { * by "other" relations is not collected. * * Both of this can be overridden by using specific orderConstraints on particular inducement. - * (TODO this is just an idea, not implemented yet) * Set of order constraint is considered to match evaluation order with "other" relations, if for each such "other" * relation it contains related constraint. So, if one explicitly wants an inducement to be applied when * "approver" relation is encountered, he may do so. @@ -180,15 +183,15 @@ public class AssignmentPathSegmentImpl implements AssignmentPathSegment { * * When evaluating jack->Pirate assignment, rule1 would not be normally taken into account, because its assignment * (Pirate->rule1) has an order of 2. However, we want to collect it - but not as an item related to focus, but - * as an item related to evaluated assignment's target. Therefore besides isMatchingOrder we maintain isMatchingOrderPlusOne + * as an item related to evaluated assignment's target. Therefore besides isMatchingOrder we maintain isMatchingOrderForTarget * that marks all segments (assignments/inducements) that contain policy rules relevant to the evaluated assignment's target. * * TODO how exactly do we compute it */ private Boolean isMatchingOrder = null; private EvaluationOrder evaluationOrder; - - private Boolean isMatchingOrderPlusOne = null; + private Boolean isMatchingOrderForTarget = null; + private EvaluationOrder evaluationOrderForTarget; private boolean processMembership = false; @@ -268,13 +271,25 @@ public EvaluationOrder getEvaluationOrder() { } public void setEvaluationOrder(EvaluationOrder evaluationOrder) { - setEvaluationOrder(evaluationOrder, null, null); + setEvaluationOrder(evaluationOrder, null); } - public void setEvaluationOrder(EvaluationOrder evaluationOrder, Boolean matchingOrder, Boolean matchingOrderPlusOne) { + public void setEvaluationOrder(EvaluationOrder evaluationOrder, Boolean matchingOrder) { this.evaluationOrder = evaluationOrder; this.isMatchingOrder = matchingOrder; - this.isMatchingOrderPlusOne = matchingOrderPlusOne; + } + + public EvaluationOrder getEvaluationOrderForTarget() { + return evaluationOrderForTarget; + } + + public void setEvaluationOrderForTarget(EvaluationOrder evaluationOrder) { + setEvaluationOrderForTarget(evaluationOrder, null); + } + + public void setEvaluationOrderForTarget(EvaluationOrder evaluationOrderForTarget, Boolean matching) { + this.evaluationOrderForTarget = evaluationOrderForTarget; + this.isMatchingOrderForTarget = matching; } public ObjectType getOrderOneObject() { @@ -299,44 +314,54 @@ public void setProcessMembership(boolean processMembership) { */ public boolean isMatchingOrder() { if (isMatchingOrder == null) { - isMatchingOrder = computeMatchingOrder(getAssignment(), evaluationOrder, 0); + isMatchingOrder = computeMatchingOrder(evaluationOrder, getAssignment()); } return isMatchingOrder; } - public boolean isMatchingOrderPlusOne() { - if (isMatchingOrderPlusOne == null) { - isMatchingOrderPlusOne = computeMatchingOrder(getAssignment(), evaluationOrder, 1); + public boolean isMatchingOrderForTarget() { + if (isMatchingOrderForTarget == null) { + isMatchingOrderForTarget = computeMatchingOrder(evaluationOrderForTarget, getAssignment()); } - return isMatchingOrderPlusOne; + return isMatchingOrderForTarget; } - static boolean computeMatchingOrder(AssignmentType assignmentType, EvaluationOrder evaluationOrder, int offset) { + static boolean computeMatchingOrder(EvaluationOrder evaluationOrder, AssignmentType assignmentType) { + return computeMatchingOrder(evaluationOrder, assignmentType.getOrder(), assignmentType.getOrderConstraint()); + } + + static boolean computeMatchingOrder(EvaluationOrder evaluationOrder, Integer assignmentOrder, + List assignmentOrderConstraint) { boolean rv; - if (assignmentType.getOrder() == null && assignmentType.getOrderConstraint().isEmpty()) { + List extraRelations = new ArrayList<>(evaluationOrder.getExtraRelations()); + if (assignmentOrder == null && assignmentOrderConstraint.isEmpty()) { // compatibility - rv = evaluationOrder.getSummaryOrder() - offset == 1; + rv = evaluationOrder.getSummaryOrder() == 1; } else { rv = true; - if (assignmentType.getOrder() != null) { - if (evaluationOrder.getSummaryOrder() - offset != assignmentType.getOrder()) { + if (assignmentOrder != null) { + if (evaluationOrder.getSummaryOrder() != assignmentOrder) { rv = false; } } - for (OrderConstraintsType orderConstraint : assignmentType.getOrderConstraint()) { - if (!isMatchingConstraint(orderConstraint, evaluationOrder, offset)) { + for (OrderConstraintsType orderConstraint : assignmentOrderConstraint) { + if (!isMatchingConstraint(orderConstraint, evaluationOrder)) { rv = false; break; } + extraRelations.removeIf(r -> QNameUtil.match(r, orderConstraint.getRelation())); } } - LOGGER.trace("computeMatchingOrder => {}, for offset={}; assignment.order={}, assignment.orderConstraint={}, evaluationOrder={} ... assignment = {}", - rv, offset, assignmentType.getOrder(), assignmentType.getOrderConstraint(), evaluationOrder); + if (!extraRelations.isEmpty()) { + rv = false; + } + LOGGER.trace("computeMatchingOrder => {}, for offset={}; assignment.order={}, assignment.orderConstraint={}, evaluationOrder={}, remainingExtraRelations={}", + rv, assignmentOrder, assignmentOrderConstraint, evaluationOrder, extraRelations); return rv; } - private static boolean isMatchingConstraint(OrderConstraintsType orderConstraint, EvaluationOrder evaluationOrder, int offset) { - int evaluationOrderInt = evaluationOrder.getMatchingRelationOrder(orderConstraint.getRelation()) - offset; + private static boolean isMatchingConstraint(OrderConstraintsType orderConstraint, EvaluationOrder evaluationOrder) { + int evaluationOrderInt = evaluationOrder.getMatchingRelationOrder(orderConstraint.getRelation()); if (orderConstraint.getOrder() != null) { return orderConstraint.getOrder() == evaluationOrderInt; } else { @@ -401,8 +426,8 @@ public String toString() { if (isMatchingOrder()) { // here is a side effect but most probably it's harmless sb.append("(match)"); } - if (isMatchingOrderPlusOne()) { // the same here - sb.append("(match+1)"); + if (isMatchingOrderForTarget()) { // the same here + sb.append("(match-target)"); } sb.append(": "); sb.append(source).append(" "); @@ -459,7 +484,7 @@ public String debugDump(int indent) { DebugUtil.debugDumpWithLabelLn(sb, "target", target==null?"null":target.toString(), indent + 1); DebugUtil.debugDumpWithLabelLn(sb, "evaluationOrder", evaluationOrder, indent + 1); DebugUtil.debugDumpWithLabelLn(sb, "isMatchingOrder", isMatchingOrder, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "isMatchingOrderPlusOne", isMatchingOrderPlusOne, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "isMatchingOrderForTarget", isMatchingOrderForTarget, indent + 1); DebugUtil.debugDumpWithLabelLn(sb, "relation", relation, indent + 1); DebugUtil.debugDumpWithLabelLn(sb, "pathToSourceValid", pathToSourceValid, indent + 1); DebugUtil.debugDumpWithLabelLn(sb, "validityOverride", validityOverride, indent + 1); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentTargetImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentTargetImpl.java index e222f7d9270..7f33a222347 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentTargetImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentTargetImpl.java @@ -125,7 +125,9 @@ public String debugDump(int indent) { sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "Assignment", String.valueOf(assignment), indent + 1); sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, "isValid", isValid, indent + 1); + DebugUtil.debugDumpWithLabel(sb, "EvaluateConstructions", evaluateConstructions, indent + 1); + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, "Valid", isValid, indent + 1); return sb.toString(); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluationOrderImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluationOrderImpl.java index 2f42f63f9ae..b34e64f54e1 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluationOrderImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluationOrderImpl.java @@ -15,8 +15,10 @@ */ package com.evolveum.midpoint.model.impl.lens; +import java.util.Collection; import java.util.HashMap; import java.util.Map.Entry; +import java.util.stream.Collectors; import javax.xml.namespace.QName; @@ -168,4 +170,11 @@ public String shortDump() { sb.append("=").append(summaryOrder); return sb.toString(); } + + @Override + public Collection getExtraRelations() { + return orderMap.keySet().stream() + .filter(r -> !DeputyUtils.isMembershipRelation(r) && !DeputyUtils.isDelegationRelation(r)) + .collect(Collectors.toSet()); + } } diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java index a3ad5fb6235..e8e36a4bb5c 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java @@ -23,6 +23,7 @@ import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ActivationUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; @@ -139,7 +140,8 @@ public void test010AssignR1ToJack() throws Exception { EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); - assertTargets(evaluatedAssignment, "R1 R2 O3 R4 R5 R6 MR1 MR2 MR3 MR4 MMR1", null, null, null, null, null); + assertTargets(evaluatedAssignment, true, "R1 R2 O3 R4 R5 R6", null, null, null, null, null); + assertTargets(evaluatedAssignment, false, "MR1 MR2 MR3 MR4 MMR1", null, null, null, null, null); assertMembershipRef(evaluatedAssignment, "R1 R2 O3 R4 R5 R6"); assertOrgRef(evaluatedAssignment, "O3"); assertDelegation(evaluatedAssignment, null); @@ -189,6 +191,53 @@ public void test020AssignR1ToJackProjectorDisabled() throws Exception { context.getFocusContext().getObjectNew().asObjectable().getRoleMembershipRef().size()); } + /** + * As R1 is assigned with the relation=approver, jack will "see" only this role. + * However, we must collect all relevant target policy rules. + */ + @Test + public void test030AssignR1ToJackAsApprover() throws Exception { + final String TEST_NAME = "test030AssignR1ToJackAsApprover"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestAssignmentProcessor.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + LensContext context = createContextForRoleAssignment(USER_JACK_OID, R1_OID, SchemaConstants.ORG_APPROVER, null, result); + + // WHEN + assignmentProcessor.processAssignmentsProjections(context, clock.currentTimeXMLGregorianCalendar(), task, result); + + // THEN + display("Output context", context); + display("Evaluated assignment triple", context.getEvaluatedAssignmentTriple()); + + result.computeStatus(); + assertSuccess("Assignment processor failed (result)", result); + + Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); + assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); + + assertTargets(evaluatedAssignment, false, "R1 R2 O3 R4 R5 R6 MR1 MR2 MR3 MR4 MMR1", null, null, null, null, null); + assertMembershipRef(evaluatedAssignment, "R1"); + assertOrgRef(evaluatedAssignment, null); + assertDelegation(evaluatedAssignment, null); + + assertConstructions(evaluatedAssignment, "", null, null, null, null, null); + assertFocusMappings(evaluatedAssignment, ""); + assertFocusPolicyRules(evaluatedAssignment, ""); + + // TODO why R4-0 R5-0 R6-0 ? + String expectedThisTargetRules = "R1-0 R4-0 R5-0 R6-0 MR1-1 MR3-1 MR4-1 MMR1-2"; + String expectedTargetRules = expectedThisTargetRules + " R2-0 O3-0 MR2-1"; + assertTargetPolicyRules(evaluatedAssignment, getList(expectedTargetRules), getList(expectedThisTargetRules)); + assertAuthorizations(evaluatedAssignment, ""); + assertGuiConfig(evaluatedAssignment, ""); + } + + /** * Now disable some roles. Their administrative status is simply set to DISABLED. * @@ -249,7 +298,8 @@ public void test110AssignR1ToJack() throws Exception { EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); - assertTargets(evaluatedAssignment, "R1 MR1", null, null, null, null, null); + assertTargets(evaluatedAssignment, true, "R1", null, null, null, null, null); + assertTargets(evaluatedAssignment, false, "MR1", null, null, null, null, null); assertMembershipRef(evaluatedAssignment, "R1"); assertOrgRef(evaluatedAssignment, null); assertDelegation(evaluatedAssignment, null); @@ -326,7 +376,8 @@ public void test160AssignR1ToJack() throws Exception { EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); - assertTargets(evaluatedAssignment, "R1 MR1 MMR1 MR4 R4", null, null, null, null, null); + assertTargets(evaluatedAssignment, true, "R1 R4", null, null, null, null, null); + assertTargets(evaluatedAssignment, false, "MR1 MMR1 MR4", null, null, null, null, null); assertMembershipRef(evaluatedAssignment, "R1 R4"); assertOrgRef(evaluatedAssignment, null); assertDelegation(evaluatedAssignment, null); @@ -414,7 +465,8 @@ public void test210AssignR1ToJack() throws Exception { assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); // R4 is not in plusInvalid, because only directly assigned targets are listed among targets (see validityOverride) - assertTargets(evaluatedAssignment, "R1", null, "MR1 MMR1", null, "R2 MR2", null); + assertTargets(evaluatedAssignment, true, "R1", null, "", null, "R2", null); + assertTargets(evaluatedAssignment, false, "", null, "MR1 MMR1", null, "MR2", null); assertMembershipRef(evaluatedAssignment, "R1"); assertOrgRef(evaluatedAssignment, null); assertDelegation(evaluatedAssignment, null); @@ -712,35 +764,44 @@ private void assertTargetPolicyRules(EvaluatedAssignmentImpl evaluated } private void assertTargets(EvaluatedAssignmentImpl evaluatedAssignment, + Boolean evaluateConstructions, String zeroValid, String zeroInvalid, String plusValid, String plusInvalid, String minusValid, String minusInvalid) { - assertTargets(evaluatedAssignment, getList(zeroValid), getList(zeroInvalid), + assertTargets(evaluatedAssignment, evaluateConstructions, getList(zeroValid), getList(zeroInvalid), getList(plusValid), getList(plusInvalid), getList(minusValid), getList(minusInvalid)); } private void assertTargets(EvaluatedAssignmentImpl evaluatedAssignment, + Boolean evaluateConstructions, List zeroValid, List zeroInvalid, List plusValid, List plusInvalid, List minusValid, List minusInvalid) { - assertTargets("zero", evaluatedAssignment.getRoles().getZeroSet(), zeroValid, zeroInvalid); - assertTargets("plus", evaluatedAssignment.getRoles().getPlusSet(), plusValid, plusInvalid); - assertTargets("minus", evaluatedAssignment.getRoles().getMinusSet(), minusValid, minusInvalid); + assertTargets("zero", evaluatedAssignment.getRoles().getZeroSet(), evaluateConstructions, zeroValid, zeroInvalid); + assertTargets("plus", evaluatedAssignment.getRoles().getPlusSet(), evaluateConstructions, plusValid, plusInvalid); + assertTargets("minus", evaluatedAssignment.getRoles().getMinusSet(), evaluateConstructions, minusValid, minusInvalid); } - private void assertTargets(String type, Collection targets, List expectedValid, - List expectedInvalid) { + private void assertTargets(String type, Collection targets, Boolean evaluateConstructions, + List expectedValid, List expectedInvalid) { targets = CollectionUtils.emptyIfNull(targets); - Collection realValid = targets.stream().filter(t -> t.isValid()).collect(Collectors.toList()); - Collection realInvalid = targets.stream().filter(t -> !t.isValid()).collect(Collectors.toList()); - assertEquals("Wrong # of valid targets in " + type + " set", expectedValid.size(), realValid.size()); - assertEquals("Wrong # of invalid targets in " + type + " set", expectedInvalid.size(), realInvalid.size()); - assertEquals("Wrong valid targets in " + type + " set", new HashSet<>(expectedValid), + Collection realValid = targets.stream() + .filter(t -> t.isValid() && matchesConstructions(t, evaluateConstructions)).collect(Collectors.toList()); + Collection realInvalid = targets.stream() + .filter(t -> !t.isValid() && matchesConstructions(t, evaluateConstructions)).collect(Collectors.toList()); + String ec = evaluateConstructions != null ? " (evaluateConstructions: " + evaluateConstructions + ")" : ""; + assertEquals("Wrong # of valid targets in " + type + " set" + ec, expectedValid.size(), realValid.size()); + assertEquals("Wrong # of invalid targets in " + type + " set" + ec, expectedInvalid.size(), realInvalid.size()); + assertEquals("Wrong valid targets in " + type + " set" + ec, new HashSet<>(expectedValid), realValid.stream().map(t -> t.getTarget().getName().getOrig()).collect(Collectors.toSet())); - assertEquals("Wrong invalid targets in " + type + " set", new HashSet<>(expectedInvalid), + assertEquals("Wrong invalid targets in " + type + " set" + ec, new HashSet<>(expectedInvalid), realInvalid.stream().map(t -> t.getTarget().getName().getOrig()).collect(Collectors.toSet())); } + private boolean matchesConstructions(EvaluatedAssignmentTargetImpl t, Boolean evaluateConstructions) { + return evaluateConstructions == null || t.isEvaluateConstructions() == evaluateConstructions; + } + private void assertConstructions(EvaluatedAssignmentImpl evaluatedAssignment, String zeroValid, String zeroInvalid, String plusValid, String plusInvalid,