diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/builder/S_ValuesEntry.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/builder/S_ValuesEntry.java index 3fb9fdb29d7..69fdfed74ce 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/builder/S_ValuesEntry.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/builder/S_ValuesEntry.java @@ -13,7 +13,7 @@ import java.util.Collection; /** - * @author mederly + * Note: When dealing with PolyStrings, the real values should be of PolyString, not of PolyStringType type. */ public interface S_ValuesEntry { diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/expression/VariablesMap.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/expression/VariablesMap.java index e79dbb65145..035129e6f01 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/expression/VariablesMap.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/expression/VariablesMap.java @@ -12,6 +12,7 @@ import javax.xml.namespace.QName; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.w3c.dom.Element; import com.evolveum.midpoint.prism.ItemDefinition; @@ -85,7 +86,8 @@ public TypedValue put(String key, TypedValue typedValue) { return variables.put(key, typedValue); } - public void registerAlias(String alias, String realName) { + // mainVariable of "null" means the default source + public void registerAlias(String alias, @Nullable String realName) { if (isAlias(realName)) { throw new IllegalArgumentException("Trying to put alias definition: " + alias + "->" + realName + ", but " + realName + " is itself an alias"); } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java index 5b21ef5d556..d955eb02805 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java @@ -55,21 +55,30 @@ public static AssignmentType createTargetAssignment(String targetOid, QName type return assignmentType; } - public static String dumpAssignment(AssignmentType assignmentType) { + public static String dumpAssignment(AssignmentType assignment) { StringBuilder sb = new StringBuilder(); - if (assignmentType.getConstruction() != null) { - sb.append("Constr(").append(assignmentType.getConstruction().getDescription()).append(") "); + if (assignment.getConstruction() != null) { + sb.append("Constr(").append(assignment.getConstruction().getDescription()).append(") "); } - if (assignmentType.getTargetRef() != null) { + if (assignment.getTargetRef() != null) { sb.append("-["); - if (assignmentType.getTargetRef().getRelation() != null) { - sb.append(assignmentType.getTargetRef().getRelation().getLocalPart()); + if (assignment.getTargetRef().getRelation() != null) { + sb.append(assignment.getTargetRef().getRelation().getLocalPart()); } - sb.append("]-> ").append(assignmentType.getTargetRef().getOid()); + sb.append("]-> ").append(assignment.getTargetRef().getOid()); } return sb.toString(); } + public static Object dumpAssignmentLazily(AssignmentType assignment) { + return new Object() { + @Override + public String toString() { + return dumpAssignment(assignment); + } + }; + } + public static String dumpInducementConstraints(AssignmentType assignmentType) { if (assignmentType.getOrder() != null) { return assignmentType.getOrder().toString(); diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java index d4140245a6f..11aa80050c7 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java @@ -685,12 +685,6 @@ public static boolean referenceMatches(ObjectReferenceType ref, String targetOid return true; } - public static OrderConstraintsType getConstraintFor(List constraints, QName relation) { - return CollectionUtils.emptyIfNull(constraints).stream() - .filter(c -> QNameUtil.match(c.getRelation(), relation)) // intentionally not using default/null equivalence here - .findFirst().orElse(null); - } - public static T asObjectable(PrismObject prismObject) { return prismObject != null ? prismObject.asObjectable() : null; } diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd index cbf9c26aa96..ce052bae5ca 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd @@ -1,2541 +1,2600 @@ - - - - - - - - - TODO - - - - - - - - - - - - - - - Set of governance, risk management, compliance (GRC) and similar policy constraints - that influence the identity model. These constraints act as triggers for the rules. - - - - - - - - - - - - Particular state of the object, described by a filter or an expression. - - - 3.7 - PolicyConstraintsType.objectState - PolicyConstraintsType.objectState.help - - - - - - - Particular state of an assignment, described by a filter or an expression. - - - 3.7 - PolicyConstraintsType.assignmentState - PolicyConstraintsType.assignmentState.help - - - - - - - The focus has assignment(s) of given object(s). - - - 3.7 - PolicyConstraintsType.hasAssignment - PolicyConstraintsType.hasAssignment.help - - - - - - - The focus has no assignment(s) of given object(s). - - - 3.7 - PolicyConstraintsType.hasNoAssignment - PolicyConstraintsType.hasNoAssignment.help - - - - - - - Exclusion constraint. Constraint that forbids this object to be assigned together with other object. - - - PolicyConstraintsType.exclusion - PolicyConstraintsType.exclusion.help - - - - - - - Actual number of objects that have this role assigned is lower than prescribed value. - (The constraint should be perhaps named minAssigneesViolation.) - - - PolicyConstraintsType.minAssignees - PolicyConstraintsType.minAssignees.help - - - - - - - Actual number of objects that have this role assigned is higher than prescribed value. - (The constraint should be perhaps named maxAssigneesViolation.) - - - PolicyConstraintsType.maxAssignees - PolicyConstraintsType.maxAssignees.help - - - - - - - Actual number of objects that have this object (e.g. role) assigned is lower than prescribed value. - This is the same as minAssignees, but indicates that the constraint should be evaluated against an object - when evaluating object constraints. HIGHLY EXPERIMENTAL - - - true - PolicyConstraintsType.objectMinAssigneesViolation - - - - - - - Actual number of objects that have this object (e.g. role) assigned is higher than prescribed value. - This is the same as minAssignees, but indicates that the constraint should be evaluated against an object - when evaluating object constraints. HIGHLY EXPERIMENTAL - - - true - PolicyConstraintsType.objectMaxAssigneesViolation - - - - - - - Constraint that triggers when the object is modified. - Modification of the object (add, modify, delete). - (This constraint should be perhaps named objectModification.) - - - PolicyConstraintsType.modification - PolicyConstraintsType.modification.help - - - - - - - Constraint that triggers when the object is assigned, unassigned or the assignment is modified. - Modification of an assignment, i.e. where this object is a target of assignment that is being modified. - (This constraint should be perhaps named assignmentModification.) - - - PolicyConstraintsType.assignment - PolicyConstraintsType.assignment.help - - - - - - - A constraint that triggers when validity of an object or any of its time-sensitive - items (e.g. password) is about to end. - - - 3.6 - - PolicyConstraintsType.objectTimeValidity - PolicyConstraintsType.objectTimeValidity.help - - - - - - - - A constraint that triggers when validity of an assignment or any of its time-sensitive - items (e.g. password) is about to end. - - - 3.6 - - PolicyConstraintsType.assignmentTimeValidity - PolicyConstraintsType.assignmentTimeValidity.help - - - - - - - - Occurrence of the policy situation within an object or an assignment. - - - - PolicyConstraintsType.situation - PolicyConstraintsType.situation.help - - - - - - - -

- Constraints for collection stats (statistics). Can be used to trigger policy - rules on the number of objects in collection, percentage and so on. -

-

- Policy rules with this constraint are ignored during normal processing. - Evaluation of such policy rules require special handling (e.g. scanner task). -

-
- - - PolicyConstraintsType.collectionStats - PolicyConstraintsType.collectionStats.help - 4.0 - true - - -
-
- - - - Constraints that must all apply. (But note that if there are more values of this item, - they are interpreted according to the enclosing operator.) - - - 3.7 - true - - - - - - - Constraints from which at least one must apply. (But note that if there are more values of this item, - they are interpreted according to the enclosing operator.) - - - 3.7 - true - - - - - - - The inner constraint must not apply. - - - 3.7 - true - - - - - - - Specifies how the inner constraints are to be evaluated with regards to operation start and end state. - May not include any transitional constraints itself! - - - 3.7 - - PolicyConstraintsType.transition - PolicyConstraintsType.transition.help - true - - - - - - - - References another policy constraint e.g. by its name. - The referenced constraint must be present as part of policy rules gathered for a given - assignment or object. Also, all declared global constraints are available for referencing, - regardless of which objects they are attached to or whether they are active or not. - - - 3.7 - - PolicyConstraintsType.ref - PolicyConstraintsType.ref.help - - - - -
-
-
- -
- - - - - References another policy constraint. - - - - 3.7 - - - - - - - Name of the constraint referenced. - - - PolicyConstraintReferenceType.name - - - - - - - - - - - - Basic data structure for all policy constraints. - - - - - tns:enforcement - 4.0 - removed - - - - - - - - Identifier of the constraint. It is used when midPoint - needs to refer to a specific constraint, e.g. in order - to record exception. - This element is formally specified as optional, mostly - due to the compatibility reasons. But it - has to be provided in order for some functionality to - work correctly. - - - 3.5 - - AbstractPolicyConstraintType.name - AbstractPolicyConstraintType.name.help - - - - - - - - AbstractPolicyConstraintType.description - AbstractPolicyConstraintType.description.help - - - - - - - - How should be triggering of this constraint presented, e.g. in enforcement messages, - in approvals, in certification, in notifications, etc. - - - 3.7 - AbstractPolicyConstraintType.presentation - AbstractPolicyConstraintType.presentation.help - - - - - - - - - - - - How should be triggering of this constraint presented, e.g. in enforcement messages, - in approvals, in certification, in notifications, etc. - - - - 3.7 - - - - - - - Message to be conveyed to the user. - - - PolicyConstraintPresentationType.message - PolicyConstraintPresentationType.message.help - - - - - - - Very short message describing the situation. Could be used for e.g. notification messages subject, approval process or work item names. - - - PolicyConstraintPresentationType.shortMessage - PolicyConstraintPresentationType.shortMessage.help - - - - - - - Long, documentation-like explanation of the rule. - - - PolicyConstraintPresentationType.longMessage - PolicyConstraintPresentationType.longMessage.help - - - - - - - - If set to true, no embedded triggers will be presented. Use for hiding constraints that are - to be considered too technical to be shown to user. EXPERIMENTAL - - - true - PolicyConstraintPresentationType.final - PolicyConstraintPresentationType.final.help - - - - - - - If set to true, this trigger will be excluded from presentation. EXPERIMENTAL - - - true - PolicyConstraintPresentationType.hidden - PolicyConstraintPresentationType.hidden.help - - - - - - - Ordinal number that determines ordering of displayed messages. EXPERIMENTAL. - - - true - PolicyConstraintPresentationType.displayOrder - PolicyConstraintPresentationType.displayOrder.help - - - - - - - - - - - - How much information about triggered policy rules should be stored? - EXPERIMENTAL - - - - true - - - - - - - Triggered policy rules will not be stored. - - - - - - - - - - Only message and short message for each trigger will be stored. Hidden and final presentation settings are respected. - - - - - - - - - - The triggers and other information for the rule will be stored (including subtriggers). - Hidden and final presentation settings are respected. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Container that defines exclusion of entities (e.g. roles). - It is a part of Segregation of Duties (SoD) mechanism. - - - - ExclusionPolicyConstraintType.details - - tns:policy - 4.0 - removed - - - - - - - - - -

- Target of exclusion. The object defining this "exclusion" and - the object defined as target cannot be assigned at the same time. - Filter in the reference may be used to dynamically exclude broader - range of roles - assuming that runtime resolution is used. -

-
- - tns:AbstractRoleType - ExclusionPolicyConstraintType.targetRef - ExclusionPolicyConstraintType.targetRef.help - -
-
- - - - Specification of relation(s) when this exclusion constraint should be applied at the source side. - (I.e. this is evaluated against the object defining this exclusion constraint.) - The default is "order = 1". - - EXPERIMENTAL. Currently it does not work with non-member relations because of assignment - evaluation optimizations (see TestSegregationOfDuties.test950JackSelfExclusion). So it can - be used only for default and manager relations. - - - 3.7.1 - ExclusionPolicyConstraintType.orderConstraint - - - - - - - Specification of relation(s) when this exclusion constraint should be applied at the target side. - (I.e. this is evaluated against the target object.) - The default is "order = 1". - - EXPERIMENTAL. Currently it does not work with non-member relations because of assignment - evaluation optimizations (see TestSegregationOfDuties.test950JackSelfExclusion). So it can - be used only for default and manager relations. - - - 3.7.1 - ExclusionPolicyConstraintType.targetOrderConstraint - - - -
-
-
-
- - - - - - Constraint on multiplicity of assigned objects. - (Other multiplicity requirements will be probably treated in separate constraints, like "constraint that - restricts value(s) of given item, based on a set of conditions that must be met".) - - - - - - - - - - - - Numeric value or string "unbounded". - - - MultiplicityPolicyConstraintType.multiplicity - - - - - - - Relation(s) to which this constraint applies. All of these relations must match - the defined multiplicity. If no relation is present, org:default (i.e. null) is assumed. - - - MultiplicityPolicyConstraintType.relation - - - - - - - - - - - - - - - Constraint that triggers the rule on object modification, addition or deletion. - - - - 3.5 - true - - - - - - - - - Specifies the operation(s) for which this constraints should be triggered. - If not specified then it will be triggered for all operations. - This defines the object operation (add/modify/delete of the entire object) - or assignment operation (add/modify/delete of the given assignment). - - - ModificationPolicyConstraintType.operation - - - - - - - Specification of items that must be modified (all of them) in order for this - constraint be triggered. If no items are specified then any modification will - trigger this constraint. - - - ModificationPolicyConstraintType.item - - - - - - - If true, item paths to be matched must match exactly. E.g. if "inducement" is specified - as an item to be matched, then only object modifications having "inducement" in the path - (i.e. whole inducement being added/deleted/replaced) would match. - - This is applicable only for modification operations. For add and delete operations the - value of this flag is ignored. - - - 3.7 - ModificationPolicyConstraintType.exactPathMatch - - - - - - - Expression that is used to determine the result. It is evaluated in addition to all the other - conditions, and must have a value of true in order for constraint to be triggered. - - - 3.7 - ModificationPolicyConstraintType.expression - - - - - - - - - - - - - - - - - - - - - - - - - Constraint that triggers the rule when the object is assigned. - - - - 3.5 - true - - - - - - - - - This constraint only applies to relations of the specified type. The value - of this element is compared to the relation of the targetRef relation - in the assignment. If not specified then this policy only - applies to the null (default) relation. If all relations need to be - affected by this policy then the special value of "any" should be specified - in this element. - - - AssignmentModificationPolicyConstraintType.relation - - - - - - - - - - - - - - - - - - - - - - - Constraint that triggers when the focus has given assignment(s). - - - - 3.7 - true - - - - - - - - - Assignment target to be matched. Might be dynamic (with a resolution time of "run"), just - like the one in exclusion constraint. If a relation here is specified, it takes precedence - over "relation" item in this constraint. - - - HasAssignmentPolicyConstraintType.targetRef - - - - - - - If true the assignment to given target must be direct. If false, it must be indirect (induced). - If not specified, it might be either direct or indirect. But in all cases it must be of the order - one, i.e. metaroles are not considered. - - - HasAssignmentPolicyConstraintType.direct - - - - - - - If true the assignment to given target must be effectively enabled. If false, it must be present - but effectively disabled (beware, the behavior of midPoint for disabled indirect assignments may - be fragile). If not specified, the effective status is not considered. - - - HasAssignmentPolicyConstraintType.enabled - - - - - - - This policy only applies to relations of the specified type. (Provided that the relation - is not set in the targetRef.) The value of this element is compared to the relation of the - targetRef relation in the assignment/inducement. If not specified then this policy only - applies to the null (default) relation. If all relations need to be affected by this policy - then the special value of "any" should be specified in this element or in the targetRef element. - - - HasAssignmentPolicyConstraintType.relation - - - - - - - - - - - - - - A constraint that triggers when validity of an object, assignment, or basically any time-sensitive - item (e.g. password) is about to end. - - - - 3.6 - true - - - - - - - - - The item whose validity is to be checked. If not specified, activation/validTo is assumed. - - - TimeValidityPolicyConstraintType.item - - - - - - - If set to true, validity of assignments of the relevant object are to be checked, not - items of the object itself. So, for example, if you want to send notifications before validity of - assignments to roles A, B, C expire, you'd need to attach a policy rule with this constraint - having assignment=true to roles A, B, and C. - - - TimeValidityPolicyConstraintType.assignment - - - - - - - When will this policy constraint activate (trigger)? If not specified, activation will occur - on the moment of validity change. Specify negative durations if you need to activate the trigger - before that moment; and positive ones if the trigger should be activated after that. - - - TimeValidityPolicyConstraintType.activateOn - - - - - - - When will be this policy constraint deactivated? If not specified, it will be active forever. - Specify negative durations if you need to activate the trigger before the moment of validity - change; and positive ones is the trigger should be deactivated after that. - - If not specified, the trigger will be active forever (TODO ok? or only until validTo occurs?) - - - TimeValidityPolicyConstraintType.deactivateOn - - - - - - - - - - - - - A constraint that triggers when a object or assignment is in a given state. - If both filter and expressions are specified, both must be true in order for constraint to be triggered. - - - - 3.7 - true - - - - - - - - - Filter that is to be used to determine the state. Mutually exclusive with expression, messageExpression, and executeScript. - - - StatePolicyConstraintType.filter - - - - - - - Expression that is to be used to determine the state. Mutually exclusive with messageExpression, filter, and executeScript. - - - StatePolicyConstraintType.expression - - - - - - - Expression that is to be used to determine the state: if state matches it should return LocalizableMessageType to be used - (both as normal and short messages). Mutually exclusive with message, filter, and executeScript. - - HIGHLY EXPERIMENTAL. Probably will change in the future. - - - StatePolicyConstraintType.messageExpression - - - - - - - Scripting expression (bulk action) to be used to determine the state. Mutually exclusive with filter, expression and messageExpression. - - - StatePolicyConstraintType.executeScript - - - - - - - - - - - - - - Constraint that triggers the rule when the object is in a given policy situation(s). - - - - 3.6 - true - - - - - - - - - Specifies the policy situation URI(s) to look for. - - - PolicySituationPolicyConstraintType.situation - - - - - - - - - - - - - -

- Constraints for collection stats (statistics). Can be used to trigger policy - rules on the number of objects in collection, percentage and so on. -

-

- Policy rules with this constraint are ignored during normal processing. - Evaluation of such policy rules require special handling (e.g. scanner task). -

-
- - - 4.0 - true - -
- - - - - - - Refers to "itself" as a collection. This means that it interprets the object - to which the rule applies as a collection. And this element specifies the - details how the collection should be interpreted. E.g. whether we mean - the list of objects that ObjectCollectionType refers to by filter. Or - whether we mean assignees (owners/approvers) of that object. - - - CollectionStatsPolicyConstraintType.collection - - - - - - -
- - - - - - Specifies how the inner constraints are to be evaluated with regards to operation start and end state. - May not include any transitional constraints itself! - - - - 3.7 - true - - - - - - - - - Expected inner constraint state before the operation. - True means the constraint must evaluate to true. False means the constraint must evaluate to false. - Unspecified means the constraint is not checked before the operation. - - - TransitionPolicyConstraintType.stateBefore - - - - - - - Expected inner constraint state after the operation. - True means the constraint must evaluate to true. False means the constraint must evaluate to false. - Unspecified means the constraint is not checked after the operation. - - - TransitionPolicyConstraintType.stateAfter - - - - - - - Non-transitional policy constraints to be evaluated. - - - TransitionPolicyConstraintType.constraints - - - - - - - - - - - - - Specifies limits when is the action executed. - - - - 4.0 - true - - - - - - - Lower bound of the threshold. - Lowest value for which the policy rule is activated. The policy rule - will be triggered for all values starting from this value up until the - high water mark (closed interval). If no low water mark is specified then - the policy rule will be activated for all values up to the high water mark. - Policy rule with a threshold that does not have any water marks will - never be activated. - - - PolicyThresholdType.lowWaterMark - - - - - - - Upper bound of the threshold. - Highest value for which the policy rule is activated. The policy rule - will be triggered for all values starting from low water mark up until - this value (closed interval). If no high water mark is specified then - the policy rule will be activated for all values that are greater than - or equal to high water mark. - - - PolicyThresholdType.highWaterMark - - - - - - - TODO: time interval! - - - PolicyThresholdType.timeInterval - - - - - - - - - - - - Water mark (bound). Lowest or highest value defining an interval. - - - - 4.0 - true - - - - - - - Absolute count of objects that triggers the watermark. - - - WaterMarkType.count - - - - - - - Relative proportion (percentage) of objects that triggers - the watermark. Value in percents (0-100). The value is relative - to the "domain". This only works if a domain is specified. - E.g. it will work for object collections with a domain or - for processes where we know total count of all processed - objects. Percentage watermarks cannot be used if domain - is not specified. - - - WaterMarkType.percentage - - - - - - - - - - Water mark (bound). Lowest or highest value defining an interval. - - - - 4.0 - true - - - - - - - Time interval. Interpretation of this time interval depends on the context - in which it is used. E.g. for synchronization thresholds the interval of 1 hour - usually means "number of changes in one hour interval". For dashboards it may - mean "number of errors in last hour" and so on. - If there is a danger that the interval might be misinterpreted then the - precise interpretation may be specified by using additional items in this - data structure. - - - TimeIntervalType.interval - - - - - - - - - - - Actions that are executed as reactions to triggered policy rules. - - - - true - - - - - - - Enforcement action. This action stops the operation and results with the error. - - - PolicyActionsType.enforcement - PolicyActionsType.enforcement.help - - - - - - - Approval action(s). The operation will be suspended and the approval workflow will be started. - If the workflow ends with success the operation will proceed. If the workflow ends with - an error the operation will also end with an error. - - More approval actions can be specified. They will be eventually merged into one approval process. - - - PolicyActionsType.approval - PolicyActionsType.approval.help - - - - - - - Remediation action. The operation will proceed. A workflow will be started to remediate the - result of the operation after the operation is done. - This action is ideal for operations that cannot be stopped, e.g. when manager - of an organizational unit is deleted because he quits the job. - This action starts a workflow. Therefore it is ideal for actions that are not frequent but - that require immediate attention. E.g. missing manager for organizational unit. - - - true - PolicyActionsType.remediation - PolicyActionsType.remediation.help - - - - - - - Prune action. The operation will proceed. Any other assignments that are in conflict with - this assignment that triggered the rule will be "pruned": they will be removed. The removal - of the conflicting assignments is automatic and silent. It will not be subject to approvals - or other policy constraints. - This mechanism can be used for example to implement set of roles where only one of the roles - can be assigned at a time. - - - 3.6 - PolicyActionsType.prune - PolicyActionsType.prune.help - - - - - - - Certification action. The operation will proceed. The object will be scheduled for - a certification campaign after the operation is done. - This action works with certification campaign. Therefore it is ideal for actions that are frequent - and can occur on large number of objects. The campaign is an efficient method how to handle mass - decisions. However it mat not be started immediately. - - - PolicyActionsType.certification - PolicyActionsType.certification.help - - - - - - - Notification action. The operation will proceed. Notifications are sent at the end of the - operation. More notification actions can be specified. - - - PolicyActionsType.notification - PolicyActionsType.notification.help - - - - - - - Record action. The operation will proceed. Policy situation will be recorded for - given object or assignment. It can be reported on or certified later on. - - - PolicyActionsType.record - PolicyActionsType.record.help - - - - - - - Execute script (bulk action). The operation will proceed. Script(s) are executed at the end of the operation. - HIGHLY EXPERIMENTAL. Use at your own risk. No guarantees here. - - - true - PolicyActionsType.scriptExecution - PolicyActionsType.scriptExecution.help - - - - - - - Stop action. This action stops the operation after n executions and results with the error. - - - PolicyActionsType.stop - PolicyActionsType.stop.help - - - - - - - - - - - Common supertype for policy actions. - - - - 3.5 - true - - - - - - -

- Name for the action. It is used for logging and other diagnostic purposes. -

-
- - PolicyActionType.name - PolicyActionType.name.help - -
-
- - - -

- Free-form description (e.g. comments about the action purpose) -

-
- - PolicyActionType.description - PolicyActionType.description.help - -
-
- - - - -

- When should this action be applied? -

-
- - 3.7 - PolicyActionType.condition - PolicyActionType.condition.help - -
-
-
- -
- - - - - Enforcement action. This action stops the operation and results with the error. - - - - 3.5 - true - - - - - - - - - - - - - - - Stop action. This action stops the operation and results with the error. - - - - 4.0 - true - - - - - - - - - - - - - - - Approval action. The operation will be suspended and the approval workflow will be started. - If the workflow ends with success the operation will proceed. If the workflow ends with - an error the operation will also end with an error. - - - - 3.5 - true - ApprovalPolicyActionType.details - - tns:automaticallyApproved - 4.0 - removed - - - - - - - - - - TODO - EXPERIMENTAL - - - 3.7 - true - ApprovalPolicyActionType.processSpecification - - - - - - - How should be this approval policy action composed with other ones that - might be related to the same item (object or assignment)? - - - ApprovalPolicyActionType.compositionStrategy - ApprovalPolicyActionType.compositionStrategy.help - - - - - - - Display name for this approval definition. If not specified, the name is derived from triggered constraints (short messages). - - - ApprovalPolicyActionType.approvalDisplayName - ApprovalPolicyActionType.approvalDisplayName.help - - - - - - - - -

- What relation(s) to use when determining approvers? E.g. "approver", "owner", - "securityApprover", and so on. -

-
- - ApprovalPolicyActionType.approverRelation - ApprovalPolicyActionType.approverRelation.help - -
-
- - - -

- Direct enumeration of the approvers to be used. - May be used with approverRelation and approverExpression element(s). -

-
- - ApprovalPolicyActionType.approverRef - ApprovalPolicyActionType.approverRef.help - -
-
- - - -

- Expression(s) that yield approvers to be used. If specified, the expression(s) are evaluated and - the result is used as a set of approvers (UserType, OrgType, RoleType, or any combination of them). - May be used with approverRelation and approverRef element(s). -

-
- - ApprovalPolicyActionType.approverExpression - ApprovalPolicyActionType.approverExpression.help - -
-
-
- - - -

- More complex (multi-stages) approval schema. -

-
- - ApprovalPolicyActionType.approvalSchema - ApprovalPolicyActionType.approvalSchema.help - -
-
- - - -

- Name of custom approval process. -

-

- THIS PROPERTY (approvalProcess) IS NOT SUPPORTED YET. -

-
- - true - -
-
-
-
-
-
-
- - - - - Prescribes how individual approval requirements should be composed into overall approval schema. - - - - 3.5 - true - - - - - - - ApprovalCompositionStrategyType.order - - - - - - - If set to true, this fragment might be the only one with this particular order number. - (Other fragments could be merged into it, but in "mergeIntoOrder" mode. TODO TODO TODO) - - - 3.6 - true - ApprovalCompositionStrategyType.exclusive - - - - - - - If set to true, this schema fragment is eligible to merging with other fragments of the same order. - (Non-mergeable fragments with the same order are ordered randomly. Mergeable fragments must have - order filled-in.) - - Mergeable fragments must have "order" attribute set. - - Restrictions on merging schema fragments: - 1) Mergeable fragments must contain exactly one stage. It is merged with the stage(s) of the other - fragment(s) without any further considerations (e.g. regarding stage order or name or whatever). - 2) It is expected that single policy rule triggered all the fragments that are being merged. So - we consider any of these rules. - - - 3.6 - true - ApprovalCompositionStrategyType.mergeable - - - - - - - Which fragments should this one be merged with. This is mutually exclusive with the "order" attribute. - If order is set, it is not possible to specify mergeIntoOrder. Both fragments with the same order are - considered "equal". This can be used e.g. to provide a different set of approvers (for the given stage). - If mergeIntoOrder is set (usually with multiple values), this fragment is considered as an augmentation - of other ones. This can be used e.g. to provide an escalation or notification strategy, custom form information, etc. - - - 3.6 - true - ApprovalCompositionStrategyType.mergeIntoOrder - - - - - - - Indicates that this fragment is to be merged into all the other "base" fragments. - This is mutually exclusive with the "order" and "mergeIntoOrder" attributes. - - - 3.6 - true - ApprovalCompositionStrategyType.mergeIntoAll - - - - - - - Provides an information about order in which this fragment is merged (lower numbers are processed before higher ones). - - - 3.6 - true - ApprovalCompositionStrategyType.mergePriority - - - - - - - If stage B is being merged into existing stage A (i.e. mergeOrder B is greater mergeOrder A), - all non-null values of B will be added to values of A; overwriting them in case of single-valued items. - For items marked as "mergeOverwriting", the actual values of B (even those that are null or empty) - overwrite those in A. - - - 3.6 - true - ApprovalCompositionStrategyType.mergeOverwriting - - - - - - - - - - Specification of a process that is to be started. - EXPERIMENTAL - - - - 3.7 - true - - - - - - - - Reference to existing process specification. - - - WfProcessSpecificationType.ref - - - - - - - - Name for this process specification. - - - WfProcessSpecificationType.name - - - - - - - In what order this process specification is to be considered. - - - WfProcessSpecificationType.order - - - - - - - TODO - - - WfProcessSpecificationType.deltaFrom - - - - - - - What approval actions to include during approval schema creation for this process. - If they are not triggered an exception is thrown. - - - WfProcessSpecificationType.includeAction - - - - - - - What approval actions to include during approval schema creation for this process. - If they are not triggered they are silently skipped. - - - WfProcessSpecificationType.includeActionIfPresent - - - - - - - Name for approval process. If not specified, name from approval action(s) is taken. - If not specified even there, the name is derived from triggered constraints (short messages). - - - WfProcessSpecificationType.approvalDisplayName - - - - - - - - - - - - TODO - EXPERIMENTAL - - - - 3.7 - true - DeltaSourceSpecificationType.details - - - - - - - - Include all modifications of these items as a delta that is to be approved. - - - DeltaSourceSpecificationType.item - - - - - - - Create a delta for each value of this item that is to be added, deleted or modified. - - - DeltaSourceSpecificationType.itemValue - - - - - - - - - - - TODO - - - - 3.5 - true - - - - - - - - - - - - - - - TODO - - - - 3.5 - true - - - - - - - - - - - - - - - TODO - - - - 3.5 - true - - - - - - - - -

- Certification definition(s) to be started as part of the action execution. -

-
- - tns:AccessCertificationDefinitionType - 3.6 - CertificationPolicyActionType.definitionRef - CertificationPolicyActionType.definitionRef.help - -
-
-
-
-
-
- - - - - TODO - - - - 3.5 - true - NotificationPolicyActionType.details - - - - - - - - - - - - - - - TODO - HIGHLY EXPERIMENTAL. Use at your own risk. No guarantees here. - - - - 3.9 - true - ExecuteScriptPolicyActionType.details - - - - - - - - - - - - - - - TODO - - - - 3.7 - true - - - - - - - - - How much information about triggered policy rules should be stored? - EXPERIMENTAL - - - true - RecordPolicyActionType.policyRules - - - - - - - - - - - - - - - - - - - - - Recorded exception from a policy rule. The exceptions that are approved are - recoded here to avoid re-evaluating and re-approving them all the time. - This is EXPERIMENTAL functionality. It is likely to change in the near future. - - - - 3.5 - true - - - - - - -

- Name for the rule for which this is an exception. -

-
-
-
- - - -

- Policy situation for which this is an exception. -

-
-
-
- - -
-
- - - - - TODO - EXPERIMENTAL - - - - 3.7 - true - - - - - - -

- TODO -

-
-
-
-
-
- - - - - Specification of lifecycle states and state transitions. - EXPERIMENTAL - - - - 3.7.1 - true - - - - - - -

- Specification of lifecycle state. -

-
- - LifecycleStateModelType.state - -
-
-
-
- - - - - Specification of lifecycle state. - EXPERIMENTAL - - - - 3.7.1 - true - LifecycleStateType.details - - - - - - -

- Identifier of the state. This is the value that is used in the - lifecycleState property. -

-
- - LifecycleStateType.name - -
-
- - - -

- Free-form description of the state (e.g. purpose, mechanisms, etc.) - Used for documentation purposes (comment). -

-
- - LifecycleStateType.description - -
-
- - - - -

- User-friendly name of the state, e.g. for displaying in the user interface. -

-
- - LifecycleStateType.displayName - -
-
- - - -

- Activation status forced by this lifecycle state. If lifecycle state - implies activation status, then such status will be forced to all objects - that are in that lifecycle state. Such objects will have the effective status - set to the value of forcedActivationStatus regardless of other activation setting. - I.e. administrativeStatus and validity are not considered in this case. -

-

- If no forced status is specified for a state that is explicitly defined, - the "undefined" is implied. In that case the usual activation computation - takes place (e.g. administrativeStatus and validity). -

-

- However, there is a couple of hardcoded lifecycle states. If these states - are not explicitly defined in a lifecycle model, the hardcoded defaults are applied - for activation in these hardcoded states ("undefined" for active and deprecated - states, "archived" for archived state, "disabled" for all other states). To turn off this default behaviour - those hardcoded lifecycle states need to be explicitly defined in the state model - and the forcedActivationStatus property should be left undefined. -

-
- - 3.8 - LifecycleStateType.forcedActivationStatus - -
-
- - - -

- There are cases when you need to force midpoint thinks that user has assigned some - role. The assignment actually doesn't exist but there is a need to pretend as it does. - This can be used e.g. for post-authentication flow. The user has assigned all business, - application, etc. roles but we don't want to consider these roles during his - post-authentication process. Instead, we want to pretend he has "temporary" role assigned - which allows him to perform post-authentication. -

-
- - LifecycleStateType.forcedAssignment - -
-
- - - -

- Setting that specifies whether object assignments should be considered - active in this lifecycle state. If set to true, then assignments are considered - active. That means that the assignments will be computed as usual. - If set to false then all object assignments are considered inactive. Which - means that they will be ignored (as if they do not exist at all). -

-

- If this setting is not specified then the result of forced activation status - determines behavior of the assignments. If forced activation is "disabled" or - "archived" then the assignments are considered to be inactive. If forced activation - status is "enabled" or if it is not defined at all then the assignments are considered - active. -

-

- However, there is a couple of hardcoded lifecycle states. If these states - are not explicitly defined in a lifecycle model, the hardcoded defaults are applied - for activation in these hardcoded states ("undefined" for active and deprecated - states, "disabled" for all other states). To turn off this default behaviour - those hardcoded lifecycle states need to be explicitly defined in the state model - and the forcedActivationStatus property shoule be left undefined. -

- -
- - 3.8 - LifecycleStateType.activeAssignments - -
-
- - - - - -

- State entry action. Action that is executed when the state is entered. -

-
- - LifecycleStateType.entryAction - -
-
- - - -

- State exit action. Action that is executed when the state is exited. -

-
- - LifecycleStateType.exitAction - -
-
- - - -

- Transition to another state. -

-
- - LifecycleStateType.transition - -
-
-
-
- - - - -

- There are cases when you need to force midpoint thinks that user has assigned some - role. The assignment actually doesn't exist but there is a need to pretend as it does. - This can be used e.g. for post-authentication flow. The user has assigned all business, - application, etc. roles but we don't want to consider these roles during his - post-authentication process. Instead, we want to pretend he has "temporary" role assigned - which allows him to perform post-authentication. -

-
- - - 3.8.1 - true - -
- - - - -

- The target type of the assignment, e.g RoleType, ServiceType, OrgType, ... -

-
- - VirtualAssignmentSpecificationType.targetType - -
-
- - - -

- No filter, no virtual assignemnts. -

-
- - VirtualAssignmentSpecificationType.filter - -
-
-
-
- - - - - Specification of lifecycle state transition. - EXPERIMENTAL - - - - 3.7.1 - true - - - - - - -

- Short name for the transition. It may be used in log files, user interface, etc. -

-
- - LifecycleStateTransitionType.name - -
-
- - - -

- Free-form description of transition purpose, mechanisms, etc. - Used for documentation purposes (comment). -

-
- - LifecycleStateTransitionType.description - -
-
- - - - -

- Identifier of the state that is the target of the transition. -

-
- - LifecycleStateTransitionType.targetState - -
-
- - - -

- Condition for automatic state transition. If the condition returns true value - then lifecycle transitions to the target state. -

-
- - LifecycleStateTransitionType.condition - -
-
-
-
- - - - - TODO - EXPERIMENTAL - - - - 3.7.1 - true - LifecycleStateActionType.details - - - - - - -

- Short name of the action. It may be used in log files, user interface, etc. -

-
- - LifecycleStateActionType.name - -
-
- - - -

- Free-form description of the action purpose, mechanisms, etc. - Used for documentation purposes (comment). -

-
- - LifecycleStateActionType.description - -
-
- - - - -

- Action that reduces (purges) object data. -

-
- - LifecycleStateActionType.dataReduction - -
-
- -
-
- - - - - Action that reduces (purges) object data. - EXPERIMENTAL - - - - 3.7.1 - true - - - - - - -

- Remove all values of the item. -

-
- - LifecycleStateActionDataReductionType.purgeItem - -
-
- - -
-
- - -
+ + + + + + + + + TODO + + + + + + + + + + + + + + + Set of governance, risk management, compliance (GRC) and similar policy constraints + that influence the identity model. These constraints act as triggers for the rules. + + + + + + + + + + + + Particular state of the object, described by a filter or an expression. + + + 3.7 + PolicyConstraintsType.objectState + PolicyConstraintsType.objectState.help + + + + + + + Particular state of an assignment, described by a filter or an expression. + + + 3.7 + PolicyConstraintsType.assignmentState + PolicyConstraintsType.assignmentState.help + + + + + + + The focus has assignment(s) of given object(s). + + + 3.7 + PolicyConstraintsType.hasAssignment + PolicyConstraintsType.hasAssignment.help + + + + + + + The focus has no assignment(s) of given object(s). + + + 3.7 + PolicyConstraintsType.hasNoAssignment + PolicyConstraintsType.hasNoAssignment.help + + + + + + + Exclusion constraint. Constraint that forbids this object to be assigned together with other object. + + + PolicyConstraintsType.exclusion + PolicyConstraintsType.exclusion.help + + + + + + + Actual number of objects that have this role assigned is lower than prescribed value. + (The constraint should be perhaps named minAssigneesViolation.) + + + PolicyConstraintsType.minAssignees + PolicyConstraintsType.minAssignees.help + + + + + + + Actual number of objects that have this role assigned is higher than prescribed value. + (The constraint should be perhaps named maxAssigneesViolation.) + + + PolicyConstraintsType.maxAssignees + PolicyConstraintsType.maxAssignees.help + + + + + + + Actual number of objects that have this object (e.g. role) assigned is lower than prescribed value. + This is the same as minAssignees, but indicates that the constraint should be evaluated against an object + when evaluating object constraints. HIGHLY EXPERIMENTAL + + + true + PolicyConstraintsType.objectMinAssigneesViolation + + + + + + + Actual number of objects that have this object (e.g. role) assigned is higher than prescribed value. + This is the same as minAssignees, but indicates that the constraint should be evaluated against an object + when evaluating object constraints. HIGHLY EXPERIMENTAL + + + true + PolicyConstraintsType.objectMaxAssigneesViolation + + + + + + + Constraint that triggers when the object is modified. + Modification of the object (add, modify, delete). + (This constraint should be perhaps named objectModification.) + + + PolicyConstraintsType.modification + PolicyConstraintsType.modification.help + + + + + + + Constraint that triggers when the object is assigned, unassigned or the assignment is modified. + Modification of an assignment, i.e. where this object is a target of assignment that is being modified. + (This constraint should be perhaps named assignmentModification.) + + + PolicyConstraintsType.assignment + PolicyConstraintsType.assignment.help + + + + + + + A constraint that triggers when validity of an object or any of its time-sensitive + items (e.g. password) is about to end. + + + 3.6 + + PolicyConstraintsType.objectTimeValidity + PolicyConstraintsType.objectTimeValidity.help + + + + + + + + A constraint that triggers when validity of an assignment or any of its time-sensitive + items (e.g. password) is about to end. + + + 3.6 + + PolicyConstraintsType.assignmentTimeValidity + PolicyConstraintsType.assignmentTimeValidity.help + + + + + + + + Occurrence of the policy situation within an object or an assignment. + + + + PolicyConstraintsType.situation + PolicyConstraintsType.situation.help + + + + + + + +

+ Constraints for collection stats (statistics). Can be used to trigger policy + rules on the number of objects in collection, percentage and so on. +

+

+ Policy rules with this constraint are ignored during normal processing. + Evaluation of such policy rules require special handling (e.g. scanner task). +

+
+ + + PolicyConstraintsType.collectionStats + PolicyConstraintsType.collectionStats.help + 4.0 + true + + +
+
+ + + + Constraints that must all apply. (But note that if there are more values of this item, + they are interpreted according to the enclosing operator.) + + + 3.7 + true + + + + + + + Constraints from which at least one must apply. (But note that if there are more values of this item, + they are interpreted according to the enclosing operator.) + + + 3.7 + true + + + + + + + The inner constraint must not apply. + + + 3.7 + true + + + + + + + Specifies how the inner constraints are to be evaluated with regards to operation start and end state. + May not include any transitional constraints itself! + + + 3.7 + + PolicyConstraintsType.transition + PolicyConstraintsType.transition.help + true + + + + + + + + References another policy constraint e.g. by its name. + The referenced constraint must be present as part of policy rules gathered for a given + assignment or object. Also, all declared global constraints are available for referencing, + regardless of which objects they are attached to or whether they are active or not. + + + 3.7 + + PolicyConstraintsType.ref + PolicyConstraintsType.ref.help + + + + +
+
+
+ +
+ + + + + References another policy constraint. + + + + 3.7 + + + + + + + Name of the constraint referenced. + + + PolicyConstraintReferenceType.name + + + + + + + + + + + + Basic data structure for all policy constraints. + + + + + tns:enforcement + 4.0 + removed + + + + + + + + Identifier of the constraint. It is used when midPoint + needs to refer to a specific constraint, e.g. in order + to record exception. + This element is formally specified as optional, mostly + due to the compatibility reasons. But it + has to be provided in order for some functionality to + work correctly. + + + 3.5 + + AbstractPolicyConstraintType.name + AbstractPolicyConstraintType.name.help + + + + + + + + AbstractPolicyConstraintType.description + AbstractPolicyConstraintType.description.help + + + + + + + + How should be triggering of this constraint presented, e.g. in enforcement messages, + in approvals, in certification, in notifications, etc. + + + 3.7 + AbstractPolicyConstraintType.presentation + AbstractPolicyConstraintType.presentation.help + + + + + + + + + + + + How should be triggering of this constraint presented, e.g. in enforcement messages, + in approvals, in certification, in notifications, etc. + + + + 3.7 + + + + + + + Message to be conveyed to the user. + + + PolicyConstraintPresentationType.message + PolicyConstraintPresentationType.message.help + + + + + + + Very short message describing the situation. Could be used for e.g. notification messages subject, approval process or work item names. + + + PolicyConstraintPresentationType.shortMessage + PolicyConstraintPresentationType.shortMessage.help + + + + + + + Long, documentation-like explanation of the rule. + + + PolicyConstraintPresentationType.longMessage + PolicyConstraintPresentationType.longMessage.help + + + + + + + + If set to true, no embedded triggers will be presented. Use for hiding constraints that are + to be considered too technical to be shown to user. EXPERIMENTAL + + + true + PolicyConstraintPresentationType.final + PolicyConstraintPresentationType.final.help + + + + + + + If set to true, this trigger will be excluded from presentation. EXPERIMENTAL + + + true + PolicyConstraintPresentationType.hidden + PolicyConstraintPresentationType.hidden.help + + + + + + + Ordinal number that determines ordering of displayed messages. EXPERIMENTAL. + + + true + PolicyConstraintPresentationType.displayOrder + PolicyConstraintPresentationType.displayOrder.help + + + + + + + + + + + + How much information about triggered policy rules should be stored? + EXPERIMENTAL + + + + true + + + + + + + Triggered policy rules will not be stored. + + + + + + + + + + Only message and short message for each trigger will be stored. Hidden and final presentation settings are respected. + + + + + + + + + + The triggers and other information for the rule will be stored (including subtriggers). + Hidden and final presentation settings are respected. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Container that defines exclusion of entities (e.g. roles). + It is a part of Segregation of Duties (SoD) mechanism. + + + + ExclusionPolicyConstraintType.details + + tns:policy + 4.0 + removed + + + + + + + + + +

+ Target of exclusion. The object defining this "exclusion" and + the object defined as target cannot be assigned at the same time. + Filter in the reference may be used to dynamically exclude broader + range of roles - assuming that runtime resolution is used. +

+
+ + tns:AbstractRoleType + ExclusionPolicyConstraintType.targetRef + ExclusionPolicyConstraintType.targetRef.help + +
+
+ + + + Specification of relation(s) when this exclusion constraint should be applied at the source side. + (I.e. this is evaluated against the object defining this exclusion constraint.) + The default is "order = 1". + + EXPERIMENTAL. Currently it does not work with non-member relations because of assignment + evaluation optimizations (see TestSegregationOfDuties.test950JackSelfExclusion). So it can + be used only for default and manager relations. + + + 3.7.1 + ExclusionPolicyConstraintType.orderConstraint + + + + + + + Specification of relation(s) when this exclusion constraint should be applied at the target side. + (I.e. this is evaluated against the target object.) + The default is "order = 1". + + EXPERIMENTAL. Currently it does not work with non-member relations because of assignment + evaluation optimizations (see TestSegregationOfDuties.test950JackSelfExclusion). So it can + be used only for default and manager relations. + + + 3.7.1 + ExclusionPolicyConstraintType.targetOrderConstraint + + + +
+
+
+
+ + + + + + Constraint on multiplicity of assigned objects. + (Other multiplicity requirements will be probably treated in separate constraints, like "constraint that + restricts value(s) of given item, based on a set of conditions that must be met".) + + + + + + + + + + + + Numeric value or string "unbounded". + + + MultiplicityPolicyConstraintType.multiplicity + + + + + + + Relation(s) to which this constraint applies. All of these relations must match + the defined multiplicity. If no relation is present, org:default (i.e. null) is assumed. + + + MultiplicityPolicyConstraintType.relation + + + + + + + + + + + + + + + Constraint that triggers the rule on object modification, addition or deletion. + + + + 3.5 + true + + + + + + + + + Specifies the operation(s) for which this constraints should be triggered. + If not specified then it will be triggered for all operations. + This defines the object operation (add/modify/delete of the entire object) + or assignment operation (add/modify/delete of the given assignment). + + + ModificationPolicyConstraintType.operation + + + + + + + Specification of items that must be modified (all of them) in order for this + constraint be triggered. If no items are specified then any modification will + trigger this constraint. + + + ModificationPolicyConstraintType.item + + + + + + + If true, item paths to be matched must match exactly. E.g. if "inducement" is specified + as an item to be matched, then only object modifications having "inducement" in the path + (i.e. whole inducement being added/deleted/replaced) would match. + + This is applicable only for modification operations. For add and delete operations the + value of this flag is ignored. + + + 3.7 + ModificationPolicyConstraintType.exactPathMatch + + + + + + + Expression that is used to determine the result. It is evaluated in addition to all the other + conditions, and must have a value of true in order for constraint to be triggered. + + + 3.7 + ModificationPolicyConstraintType.expression + + + + + + + + + + + + + + + + + + + + + + + + + Constraint that triggers the rule when the object is assigned. + + + + 3.5 + true + + + + + + + + + This constraint only applies to relations of the specified type. The value + of this element is compared to the relation of the targetRef relation + in the assignment. If not specified then this policy only + applies to the null (default) relation. If all relations need to be + affected by this policy then the special value of "any" should be specified + in this element. + + + AssignmentModificationPolicyConstraintType.relation + + + + + + + + + + + + + + + + + + + + + + + Constraint that triggers when the focus has given assignment(s). + + + + 3.7 + true + + + + + + + + + Assignment target to be matched. Might be dynamic (with a resolution time of "run"), just + like the one in exclusion constraint. If a relation here is specified, it takes precedence + over "relation" item in this constraint. + + + HasAssignmentPolicyConstraintType.targetRef + + + + + + + If true the assignment to given target must be direct. If false, it must be indirect (induced). + If not specified, it might be either direct or indirect. But in all cases it must be of the order + one, i.e. metaroles are not considered. + + + HasAssignmentPolicyConstraintType.direct + + + + + + + If true the assignment to given target must be effectively enabled. If false, it must be present + but effectively disabled (beware, the behavior of midPoint for disabled indirect assignments may + be fragile). If not specified, the effective status is not considered. + + + HasAssignmentPolicyConstraintType.enabled + + + + + + + This policy only applies to relations of the specified type. (Provided that the relation + is not set in the targetRef.) The value of this element is compared to the relation of the + targetRef relation in the assignment/inducement. If not specified then this policy only + applies to the null (default) relation. If all relations need to be affected by this policy + then the special value of "any" should be specified in this element or in the targetRef element. + + + HasAssignmentPolicyConstraintType.relation + + + + + + + + + + + + + + A constraint that triggers when validity of an object, assignment, or basically any time-sensitive + item (e.g. password) is about to end. + + + + 3.6 + true + + + + + + + + + The item whose validity is to be checked. If not specified, activation/validTo is assumed. + + + TimeValidityPolicyConstraintType.item + + + + + + + If set to true, validity of assignments of the relevant object are to be checked, not + items of the object itself. So, for example, if you want to send notifications before validity of + assignments to roles A, B, C expire, you'd need to attach a policy rule with this constraint + having assignment=true to roles A, B, and C. + + + TimeValidityPolicyConstraintType.assignment + + + + + + + When will this policy constraint activate (trigger)? If not specified, activation will occur + on the moment of validity change. Specify negative durations if you need to activate the trigger + before that moment; and positive ones if the trigger should be activated after that. + + + TimeValidityPolicyConstraintType.activateOn + + + + + + + When will be this policy constraint deactivated? If not specified, it will be active forever. + Specify negative durations if you need to activate the trigger before the moment of validity + change; and positive ones is the trigger should be deactivated after that. + + If not specified, the trigger will be active forever (TODO ok? or only until validTo occurs?) + + + TimeValidityPolicyConstraintType.deactivateOn + + + + + + + + + + + + + A constraint that triggers when a object or assignment is in a given state. + If both filter and expressions are specified, both must be true in order for constraint to be triggered. + + + + 3.7 + true + + + + + + + + + Filter that is to be used to determine the state. Mutually exclusive with expression, messageExpression, and executeScript. + + + StatePolicyConstraintType.filter + + + + + + + Expression that is to be used to determine the state. Mutually exclusive with messageExpression, filter, and executeScript. + + + StatePolicyConstraintType.expression + + + + + + + Expression that is to be used to determine the state: if state matches it should return LocalizableMessageType to be used + (both as normal and short messages). Mutually exclusive with message, filter, and executeScript. + + HIGHLY EXPERIMENTAL. Probably will change in the future. + + + StatePolicyConstraintType.messageExpression + + + + + + + Scripting expression (bulk action) to be used to determine the state. Mutually exclusive with filter, expression and messageExpression. + + + StatePolicyConstraintType.executeScript + + + + + + + + + + + + + + Constraint that triggers the rule when the object is in a given policy situation(s). + + + + 3.6 + true + + + + + + + + + Specifies the policy situation URI(s) to look for. + + + PolicySituationPolicyConstraintType.situation + + + + + + + + + + + + + +

+ Constraints for collection stats (statistics). Can be used to trigger policy + rules on the number of objects in collection, percentage and so on. +

+

+ Policy rules with this constraint are ignored during normal processing. + Evaluation of such policy rules require special handling (e.g. scanner task). +

+
+ + + 4.0 + true + +
+ + + + + + + Refers to "itself" as a collection. This means that it interprets the object + to which the rule applies as a collection. And this element specifies the + details how the collection should be interpreted. E.g. whether we mean + the list of objects that ObjectCollectionType refers to by filter. Or + whether we mean assignees (owners/approvers) of that object. + + + CollectionStatsPolicyConstraintType.collection + + + + + + +
+ + + + + + Specifies how the inner constraints are to be evaluated with regards to operation start and end state. + May not include any transitional constraints itself! + + + + 3.7 + true + + + + + + + + + Expected inner constraint state before the operation. + True means the constraint must evaluate to true. False means the constraint must evaluate to false. + Unspecified means the constraint is not checked before the operation. + + + TransitionPolicyConstraintType.stateBefore + + + + + + + Expected inner constraint state after the operation. + True means the constraint must evaluate to true. False means the constraint must evaluate to false. + Unspecified means the constraint is not checked after the operation. + + + TransitionPolicyConstraintType.stateAfter + + + + + + + Non-transitional policy constraints to be evaluated. + + + TransitionPolicyConstraintType.constraints + + + + + + + + + + + + + Specifies limits when is the action executed. + + + + 4.0 + true + + + + + + + Lower bound of the threshold. + Lowest value for which the policy rule is activated. The policy rule + will be triggered for all values starting from this value up until the + high water mark (closed interval). If no low water mark is specified then + the policy rule will be activated for all values up to the high water mark. + Policy rule with a threshold that does not have any water marks will + never be activated. + + + PolicyThresholdType.lowWaterMark + + + + + + + Upper bound of the threshold. + Highest value for which the policy rule is activated. The policy rule + will be triggered for all values starting from low water mark up until + this value (closed interval). If no high water mark is specified then + the policy rule will be activated for all values that are greater than + or equal to high water mark. + + + PolicyThresholdType.highWaterMark + + + + + + + TODO: time interval! + + + PolicyThresholdType.timeInterval + + + + + + + + + + + + Water mark (bound). Lowest or highest value defining an interval. + + + + 4.0 + true + + + + + + + Absolute count of objects that triggers the watermark. + + + WaterMarkType.count + + + + + + + Relative proportion (percentage) of objects that triggers + the watermark. Value in percents (0-100). The value is relative + to the "domain". This only works if a domain is specified. + E.g. it will work for object collections with a domain or + for processes where we know total count of all processed + objects. Percentage watermarks cannot be used if domain + is not specified. + + + WaterMarkType.percentage + + + + + + + + + + Water mark (bound). Lowest or highest value defining an interval. + + + + 4.0 + true + + + + + + + Time interval. Interpretation of this time interval depends on the context + in which it is used. E.g. for synchronization thresholds the interval of 1 hour + usually means "number of changes in one hour interval". For dashboards it may + mean "number of errors in last hour" and so on. + If there is a danger that the interval might be misinterpreted then the + precise interpretation may be specified by using additional items in this + data structure. + + + TimeIntervalType.interval + + + + + + + + + + + Actions that are executed as reactions to triggered policy rules. + + + + true + + + + + + + Enforcement action. This action stops the operation and results with the error. + + + PolicyActionsType.enforcement + PolicyActionsType.enforcement.help + + + + + + + Approval action(s). The operation will be suspended and the approval workflow will be started. + If the workflow ends with success the operation will proceed. If the workflow ends with + an error the operation will also end with an error. + + More approval actions can be specified. They will be eventually merged into one approval process. + + + PolicyActionsType.approval + PolicyActionsType.approval.help + + + + + + + Remediation action. The operation will proceed. A workflow will be started to remediate the + result of the operation after the operation is done. + This action is ideal for operations that cannot be stopped, e.g. when manager + of an organizational unit is deleted because he quits the job. + This action starts a workflow. Therefore it is ideal for actions that are not frequent but + that require immediate attention. E.g. missing manager for organizational unit. + + + true + PolicyActionsType.remediation + PolicyActionsType.remediation.help + + + + + + + Prune action. The operation will proceed. Any other assignments that are in conflict with + this assignment that triggered the rule will be "pruned": they will be removed. The removal + of the conflicting assignments is automatic and silent. It will not be subject to approvals + or other policy constraints. + This mechanism can be used for example to implement set of roles where only one of the roles + can be assigned at a time. + + + 3.6 + PolicyActionsType.prune + PolicyActionsType.prune.help + + + + + + + Certification action. The operation will proceed. The object will be scheduled for + a certification campaign after the operation is done. + This action works with certification campaign. Therefore it is ideal for actions that are frequent + and can occur on large number of objects. The campaign is an efficient method how to handle mass + decisions. However it mat not be started immediately. + + + PolicyActionsType.certification + PolicyActionsType.certification.help + + + + + + + Notification action. The operation will proceed. Notifications are sent at the end of the + operation. More notification actions can be specified. + + + PolicyActionsType.notification + PolicyActionsType.notification.help + + + + + + + Record action. The operation will proceed. Policy situation will be recorded for + given object or assignment. It can be reported on or certified later on. + + + PolicyActionsType.record + PolicyActionsType.record.help + + + + + + + Execute script (bulk action). The operation will proceed. Script(s) are executed at the end of the operation. + HIGHLY EXPERIMENTAL. Use at your own risk. No guarantees here. + + + true + PolicyActionsType.scriptExecution + PolicyActionsType.scriptExecution.help + + + + + + + Stop action. This action stops the operation after n executions and results with the error. + + + PolicyActionsType.stop + PolicyActionsType.stop.help + + + + + + + + + + + Common supertype for policy actions. + + + + 3.5 + true + + + + + + +

+ Name for the action. It is used for logging and other diagnostic purposes. +

+
+ + PolicyActionType.name + PolicyActionType.name.help + +
+
+ + + +

+ Free-form description (e.g. comments about the action purpose) +

+
+ + PolicyActionType.description + PolicyActionType.description.help + +
+
+ + + + +

+ When should this action be applied? +

+
+ + 3.7 + PolicyActionType.condition + PolicyActionType.condition.help + +
+
+
+ +
+ + + + + Enforcement action. This action stops the operation and results with the error. + + + + 3.5 + true + + + + + + + + + + + + + + + Stop action. This action stops the operation and results with the error. + + + + 4.0 + true + + + + + + + + + + + + + + + Approval action. The operation will be suspended and the approval workflow will be started. + If the workflow ends with success the operation will proceed. If the workflow ends with + an error the operation will also end with an error. + + + + 3.5 + true + ApprovalPolicyActionType.details + + tns:automaticallyApproved + 4.0 + removed + + + + + + + + + + TODO + EXPERIMENTAL + + + 3.7 + true + ApprovalPolicyActionType.processSpecification + + + + + + + How should be this approval policy action composed with other ones that + might be related to the same item (object or assignment)? + + + ApprovalPolicyActionType.compositionStrategy + ApprovalPolicyActionType.compositionStrategy.help + + + + + + + Display name for this approval definition. If not specified, the name is derived from triggered constraints (short messages). + + + ApprovalPolicyActionType.approvalDisplayName + ApprovalPolicyActionType.approvalDisplayName.help + + + + + + + + +

+ What relation(s) to use when determining approvers? E.g. "approver", "owner", + "securityApprover", and so on. +

+
+ + ApprovalPolicyActionType.approverRelation + ApprovalPolicyActionType.approverRelation.help + +
+
+ + + +

+ Direct enumeration of the approvers to be used. + May be used with approverRelation and approverExpression element(s). +

+
+ + ApprovalPolicyActionType.approverRef + ApprovalPolicyActionType.approverRef.help + +
+
+ + + +

+ Expression(s) that yield approvers to be used. If specified, the expression(s) are evaluated and + the result is used as a set of approvers (UserType, OrgType, RoleType, or any combination of them). + May be used with approverRelation and approverRef element(s). +

+
+ + ApprovalPolicyActionType.approverExpression + ApprovalPolicyActionType.approverExpression.help + +
+
+
+ + + +

+ More complex (multi-stages) approval schema. +

+
+ + ApprovalPolicyActionType.approvalSchema + ApprovalPolicyActionType.approvalSchema.help + +
+
+ + + +

+ Name of custom approval process. +

+

+ THIS PROPERTY (approvalProcess) IS NOT SUPPORTED YET. +

+
+ + true + +
+
+
+
+
+
+
+ + + + + Prescribes how individual approval requirements should be composed into overall approval schema. + + + + 3.5 + true + + + + + + + ApprovalCompositionStrategyType.order + + + + + + + If set to true, this fragment might be the only one with this particular order number. + (Other fragments could be merged into it, but in "mergeIntoOrder" mode. TODO TODO TODO) + + + 3.6 + true + ApprovalCompositionStrategyType.exclusive + + + + + + + If set to true, this schema fragment is eligible to merging with other fragments of the same order. + (Non-mergeable fragments with the same order are ordered randomly. Mergeable fragments must have + order filled-in.) + + Mergeable fragments must have "order" attribute set. + + Restrictions on merging schema fragments: + 1) Mergeable fragments must contain exactly one stage. It is merged with the stage(s) of the other + fragment(s) without any further considerations (e.g. regarding stage order or name or whatever). + 2) It is expected that single policy rule triggered all the fragments that are being merged. So + we consider any of these rules. + + + 3.6 + true + ApprovalCompositionStrategyType.mergeable + + + + + + + Which fragments should this one be merged with. This is mutually exclusive with the "order" attribute. + If order is set, it is not possible to specify mergeIntoOrder. Both fragments with the same order are + considered "equal". This can be used e.g. to provide a different set of approvers (for the given stage). + If mergeIntoOrder is set (usually with multiple values), this fragment is considered as an augmentation + of other ones. This can be used e.g. to provide an escalation or notification strategy, custom form information, etc. + + + 3.6 + true + ApprovalCompositionStrategyType.mergeIntoOrder + + + + + + + Indicates that this fragment is to be merged into all the other "base" fragments. + This is mutually exclusive with the "order" and "mergeIntoOrder" attributes. + + + 3.6 + true + ApprovalCompositionStrategyType.mergeIntoAll + + + + + + + Provides an information about order in which this fragment is merged (lower numbers are processed before higher ones). + + + 3.6 + true + ApprovalCompositionStrategyType.mergePriority + + + + + + + If stage B is being merged into existing stage A (i.e. mergeOrder B is greater mergeOrder A), + all non-null values of B will be added to values of A; overwriting them in case of single-valued items. + For items marked as "mergeOverwriting", the actual values of B (even those that are null or empty) + overwrite those in A. + + + 3.6 + true + ApprovalCompositionStrategyType.mergeOverwriting + + + + + + + + + + Specification of a process that is to be started. + EXPERIMENTAL + + + + 3.7 + true + + + + + + + + Reference to existing process specification. + + + WfProcessSpecificationType.ref + + + + + + + + Name for this process specification. + + + WfProcessSpecificationType.name + + + + + + + In what order this process specification is to be considered. + + + WfProcessSpecificationType.order + + + + + + + TODO + + + WfProcessSpecificationType.deltaFrom + + + + + + + What approval actions to include during approval schema creation for this process. + If they are not triggered an exception is thrown. + + + WfProcessSpecificationType.includeAction + + + + + + + What approval actions to include during approval schema creation for this process. + If they are not triggered they are silently skipped. + + + WfProcessSpecificationType.includeActionIfPresent + + + + + + + Name for approval process. If not specified, name from approval action(s) is taken. + If not specified even there, the name is derived from triggered constraints (short messages). + + + WfProcessSpecificationType.approvalDisplayName + + + + + + + + + + + + TODO + EXPERIMENTAL + + + + 3.7 + true + DeltaSourceSpecificationType.details + + + + + + + + Include all modifications of these items as a delta that is to be approved. + + + DeltaSourceSpecificationType.item + + + + + + + Create a delta for each value of this item that is to be added, deleted or modified. + + + DeltaSourceSpecificationType.itemValue + + + + + + + + + + + TODO + + + + 3.5 + true + + + + + + + + + + + + + + + TODO + + + + 3.5 + true + + + + + + + + + + + + + + + TODO + + + + 3.5 + true + + + + + + + + +

+ Certification definition(s) to be started as part of the action execution. +

+
+ + tns:AccessCertificationDefinitionType + 3.6 + CertificationPolicyActionType.definitionRef + CertificationPolicyActionType.definitionRef.help + +
+
+
+
+
+
+ + + + + TODO + + + + 3.5 + true + NotificationPolicyActionType.details + + + + + + + + + + + + + + + TODO + HIGHLY EXPERIMENTAL. Use at your own risk. No guarantees here. + + + + 3.9 + true + ExecuteScriptPolicyActionType.details + + + + + + + + + Object(s) on which the script should be executed. + (If not specified, current focus is assumed.) + + + + + + + + + + + + + TODO + + + + 3.7 + true + + + + + + + + + How much information about triggered policy rules should be stored? + EXPERIMENTAL + + + true + RecordPolicyActionType.policyRules + + + + + + + + + + + + + + + + + + + + + Recorded exception from a policy rule. The exceptions that are approved are + recoded here to avoid re-evaluating and re-approving them all the time. + This is EXPERIMENTAL functionality. It is likely to change in the near future. + + + + 3.5 + true + + + + + + +

+ Name for the rule for which this is an exception. +

+
+
+
+ + + +

+ Policy situation for which this is an exception. +

+
+
+
+ + +
+
+ + + + + TODO + EXPERIMENTAL + + + + 3.7 + true + + + + + + +

+ TODO +

+
+
+
+
+
+ + + + + Specification of lifecycle states and state transitions. + EXPERIMENTAL + + + + 3.7.1 + true + + + + + + +

+ Specification of lifecycle state. +

+
+ + LifecycleStateModelType.state + +
+
+
+
+ + + + + Specification of lifecycle state. + EXPERIMENTAL + + + + 3.7.1 + true + LifecycleStateType.details + + + + + + +

+ Identifier of the state. This is the value that is used in the + lifecycleState property. +

+
+ + LifecycleStateType.name + +
+
+ + + +

+ Free-form description of the state (e.g. purpose, mechanisms, etc.) + Used for documentation purposes (comment). +

+
+ + LifecycleStateType.description + +
+
+ + + + +

+ User-friendly name of the state, e.g. for displaying in the user interface. +

+
+ + LifecycleStateType.displayName + +
+
+ + + +

+ Activation status forced by this lifecycle state. If lifecycle state + implies activation status, then such status will be forced to all objects + that are in that lifecycle state. Such objects will have the effective status + set to the value of forcedActivationStatus regardless of other activation setting. + I.e. administrativeStatus and validity are not considered in this case. +

+

+ If no forced status is specified for a state that is explicitly defined, + the "undefined" is implied. In that case the usual activation computation + takes place (e.g. administrativeStatus and validity). +

+

+ However, there is a couple of hardcoded lifecycle states. If these states + are not explicitly defined in a lifecycle model, the hardcoded defaults are applied + for activation in these hardcoded states ("undefined" for active and deprecated + states, "archived" for archived state, "disabled" for all other states). To turn off this default behaviour + those hardcoded lifecycle states need to be explicitly defined in the state model + and the forcedActivationStatus property should be left undefined. +

+
+ + 3.8 + LifecycleStateType.forcedActivationStatus + +
+
+ + + +

+ There are cases when you need to force midpoint thinks that user has assigned some + role. The assignment actually doesn't exist but there is a need to pretend as it does. + This can be used e.g. for post-authentication flow. The user has assigned all business, + application, etc. roles but we don't want to consider these roles during his + post-authentication process. Instead, we want to pretend he has "temporary" role assigned + which allows him to perform post-authentication. +

+
+ + LifecycleStateType.forcedAssignment + +
+
+ + + +

+ Setting that specifies whether object assignments should be considered + active in this lifecycle state. If set to true, then assignments are considered + active. That means that the assignments will be computed as usual. + If set to false then all object assignments are considered inactive. Which + means that they will be ignored (as if they do not exist at all). +

+

+ If this setting is not specified then the result of forced activation status + determines behavior of the assignments. If forced activation is "disabled" or + "archived" then the assignments are considered to be inactive. If forced activation + status is "enabled" or if it is not defined at all then the assignments are considered + active. +

+

+ However, there is a couple of hardcoded lifecycle states. If these states + are not explicitly defined in a lifecycle model, the hardcoded defaults are applied + for activation in these hardcoded states ("undefined" for active and deprecated + states, "disabled" for all other states). To turn off this default behaviour + those hardcoded lifecycle states need to be explicitly defined in the state model + and the forcedActivationStatus property shoule be left undefined. +

+ +
+ + 3.8 + LifecycleStateType.activeAssignments + +
+
+ + + + + +

+ State entry action. Action that is executed when the state is entered. +

+
+ + LifecycleStateType.entryAction + +
+
+ + + +

+ State exit action. Action that is executed when the state is exited. +

+
+ + LifecycleStateType.exitAction + +
+
+ + + +

+ Transition to another state. +

+
+ + LifecycleStateType.transition + +
+
+
+
+ + + + +

+ There are cases when you need to force midpoint thinks that user has assigned some + role. The assignment actually doesn't exist but there is a need to pretend as it does. + This can be used e.g. for post-authentication flow. The user has assigned all business, + application, etc. roles but we don't want to consider these roles during his + post-authentication process. Instead, we want to pretend he has "temporary" role assigned + which allows him to perform post-authentication. +

+
+ + + 3.8.1 + true + +
+ + + + +

+ The target type of the assignment, e.g RoleType, ServiceType, OrgType, ... +

+
+ + VirtualAssignmentSpecificationType.targetType + +
+
+ + + +

+ No filter, no virtual assignemnts. +

+
+ + VirtualAssignmentSpecificationType.filter + +
+
+
+
+ + + + + Specification of lifecycle state transition. + EXPERIMENTAL + + + + 3.7.1 + true + + + + + + +

+ Short name for the transition. It may be used in log files, user interface, etc. +

+
+ + LifecycleStateTransitionType.name + +
+
+ + + +

+ Free-form description of transition purpose, mechanisms, etc. + Used for documentation purposes (comment). +

+
+ + LifecycleStateTransitionType.description + +
+
+ + + + +

+ Identifier of the state that is the target of the transition. +

+
+ + LifecycleStateTransitionType.targetState + +
+
+ + + +

+ Condition for automatic state transition. If the condition returns true value + then lifecycle transitions to the target state. +

+
+ + LifecycleStateTransitionType.condition + +
+
+
+
+ + + + + TODO + EXPERIMENTAL + + + + 3.7.1 + true + LifecycleStateActionType.details + + + + + + +

+ Short name of the action. It may be used in log files, user interface, etc. +

+
+ + LifecycleStateActionType.name + +
+
+ + + +

+ Free-form description of the action purpose, mechanisms, etc. + Used for documentation purposes (comment). +

+
+ + LifecycleStateActionType.description + +
+
+ + + + +

+ Action that reduces (purges) object data. +

+
+ + LifecycleStateActionType.dataReduction + +
+
+ +
+
+ + + + + Action that reduces (purges) object data. + EXPERIMENTAL + + + + 3.7.1 + true + + + + + + +

+ Remove all values of the item. +

+
+ + LifecycleStateActionDataReductionType.purgeItem + +
+
+ + +
+
+ + + + + Object(s) on which the script should be executed. + EXPERIMENTAL + + + + 4.2 + true + + + + + + +

+ Assigned object(s) matching given selector(s). +

+
+
+
+ + + +

+ Assigned object(s) that were matched by the policy constraint(s); and are also matching given selector(s). +

+
+
+
+ + + +

+ Assigned object(s) that are present on assignment path that leads to the particular policy rule; and are also matching given selector(s). +

+
+
+
+ + + +

+ Assignee object(s) matching given selector(s). +

+
+
+
+
+
+ +
diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java index 0f32e5eea89..646ab2d602e 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java @@ -6,9 +6,6 @@ */ package com.evolveum.midpoint.util; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; - import java.io.*; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; @@ -43,6 +40,8 @@ import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.exception.TunnelException; +import static java.util.Collections.*; + /** * @author semancik */ @@ -780,7 +779,11 @@ public static String readZipFile(File file, Charset charset) throws IOException } } - public static Set singletonOrEmptySet(String value) { + public static Set singletonOrEmptySet(T value) { return value != null ? singleton(value) : emptySet(); } + + public static List singletonOrEmptyList(T value) { + return value != null ? singletonList(value) : emptyList(); + } } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java index 8906fcb4c5d..5cbbae08296 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java @@ -47,6 +47,10 @@ public interface EvaluatedAssignment extends De QName getNormalizedRelation(RelationRegistry relationRegistry); + /** + * TODO Define this concept. It looks like it mixes ideas of validity (activation, lifecycle state) + * and relativity mode (condition). + */ boolean isValid(); boolean isPresentInCurrentObject(); diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignmentTarget.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignmentTarget.java index 5611cf28c65..05d7047096d 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignmentTarget.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignmentTarget.java @@ -19,6 +19,7 @@ */ public interface EvaluatedAssignmentTarget extends DebugDumpable { + @NotNull PrismObject getTarget(); boolean isDirectlyAssigned(); diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedExclusionTrigger.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedExclusionTrigger.java index 44b6f939a2a..0c65404f20d 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedExclusionTrigger.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedExclusionTrigger.java @@ -8,16 +8,21 @@ package com.evolveum.midpoint.model.api.context; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.LocalizableMessage; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.Objects; import static com.evolveum.midpoint.xml.ns._public.common.common_3.TriggeredPolicyRulesStorageStrategyType.FULL; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; + /** * @author mederly */ @@ -98,4 +103,10 @@ public EvaluatedExclusionTriggerType toEvaluatedPolicyRuleTriggerType(PolicyRule } return rv; } + + @Override + public Collection> getTargetObjects() { + // conflicting target should be non-empty ... but who knows for sure? + return conflictingTarget != null ? singleton(conflictingTarget.asPrismObject()) : emptySet(); + } } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedHasAssignmentTrigger.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedHasAssignmentTrigger.java index e6c327fcab7..202d143c48b 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedHasAssignmentTrigger.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedHasAssignmentTrigger.java @@ -8,18 +8,21 @@ package com.evolveum.midpoint.model.api.context; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.util.LocalizableMessage; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; -/** - * @author mederly - */ +import java.util.Collection; + public class EvaluatedHasAssignmentTrigger extends EvaluatedPolicyRuleTrigger { + @NotNull private final Collection> matchingTargets; + public EvaluatedHasAssignmentTrigger(@NotNull PolicyConstraintKindType kind, @NotNull HasAssignmentPolicyConstraintType constraint, - LocalizableMessage message, LocalizableMessage shortMessage) { + @NotNull Collection> matchingTargets, LocalizableMessage message, LocalizableMessage shortMessage) { super(kind, constraint, message, shortMessage, false); + this.matchingTargets = matchingTargets; } @Override @@ -29,4 +32,9 @@ public EvaluatedHasAssignmentTriggerType toEvaluatedPolicyRuleTriggerType(Policy fillCommonContent(rv); return rv; } + + @Override + public Collection> getTargetObjects() { + return matchingTargets; + } } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedModificationTrigger.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedModificationTrigger.java index d29f1e1bf97..529285a3925 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedModificationTrigger.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedModificationTrigger.java @@ -8,18 +8,25 @@ package com.evolveum.midpoint.model.api.context; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.util.LocalizableMessage; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; -/** - * @author mederly - */ public class EvaluatedModificationTrigger extends EvaluatedPolicyRuleTrigger { + @NotNull private final Collection> matchingTargets; + public EvaluatedModificationTrigger(@NotNull PolicyConstraintKindType kind, @NotNull ModificationPolicyConstraintType constraint, - LocalizableMessage message, LocalizableMessage shortMessage) { + @Nullable PrismObject targetObject, LocalizableMessage message, LocalizableMessage shortMessage) { super(kind, constraint, message, shortMessage, false); + matchingTargets = targetObject != null ? singleton(targetObject) : emptySet(); } @Override @@ -29,4 +36,9 @@ public EvaluatedModificationTriggerType toEvaluatedPolicyRuleTriggerType(PolicyR fillCommonContent(rv); return rv; } + + @Override + public Collection> getTargetObjects() { + return matchingTargets; + } } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedPolicyRuleTrigger.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedPolicyRuleTrigger.java index 1acc99892df..57804079066 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedPolicyRuleTrigger.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedPolicyRuleTrigger.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.model.api.context; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.util.LocalizationUtil; import com.evolveum.midpoint.schema.util.PolicyRuleTypeUtil; import com.evolveum.midpoint.util.DebugDumpable; @@ -103,6 +104,7 @@ public String debugDump(int indent) { DebugUtil.debugDumpLabelLn(sb, getClass().getSimpleName(), indent); debugDumpCommon(sb, indent + 1); debugDumpSpecific(sb, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "targetObjects", getTargetObjects(), indent + 1); return sb.toString(); } @@ -162,4 +164,12 @@ public Collection> getInnerTriggers() { public boolean isEnforcementOverride() { return enforcementOverride; } + + /** + * @return Target object(s) that were matched by constraint that produced this trigger. + * For example: target of the assignment that was added (and that matched "assignment" constraint). + */ + public Collection> getTargetObjects() { + return Collections.emptyList(); + } } 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 3829a9d4242..3681e615368 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 @@ -7,15 +7,17 @@ package com.evolveum.midpoint.model.api.context; -import com.evolveum.midpoint.util.DebugDumpable; -import com.evolveum.midpoint.util.ShortDumpable; - -import org.apache.commons.collections4.MultiSet; - -import javax.xml.namespace.QName; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; +import javax.xml.namespace.QName; + +import org.apache.commons.collections4.MultiSet; + +import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.ShortDumpable; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OrderConstraintsType; /** * @author semancik @@ -50,4 +52,6 @@ public interface EvaluationOrder extends DebugDumpable, ShortDumpable, Cloneable boolean isValid(); boolean isOrderOne(); + + boolean matches(Integer assignmentOrder, List assignmentOrderConstraint); } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/ArchetypeManager.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/ArchetypeManager.java index 5c67dd0058c..84b898ccb51 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/ArchetypeManager.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/ArchetypeManager.java @@ -1,257 +1,258 @@ -/* - * Copyright (c) 2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.common; - -import java.util.List; -import java.util.stream.Collectors; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.collections4.CollectionUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.FocusTypeUtil; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; - -/** - * Component that can efficiently determine archetypes for objects. - * It is backed by caches, therefore this is supposed to be a low-overhead service that can be - * used in many places. - * - * @author Radovan Semancik - */ -@Component -public class ArchetypeManager { - - private static final Trace LOGGER = TraceManager.getTrace(ArchetypeManager.class); - - @Autowired private SystemObjectCache systemObjectCache; - - public PrismObject getArchetype(String oid, OperationResult result) throws ObjectNotFoundException, SchemaException { - // TODO: make this efficient (use cache) - return systemObjectCache.getArchetype(oid, result); - } - - public ObjectReferenceType determineArchetypeRef(PrismObject assignmentHolder, OperationResult result) throws SchemaException, ConfigurationException { - if (assignmentHolder == null) { - return null; - } - if (!assignmentHolder.canRepresent(AssignmentHolderType.class)) { - return null; - } - - List archetypeAssignmentsRef = determineArchetypesFromAssignments(assignmentHolder.asObjectable()); - - if (CollectionUtils.isNotEmpty(archetypeAssignmentsRef)) { - if (archetypeAssignmentsRef.size() > 1) { - throw new SchemaException("Only a single archetype for an object is supported: "+assignmentHolder); - } - } - - List archetypeRefs = assignmentHolder.asObjectable().getArchetypeRef(); - if (CollectionUtils.isEmpty(archetypeRefs)) { - if (CollectionUtils.isEmpty(archetypeAssignmentsRef)) { - return null; - } - return archetypeAssignmentsRef.get(0); - } - if (archetypeRefs.size() > 1) { - throw new SchemaException("Only a single archetype for an object is supported: "+assignmentHolder); - } - - //check also assignments - - return archetypeRefs.get(0); - } - - private List determineArchetypesFromAssignments(O assignmentHolder) { - List assignments = assignmentHolder.getAssignment(); - return assignments.stream() - .filter(a -> { - ObjectReferenceType target = a.getTargetRef(); - if (target == null) { - return false; - } - return QNameUtil.match(ArchetypeType.COMPLEX_TYPE, target.getType()); - }) - .map(archetypeAssignment -> archetypeAssignment.getTargetRef()) - .collect(Collectors.toList()); - } - - public PrismObject determineArchetype(PrismObject assignmentHolder, OperationResult result) throws SchemaException, ConfigurationException { - return determineArchetype(assignmentHolder, null, result); - } - - public PrismObject determineArchetype(PrismObject assignmentHolder, String explicitArchetypeOid, OperationResult result) throws SchemaException, ConfigurationException { - String archetypeOid; - if (explicitArchetypeOid != null) { - archetypeOid = explicitArchetypeOid; - } else { - ObjectReferenceType archetypeRef = determineArchetypeRef(assignmentHolder, result); - if (archetypeRef == null) { - return null; - } - archetypeOid = archetypeRef.getOid(); - } - - PrismObject archetype; - try { - archetype = systemObjectCache.getArchetype(archetypeOid, result); - } catch (ObjectNotFoundException e) { - LOGGER.warn("Archetype {} for object {} cannot be found", archetypeOid, assignmentHolder); - return null; - } - return archetype; - } - - public ArchetypePolicyType determineArchetypePolicy(PrismObject object, OperationResult result) throws SchemaException, ConfigurationException { - return determineArchetypePolicy(object, null, result); - } - - public ArchetypePolicyType determineArchetypePolicy(PrismObject object, String explicitArchetypeOid, OperationResult result) throws SchemaException, ConfigurationException { - if (object == null) { - return null; - } - ArchetypePolicyType archetypePolicy = null; - if (object.canRepresent(AssignmentHolderType.class)) { - PrismObject archetype = determineArchetype((PrismObject) object, explicitArchetypeOid, result); - if (archetype != null) { - archetypePolicy = archetype.asObjectable().getArchetypePolicy(); - } - } - // No archetype for this object. Try to find appropriate system configuration section for this object. - ObjectPolicyConfigurationType objectPolicy = determineObjectPolicyConfiguration(object, result); - // TODO: cache the result of the merge - return merge(archetypePolicy, objectPolicy); - } - - private ArchetypePolicyType merge(ArchetypePolicyType archetypePolicy, ObjectPolicyConfigurationType objectPolicy) { - if (archetypePolicy == null && objectPolicy == null) { - return null; - } - if (archetypePolicy == null) { - return objectPolicy.clone(); - } - if (objectPolicy == null) { - return archetypePolicy.clone(); - } - ArchetypePolicyType resultPolicy = archetypePolicy.clone(); - if (archetypePolicy.getApplicablePolicies() == null && objectPolicy.getApplicablePolicies() != null) { - resultPolicy.setApplicablePolicies(objectPolicy.getApplicablePolicies().clone()); - } - if (archetypePolicy.getConflictResolution() == null && objectPolicy.getConflictResolution() != null) { - resultPolicy.setConflictResolution(objectPolicy.getConflictResolution().clone()); - } - if (archetypePolicy.getDisplay() == null && objectPolicy.getDisplay() != null) { - resultPolicy.setDisplay(objectPolicy.getDisplay().clone()); - } - if (archetypePolicy.getExpressionProfile() == null && objectPolicy.getExpressionProfile() != null) { - resultPolicy.setExpressionProfile(objectPolicy.getExpressionProfile()); - } - if (archetypePolicy.getLifecycleStateModel() == null && objectPolicy.getLifecycleStateModel() != null) { - resultPolicy.setLifecycleStateModel(objectPolicy.getLifecycleStateModel().clone()); - } - if (archetypePolicy.getObjectTemplateRef() == null && objectPolicy.getObjectTemplateRef() != null) { - resultPolicy.setObjectTemplateRef(objectPolicy.getObjectTemplateRef().clone()); - } - if (archetypePolicy.getItemConstraint().isEmpty()) { - for (ItemConstraintType objItemConstraint : objectPolicy.getItemConstraint()) { - resultPolicy.getItemConstraint().add(objItemConstraint.clone()); - } - } - // Deprecated - if (archetypePolicy.getPropertyConstraint().isEmpty()) { - for (ItemConstraintType objPropertyConstraint : objectPolicy.getPropertyConstraint()) { - resultPolicy.getPropertyConstraint().add(objPropertyConstraint.clone()); - } - } - return resultPolicy; - } - - public ObjectPolicyConfigurationType determineObjectPolicyConfiguration(PrismObject object, OperationResult result) throws SchemaException, ConfigurationException { - if (object == null) { - return null; - } - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); - if (systemConfiguration == null) { - return null; - } - return determineObjectPolicyConfiguration(object, systemConfiguration.asObjectable()); - } - - public ExpressionProfile determineExpressionProfile(PrismObject object, OperationResult result) throws SchemaException, ConfigurationException { - ArchetypePolicyType archetypePolicy = determineArchetypePolicy(object, result); - if (archetypePolicy == null) { - return null; - } - String expressionProfileId = archetypePolicy.getExpressionProfile(); - return systemObjectCache.getExpressionProfile(expressionProfileId, result); - } - - /** - * This has to remain static due to use from LensContext. Hopefully it will get refactored later. - */ - public static ObjectPolicyConfigurationType determineObjectPolicyConfiguration(PrismObject object, SystemConfigurationType systemConfigurationType) throws ConfigurationException { - List subTypes = FocusTypeUtil.determineSubTypes(object); - return determineObjectPolicyConfiguration(object.getCompileTimeClass(), subTypes, systemConfigurationType); - } - - public static ObjectPolicyConfigurationType determineObjectPolicyConfiguration(Class objectClass, List objectSubtypes, SystemConfigurationType systemConfigurationType) throws ConfigurationException { - ObjectPolicyConfigurationType applicablePolicyConfigurationType = null; - for (ObjectPolicyConfigurationType aPolicyConfigurationType: systemConfigurationType.getDefaultObjectPolicyConfiguration()) { - QName typeQName = aPolicyConfigurationType.getType(); - if (typeQName == null) { - continue; // TODO implement correctly (using 'applicable policies' perhaps) - } - ObjectTypes objectType = ObjectTypes.getObjectTypeFromTypeQName(typeQName); - if (objectType == null) { - throw new ConfigurationException("Unknown type "+typeQName+" in default object policy definition in system configuration"); - } - if (objectType.getClassDefinition() == objectClass) { - String aSubType = aPolicyConfigurationType.getSubtype(); - if (aSubType == null) { - if (applicablePolicyConfigurationType == null) { - applicablePolicyConfigurationType = aPolicyConfigurationType; - } - } else if (objectSubtypes != null && objectSubtypes.contains(aSubType)) { - applicablePolicyConfigurationType = aPolicyConfigurationType; - } - } - } - if (applicablePolicyConfigurationType != null) { - return applicablePolicyConfigurationType; - } - - return null; - } - - public static LifecycleStateModelType determineLifecycleModel(PrismObject object, PrismObject systemConfiguration) throws ConfigurationException { - if (systemConfiguration == null) { - return null; - } - return determineLifecycleModel(object, systemConfiguration.asObjectable()); - } - - public static LifecycleStateModelType determineLifecycleModel(PrismObject object, SystemConfigurationType systemConfigurationType) throws ConfigurationException { - ObjectPolicyConfigurationType objectPolicyConfiguration = determineObjectPolicyConfiguration(object, systemConfigurationType); - if (objectPolicyConfiguration == null) { - return null; - } - return objectPolicyConfiguration.getLifecycleStateModel(); - } -} +/* + * Copyright (c) 2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.common; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.FocusTypeUtil; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; + +/** + * Component that can efficiently determine archetypes for objects. + * It is backed by caches, therefore this is supposed to be a low-overhead service that can be + * used in many places. + * + * @author Radovan Semancik + */ +@Component +public class ArchetypeManager { + + private static final Trace LOGGER = TraceManager.getTrace(ArchetypeManager.class); + + @Autowired private SystemObjectCache systemObjectCache; + + public PrismObject getArchetype(String oid, OperationResult result) throws ObjectNotFoundException, SchemaException { + // TODO: make this efficient (use cache) + return systemObjectCache.getArchetype(oid, result); + } + + public ObjectReferenceType determineArchetypeRef(PrismObject assignmentHolder, OperationResult result) throws SchemaException, ConfigurationException { + if (assignmentHolder == null) { + return null; + } + if (!assignmentHolder.canRepresent(AssignmentHolderType.class)) { + return null; + } + + List archetypeAssignmentsRef = determineArchetypesFromAssignments(assignmentHolder.asObjectable()); + + if (CollectionUtils.isNotEmpty(archetypeAssignmentsRef)) { + if (archetypeAssignmentsRef.size() > 1) { + throw new SchemaException("Only a single archetype for an object is supported: "+assignmentHolder); + } + } + + List archetypeRefs = assignmentHolder.asObjectable().getArchetypeRef(); + if (CollectionUtils.isEmpty(archetypeRefs)) { + if (CollectionUtils.isEmpty(archetypeAssignmentsRef)) { + return null; + } + return archetypeAssignmentsRef.get(0); + } + if (archetypeRefs.size() > 1) { + throw new SchemaException("Only a single archetype for an object is supported: "+assignmentHolder); + } + + //check also assignments + + return archetypeRefs.get(0); + } + + private List determineArchetypesFromAssignments(O assignmentHolder) { + List assignments = assignmentHolder.getAssignment(); + return assignments.stream() + .filter(a -> { + ObjectReferenceType target = a.getTargetRef(); + if (target == null) { + return false; + } + return QNameUtil.match(ArchetypeType.COMPLEX_TYPE, target.getType()); + }) + .map(archetypeAssignment -> archetypeAssignment.getTargetRef()) + .collect(Collectors.toList()); + } + + public PrismObject determineArchetype(PrismObject assignmentHolder, OperationResult result) throws SchemaException, ConfigurationException { + return determineArchetype(assignmentHolder, null, result); + } + + public PrismObject determineArchetype(PrismObject assignmentHolder, String explicitArchetypeOid, OperationResult result) throws SchemaException, ConfigurationException { + String archetypeOid; + if (explicitArchetypeOid != null) { + archetypeOid = explicitArchetypeOid; + } else { + ObjectReferenceType archetypeRef = determineArchetypeRef(assignmentHolder, result); + if (archetypeRef == null) { + return null; + } + archetypeOid = archetypeRef.getOid(); + } + + PrismObject archetype; + try { + archetype = systemObjectCache.getArchetype(archetypeOid, result); + } catch (ObjectNotFoundException e) { + LOGGER.warn("Archetype {} for object {} cannot be found", archetypeOid, assignmentHolder); + return null; + } + return archetype; + } + + public ArchetypePolicyType determineArchetypePolicy(PrismObject object, OperationResult result) throws SchemaException, ConfigurationException { + return determineArchetypePolicy(object, null, result); + } + + public ArchetypePolicyType determineArchetypePolicy(PrismObject object, String explicitArchetypeOid, OperationResult result) throws SchemaException, ConfigurationException { + if (object == null) { + return null; + } + ArchetypePolicyType archetypePolicy = null; + if (object.canRepresent(AssignmentHolderType.class)) { + PrismObject archetype = determineArchetype((PrismObject) object, explicitArchetypeOid, result); + if (archetype != null) { + archetypePolicy = archetype.asObjectable().getArchetypePolicy(); + } + } + // No archetype for this object. Try to find appropriate system configuration section for this object. + ObjectPolicyConfigurationType objectPolicy = determineObjectPolicyConfiguration(object, result); + // TODO: cache the result of the merge + return merge(archetypePolicy, objectPolicy); + } + + private ArchetypePolicyType merge(ArchetypePolicyType archetypePolicy, ObjectPolicyConfigurationType objectPolicy) { + if (archetypePolicy == null && objectPolicy == null) { + return null; + } + if (archetypePolicy == null) { + return objectPolicy.clone(); + } + if (objectPolicy == null) { + return archetypePolicy.clone(); + } + ArchetypePolicyType resultPolicy = archetypePolicy.clone(); + if (archetypePolicy.getApplicablePolicies() == null && objectPolicy.getApplicablePolicies() != null) { + resultPolicy.setApplicablePolicies(objectPolicy.getApplicablePolicies().clone()); + } + if (archetypePolicy.getConflictResolution() == null && objectPolicy.getConflictResolution() != null) { + resultPolicy.setConflictResolution(objectPolicy.getConflictResolution().clone()); + } + if (archetypePolicy.getDisplay() == null && objectPolicy.getDisplay() != null) { + resultPolicy.setDisplay(objectPolicy.getDisplay().clone()); + } + if (archetypePolicy.getExpressionProfile() == null && objectPolicy.getExpressionProfile() != null) { + resultPolicy.setExpressionProfile(objectPolicy.getExpressionProfile()); + } + if (archetypePolicy.getLifecycleStateModel() == null && objectPolicy.getLifecycleStateModel() != null) { + resultPolicy.setLifecycleStateModel(objectPolicy.getLifecycleStateModel().clone()); + } + if (archetypePolicy.getObjectTemplateRef() == null && objectPolicy.getObjectTemplateRef() != null) { + resultPolicy.setObjectTemplateRef(objectPolicy.getObjectTemplateRef().clone()); + } + if (archetypePolicy.getItemConstraint().isEmpty()) { + for (ItemConstraintType objItemConstraint : objectPolicy.getItemConstraint()) { + resultPolicy.getItemConstraint().add(objItemConstraint.clone()); + } + } + // Deprecated + if (archetypePolicy.getPropertyConstraint().isEmpty()) { + for (ItemConstraintType objPropertyConstraint : objectPolicy.getPropertyConstraint()) { + resultPolicy.getPropertyConstraint().add(objPropertyConstraint.clone()); + } + } + return resultPolicy; + } + + public ObjectPolicyConfigurationType determineObjectPolicyConfiguration(PrismObject object, OperationResult result) throws SchemaException, ConfigurationException { + if (object == null) { + return null; + } + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); + if (systemConfiguration == null) { + return null; + } + return determineObjectPolicyConfiguration(object, systemConfiguration.asObjectable()); + } + + public ExpressionProfile determineExpressionProfile(PrismObject object, OperationResult result) throws SchemaException, ConfigurationException { + ArchetypePolicyType archetypePolicy = determineArchetypePolicy(object, result); + if (archetypePolicy == null) { + return null; + } + String expressionProfileId = archetypePolicy.getExpressionProfile(); + return systemObjectCache.getExpressionProfile(expressionProfileId, result); + } + + /** + * This has to remain static due to use from LensContext. Hopefully it will get refactored later. + */ + public static ObjectPolicyConfigurationType determineObjectPolicyConfiguration(PrismObject object, SystemConfigurationType systemConfigurationType) throws ConfigurationException { + List subTypes = FocusTypeUtil.determineSubTypes(object); + return determineObjectPolicyConfiguration(object.getCompileTimeClass(), subTypes, systemConfigurationType); + } + + public static ObjectPolicyConfigurationType determineObjectPolicyConfiguration(Class objectClass, List objectSubtypes, SystemConfigurationType systemConfigurationType) throws ConfigurationException { + ObjectPolicyConfigurationType applicablePolicyConfigurationType = null; + for (ObjectPolicyConfigurationType aPolicyConfigurationType: systemConfigurationType.getDefaultObjectPolicyConfiguration()) { + QName typeQName = aPolicyConfigurationType.getType(); + if (typeQName == null) { + continue; // TODO implement correctly (using 'applicable policies' perhaps) + } + ObjectTypes objectType = ObjectTypes.getObjectTypeFromTypeQName(typeQName); + if (objectType == null) { + throw new ConfigurationException("Unknown type "+typeQName+" in default object policy definition in system configuration"); + } + if (objectType.getClassDefinition() == objectClass) { + String aSubType = aPolicyConfigurationType.getSubtype(); + if (aSubType == null) { + if (applicablePolicyConfigurationType == null) { + applicablePolicyConfigurationType = aPolicyConfigurationType; + } + } else if (objectSubtypes != null && objectSubtypes.contains(aSubType)) { + applicablePolicyConfigurationType = aPolicyConfigurationType; + } + } + } + if (applicablePolicyConfigurationType != null) { + return applicablePolicyConfigurationType; + } + + return null; + } + + // TODO take object's archetype into account + public static LifecycleStateModelType determineLifecycleModel(PrismObject object, PrismObject systemConfiguration) throws ConfigurationException { + if (systemConfiguration == null) { + return null; + } + return determineLifecycleModel(object, systemConfiguration.asObjectable()); + } + + public static LifecycleStateModelType determineLifecycleModel(PrismObject object, SystemConfigurationType systemConfigurationType) throws ConfigurationException { + ObjectPolicyConfigurationType objectPolicyConfiguration = determineObjectPolicyConfiguration(object, systemConfigurationType); + if (objectPolicyConfiguration == null) { + return null; + } + return objectPolicyConfiguration.getLifecycleStateModel(); + } +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java index cca8213dfee..a9af081d373 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java @@ -46,6 +46,8 @@ import com.evolveum.prism.xml.ns._public.types_3.DeltaSetTripleType; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import org.jetbrains.annotations.Nullable; + /** * Mapping is non-recyclable single-use object. Once evaluated it should not be evaluated again. It will retain its original * inputs and outputs that can be read again and again. But these should not be changed after evaluation. @@ -1841,7 +1843,8 @@ public Builder addVariableDefinition(String name, ObjectDeltaObject val return addVariableDefinition(name, value, definition); } - public Builder addAliasRegistration(String alias, String mainVariable) { + // mainVariable of "null" means the default source + public Builder addAliasRegistration(String alias, @Nullable String mainVariable) { variables.registerAlias(alias, mainVariable); return this; } diff --git a/model/model-impl/pom.xml b/model/model-impl/pom.xml index 35c4af4d395..b8c4a3ad830 100644 --- a/model/model-impl/pom.xml +++ b/model/model-impl/pom.xml @@ -142,6 +142,10 @@ 4.2-SNAPSHOT + + com.google.guava + guava + commons-io commons-io diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/CollectionProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/CollectionProcessor.java index 2acf6bf6741..99e224efcf3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/CollectionProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/CollectionProcessor.java @@ -11,6 +11,7 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; @@ -27,10 +28,10 @@ import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; import com.evolveum.midpoint.model.common.ArchetypeManager; -import com.evolveum.midpoint.model.impl.lens.AssignmentPathImpl; -import com.evolveum.midpoint.model.impl.lens.AssignmentPathSegmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentPathImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentPathSegmentImpl; import com.evolveum.midpoint.model.impl.lens.EvaluatedPolicyRuleImpl; -import com.evolveum.midpoint.model.impl.lens.EvaluationOrderImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluationOrderImpl; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.query.ObjectFilter; @@ -113,10 +114,19 @@ public Collection evaluateCollectionPolicyRules(PrismObject @NotNull private EvaluatedPolicyRule evaluatePolicyRule(PrismObject collection, CompiledObjectCollectionView collectionView, @NotNull AssignmentType assignmentType, @NotNull PolicyRuleType policyRuleType, Class targetTypeClass, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException, ExpressionEvaluationException { AssignmentPathImpl assignmentPath = new AssignmentPathImpl(prismContext); - AssignmentPathSegmentImpl assignmentPathSegment = new AssignmentPathSegmentImpl( - collection.asObjectable(), "object collection "+collection, assignmentType, true, relationRegistry, prismContext); - assignmentPathSegment.setEvaluationOrder(EvaluationOrderImpl.zero(relationRegistry)); - assignmentPathSegment.setEvaluationOrderForTarget(EvaluationOrderImpl.zero(relationRegistry)); + AssignmentPathSegmentImpl assignmentPathSegment = new AssignmentPathSegmentImpl.Builder() + .source(collection.asObjectable()) + .sourceDescription("object collection "+collection) + .assignment(assignmentType) + .isAssignment(true) + .relationRegistry(relationRegistry) + .prismContext(prismContext) + .evaluationOrder(EvaluationOrderImpl.zero(relationRegistry)) + .evaluationOrderForTarget(EvaluationOrderImpl.zero(relationRegistry)) + .direct(true) // to be reconsidered - but assignment path is empty, so we consider this to be directly assigned + .pathToSourceValid(true) + .sourceRelativityMode(PlusMinusZero.ZERO) + .build(); assignmentPath.add(assignmentPathSegment); EvaluatedPolicyRuleImpl evaluatedPolicyRule = new EvaluatedPolicyRuleImpl(policyRuleType.clone(), assignmentPath, prismContext); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java index df9317431c7..f33a181539f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java @@ -79,7 +79,7 @@ import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.ModelCrudService; import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.impl.lens.AssignmentEvaluator; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentEvaluator; import com.evolveum.midpoint.model.impl.lens.Clockwork; import com.evolveum.midpoint.model.impl.lens.ContextFactory; import com.evolveum.midpoint.model.impl.lens.LensContext; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java index cf48794f807..f264c3e4f54 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java @@ -6,12 +6,12 @@ */ package com.evolveum.midpoint.model.impl.expr; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; +import static java.util.Collections.*; import static org.apache.commons.collections4.CollectionUtils.emptyIfNull; import static com.evolveum.midpoint.schema.constants.SchemaConstants.PATH_CREDENTIALS_PASSWORD; import static com.evolveum.midpoint.schema.constants.SchemaConstants.PATH_CREDENTIALS_PASSWORD_VALUE; +import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.asObjectables; import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionStatusType.RUNNABLE; @@ -57,6 +57,8 @@ import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentTargetImpl; import com.evolveum.midpoint.model.impl.messaging.MessageWrapper; import com.evolveum.midpoint.model.impl.sync.SynchronizationContext; import com.evolveum.midpoint.model.impl.sync.SynchronizationExpressionsEvaluator; @@ -1892,16 +1894,6 @@ public boolean hasArchetype(O object, String archetypeOid if (!(object instanceof AssignmentHolderType)) { return archetypeOid == null; } - - ModelContext lensContext = ModelExpressionThreadLocalHolder.getLensContext(); - if (lensContext != null) { - ModelElementContext focusContext = lensContext.getFocusContext(); - ArchetypeType archetypeType = focusContext.getArchetype(); - if (archetypeType != null) { - return archetypeType.getOid().equals(archetypeOid); - } - } - List archetypeRefs = ((AssignmentHolderType) object).getArchetypeRef(); if (archetypeOid == null) { return archetypeRefs.isEmpty(); @@ -2033,6 +2025,40 @@ public List findAssignees(Class type) thr return searchObjects(type, query, null); } + // Should be used after assignment evaluation! + @Experimental + public T findAssignedObject(Class type, String archetypeOid) { + return MiscUtil.extractSingleton(findAssignedObjects(type, archetypeOid), + () -> new IllegalStateException("More than one assigned object found")); + } + + // Should be used after assignment evaluation! + @Experimental + @NotNull + public List findAssignedObjects(Class type, String archetypeOid) { + LensContext lensContext = (LensContext) getModelContext(); + DeltaSetTriple> assignmentTriple = lensContext.getEvaluatedAssignmentTriple(); + if (assignmentTriple == null) { + return emptyList(); + } + Map> assignedMap = new HashMap<>(); + for (EvaluatedAssignmentImpl evaluatedAssignment : assignmentTriple.getNonNegativeValues()) { + for (EvaluatedAssignmentTargetImpl target : evaluatedAssignment.getNonNegativeTargets()) { + if (!assignedMap.containsKey(target.getOid()) && matches(target, type, archetypeOid)) { + //noinspection unchecked + assignedMap.put(target.getOid(), (PrismObject) target.getTarget()); + } + } + } + return asObjectables(assignedMap.values()); + } + + private boolean matches(EvaluatedAssignmentTargetImpl target, Class type, String archetypeOid) { + AssignmentHolderType targetObject = target.getTarget().asObjectable(); + return type.isAssignableFrom(targetObject.getClass()) + && (archetypeOid == null || hasArchetype(targetObject, archetypeOid)); + } + @Experimental @NotNull private ObjectReferenceType getFocusObjectReference() { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentCollector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentCollector.java index a255c27733e..c5fba1df95b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentCollector.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentCollector.java @@ -10,6 +10,7 @@ import java.util.Collection; import java.util.List; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentEvaluator; import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; import com.evolveum.midpoint.prism.*; @@ -76,28 +77,28 @@ public class AssignmentCollector { @Autowired private CacheConfigurationManager cacheConfigurationManager; @Autowired private ContextLoader contextLoader; - public Collection> collect(PrismObject assignmentHolder, + public Collection> collect(PrismObject focus, boolean loginMode, Task task, OperationResult result) throws SchemaException { - LensContext lensContext = createAuthenticationLensContext(assignmentHolder, result); + LensContext lensContext = createAuthenticationLensContext(focus, result); - AH assignmentHolderType = assignmentHolder.asObjectable(); + AH focusBean = focus.asObjectable(); Collection forcedAssignments; try { forcedAssignments = LensUtil.getForcedAssignments(lensContext.getFocusContext().getLifecycleModel(), - assignmentHolderType.getLifecycleState(), objectResolver, prismContext, task, result); + focusBean.getLifecycleState(), objectResolver, prismContext, task, result); } catch (ObjectNotFoundException | CommunicationException | ConfigurationException | SecurityViolationException | ExpressionEvaluationException e1) { - LOGGER.error("Forced assignments defined for lifecycle {} won't be evaluated", assignmentHolderType.getLifecycleState(), e1); + LOGGER.error("Forced assignments defined for lifecycle {} won't be evaluated", focusBean.getLifecycleState(), e1); forcedAssignments = null; } Collection> evaluatedAssignments = new ArrayList<>(); - if (!assignmentHolderType.getAssignment().isEmpty() || forcedAssignments != null) { + if (!focusBean.getAssignment().isEmpty() || forcedAssignments != null) { AssignmentEvaluator.Builder builder = new AssignmentEvaluator.Builder() .repository(repositoryService) - .focusOdo(new ObjectDeltaObject<>(assignmentHolder, null, assignmentHolder, assignmentHolder.getDefinition())) + .focusOdo(new ObjectDeltaObject<>(focus, null, focus, focus.getDefinition())) .channel(null) .objectResolver(objectResolver) .systemObjectCache(systemObjectCache) @@ -119,17 +120,17 @@ public Collection> col AssignmentEvaluator assignmentEvaluator = builder.build(); - evaluatedAssignments.addAll(evaluateAssignments(assignmentHolderType, assignmentHolderType.getAssignment(), + evaluatedAssignments.addAll(evaluateAssignments(focusBean, focusBean.getAssignment(), AssignmentOrigin.createInObject(), assignmentEvaluator,task, result)); - evaluatedAssignments.addAll(evaluateAssignments(assignmentHolderType, forcedAssignments, + evaluatedAssignments.addAll(evaluateAssignments(focusBean, forcedAssignments, AssignmentOrigin.createVirtual(), assignmentEvaluator, task, result)); } return evaluatedAssignments; } - private Collection> evaluateAssignments(AH assignmentHolder, + private Collection> evaluateAssignments(AH focus, Collection assignments, AssignmentOrigin origin, AssignmentEvaluator assignmentEvaluator, Task task, OperationResult result) { List> evaluatedAssignments = new ArrayList<>(); @@ -145,11 +146,11 @@ private Collection> ev assignmentType.asPrismContainerValue().getDefinition(), standardAssignmentDefinition); ItemDeltaItem,PrismContainerDefinition> assignmentIdi = new ItemDeltaItem<>(LensUtil.createAssignmentSingleValueContainer(assignmentType), definition); - EvaluatedAssignment assignment = assignmentEvaluator.evaluate(assignmentIdi, PlusMinusZero.ZERO, false, assignmentHolder, assignmentHolder.toString(), origin, task, result); + EvaluatedAssignment assignment = assignmentEvaluator.evaluate(assignmentIdi, PlusMinusZero.ZERO, false, focus, focus.toString(), origin, task, result); evaluatedAssignments.add(assignment); } catch (SchemaException | ObjectNotFoundException | ExpressionEvaluationException | PolicyViolationException | SecurityViolationException | ConfigurationException | CommunicationException e) { LOGGER.error("Error while processing assignment of {}: {}; assignment: {}", - assignmentHolder, e.getMessage(), assignmentType, e); + focus, e.getMessage(), assignmentType, e); } } } finally { 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 deleted file mode 100644 index 223391c0c80..00000000000 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java +++ /dev/null @@ -1,1561 +0,0 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import java.util.*; -import java.util.stream.Collectors; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.common.ActivationComputer; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.impl.lens.construction.Construction; -import com.evolveum.midpoint.model.impl.lens.construction.EvaluatedConstructionImpl; -import com.evolveum.midpoint.model.impl.lens.construction.PersonaConstruction; -import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; -import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.AssignedFocusMappingEvaluationRequest; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.util.CloneUtil; -import com.evolveum.midpoint.repo.common.ObjectResolver; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; -import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; -import com.evolveum.midpoint.model.api.context.EvaluationOrder; -import com.evolveum.midpoint.model.common.ArchetypeManager; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; -import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; -import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.PlusMinusZero; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.internals.InternalMonitor; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.FocusTypeUtil; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.security.api.Authorization; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.Holder; -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.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.*; -import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; - -import com.evolveum.prism.xml.ns._public.types_3.PlusMinusZeroType; -import org.apache.commons.lang.BooleanUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * An engine that creates EvaluatedAssignment from an assignment IDI. It collects induced roles, constructions, - * authorizations, policy rules, and so on. - * - * @author semancik - */ -public class AssignmentEvaluator { - - private static final QName CONDITION_OUTPUT_NAME = new QName(SchemaConstants.NS_C, "condition"); - - private static final String OP_EVALUATE = AssignmentEvaluator.class.getName()+".evaluate"; - private static final String OP_EVALUATE_FROM_SEGMENT = AssignmentEvaluator.class.getName()+".evaluateFromSegment"; - - private static final Trace LOGGER = TraceManager.getTrace(AssignmentEvaluator.class); - - // "Configuration parameters" - private final RepositoryService repository; - private final ObjectDeltaObject focusOdo; - private final LensContext lensContext; - private final String channel; - private final ObjectResolver objectResolver; - private final SystemObjectCache systemObjectCache; - private final RelationRegistry relationRegistry; - private final PrismContext prismContext; - private final MappingFactory mappingFactory; - private final ActivationComputer activationComputer; - private final XMLGregorianCalendar now; - private final boolean loginMode; // restricted mode, evaluating only authorizations and gui config (TODO name) - private final PrismObject systemConfiguration; - private final MappingEvaluator mappingEvaluator; - private final ContextLoader contextLoader; - private final EvaluatedAssignmentTargetCache evaluatedAssignmentTargetCache; - private final LifecycleStateModelType focusStateModel; - - // Evaluation state - private final List memberOfInvocations = new ArrayList<>(); // experimental - - private AssignmentEvaluator(Builder builder) { - repository = builder.repository; - focusOdo = builder.focusOdo; - lensContext = builder.lensContext; - channel = builder.channel; - objectResolver = builder.objectResolver; - systemObjectCache = builder.systemObjectCache; - relationRegistry = builder.relationRegistry; - prismContext = builder.prismContext; - mappingFactory = builder.mappingFactory; - activationComputer = builder.activationComputer; - now = builder.now; - loginMode = builder.loginMode; - systemConfiguration = builder.systemConfiguration; - mappingEvaluator = builder.mappingEvaluator; - contextLoader = builder.contextLoader; - evaluatedAssignmentTargetCache = new EvaluatedAssignmentTargetCache(); - - LensFocusContext focusContext = lensContext.getFocusContext(); - if (focusContext != null) { - focusStateModel = focusContext.getLifecycleModel(); - } else { - focusStateModel = null; - } - } - - public RepositoryService getRepository() { - return repository; - } - - @SuppressWarnings("unused") - public ObjectDeltaObject getFocusOdo() { - return focusOdo; - } - - public LensContext getLensContext() { - return lensContext; - } - - public String getChannel() { - return channel; - } - - public ObjectResolver getObjectResolver() { - return objectResolver; - } - - public SystemObjectCache getSystemObjectCache() { - return systemObjectCache; - } - - public PrismContext getPrismContext() { - return prismContext; - } - - public MappingFactory getMappingFactory() { - return mappingFactory; - } - - public ActivationComputer getActivationComputer() { - return activationComputer; - } - - public XMLGregorianCalendar getNow() { - return now; - } - - @SuppressWarnings("unused") - public boolean isLoginMode() { - return loginMode; - } - - public PrismObject getSystemConfiguration() { - return systemConfiguration; - } - - @SuppressWarnings("unused") - public MappingEvaluator getMappingEvaluator() { - return mappingEvaluator; - } - - public void reset(boolean alsoMemberOfInvocations) { - evaluatedAssignmentTargetCache.reset(); - if (alsoMemberOfInvocations) { - memberOfInvocations.clear(); - } - } - - // This is to reduce the number of parameters passed between methods in this class. - // Moreover, it highlights the fact that identity of objects referenced here is fixed for any invocation of the evaluate() method. - // (There is single EvaluationContext instance for any call to evaluate().) - private class EvaluationContext { - @NotNull private final EvaluatedAssignmentImpl evalAssignment; - @NotNull private final AssignmentPathImpl assignmentPath; - // The primary assignment mode tells whether the primary assignment was added, removed or it is unchanged. - // The primary assignment is the first assignment in the assignment path, the assignment that is located in the - // focal object. - private final PlusMinusZero primaryAssignmentMode; - private final boolean evaluateOld; - private final Task task; - private EvaluationContext(@NotNull EvaluatedAssignmentImpl evalAssignment, - @NotNull AssignmentPathImpl assignmentPath, - PlusMinusZero primaryAssignmentMode, boolean evaluateOld, Task task) { - this.evalAssignment = evalAssignment; - this.assignmentPath = assignmentPath; - this.primaryAssignmentMode = primaryAssignmentMode; - this.evaluateOld = evaluateOld; - this.task = task; - } - } - - /** - * evaluateOld: If true, we take the 'old' value from assignmentIdi. If false, we take the 'new' one. - */ - public EvaluatedAssignmentImpl evaluate( - ItemDeltaItem,PrismContainerDefinition> assignmentIdi, - PlusMinusZero primaryAssignmentMode, boolean evaluateOld, AssignmentHolderType source, String sourceDescription, - AssignmentOrigin origin, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - OperationResult result = parentResult.subresult(OP_EVALUATE) - .setMinor() - .addArbitraryObjectAsParam("primaryAssignmentMode", primaryAssignmentMode) - .addParam("evaluateOld", evaluateOld) - .addArbitraryObjectAsParam("source", source) - .addParam("sourceDescription", sourceDescription) - .addArbitraryObjectAsParam("origin", origin) - .build(); - AssignmentEvaluationTraceType trace; - if (result.isTracingNormal(AssignmentEvaluationTraceType.class)) { - trace = new AssignmentEvaluationTraceType(prismContext) - .assignmentOld(CloneUtil.clone(getAssignmentBean(assignmentIdi, true))) - .assignmentNew(CloneUtil.clone(getAssignmentBean(assignmentIdi, false))) - .primaryAssignmentMode(PlusMinusZeroType.fromValue(primaryAssignmentMode)) - .evaluateOld(evaluateOld) - .textSource(source != null ? source.asPrismObject().debugDump() : "null") - .sourceDescription(sourceDescription); - result.addTrace(trace); - } else { - trace = null; - } - try { - assertSourceNotNull(source, assignmentIdi); - - EvaluatedAssignmentImpl evalAssignmentImpl = new EvaluatedAssignmentImpl<>(assignmentIdi, evaluateOld, origin, prismContext); - - EvaluationContext ctx = new EvaluationContext( - evalAssignmentImpl, - new AssignmentPathImpl(prismContext), - primaryAssignmentMode, evaluateOld, task); - - evaluatedAssignmentTargetCache.resetForNextAssignment(); - - AssignmentPathSegmentImpl segment = new AssignmentPathSegmentImpl(source, sourceDescription, assignmentIdi, true, - evaluateOld, relationRegistry, prismContext); - segment.setEvaluationOrder(getInitialEvaluationOrder(assignmentIdi, ctx)); - segment.setEvaluationOrderForTarget(EvaluationOrderImpl.zero(relationRegistry)); - segment.setValidityOverride(true); - segment.setPathToSourceValid(true); - segment.setProcessMembership(true); - segment.setRelation(getRelation(getAssignmentType(segment, ctx))); - - evaluateFromSegment(segment, PlusMinusZero.ZERO, ctx, result); - - if (segment.getTarget() != null) { - result.addContext("assignmentTargetName", PolyString.getOrig(segment.getTarget().getName())); - } - - LOGGER.trace("Assignment evaluation finished:\n{}", ctx.evalAssignment.debugDumpLazily()); - if (trace != null) { - trace.setTextResult(ctx.evalAssignment.debugDump()); - } - result.computeStatusIfUnknown(); - return ctx.evalAssignment; - } catch (Throwable t) { - result.recordFatalError(t.getMessage(), t); - throw t; - } - } - - private AssignmentType getAssignmentBean( - ItemDeltaItem, PrismContainerDefinition> assignmentIdi, - boolean old) { - PrismContainerValue pcv = assignmentIdi.getSingleValue(old); - return pcv != null ? pcv.asContainerable() : null; - } - - private EvaluationOrder getInitialEvaluationOrder( - ItemDeltaItem, PrismContainerDefinition> assignmentIdi, - EvaluationContext ctx) { - AssignmentType assignmentType = LensUtil.getAssignmentType(assignmentIdi, ctx.evaluateOld); - return EvaluationOrderImpl.zero(relationRegistry).advance(getRelation(assignmentType)); - } - - /** - * @param relativeMode - * - * Where to put constructions and target roles/orgs/services (PLUS/MINUS/ZERO/null; null means "nowhere"). - * This is a mode relative to the primary assignment. It does NOT tell whether the assignment as a whole - * is added or removed. It tells whether the part of the assignment that we are processing is to be - * added or removed. This may happen, e.g. if a condition in an existing assignment turns from false to true. - * In that case the primary assignment mode is ZERO, but the relative mode is PLUS. - * The relative mode always starts at ZERO, even for added or removed assignments. - * - * This depends on the status of conditions. E.g. if condition evaluates 'false -> true' (i.e. in old - * state the value is false, and in new state the value is true), then the mode is PLUS. - * - * This "triples algebra" is based on the following two methods: - * - * @see ExpressionUtil#computeConditionResultMode(boolean, boolean) - Based on condition values "old+new" determines - * into what set (PLUS/MINUS/ZERO/none) should the result be placed. Irrespective of what is the current mode. So, - * in order to determine "real" place where to put it (i.e. the new mode) the following method is used. - * - * @see PlusMinusZero#compute(PlusMinusZero, PlusMinusZero) - Takes original mode and the mode from recent condition - * and determines the new mode (commutatively): - * - * PLUS + PLUS/ZERO = PLUS - * MINUS + MINUS/ZERO = MINUS - * ZERO + ZERO = ZERO - * PLUS + MINUS = none - * - * This is quite straightforward, although the last rule deserves a note. If we have an assignment that was originally - * disabled and becomes enabled by the current delta (i.e. PLUS), and that assignment contains an inducement that was originally - * enabled and becomes disabled (i.e. MINUS), the result is that the (e.g.) constructions within the inducement were not - * present in the old state (because assignment was disabled) and are not present in the new state (because inducement is disabled). - * - * Note: this parameter could be perhaps renamed to "tripleMode" or "destination" or something like that. - */ - private void evaluateFromSegment(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, EvaluationContext ctx, - OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - OperationResult result = parentResult.subresult(OP_EVALUATE_FROM_SEGMENT) - .setMinor() - .addParam("segment", segment.shortDump()) - .addArbitraryObjectAsParam("relativeMode", relativeMode) - .build(); - AssignmentSegmentEvaluationTraceType trace; - if (result.isTracingNormal(AssignmentSegmentEvaluationTraceType.class)) { - trace = new AssignmentSegmentEvaluationTraceType(prismContext) - .segment(segment.toAssignmentPathSegmentType(true)) - .mode(PlusMinusZeroType.fromValue(relativeMode)); - result.addTrace(trace); - } else { - trace = null; - } - try { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("*** Evaluate from segment: {}", segment); - LOGGER.trace("*** Evaluation order - standard: {}, matching: {}", segment.getEvaluationOrder(), segment.isMatchingOrder()); - LOGGER.trace("*** Evaluation order - for target: {}, matching: {}", segment.getEvaluationOrderForTarget(), segment.isMatchingOrderForTarget()); - LOGGER.trace("*** mode: {}, process membership: {}", relativeMode, segment.isProcessMembership()); - LOGGER.trace("*** path to source valid: {}, validity override: {}", segment.isPathToSourceValid(), segment.isValidityOverride()); - } - - assertSourceNotNull(segment.source, ctx.evalAssignment); - checkSchema(segment, ctx); - - ctx.assignmentPath.add(segment); - LOGGER.trace("*** Path (with current segment already added):\n{}", ctx.assignmentPath.debugDumpLazily()); - - boolean evaluateContent; - AssignmentType assignmentType = getAssignmentType(segment, ctx); - MappingType assignmentCondition = assignmentType.getCondition(); - if (assignmentCondition != null) { - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(assignmentCondition, - segment.source, assignmentPathVariables, - "condition in assignment in " + segment.getSourceDescription(), ctx, result); - boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); - boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); - PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); - if (modeFromCondition == null) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: {})", - FocusTypeUtil.dumpAssignment(assignmentType), condOld, condNew, null); - } - evaluateContent = false; - } else { - PlusMinusZero origMode = relativeMode; - relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); - LOGGER.trace("Evaluated condition in assignment {} -> {}: {} + {} = {}", condOld, condNew, origMode, - modeFromCondition, relativeMode); - evaluateContent = true; - } - } else { - evaluateContent = true; - } - - if (ctx.assignmentPath.isEmpty() && ctx.evalAssignment.isVirtual()) { - segment.setValidityOverride(ctx.evalAssignment.isVirtual()); - } - - boolean isValid = - (evaluateContent && evaluateSegmentContent(segment, relativeMode, ctx, result)) || ctx.evalAssignment.isVirtual(); - - ctx.assignmentPath.removeLast(segment); - if (ctx.assignmentPath.isEmpty()) { // direct assignment - ctx.evalAssignment.setValid(isValid); - } - - LOGGER.trace("evalAssignment isVirtual {} ", ctx.evalAssignment.isVirtual()); - if (segment.getSource() != null) { - result.addContext("segmentSourceName", PolyString.getOrig(segment.getSource().getName())); - } - if (segment.getTarget() != null) { - result.addContext("segmentTargetName", PolyString.getOrig(segment.getTarget().getName())); - } - result.addArbitraryObjectAsReturn("relativeMode", relativeMode); - result.addReturn("evaluateContent", evaluateContent); - result.addReturn("isValid", isValid); - if (trace != null) { - trace.setTextResult(segment.debugDump()); - } - } catch (Throwable t) { - result.recordFatalError(t.getMessage(), t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } - } - - // "content" means "payload + targets" here - private boolean evaluateSegmentContent(AssignmentPathSegmentImpl segment, - PlusMinusZero relativeMode, EvaluationContext ctx, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - - assert ctx.assignmentPath.last() == segment; - - final boolean isDirectAssignment = ctx.assignmentPath.size() == 1; - - AssignmentType assignment = getAssignmentType(segment, ctx); - - // Assignment validity is checked with respect to the assignment source, not to the focus object. - // So, if (e.g.) focus is in "draft" state, only validity of direct assignments should be influenced by this fact. - // Other assignments (e.g. from roles to metaroles) should be considered valid, provided these roles are - // in active lifecycle states. See also MID-6114. - AssignmentHolderType source = segment.isMatchingOrder() ? focusOdo.getNewObject().asObjectable() : segment.getSource(); - boolean isAssignmentValid = LensUtil.isAssignmentValid(source, assignment, now, activationComputer, focusStateModel); - if (isAssignmentValid || segment.isValidityOverride()) { - // Note: validityOverride is currently the same as "isDirectAssignment" - which is very probably OK. - // Direct assignments are visited even if they are not valid (i.e. effectively disabled). - // It is because we need to collect e.g. assignment policy rules for them. - // Also because we could have deltas that disable/enable these assignments. - boolean reallyValid = segment.isPathToSourceValid() && isAssignmentValid; - if (!loginMode && segment.isMatchingOrder()) { - if (assignment.getConstruction() != null) { - collectConstruction(segment, relativeMode, reallyValid, ctx); - } - if (assignment.getPersonaConstruction() != null) { - collectPersonaConstruction(segment, relativeMode, reallyValid, ctx); - } - if (assignment.getFocusMappings() != null) { - if (reallyValid && relativeMode != null) { // null relative mode means both PLUS and MINUS - collectFocusMappings(segment, relativeMode, ctx); - } - } - } - if (!loginMode && assignment.getPolicyRule() != null) { - // Here we ignore "reallyValid". It is OK, because reallyValid can be false here only when - // evaluating direct assignments; and invalid ones are marked as such via EvaluatedAssignment.isValid. - // TODO is it ok? - if (isNonNegative(relativeMode)) { - if (segment.isMatchingOrder()) { - collectPolicyRule(true, segment, ctx); - } - if (segment.isMatchingOrderForTarget()) { - collectPolicyRule(false, segment, ctx); - } - } - } - if (assignment.getTargetRef() != null) { - QName relation = getRelation(assignment); - if (loginMode && !relationRegistry.isProcessedOnLogin(relation)) { - LOGGER.trace("Skipping processing of assignment target {} because relation {} is configured for login skip", assignment.getTargetRef().getOid(), relation); - // Skip - to optimize logging-in, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) - // We want to make this configurable in the future MID-3581 - } else if (!loginMode && !isChanged(ctx.primaryAssignmentMode) && - !relationRegistry.isProcessedOnRecompute(relation) && !shouldEvaluateAllAssignmentRelationsOnRecompute()) { - LOGGER.debug("Skipping processing of assignment target {} because relation {} is configured for recompute skip (mode={})", assignment.getTargetRef().getOid(), relation, relativeMode); - // Skip - to optimize recompute, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) - // never skip this if assignment has changed. We want to process this, e.g. to enforce min/max assignee rules - // We want to make this configurable in the future MID-3581 - - // Important: but we still want this to be reflected in roleMembershipRef - if ((isNonNegative(relativeMode)) && segment.isProcessMembership()) { - if (assignment.getTargetRef().getOid() != null) { - addToMembershipLists(assignment.getTargetRef(), relation, ctx); - } else { - // no OID, so we have to resolve the filter - for (PrismObject targetObject : getTargets(segment, ctx, result)) { - ObjectType target = targetObject.asObjectable(); - if (target instanceof FocusType) { - addToMembershipLists((FocusType) target, relation, ctx); - } - } - } - } - } else { - List> targets = getTargets(segment, ctx, result); - LOGGER.trace("Targets in {}, assignment ID {}: {}", segment.source, assignment.getId(), targets); - if (isDirectAssignment) { - setEvaluatedAssignmentTarget(segment, targets, ctx); - } - for (PrismObject target : targets) { - if (hasCycle(segment, target, ctx)) { - continue; - } - if (isDelegationToNonDelegableTarget(assignment, target, ctx)) { - continue; - } - evaluateSegmentTarget(segment, relativeMode, reallyValid, (AssignmentHolderType) target.asObjectable(), relation, ctx, result); - } - } - } - } else { - LOGGER.trace("Skipping evaluation of assignment {} because it is not valid", assignment); - } - return isAssignmentValid; - } - - private boolean shouldEvaluateAllAssignmentRelationsOnRecompute() { - return ModelExecuteOptions.isEvaluateAllAssignmentRelationsOnRecompute(lensContext.getOptions()); - } - - private boolean isDelegationToNonDelegableTarget(AssignmentType assignmentType, @NotNull PrismObject target, - EvaluationContext ctx) { - AssignmentPathSegment previousSegment = ctx.assignmentPath.beforeLast(1); - if (previousSegment == null || !previousSegment.isDelegation() || !target.canRepresent(AbstractRoleType.class)) { - return false; - } - if (!Boolean.TRUE.equals(((AbstractRoleType)target.asObjectable()).isDelegable())) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping evaluation of {} because it delegates to a non-delegable target {}", - FocusTypeUtil.dumpAssignment(assignmentType), target); - } - return true; - } else { - return false; - } - } - - // number of times any given target is allowed to occur in the assignment path - private static final int MAX_TARGET_OCCURRENCES = 2; - - private boolean hasCycle(AssignmentPathSegmentImpl segment, @NotNull PrismObject target, - EvaluationContext ctx) throws PolicyViolationException { - // TODO reconsider this - if (target.getOid().equals(segment.source.getOid())) { - throw new PolicyViolationException("The "+segment.source+" refers to itself in assignment/inducement"); - } - // removed condition "&& segment.getEvaluationOrder().equals(ctx.assignmentPath.getEvaluationOrder())" - // as currently it is always true - // TODO reconsider this - int count = ctx.assignmentPath.countTargetOccurrences(target.asObjectable()); - if (count >= MAX_TARGET_OCCURRENCES) { - LOGGER.debug("Max # of target occurrences ({}) detected for target {} in {} - stopping evaluation here", - MAX_TARGET_OCCURRENCES, ObjectTypeUtil.toShortString(target), ctx.assignmentPath); - return true; - } else { - return false; - } - } - - private void collectConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - AssignmentType assignmentType = getAssignmentType(segment, ctx); - ConstructionType constructionType = assignmentType.getConstruction(); - - LOGGER.trace("Preparing construction '{}' in {}", constructionType.getDescription(), segment.source); - - Construction> construction = new Construction<>(constructionType, segment.source); - // We have to clone here as the path is constantly changing during evaluation - construction.setAssignmentPath(ctx.assignmentPath.clone()); - construction.setFocusOdo(focusOdo); - construction.setLensContext(lensContext); - construction.setObjectResolver(objectResolver); - construction.setPrismContext(prismContext); - construction.setMappingFactory(mappingFactory); - construction.setMappingEvaluator(mappingEvaluator); - construction.setNow(now); - construction.setContextLoader(contextLoader); - construction.setOriginType(OriginType.ASSIGNMENTS); - construction.setChannel(channel); - construction.setOrderOneObject(segment.getOrderOneObject()); - construction.setValid(isValid); - construction.setRelativityMode(mode); - - // Do not evaluate the construction here. We will do it in the second pass. Just prepare everything to be evaluated. - if (mode == null) { - return; // null mode (i.e. plus + minus) means 'ignore the payload' - } - ctx.evalAssignment.addConstruction(construction, mode); - } - - private void collectPersonaConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { - assertSourceNotNull(segment.source, ctx.evalAssignment); - if (mode == null) { - return; // null mode (i.e. plus + minus) means 'ignore the payload' - } - - AssignmentType assignmentType = getAssignmentType(segment, ctx); - PersonaConstructionType constructionType = assignmentType.getPersonaConstruction(); - - LOGGER.trace("Preparing persona construction '{}' in {}", constructionType.getDescription(), segment.source); - - PersonaConstruction construction = new PersonaConstruction<>(constructionType, segment.source); - // We have to clone here as the path is constantly changing during evaluation - construction.setAssignmentPath(ctx.assignmentPath.clone()); - construction.setFocusOdo(focusOdo); - construction.setLensContext(lensContext); - construction.setObjectResolver(objectResolver); - construction.setPrismContext(prismContext); - construction.setOriginType(OriginType.ASSIGNMENTS); - construction.setChannel(channel); - construction.setValid(isValid); - construction.setRelativityMode(mode); - - ctx.evalAssignment.addPersonaConstruction(construction, mode); - } - - private void collectFocusMappings(AssignmentPathSegmentImpl segment, @NotNull PlusMinusZero relativeMode, EvaluationContext ctx) - throws SchemaException { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - AssignmentType assignmentBean = getAssignmentType(segment, ctx); - MappingsType mappingsBean = assignmentBean.getFocusMappings(); - - LOGGER.trace("Request evaluation of focus mappings '{}' in {} ({} mappings)", - mappingsBean.getDescription(), segment.source, mappingsBean.getMapping().size()); - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - - for (MappingType mappingBean: mappingsBean.getMapping()) { - AssignedFocusMappingEvaluationRequest request = new AssignedFocusMappingEvaluationRequest(mappingBean, segment.source, - ctx.evalAssignment, relativeMode, assignmentPathVariables, segment.sourceDescription); - ctx.evalAssignment.addFocusMappingEvaluationRequest(request); - } - } - - private void collectPolicyRule(boolean focusRule, AssignmentPathSegmentImpl segment, EvaluationContext ctx) { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - AssignmentType assignmentType = getAssignmentType(segment, ctx); - PolicyRuleType policyRuleType = assignmentType.getPolicyRule(); - - LOGGER.trace("Collecting {} policy rule '{}' in {}", focusRule ? "focus" : "target", policyRuleType.getName(), segment.source); - - EvaluatedPolicyRuleImpl policyRule = new EvaluatedPolicyRuleImpl(policyRuleType.clone(), ctx.assignmentPath.clone(), prismContext); - - if (focusRule) { - ctx.evalAssignment.addFocusPolicyRule(policyRule); - } else { - if (appliesDirectly(ctx.assignmentPath)) { - ctx.evalAssignment.addThisTargetPolicyRule(policyRule); - } else { - ctx.evalAssignment.addOtherTargetPolicyRule(policyRule); - } - } - } - - private boolean appliesDirectly(AssignmentPathImpl assignmentPath) { - assert !assignmentPath.isEmpty(); - // TODO what about deputy relation which does not increase summaryOrder? - long zeroOrderCount = assignmentPath.getSegments().stream() - .filter(seg -> seg.getEvaluationOrderForTarget().getSummaryOrder() == 0) - .count(); - return zeroOrderCount == 1; - } - - @NotNull - private List> getTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, - OperationResult result) throws SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - AssignmentType assignmentType = getAssignmentType(segment, ctx); - if (assignmentType.getTargetRef() != null) { - try { - return resolveTargets(segment, ctx, result); - } catch (ObjectNotFoundException ex) { - // Do not throw an exception. We don't have referential integrity. Therefore if a role is deleted then throwing - // an exception would prohibit any operations with the users that have the role, including removal of the reference. - // The failure is recorded in the result and we will log it. It should be enough. - LOGGER.error(ex.getMessage()+" in assignment target reference in "+segment.sourceDescription,ex); - // For OrgType references we trigger the reconciliation (see MID-2242) - ctx.evalAssignment.setForceRecon(true); - return Collections.emptyList(); - } - } else { - throw new IllegalStateException("Both target and targetRef are null. We should not be here. Assignment: " + assignmentType); - } - } - - @NotNull - private List> resolveTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, - OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException { - AssignmentType assignmentType = getAssignmentType(segment, ctx); - ObjectReferenceType targetRef = assignmentType.getTargetRef(); - String oid = targetRef.getOid(); - - // Target is referenced, need to fetch it - Class targetClass; - if (targetRef.getType() != null) { - targetClass = prismContext.getSchemaRegistry().determineCompileTimeClass(targetRef.getType()); - if (targetClass == null) { - throw new SchemaException("Cannot determine type from " + targetRef.getType() + " in target reference in " + assignmentType + " in " + segment.sourceDescription); - } - } else { - throw new SchemaException("Missing type in target reference in " + assignmentType + " in " + segment.sourceDescription); - } - - if (oid == null) { - LOGGER.trace("Resolving dynamic target ref"); - if (targetRef.getFilter() == null) { - throw new SchemaException("The OID and filter are both null in assignment targetRef in "+segment.source); - } - return resolveTargetsFromFilter(targetClass, targetRef.getFilter(), segment, ctx, result); - } else { - LOGGER.trace("Resolving target {}:{} from repository", targetClass.getSimpleName(), oid); - PrismObject target; - try { - target = repository.getObject(targetClass, oid, null, result); - } catch (SchemaException e) { - throw new SchemaException(e.getMessage() + " in " + segment.sourceDescription, e); - } - // Not handling object not found exception here. Caller will handle that. - if (target == null) { - throw new IllegalArgumentException("Got null target from repository, oid:"+oid+", class:"+targetClass+" (should not happen, probably a bug) in "+segment.sourceDescription); - } - return Collections.singletonList(target); - } - } - - @NotNull - private List> resolveTargetsFromFilter(Class targetClass, - SearchFilterType filter, AssignmentPathSegmentImpl segment, - EvaluationContext ctx, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException{ - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(lensContext, null, ctx.task, result)); - try { - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(segment.source, null, null, systemConfiguration.asObjectable(), prismContext); - variables.put(ExpressionConstants.VAR_SOURCE, segment.getOrderOneObject(), ObjectType.class); - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - if (assignmentPathVariables != null) { - ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, variables, getPrismContext()); - } - variables.addVariableDefinitions(getAssignmentEvaluationVariables()); - ObjectFilter origFilter = prismContext.getQueryConverter().parseFilter(filter, targetClass); - // TODO: expression profile should be determined from the holding object archetype - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - ObjectFilter evaluatedFilter = ExpressionUtil.evaluateFilterExpressions(origFilter, variables, expressionProfile, getMappingFactory().getExpressionFactory(), prismContext, " evaluating resource filter expression ", ctx.task, result); - if (evaluatedFilter == null) { - throw new SchemaException("The OID is null and filter could not be evaluated in assignment targetRef in "+segment.source); - } - - return repository.searchObjects(targetClass, prismContext.queryFactory().createQuery(evaluatedFilter), null, result); - // we don't check for no targets here; as we don't care for referential integrity - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - private ExpressionVariables getAssignmentEvaluationVariables() { - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_LOGIN_MODE, loginMode, Boolean.class); - // e.g. AssignmentEvaluator itself, model context, etc (when needed) - return variables; - } - - // Note: This method must be single-return after targetEvaluationInformation is established. - private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, boolean isAssignmentPathValid, - AssignmentHolderType target, QName relation, EvaluationContext ctx, - OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - assert ctx.assignmentPath.last() == segment; - - segment.setTarget(target); - segment.setRelation(relation); // probably not needed - - if (evaluatedAssignmentTargetCache.canSkip(segment, ctx.primaryAssignmentMode)) { - LOGGER.trace("Skipping evaluation of segment {} because it is idempotent and we have seen the target before", segment); - InternalMonitor.recordRoleEvaluationSkip(target, true); - return; - } - - LOGGER.trace("Evaluating segment TARGET:\n{}", segment.debugDumpLazily(1)); - - checkRelationWithTarget(segment, target, relation); - - LifecycleStateModelType targetStateModel = ArchetypeManager.determineLifecycleModel(target.asPrismObject(), systemConfiguration); - boolean isTargetValid = LensUtil.isFocusValid(target, now, activationComputer, targetStateModel); - boolean isPathAndTargetValid = isAssignmentPathValid && isTargetValid; - - LOGGER.debug("Evaluating RBAC [{}]", ctx.assignmentPath.shortDumpLazily()); - InternalMonitor.recordRoleEvaluation(target, true); - - AssignmentTargetEvaluationInformation targetEvaluationInformation; - if (isPathAndTargetValid) { - // Cache it immediately, even before evaluation. So if there is a cycle in the role path - // then we can detect it and skip re-evaluation of aggressively idempotent roles. - // - // !!! Ensure we will not return without updating this object (except for exceptions). So please keep this - // method a single-return one after this point. We did not want to complicate things using try...finally. - // - targetEvaluationInformation = evaluatedAssignmentTargetCache.recordProcessing(segment, ctx.primaryAssignmentMode); - } else { - targetEvaluationInformation = null; - } - int targetPolicyRulesOnEntry = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); - - boolean skipOnConditionResult = false; - - if (isTargetValid && target instanceof AbstractRoleType) { - MappingType roleCondition = ((AbstractRoleType)target).getCondition(); - if (roleCondition != null) { - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(roleCondition, - segment.source, assignmentPathVariables, - "condition in " + segment.getTargetDescription(), ctx, result); - boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); - boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); - PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); - if (modeFromCondition == null) { - skipOnConditionResult = true; - LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: null)", - target, condOld, condNew); - } else { - PlusMinusZero origMode = relativeMode; - relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); - LOGGER.trace("Evaluated condition in {}: {} -> {}: {} + {} = {}", target, condOld, condNew, - origMode, modeFromCondition, relativeMode); - } - } - } - - if (!skipOnConditionResult) { - EvaluatedAssignmentTargetImpl evalAssignmentTarget = new EvaluatedAssignmentTargetImpl( - target.asPrismObject(), - segment.isMatchingOrder(), // evaluateConstructions: exact meaning of this is to be revised - ctx.assignmentPath.clone(), - getAssignmentType(segment, ctx), - isPathAndTargetValid); - ctx.evalAssignment.addRole(evalAssignmentTarget, relativeMode); - - // we need to evaluate assignments also for disabled targets, because of target policy rules - // ... but only for direct ones! - if (isTargetValid || ctx.assignmentPath.size() == 1) { - for (AssignmentType nextAssignment : target.getAssignment()) { - evaluateAssignment(segment, relativeMode, isPathAndTargetValid, ctx, target, relation, nextAssignment, result); - } - } - - // we need to collect membership also for disabled targets (provided the assignment itself is enabled): MID-4127 - if (isNonNegative(relativeMode) && segment.isProcessMembership()) { - addToMembershipLists(target, relation, ctx); - } - - if (isTargetValid) { - if (isNonNegative(relativeMode)) { - setAsTenantRef(target, 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 (target instanceof AbstractRoleType) { - for (AssignmentType roleInducement : ((AbstractRoleType) target).getInducement()) { - evaluateInducement(segment, relativeMode, isPathAndTargetValid, ctx, target, roleInducement, result); - } - } - - if (segment.isMatchingOrder() && target instanceof AbstractRoleType && isNonNegative(relativeMode)) { - for (AuthorizationType authorizationType : ((AbstractRoleType) target).getAuthorization()) { - Authorization authorization = createAuthorization(authorizationType, target.toString()); - if (!ctx.evalAssignment.getAuthorizations().contains(authorization)) { - ctx.evalAssignment.addAuthorization(authorization); - } - } - AdminGuiConfigurationType adminGuiConfiguration = ((AbstractRoleType) target).getAdminGuiConfiguration(); - if (adminGuiConfiguration != null && !ctx.evalAssignment.getAdminGuiConfigurations() - .contains(adminGuiConfiguration)) { - ctx.evalAssignment.addAdminGuiConfiguration(adminGuiConfiguration); - } - } - } - } - int targetPolicyRulesOnExit = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); - - LOGGER.trace("Evaluating segment target DONE for {}; target policy rules: {} -> {}", segment, targetPolicyRulesOnEntry, - targetPolicyRulesOnExit); - if (targetEvaluationInformation != null) { - targetEvaluationInformation.setBringsTargetPolicyRules(targetPolicyRulesOnExit > targetPolicyRulesOnEntry); - } - } - - // 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, - AssignmentHolderType target, QName relation, AssignmentType nextAssignment, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - - ObjectType orderOneObject = getOrderOneObject(segment); - - if (relationRegistry.isDelegation(relation)) { - // We have to handle assignments as though they were inducements here. - if (!isAllowedByLimitations(segment, nextAssignment, ctx)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of delegated assignment {} because it is limited in the delegation", - FocusTypeUtil.dumpAssignment(nextAssignment)); - } - return; - } - } - QName nextRelation = getRelation(nextAssignment); - 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(), target, FocusTypeUtil.dumpAssignment(nextAssignment), nextEvaluationOrder); - } - String nextSourceDescription = target+" in "+segment.sourceDescription; - AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, nextSourceDescription, nextAssignment, true, relationRegistry, prismContext); - nextSegment.setRelation(nextRelation); - nextSegment.setEvaluationOrder(nextEvaluationOrder); - nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTarget); - nextSegment.setOrderOneObject(orderOneObject); - nextSegment.setPathToSourceValid(isValid); - /* - * We obviously want to process membership from the segment if it's of matching order. - * - * But we want to do that also for targets obtained via delegations. The current (approximate) approach is to - * collect membership from all assignments of any user that we find on the assignment path. - * - * TODO: does this work for invalid (effectiveStatus = disabled) assignments? - */ - boolean isUser = target instanceof UserType; - nextSegment.setProcessMembership(nextSegment.isMatchingOrder() || isUser); - assert !ctx.assignmentPath.isEmpty(); - evaluateFromSegment(nextSegment, mode, ctx, result); - } - - private void evaluateInducement(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, - AssignmentHolderType target, AssignmentType inducement, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - - ObjectType orderOneObject = getOrderOneObject(segment); - - if (!isInducementApplicableToFocusType(inducement.getFocusType())) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of inducement {} because the focusType does not match (specified: {}, actual: {})", - FocusTypeUtil.dumpAssignment(inducement), inducement.getFocusType(), target.getClass().getSimpleName()); - } - return; - } - if (!isAllowedByLimitations(segment, inducement, ctx)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of inducement {} because it is limited", FocusTypeUtil.dumpAssignment(inducement)); - } - return; - } - String subSourceDescription = target+" in "+segment.sourceDescription; - AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, subSourceDescription, inducement, false, relationRegistry, prismContext); - // note that 'old' and 'new' values for assignment in nextSegment are the same - boolean nextIsMatchingOrder = AssignmentPathSegmentImpl.computeMatchingOrder( - segment.getEvaluationOrder(), nextSegment.getAssignmentNew()); - boolean nextIsMatchingOrderForTarget = AssignmentPathSegmentImpl.computeMatchingOrder( - segment.getEvaluationOrderForTarget(), nextSegment.getAssignmentNew()); - - Holder nextEvaluationOrderHolder = new Holder<>(segment.getEvaluationOrder().clone()); - Holder nextEvaluationOrderForTargetHolder = new Holder<>(segment.getEvaluationOrderForTarget().clone()); - adjustOrder(nextEvaluationOrderHolder, nextEvaluationOrderForTargetHolder, inducement.getOrderConstraint(), inducement.getOrder(), ctx.assignmentPath, nextSegment, ctx); - nextSegment.setEvaluationOrder(nextEvaluationOrderHolder.getValue(), nextIsMatchingOrder); - nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTargetHolder.getValue(), nextIsMatchingOrderForTarget); - - nextSegment.setOrderOneObject(orderOneObject); - nextSegment.setPathToSourceValid(isValid); - nextSegment.setProcessMembership(nextIsMatchingOrder); - nextSegment.setRelation(getRelation(inducement)); - - // 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(), target, FocusTypeUtil.dumpInducementConstraints(inducement), - FocusTypeUtil.dumpAssignment(inducement), nextEvaluationOrderHolder.getValue().shortDump()); - } - assert !ctx.assignmentPath.isEmpty(); - evaluateFromSegment(nextSegment, mode, ctx, result); - } - - private void adjustOrder(Holder evaluationOrderHolder, Holder targetEvaluationOrderHolder, - List constraints, Integer order, AssignmentPathImpl assignmentPath, - AssignmentPathSegmentImpl nextSegment, EvaluationContext ctx) { - - if (constraints.isEmpty()) { - if (order == null || order == 1) { - return; - } else if (order <= 0) { - throw new IllegalStateException("Wrong inducement order: it must be positive but it is " + order + " instead"); - } - // converting legacy -> new specification - int currentOrder = evaluationOrderHolder.getValue().getSummaryOrder(); - if (order > currentOrder) { - LOGGER.trace("order of the inducement ({}) is greater than the current evaluation order ({}), marking as undefined", - order, currentOrder); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } - // i.e. currentOrder >= order, i.e. currentOrder > order-1 - int newOrder = currentOrder - (order - 1); - assert newOrder > 0; - constraints = Collections.singletonList(new OrderConstraintsType(prismContext) - .order(order) - .resetOrder(newOrder)); - } - - OrderConstraintsType summaryConstraints = ObjectTypeUtil.getConstraintFor(constraints, null); - Integer resetSummaryTo = summaryConstraints != null && summaryConstraints.getResetOrder() != null ? - summaryConstraints.getResetOrder() : null; - - if (resetSummaryTo != null) { - int summaryBackwards = evaluationOrderHolder.getValue().getSummaryOrder() - resetSummaryTo; - if (summaryBackwards < 0) { - // or should we throw an exception? - LOGGER.warn("Cannot move summary order backwards to a negative value ({}). Current order: {}, requested order: {}", - summaryBackwards, evaluationOrderHolder.getValue().getSummaryOrder(), resetSummaryTo); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } else if (summaryBackwards > 0) { -// MultiSet backRelations = new HashMultiSet<>(); - int assignmentsSeen = 0; - int i = assignmentPath.size()-1; - while (assignmentsSeen < summaryBackwards) { - if (i < 0) { - LOGGER.trace("Cannot move summary order backwards by {}; only {} assignments segment seen: {}", - summaryBackwards, assignmentsSeen, assignmentPath); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } - AssignmentPathSegmentImpl segment = assignmentPath.getSegments().get(i); - if (segment.isAssignment()) { - if (!relationRegistry.isDelegation(segment.getRelation())) { - // backRelations.add(segment.getRelation()); - assignmentsSeen++; - LOGGER.trace("Going back {}: relation at assignment -{} (position -{}): {}", summaryBackwards, - assignmentsSeen, assignmentPath.size() - i, segment.getRelation()); - } - } else { - AssignmentType inducement = segment.getAssignment(ctx.evaluateOld); // for i>0 returns value regardless of evaluateOld - for (OrderConstraintsType constraint : inducement.getOrderConstraint()) { - if (constraint.getResetOrder() != null && constraint.getRelation() != null) { - LOGGER.debug("Going back {}: an inducement with non-summary resetting constraint found" - + " in the chain (at position -{}): {} in {}", summaryBackwards, assignmentPath.size()-i, - constraint, segment); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } - } - if (segment.getLastEqualOrderSegmentIndex() != null) { - i = segment.getLastEqualOrderSegmentIndex(); - continue; - } - } - i--; - } - nextSegment.setLastEqualOrderSegmentIndex(i); - evaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrder()); - targetEvaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrderForTarget()); - } else { - // summaryBackwards is 0 - nothing to change - } - for (OrderConstraintsType constraint : constraints) { - if (constraint.getRelation() != null && constraint.getResetOrder() != null) { - LOGGER.warn("Ignoring resetOrder (with a value of {} for {}) because summary order was already moved backwards by {} to {}: {}", - constraint.getResetOrder(), constraint.getRelation(), summaryBackwards, - evaluationOrderHolder.getValue().getSummaryOrder(), constraint); - } - } - } else { - EvaluationOrder beforeChange = evaluationOrderHolder.getValue().clone(); - for (OrderConstraintsType constraint : constraints) { - if (constraint.getResetOrder() != null) { - assert constraint.getRelation() != null; // already processed above - int currentOrder = evaluationOrderHolder.getValue().getMatchingRelationOrder(constraint.getRelation()); - int newOrder = constraint.getResetOrder(); - if (newOrder > currentOrder) { - LOGGER.warn("Cannot increase evaluation order for {} from {} to {}: {}", constraint.getRelation(), - currentOrder, newOrder, constraint); - } else if (newOrder < currentOrder) { - evaluationOrderHolder.setValue(evaluationOrderHolder.getValue().resetOrder(constraint.getRelation(), newOrder)); - LOGGER.trace("Reset order for {} from {} to {} -> {}", constraint.getRelation(), currentOrder, newOrder, evaluationOrderHolder.getValue()); - } else { - LOGGER.trace("Keeping order for {} at {} -> {}", constraint.getRelation(), currentOrder, evaluationOrderHolder.getValue()); - } - } - } - Map difference = beforeChange.diff(evaluationOrderHolder.getValue()); - targetEvaluationOrderHolder.setValue(targetEvaluationOrderHolder.getValue().applyDifference(difference)); - } - - if (evaluationOrderHolder.getValue().getSummaryOrder() <= 0) { - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - } - if (!targetEvaluationOrderHolder.getValue().isValid()) { - // some extreme cases like the one described in TestAssignmentProcessor2.test520 - makeUndefined(targetEvaluationOrderHolder); - } - if (!evaluationOrderHolder.getValue().isValid()) { - throw new AssertionError("Resulting evaluation order path is invalid: " + evaluationOrderHolder.getValue()); - } - } - - @SafeVarargs - private final void makeUndefined(Holder... holders) { // final because of SafeVarargs (on java8) - for (Holder holder : holders) { - holder.setValue(EvaluationOrderImpl.UNDEFINED); - } - } - - private void addToMembershipLists(AssignmentHolderType targetToAdd, QName relation, EvaluationContext ctx) { - PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); - valueToAdd.setObject(targetToAdd.asPrismObject()); - valueToAdd.setTargetType(ObjectTypes.getObjectType(targetToAdd.getClass()).getTypeQName()); - valueToAdd.setRelation(relation); - valueToAdd.setTargetName(targetToAdd.getName().toPolyString()); - - addToMembershipLists(valueToAdd, targetToAdd.getClass(), relation, targetToAdd, ctx); - } - - private void setAsTenantRef(AssignmentHolderType targetToSet, EvaluationContext ctx) { - if (targetToSet instanceof OrgType) { - if (BooleanUtils.isTrue(((OrgType)targetToSet).isTenant()) && ctx.evalAssignment.getTenantOid() == null) { - if (ctx.assignmentPath.hasOnlyOrgs()) { - ctx.evalAssignment.setTenantOid(targetToSet.getOid()); - } - } - } - } - - private void addToMembershipLists(ObjectReferenceType referenceToAdd, QName relation, EvaluationContext ctx) { - PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); - valueToAdd.setOid(referenceToAdd.getOid()); - valueToAdd.setTargetType(referenceToAdd.getType()); - valueToAdd.setRelation(relation); - valueToAdd.setTargetName(referenceToAdd.getTargetName()); - - Class targetClass = ObjectTypes.getObjectTypeFromTypeQName(referenceToAdd.getType()).getClassDefinition(); - addToMembershipLists(valueToAdd, targetClass, relation, referenceToAdd, ctx); - } - - private void addToMembershipLists(PrismReferenceValue valueToAdd, Class targetClass, QName relation, - Object targetDesc, EvaluationContext ctx) { - if (ctx.assignmentPath.containsDelegation(ctx.evaluateOld, relationRegistry)) { - addIfNotThere(ctx.evalAssignment.getDelegationRefVals(), valueToAdd, "delegationRef", targetDesc); - } else { - if (AbstractRoleType.class.isAssignableFrom(targetClass)) { - addIfNotThere(ctx.evalAssignment.getMembershipRefVals(), valueToAdd, "membershipRef", targetDesc); - } - } - if (OrgType.class.isAssignableFrom(targetClass) && relationRegistry.isStoredIntoParentOrgRef(relation)) { - addIfNotThere(ctx.evalAssignment.getOrgRefVals(), valueToAdd, "orgRef", targetDesc); - } - if (ArchetypeType.class.isAssignableFrom(targetClass)) { - addIfNotThere(ctx.evalAssignment.getArchetypeRefVals(), valueToAdd, "archetypeRef", targetDesc); - } - } - - private void addIfNotThere(Collection collection, PrismReferenceValue valueToAdd, String collectionName, - Object targetDesc) { - if (!collection.contains(valueToAdd)) { - LOGGER.trace("Adding target {} to {}", targetDesc, collectionName); - collection.add(valueToAdd); - } else { - LOGGER.trace("Would add target {} to {}, but it's already there", targetDesc, collectionName); - } - } - - 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. - return mode == PlusMinusZero.ZERO || mode == PlusMinusZero.PLUS; - } - - private boolean isChanged(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. - return mode == PlusMinusZero.PLUS || mode == PlusMinusZero.MINUS; - } - - private void checkRelationWithTarget(AssignmentPathSegmentImpl segment, AssignmentHolderType targetType, QName relation) - throws SchemaException { - if (targetType instanceof AbstractRoleType || targetType instanceof TaskType) { //TODO: - // OK, just go on - } else if (targetType instanceof UserType) { - if (!relationRegistry.isDelegation(relation)) { - throw new SchemaException("Unsupported relation " + relation + " for assignment of target type " + targetType + " in " + segment.sourceDescription); - } - } else { - throw new SchemaException("Unknown assignment target type " + targetType + " in " + segment.sourceDescription); - } - } - - private boolean isInducementApplicableToFocusType(QName inducementFocusType) throws SchemaException { - if (inducementFocusType == null) { - return true; - } - Class inducementFocusClass = prismContext.getSchemaRegistry().determineCompileTimeClass(inducementFocusType); - if (inducementFocusClass == null) { - throw new SchemaException("Could not determine class for " + inducementFocusType); - } - if (lensContext.getFocusClass() == null) { - // should not occur; it would be probably safe to throw an exception here - LOGGER.error("No focus class in lens context; inducement targeted at focus type {} will not be applied:\n{}", - inducementFocusType, lensContext.debugDump()); - return false; - } - return inducementFocusClass.isAssignableFrom(lensContext.getFocusClass()); - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean isAllowedByLimitations(AssignmentPathSegment segment, AssignmentType nextAssignment, EvaluationContext ctx) { - AssignmentType currentAssignment = segment.getAssignment(ctx.evaluateOld); - AssignmentSelectorType targetLimitation = currentAssignment.getLimitTargetContent(); - if (isDeputyDelegation(nextAssignment)) { // delegation of delegation - return targetLimitation != null && BooleanUtils.isTrue(targetLimitation.isAllowTransitive()); - } else { - // As for the case of targetRef==null: we want to pass target-less assignments (focus mappings, policy rules etc) - // from the delegator to delegatee. To block them we should use order constraints (but also for assignments?). - return targetLimitation == null || nextAssignment.getTargetRef() == null || - FocusTypeUtil.selectorMatches(targetLimitation, nextAssignment, prismContext); - } - } - - private boolean isDeputyDelegation(AssignmentType assignmentType) { - ObjectReferenceType targetRef = assignmentType.getTargetRef(); - return targetRef != null && relationRegistry.isDelegation(targetRef.getRelation()); - } - - private Authorization createAuthorization(AuthorizationType authorizationType, String sourceDesc) { - Authorization authorization = new Authorization(authorizationType); - authorization.setSourceDescription(sourceDesc); - return authorization; - } - - private void assertSourceNotNull(ObjectType source, EvaluatedAssignment assignment) { - if (source == null) { - throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignment+")"); - } - } - - private void assertSourceNotNull(ObjectType source, ItemDeltaItem,PrismContainerDefinition> assignmentIdi) { - if (source == null) { - throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignmentIdi.getAnyItem()+")"); - } - } - - private AssignmentType getAssignmentType(AssignmentPathSegmentImpl segment, EvaluationContext ctx) { - return segment.getAssignment(ctx.evaluateOld); - } - - private void checkSchema(AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException { - AssignmentType assignmentType = getAssignmentType(segment, ctx); - //noinspection unchecked - PrismContainerValue assignmentContainerValue = assignmentType.asPrismContainerValue(); - PrismContainerable assignmentContainer = assignmentContainerValue.getParent(); - if (assignmentContainer == null) { - throw new SchemaException("The assignment "+assignmentType+" does not have a parent in "+segment.sourceDescription); - } - if (assignmentContainer.getDefinition() == null) { - throw new SchemaException("The assignment "+assignmentType+" does not have definition in "+segment.sourceDescription); - } - PrismContainer extensionContainer = assignmentContainerValue.findContainer(AssignmentType.F_EXTENSION); - if (extensionContainer != null) { - if (extensionContainer.getDefinition() == null) { - throw new SchemaException("Extension does not have a definition in assignment "+assignmentType+" in "+segment.sourceDescription); - } - - if (extensionContainer.getValue().getItems() == null) { - throw new SchemaException("Extension without items in assignment " + assignmentType + " in " + segment.sourceDescription + ", empty extension tag?"); - } - - for (Item item: extensionContainer.getValue().getItems()) { - if (item == null) { - throw new SchemaException("Null item in extension in assignment "+assignmentType+" in "+segment.sourceDescription); - } - if (item.getDefinition() == null) { - throw new SchemaException("Item "+item+" has no definition in extension in assignment "+assignmentType+" in "+segment.sourceDescription); - } - } - } - } - - private void setEvaluatedAssignmentTarget(AssignmentPathSegmentImpl segment, - @NotNull List> targets, EvaluationContext ctx) { - assert ctx.evalAssignment.getTarget() == null; - if (targets.size() > 1) { - throw new UnsupportedOperationException("Multiple targets for direct focus assignment are not supported: " + segment.getAssignment(ctx.evaluateOld)); - } else if (!targets.isEmpty()) { - ctx.evalAssignment.setTarget(targets.get(0)); - } - } - - private PrismValueDeltaSetTriple> evaluateCondition(MappingType condition, - ObjectType source, AssignmentPathVariables assignmentPathVariables, String contextDescription, EvaluationContext ctx, - OperationResult result) throws ExpressionEvaluationException, - ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { - MappingImpl.Builder,PrismPropertyDefinition> builder = mappingFactory.createMappingBuilder(); - builder = builder.mappingType(condition) - .mappingKind(MappingKindType.ASSIGNMENT_CONDITION) - .contextDescription(contextDescription) - .sourceContext(focusOdo) - .originType(OriginType.ASSIGNMENTS) - .originObject(source) - .defaultTargetDefinition(prismContext.definitionFactory().createPropertyDefinition(CONDITION_OUTPUT_NAME, DOMUtil.XSD_BOOLEAN)) - .addVariableDefinitions(getAssignmentEvaluationVariables()) - .rootNode(focusOdo) - .addVariableDefinition(ExpressionConstants.VAR_USER, focusOdo) - .addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusOdo) - .addAliasRegistration(ExpressionConstants.VAR_USER, null) - .addAliasRegistration(ExpressionConstants.VAR_FOCUS, null) - .addVariableDefinition(ExpressionConstants.VAR_SOURCE, source, ObjectType.class) - .addVariableDefinition(ExpressionConstants.VAR_ASSIGNMENT_EVALUATOR, this, AssignmentEvaluator.class); - builder = LensUtil.addAssignmentPathVariables(builder, assignmentPathVariables, prismContext); - - MappingImpl, PrismPropertyDefinition> mapping = builder.build(); - - mappingEvaluator.evaluateMapping(mapping, lensContext, ctx.task, result); - - return mapping.getOutputTriple(); - } - - @Nullable - private QName getRelation(AssignmentType assignmentType) { - return assignmentType.getTargetRef() != null ? - relationRegistry.normalizeRelation(assignmentType.getTargetRef().getRelation()) : null; - } - - /* - * This "isMemberOf iteration" section is an experimental implementation of MID-5366. - * - * The main idea: In role/assignment/inducement conditions we test the membership not by querying roleMembershipRef - * on focus object but instead we call assignmentEvaluator.isMemberOf() method. This method - by default - inspects - * roleMembershipRef but also records the check result. Later, when assignment evaluation is complete, AssignmentProcessor - * will ask if all of these check results are still valid. If they are not, it requests re-evaluation of all the assignments, - * using updated check results. - * - * This should work unless there are some cyclic dependencies (like "this sentence is a lie" paradox). - */ - public boolean isMemberOf(String targetOid) { - if (targetOid == null) { - throw new IllegalArgumentException("Null targetOid in isMemberOf call"); - } - MemberOfInvocation existingInvocation = findInvocation(targetOid); - if (existingInvocation != null) { - return existingInvocation.result; - } else { - boolean result = computeIsMemberOfDuringEvaluation(targetOid); - memberOfInvocations.add(new MemberOfInvocation(targetOid, result)); - return result; - } - } - - private MemberOfInvocation findInvocation(String targetOid) { - List matching = memberOfInvocations.stream() - .filter(invocation -> targetOid.equals(invocation.targetOid)) - .collect(Collectors.toList()); - if (matching.isEmpty()) { - return null; - } else if (matching.size() == 1) { - return matching.get(0); - } else { - throw new IllegalStateException("More than one matching MemberOfInvocation for targetOid='" + targetOid + "': " + matching); - } - } - - private boolean computeIsMemberOfDuringEvaluation(String targetOid) { - // TODO Or should we consider evaluateOld? - PrismObject focus = focusOdo.getNewObject(); - return focus != null && containsMember(focus.asObjectable().getRoleMembershipRef(), targetOid); - } - - public boolean isMemberOfInvocationResultChanged(DeltaSetTriple> evaluatedAssignmentTriple) { - if (!memberOfInvocations.isEmpty()) { - // Similar code is in AssignmentProcessor.processMembershipAndDelegatedRefs -- check that if changing the business logic - List membership = evaluatedAssignmentTriple.getNonNegativeValues().stream() - .filter(EvaluatedAssignmentImpl::isValid) - .flatMap(evaluatedAssignment -> evaluatedAssignment.getMembershipRefVals().stream()) - .map(ref -> ObjectTypeUtil.createObjectRef(ref, false)) - .collect(Collectors.toList()); - LOGGER.trace("Computed new membership: {}", membership); - return updateMemberOfInvocations(membership); - } else { - return false; - } - } - - private boolean updateMemberOfInvocations(List newMembership) { - boolean changed = false; - for (MemberOfInvocation invocation : memberOfInvocations) { - boolean newResult = containsMember(newMembership, invocation.targetOid); - if (newResult != invocation.result) { - LOGGER.trace("Invocation result changed for {} - new one is '{}'", invocation, newResult); - invocation.result = newResult; - changed = true; - } - } - return changed; - } - - // todo generalize a bit (e.g. by including relation) - private boolean containsMember(List membership, String targetOid) { - return membership.stream().anyMatch(ref -> targetOid.equals(ref.getOid())); - } - - public static final class Builder { - private RepositoryService repository; - private ObjectDeltaObject focusOdo; - private LensContext lensContext; - private String channel; - private ObjectResolver objectResolver; - private SystemObjectCache systemObjectCache; - private RelationRegistry relationRegistry; - private PrismContext prismContext; - private MappingFactory mappingFactory; - private ActivationComputer activationComputer; - private XMLGregorianCalendar now; - private boolean loginMode = false; - private PrismObject systemConfiguration; - private MappingEvaluator mappingEvaluator; - private ContextLoader contextLoader; - - public Builder() { - } - - public Builder repository(RepositoryService val) { - repository = val; - return this; - } - - public Builder focusOdo(ObjectDeltaObject val) { - focusOdo = val; - return this; - } - - public Builder lensContext(LensContext val) { - lensContext = val; - return this; - } - - public Builder channel(String val) { - channel = val; - return this; - } - - public Builder objectResolver(ObjectResolver val) { - objectResolver = val; - return this; - } - - public Builder systemObjectCache(SystemObjectCache val) { - systemObjectCache = val; - return this; - } - - public Builder relationRegistry(RelationRegistry val) { - relationRegistry = val; - return this; - } - - public Builder prismContext(PrismContext val) { - prismContext = val; - return this; - } - - public Builder mappingFactory(MappingFactory val) { - mappingFactory = val; - return this; - } - - public Builder activationComputer(ActivationComputer val) { - activationComputer = val; - return this; - } - - public Builder now(XMLGregorianCalendar val) { - now = val; - return this; - } - - public Builder loginMode(boolean val) { - loginMode = val; - return this; - } - - public Builder systemConfiguration(PrismObject val) { - systemConfiguration = val; - return this; - } - - public Builder mappingEvaluator(MappingEvaluator val) { - mappingEvaluator = val; - return this; - } - - public Builder contextLoader(ContextLoader val) { - contextLoader = val; - return this; - } - - public AssignmentEvaluator build() { - return new AssignmentEvaluator<>(this); - } - } - - private static class MemberOfInvocation { - private final String targetOid; - private boolean result; - - private MemberOfInvocation(String targetOid, boolean result) { - this.targetOid = targetOid; - this.result = result; - } - - @Override - public String toString() { - return "MemberOfInvocation{" + - "targetOid='" + targetOid + '\'' + - ", result=" + result + - '}'; - } - } -} 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 deleted file mode 100644 index 69ed81a6b72..00000000000 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java +++ /dev/null @@ -1,666 +0,0 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; -import com.evolveum.midpoint.model.api.context.EvaluationOrder; -import com.evolveum.midpoint.prism.PrismContainer; -import com.evolveum.midpoint.prism.PrismContainerDefinition; -import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.prism.xml.XsdTypeMapper; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SystemException; -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; - -import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerable; - -/** - * 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 - * is stored. - * - * @author semancik - * - */ -@SuppressWarnings("WeakerAccess") -public class AssignmentPathSegmentImpl implements AssignmentPathSegment { - - private static final Trace LOGGER = TraceManager.getTrace(AssignmentPathSegmentImpl.class); - - @NotNull private final RelationRegistry relationRegistry; - @NotNull private final PrismContext prismContext; - - // "assignment path segment" information - - final AssignmentHolderType source; // we avoid "getter" notation for some final fields to simplify client code - private final ItemDeltaItem,PrismContainerDefinition> assignmentIdi; - private final boolean isAssignment; // false means inducement - private QName relation; - private ObjectType target; - - // assignment evaluation information - - final String sourceDescription; // Human readable text describing the source (for error messages) - private boolean pathToSourceValid; // Is the whole path to *source* valid, i.e. enabled (meaning activation.effectiveStatus)? - private boolean validityOverride = false; // Should we evaluate content of the assignment even if it's not valid i.e. enabled? - // This is set to true on the first assignment in the chain. - - // Last segment with the same evaluation order. Used for higher-order (summary-rewriting) inducements. - // See e.g. TestAssignmentProcessor2.test600. - - private Integer lastEqualOrderSegmentIndex; - - /** - * Assignments and inducements can carry constructions, focus mappings, and policy rules. - * We can call these "assignment/inducement payload", or "payload" for short. - * - * When looking at assignments/inducements in assignment path, payload of some assignments/inducements will be collected - * to focus, while payload from others will be not. How we know what to collect? - * - * For assignments/inducements belonging directly to the focus, we take payload from all the assignments. Not from inducements. - * For assignments/inducements belonging to roles (assigned to focus), we take payload from all the inducements of order 1. - * For assignments/inducements belonging to meta-roles (assigned to roles), we take payload from all the inducements of order 2. - * And so on. (It is in fact a bit more complicated, as described below when discussing relations. But OK for the moment.) - * - * To know whether to collect payload from assignment/inducement, i.e. from assignment path segment, we - * define "isMatchingOrder" attribute - and collect only if value of this attribute is true. - * - * How we compute this attribute? - * - * Each assignment path segment has an evaluation order. First assignment has an evaluation order of 1, second - * assignment has an order of 2, etc. Order of a segment can be seen as the number of assignments segments in the path - * (including itself). And, for "real" assignments (i.e. not inducements), we collect payload from assignment segments - * of order 1. - * - * But what about inducements? There are two - somewhat related - questions: - * - * 1. How we compute isMatchingOrder for inducement segments? - * 2. How we compute evaluation order for inducement segments? - * - * As for #1: To compute isMatchingOrder, we must take evaluation order of the _previous_ segment, and compare - * it with the order (or, more generally, order constraints) of the inducement. If they match, we say that inducement - * has matching order. - * - * As for #2: It is not usual that inducements have targets (roles) with another assignments, i.e. that evaluation continues - * after an inducement segment. But it definitely could happen. We can look at it this way: inducement is something like - * a "shortcut" that creates an assignment where no assignment was before. E.g. if we have R1 -A-> MR1 -I-> MR2, - * the "newly created" assignment is R1 -A-> MR2. I.e. as if the " -A-> MR1 -I-> " part was just replaced by " -A-> ". - * If the inducement is of higher order, even more assignments are "cut out". From R1 -A-> MR1 -A-> MMR1 -(I2)-> MR2 - * we have R1 -A-> MR2, i.e. we cut out " -A-> MR1 -A-> MMR1 -(I2)-> " and replaced it by " -A-> ". - * So it looks like that when computing new evaluation order of an inducement, we have to "go back" few steps - * through the assignment path. - * - * Such situation can also easily occur when org structures are used. As depicted in TestAssignmentProcessor2.test500 - * and later, imagine this: - * - * Org1 -----I----+ Org2 -----I----+ - * ^ | (orderConstraints 1..N) ^ | (orderConstraints: manager: 1) - * | | | | - * | V | V - * Org11 Admin Org21 Admin - * ^ ^ - * | (manager) - * | | - * jack jack - * - * In order to resolve such cases, we created the "resetOrder" attribute. It can be applied either to - * summary order, or to one or more specific relations (please, don't specify it for both summary order - * and specific relations!) - * - * In the above examples, we could specify e.g. resetOrder=1 for summary order (for both left and right situation). - * For the right one, we could instead specify resetOrder=0 for org:manager relation; although the former solution - * is preferable. - * - * By simply specifying inducement order greater than 1 (e.g. 5) without any specific order constraints, - * we implicitly provide resetOrder instruction for summary order that points order-1 levels back (i.e. 4 levels back - * for order=5). - * - * Generally, it is preferred to use resetOrder for summary order. It works well with both normal and target - * evaluation order. When resetting individual components, target evaluation order can have problems, as shown - * in TestEvaluationProcessor2.test520. - * - * ---- - * - * Because evaluation order can "increase" and "decrease", it is possible that it goes to zero or below, and then - * increase back to positive numbers. Is that OK? Imagine this: - * - * (Quite an ugly example, but such things might exist.) - * - * Metarole:CrewMember ----I----+ Metarole:Sailors - * A | A - * | | | - * | V | - * Pirate Sailor - * A - * | - * | - * jack - * - * When evaluating jack->Pirate assignment, it is OK to collect from everything (Pirate, CrewMember, Sailor, Sailors). - * - * But when evaluating Pirate as a focal object (forget about jack for the moment), we have Pirate->CrewMember assignment. - * For this assignment we should ignore payload from Sailor (obviously, because the order is not matching), but from - * Metarole:Sailors as well. Payload from Sailors is not connected to Pirate in any meaningful way. (For example, if - * Sailors prescribes an account construction for Sailor, it is of no use to collect this construction when evaluating - * Pirate as focal object!) - * - * Evaluating relations - * ==================== - * - * With the arrival of various kinds of relations (deputy, manager, approver, owner) things got a bit complicated. - * For instance, the deputy relation cannot be used to determine evaluation order in a usual way, because if - * we have this situation: - * - * Pirate -----I-----> Sailor - * A - * | (default) - * | - * jack - * A - * | (deputy) - * | - * barbossa - * - * we obviously want to have barbossa to obtain all payload from roles Pirate and Sailor: exactly as jack does. - * So, the evaluation order of " barbossa -A-> jack -A-> Pirate " should be 1, not two. So deputy is a very special - * kind of relation, that does _not_ increase the traditional evaluation order. But we really want to record - * the fact that the deputy is on the assignment path; therefore, besides traditional "scalar" evaluation order - * (called "summaryOrder") we maintain evaluation orders for each relation separately. In the above example, - * the evaluation orders would be: - * barbossa--->jack summary: 0, deputy: 1, default: 0 - * jack--->Pirate summary: 1, deputy: 1, default: 1 - * Pirate-I->Sailor summary: 1, deputy: 1, default: 1 (because the inducement has a default order of 1) - * - * When we determine matchingOrder for an inducement (question #1 above), we can ask about summary order, - * as well as about individual components (deputy and default, in this case). - * - * Actually, we have three categories of relations (see MID-3581): - * - * - membership relations: apply membership references, payload, authorizations, gui config - * - delegation relations: similar to membership, bud different handling of order - * - other relations: apply membership references but in limited way; payload, authorizations and gui config - * are - by default - not applied. - * - * Currently, membership relations are: default (i.e. member), manager, and meta. - * Delegation: deputy. - * Other: approver, owner, and custom ones. - * - * As for the "other" relations: they bring membership information, but limited to the target that is directly - * assigned. So if jack is an approver for role Landluber, his roleMembershipRef will contain ref to Landluber - * but not e.g. to role Earthworm that is induced by Landluber. In a similar way, payload from targets assigned - * by "other" relations is not collected. - * - * Both of this can be overridden by using specific orderConstraints on particular inducement. - * 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. - * - * Note: authorizations and gui config information are not considered to be a payload, because they are not - * part of an assignment/inducement - they are part of a role. In the future we might move them into - * assignments/inducements. - * - * Collecting target policy rules - * ============================== - * - * Special consideration must be given when collecting target policy rules, i.e. rules that are attached to - * assignment targets. Such rules are typically attached to roles that are being assigned. So let's consider this: - * - * rule1 (e.g. assignment approval policy rule) - * A - * | - * | - * Pirate - * A - * | - * | - * jack - * - * 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 isMatchingOrderForTarget - * that marks all segments (assignments/inducements) that contain policy rules relevant to the evaluated assignment's target. - * - * The computation is done by maintaining two evaluationOrders: - * - plain evaluationOrder that is related to the focus object [starts from ZERO.advance(first assignment relation)] - * - special evaluationOrderForTarget that is related to the target of the assignment being evaluated [starts from ZERO] - * - * Finally, how we distinguish between "this target" and "other targets" policy rules? Current approach - * is like this: count the number of times the evaluation order for target reached ZERO level. First encounter with - * that level is on "this target". And we assume that each subsequent marks a target that is among "others". - * - * See AssignmentEvaluator.appliesDirectly - */ - private Boolean isMatchingOrder = null; - private EvaluationOrder evaluationOrder; - private Boolean isMatchingOrderForTarget = null; - private EvaluationOrder evaluationOrderForTarget; - - private boolean processMembership = false; - - private ObjectType varThisObject; - - private boolean evaluatedForOld; - - AssignmentPathSegmentImpl(AssignmentHolderType source, String sourceDescription, - ItemDeltaItem, PrismContainerDefinition> assignmentIdi, - boolean isAssignment, boolean evaluatedForOld, @NotNull RelationRegistry relationRegistry, - @NotNull PrismContext prismContext) { - this.source = source; - this.sourceDescription = sourceDescription; - if (assignmentIdi.getDefinition() == null) { - throw new IllegalArgumentException("Attempt to set segment assignment IDI without a definition"); - } - this.assignmentIdi = assignmentIdi; - this.isAssignment = isAssignment; - this.evaluatedForOld = evaluatedForOld; - this.relationRegistry = relationRegistry; - this.prismContext = prismContext; - } - - public AssignmentPathSegmentImpl(AssignmentHolderType source, String sourceDescription, AssignmentType assignment, boolean isAssignment, - RelationRegistry relationRegistry, PrismContext prismContext) { - this(source, sourceDescription, createAssignmentIdi(assignment), isAssignment, false, relationRegistry, prismContext); - } - - private static ItemDeltaItem, PrismContainerDefinition> createAssignmentIdi( - AssignmentType assignment) { - try { - //noinspection unchecked,rawtypes - return new ItemDeltaItem<>(LensUtil.createAssignmentSingleValueContainer(assignment), assignment.asPrismContainerValue().getDefinition()); - } catch (SchemaException e) { - // should not really occur! - throw new SystemException("Couldn't create assignment IDI: " + e.getMessage(), e); - } - } - - @Override - public boolean isAssignment() { - return isAssignment; - } - - public ItemDeltaItem,PrismContainerDefinition> getAssignmentIdi() { - return assignmentIdi; - } - - public AssignmentType getAssignment(boolean evaluateOld) { - return asContainerable(assignmentIdi.getSingleValue(evaluateOld)); - } - - public AssignmentType getAssignment() { - return getAssignment(evaluatedForOld); - } - - public AssignmentType getAssignmentAny() { - if (assignmentIdi == null) { - return null; - } - return assignmentIdi.getItemNew() != null ? getAssignment(false) : getAssignment(true); - } - - @Override - public AssignmentType getAssignmentNew() { - if (assignmentIdi == null || assignmentIdi.getItemNew() == null || assignmentIdi.getItemNew().isEmpty()) { - return null; - } - return ((PrismContainer) assignmentIdi.getItemNew()).getRealValue(); - } - - @Override - public QName getRelation() { - return relation; - } - - public void setRelation(QName relation) { - this.relation = relation; - } - - @Override - public ObjectType getTarget() { - return target; - } - - public void setTarget(ObjectType target) { - this.target = target; - } - - @Override - public AssignmentHolderType getSource() { - return source; - } - - public String getSourceDescription() { - return sourceDescription; - } - - public String getTargetDescription() { - if (target != null) { - return target + " in " + sourceDescription; - } else { - return "(target) in " + sourceDescription; - } - } - - public boolean isPathToSourceValid() { - return pathToSourceValid; - } - - public void setPathToSourceValid(boolean pathToSourceValid) { - this.pathToSourceValid = pathToSourceValid; - } - - public boolean isValidityOverride() { - return validityOverride; - } - - @SuppressWarnings("SameParameterValue") - public void setValidityOverride(boolean validityOverride) { - this.validityOverride = validityOverride; - } - - public EvaluationOrder getEvaluationOrder() { - return evaluationOrder; - } - - public void setEvaluationOrder(EvaluationOrder evaluationOrder) { - setEvaluationOrder(evaluationOrder, null); - } - - public void setEvaluationOrder(EvaluationOrder evaluationOrder, Boolean matchingOrder) { - this.evaluationOrder = evaluationOrder; - this.isMatchingOrder = matchingOrder; - } - - 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() { - return varThisObject; - } - - public void setOrderOneObject(ObjectType varThisObject) { - this.varThisObject = varThisObject; - } - - public boolean isProcessMembership() { - return processMembership; - } - - public void setProcessMembership(boolean processMembership) { - this.processMembership = processMembership; - } - - @Override - public boolean isMatchingOrder() { - if (isMatchingOrder == null) { - isMatchingOrder = computeMatchingOrder(evaluationOrder, getAssignment()); - } - return isMatchingOrder; - } - - @Override - public boolean isMatchingOrderForTarget() { - if (isMatchingOrderForTarget == null) { - isMatchingOrderForTarget = computeMatchingOrder(evaluationOrderForTarget, getAssignment()); - } - return isMatchingOrderForTarget; - } - - 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; - List extraRelations = new ArrayList<>(evaluationOrder.getExtraRelations()); - if (assignmentOrder == null && assignmentOrderConstraint.isEmpty()) { - // compatibility - rv = evaluationOrder.getSummaryOrder() == 1; - } else { - rv = true; - if (assignmentOrder != null) { - if (evaluationOrder.getSummaryOrder() != assignmentOrder) { - rv = false; - } - } - for (OrderConstraintsType orderConstraint : assignmentOrderConstraint) { - if (!isMatchingConstraint(orderConstraint, evaluationOrder)) { - rv = false; - break; - } - extraRelations.removeIf(r -> QNameUtil.match(r, orderConstraint.getRelation())); - } - } - // TODO this is to be reconsidered -- why do we consider assignment of relation e.g. approver non-matching? - if (!extraRelations.isEmpty()) { - rv = false; - } - LOGGER.trace("computeMatchingOrder => {}, for assignment.order={}, assignment.orderConstraint={}, evaluationOrder={}, remainingExtraRelations={}", - rv, assignmentOrder, assignmentOrderConstraint, evaluationOrder, extraRelations); - return rv; - } - - private static boolean isMatchingConstraint(OrderConstraintsType orderConstraint, EvaluationOrder evaluationOrder) { - int evaluationOrderInt = evaluationOrder.getMatchingRelationOrder(orderConstraint.getRelation()); - if (orderConstraint.getOrder() != null) { - return orderConstraint.getOrder() == evaluationOrderInt; - } else { - int orderMin = 1; - int orderMax = 1; - if (orderConstraint.getOrderMin() != null) { - orderMin = XsdTypeMapper.multiplicityToInteger(orderConstraint.getOrderMin()); - } - if (orderConstraint.getOrderMax() != null) { - orderMax = XsdTypeMapper.multiplicityToInteger(orderConstraint.getOrderMax()); - } - return XsdTypeMapper.isMatchingMultiplicity(evaluationOrderInt, orderMin, orderMax); - } - } - - @Override - public boolean isDelegation() { - return relationRegistry.isDelegation(relation); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((assignmentIdi == null) ? 0 : assignmentIdi.hashCode()); - result = prime * result + ((source == null) ? 0 : source.hashCode()); - result = prime * result + ((target == null) ? 0 : target.hashCode()); - return result; - } - - @SuppressWarnings("RedundantIfStatement") - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - AssignmentPathSegmentImpl other = (AssignmentPathSegmentImpl) obj; - if (assignmentIdi == null) { - if (other.assignmentIdi != null) - return false; - } else if (!assignmentIdi.equals(other.assignmentIdi)) - return false; - if (source == null) { - if (other.source != null) - return false; - } else if (!source.equals(other.source)) - return false; - if (target == null) { - if (other.target != null) - return false; - } else if (!target.equals(other.target)) - return false; - return true; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("AssignmentPathSegment("); - shortDump(sb); - sb.append(")"); - return sb.toString(); - } - - @Override - public void shortDump(StringBuilder sb) { - evaluationOrder.shortDump(sb); - if (isMatchingOrder()) { // here is a side effect but most probably it's harmless - sb.append("(match)"); - } - if (isMatchingOrderForTarget()) { // the same here - sb.append("(match-target)"); - } - sb.append(": "); - sb.append(source).append(" "); - if (!isAssignment) { - sb.append("inducement "); - } - PrismContainer assignment = (PrismContainer) assignmentIdi.getAnyItem(); - AssignmentType assignmentType = assignment != null ? assignment.getRealValue() : null; - if (assignmentType != null) { - sb.append("id:").append(assignmentType.getId()).append(" "); - if (assignmentType.getConstruction() != null) { - sb.append("Constr '").append(assignmentType.getConstruction().getDescription()).append("' "); - } - if (assignmentType.getFocusMappings() != null) { - sb.append("FMappings (").append(assignmentType.getFocusMappings().getMapping().size()).append(") "); - } - if (assignmentType.getPolicyRule() != null) { - sb.append("Rule '").append(assignmentType.getPolicyRule().getName()).append("' "); - } - } - ObjectReferenceType targetRef = assignmentType != null ? assignmentType.getTargetRef() : null; - if (target != null || targetRef != null) { - sb.append("-["); - if (relation != null) { - sb.append(relation.getLocalPart()); - } - sb.append("]-> "); - if (target != null) { - sb.append(target); - } else { - sb.append(ObjectTypeUtil.toShortString(targetRef, true)); - } - } - // TODO eventually remove this - if (lastEqualOrderSegmentIndex != null) { - sb.append(", lastEqualOrder: ").append(lastEqualOrderSegmentIndex); - } - } - - @Override - public String debugDump(int indent) { - StringBuilder sb = new StringBuilder(); - DebugUtil.debugDumpLabel(sb, "AssignmentPathSegment", indent); - sb.append("\n"); - DebugUtil.debugDumpWithLabelLn(sb, "source", source==null?"null":source.toString(), indent + 1); - String assignmentOrInducement = isAssignment ? "assignment" : "inducement"; - if (assignmentIdi != null) { - DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement + " old", String.valueOf(assignmentIdi.getItemOld()), indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement + " delta", String.valueOf(assignmentIdi.getDelta()), indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement + " new", String.valueOf(assignmentIdi.getItemNew()), indent + 1); - } else { - DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement, "null", indent + 1); - } - 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, "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); - DebugUtil.debugDumpWithLabelLn(sb, "processMembership", processMembership, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "lastEqualOrderSegmentIndex", lastEqualOrderSegmentIndex, indent + 1); - DebugUtil.debugDumpWithLabel(sb, "varThisObject", varThisObject==null?"null":varThisObject.toString(), indent + 1); - return sb.toString(); - } - - @NotNull - @Override - public AssignmentPathSegmentType toAssignmentPathSegmentType(boolean includeAssignmentsContent) { - AssignmentPathSegmentType rv = new AssignmentPathSegmentType(); - AssignmentType assignment = getAssignment(evaluatedForOld); // a bit of hack, but probably ok for now - if (assignment != null) { - if (includeAssignmentsContent) { - rv.setAssignment(assignment.clone()); - } - rv.setAssignmentId(assignment.getId()); - } - if (source != null) { - rv.setSourceRef(ObjectTypeUtil.createObjectRef(source, prismContext)); - rv.setSourceDisplayName(ObjectTypeUtil.getDisplayName(source)); - } - if (target != null) { - rv.setTargetRef(ObjectTypeUtil.createObjectRef(target, prismContext)); - rv.setTargetDisplayName(ObjectTypeUtil.getDisplayName(target)); - } - rv.setMatchingOrder(isMatchingOrder()); - rv.setIsAssignment(isAssignment); - return rv; - } - - public Integer getLastEqualOrderSegmentIndex() { - return lastEqualOrderSegmentIndex; - } - - public void setLastEqualOrderSegmentIndex(Integer lastEqualOrderSegmentIndex) { - this.lastEqualOrderSegmentIndex = lastEqualOrderSegmentIndex; - } - - @Override - public boolean matches(@NotNull List orderConstraints) { - return computeMatchingOrder(evaluationOrder, null, orderConstraints); - } - - // preliminary implementation; use only to compare segments in paths (pointing to the same target OID) - // that are to be checked for equivalency - @SuppressWarnings("SimplifiableIfStatement") - @Override - public boolean equivalent(AssignmentPathSegment otherSegment) { - if (!prismContext.relationsEquivalent(relation, otherSegment.getRelation())) { - return false; - } - if (target == null && otherSegment.getTarget() == null) { - return true; // TODO reconsider this in general case - } - if (target == null || otherSegment.getTarget() == null) { - return false; - } - return java.util.Objects.equals(target.getOid(), otherSegment.getTarget().getOid()); - } -} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathVariables.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathVariables.java index 1e13a67efd3..d618bec11ff 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathVariables.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathVariables.java @@ -6,6 +6,7 @@ */ package com.evolveum.midpoint.model.impl.lens; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentPathImpl; import com.evolveum.midpoint.prism.PrismContainerDefinition; import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismObject; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java index 41cad83f7d9..ee9c5eb3a70 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java @@ -23,6 +23,7 @@ import javax.xml.namespace.QName; import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentSpec; import com.evolveum.midpoint.wf.api.WorkflowManager; import org.apache.commons.lang.BooleanUtils; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java index 4ec40059804..13c5dbd365a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.model.api.ProgressListener; import com.evolveum.midpoint.model.api.context.*; import com.evolveum.midpoint.model.api.util.ClockworkInspector; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.Containerable; import com.evolveum.midpoint.prism.PrismContainer; @@ -1233,7 +1234,7 @@ static void dumpRules(StringBuilder sb, String label, int indent, Collection policyRules) { + public static int getTriggeredRulesCount(Collection policyRules) { return (int) policyRules.stream().filter(EvaluatedPolicyRule::isTriggered).count(); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java index 09aeac6c462..820bd614a9b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java @@ -10,6 +10,7 @@ import java.util.function.Consumer; import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentSpec; import com.evolveum.midpoint.prism.ConsistencyCheckScope; import com.evolveum.midpoint.prism.Objectable; import com.evolveum.midpoint.prism.delta.*; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java index dd0050e839f..abf763c454e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java @@ -9,6 +9,7 @@ import java.util.*; import java.util.function.Consumer; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentSpec; import com.evolveum.midpoint.prism.delta.ObjectDeltaCollectionsUtil; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; import com.evolveum.midpoint.prism.path.UniformItemPath; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index e63de80d8ca..e2b0e124919 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -18,6 +18,8 @@ import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentPathImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentPathSegmentImpl; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/PersonaProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/PersonaProcessor.java index 8bbd4394d59..ba7de45e590 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/PersonaProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/PersonaProcessor.java @@ -16,6 +16,7 @@ import com.evolveum.midpoint.model.impl.lens.construction.EvaluatedConstructionPack; import com.evolveum.midpoint.model.impl.lens.construction.EvaluatedPersonaConstructionImpl; import com.evolveum.midpoint.model.impl.lens.construction.PersonaConstruction; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AbstractEvaluation.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AbstractEvaluation.java new file mode 100644 index 00000000000..46d270c9f16 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AbstractEvaluation.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; + +import org.jetbrains.annotations.NotNull; + +/** + * Provides functionality common to all evaluations. + */ +abstract class AbstractEvaluation { + + /** + * Context of the evaluation. + */ + @NotNull final EvaluationContext ctx; + + /** + * Segment that is being evaluated. + */ + @NotNull final AssignmentPathSegmentImpl segment; + + /** + * This is to ensure the evaluation is carried out only once. + */ + private boolean evaluated; + + AbstractEvaluation(@NotNull AssignmentPathSegmentImpl segment, @NotNull EvaluationContext ctx) { + this.segment = segment; + this.ctx = ctx; + } + + void checkIfAlreadyEvaluated() { + if (evaluated) { + throw new IllegalStateException("Already evaluated"); + } else { + evaluated = true; + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentEvaluator.java new file mode 100644 index 00000000000..0ed7f76d8bf --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentEvaluator.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens.assignments; + +import java.util.List; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.common.ActivationComputer; +import com.evolveum.midpoint.model.api.context.EvaluationOrder; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.common.mapping.MappingFactory; +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.projector.AssignmentOrigin; +import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; +import com.evolveum.midpoint.prism.PrismContainerDefinition; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.util.CloneUtil; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.PlusMinusZeroType; + +/** + * An engine that creates EvaluatedAssignment from an assignment IDI. It collects induced roles, constructions, + * authorizations, policy rules, and so on. + * + * This is the main entry point to the whole "assignments" mechanism at the level of a single assignment. + * + * It can be called repeatedly for different assignments of the same focus. + * + * @author semancik + */ +public class AssignmentEvaluator { + + private static final Trace LOGGER = TraceManager.getTrace(AssignmentEvaluator.class); + + private static final String OP_EVALUATE = AssignmentEvaluator.class.getName()+".evaluate"; + + // Context of use + + final LensContext lensContext; + final ObjectDeltaObject focusOdo; + final LifecycleStateModelType focusStateModel; + final String channel; + final XMLGregorianCalendar now; + final PrismObject systemConfiguration; + /** + * Simplified evaluation mode: evaluating only authorizations and gui config. + * Necessary during login. + */ + final boolean loginMode; + + // Spring beans and bean-like objects used + + final RepositoryService repository; + final ObjectResolver objectResolver; + final SystemObjectCache systemObjectCache; + final RelationRegistry relationRegistry; + final PrismContext prismContext; + final MappingFactory mappingFactory; + final ActivationComputer activationComputer; + final MappingEvaluator mappingEvaluator; + final ContextLoader contextLoader; + + // Evaluation state + + final EvaluatedAssignmentTargetCache evaluatedAssignmentTargetCache; + private final MemberOfEngine memberOfEngine; + + private AssignmentEvaluator(Builder builder) { + repository = builder.repository; + focusOdo = builder.focusOdo; + lensContext = builder.lensContext; + channel = builder.channel; + objectResolver = builder.objectResolver; + systemObjectCache = builder.systemObjectCache; + relationRegistry = builder.relationRegistry; + prismContext = builder.prismContext; + mappingFactory = builder.mappingFactory; + activationComputer = builder.activationComputer; + now = builder.now; + contextLoader = builder.contextLoader; + loginMode = builder.loginMode; + systemConfiguration = builder.systemConfiguration; + mappingEvaluator = builder.mappingEvaluator; + evaluatedAssignmentTargetCache = new EvaluatedAssignmentTargetCache(); + memberOfEngine = new MemberOfEngine(); + + LensFocusContext focusContext = lensContext.getFocusContext(); + if (focusContext != null) { + focusStateModel = focusContext.getLifecycleModel(); + } else { + focusStateModel = null; + } + } + + public void reset(boolean alsoMemberOfInvocations) { + evaluatedAssignmentTargetCache.reset(); + if (alsoMemberOfInvocations) { + memberOfEngine.clearInvocations(); + } + } + + /** + * Main entry point: evaluates a given focus-attached (direct) assignment. + * Returns a complex structure called {@link EvaluatedAssignmentImpl}. + * + * @param evaluateOld If true, we take the 'old' value from assignmentIdi. If false, we take the 'new' one. + * @param source FIXME The role of this parameter is quite unclear. It looks like that it is filled-in using objectNew or objectCurrent + * depending on some strange condition in AssignmentTripleEvaluator + * (see {@link com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentProcessor#determineSource(LensFocusContext)}. + */ + @SuppressWarnings("JavadocReference") + public EvaluatedAssignmentImpl evaluate( + ItemDeltaItem,PrismContainerDefinition> assignmentIdi, + PlusMinusZero primaryAssignmentMode, boolean evaluateOld, AssignmentHolderType source, String sourceDescription, + AssignmentOrigin origin, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + OperationResult result = parentResult.subresult(OP_EVALUATE) + .setMinor() + .addArbitraryObjectAsParam("primaryAssignmentMode", primaryAssignmentMode) + .addParam("evaluateOld", evaluateOld) + .addArbitraryObjectAsParam("source", source) + .addParam("sourceDescription", sourceDescription) + .addArbitraryObjectAsParam("origin", origin) + .build(); + AssignmentEvaluationTraceType trace; + if (result.isTracingNormal(AssignmentEvaluationTraceType.class)) { + trace = new AssignmentEvaluationTraceType(prismContext) + .assignmentOld(CloneUtil.clone(getAssignmentBean(assignmentIdi, true))) + .assignmentNew(CloneUtil.clone(getAssignmentBean(assignmentIdi, false))) + .primaryAssignmentMode(PlusMinusZeroType.fromValue(primaryAssignmentMode)) + .evaluateOld(evaluateOld) + .textSource(source != null ? source.asPrismObject().debugDump() : "null") + .sourceDescription(sourceDescription); + result.addTrace(trace); + } else { + trace = null; + } + try { + EvaluatedAssignmentImpl evaluatedAssignment = new EvaluatedAssignmentImpl<>(assignmentIdi, evaluateOld, origin, prismContext); + + EvaluationContext ctx = new EvaluationContext<>( + evaluatedAssignment, + new AssignmentPathImpl(prismContext), + primaryAssignmentMode, evaluateOld, task, this); + + evaluatedAssignmentTargetCache.resetForNextAssignment(); + + AssignmentPathSegmentImpl firstSegment = new AssignmentPathSegmentImpl.Builder() + .source(source) + .sourceDescription(sourceDescription) + .assignmentIdi(assignmentIdi) + .isAssignment(true) + .evaluateOld(evaluateOld) + .relationRegistry(relationRegistry) + .prismContext(prismContext) + .evaluationOrder(getInitialEvaluationOrder(assignmentIdi, ctx)) + .evaluationOrderForTarget(EvaluationOrderImpl.zero(relationRegistry)) + .pathToSourceValid(true) + .sourceRelativityMode(PlusMinusZero.ZERO) + .direct(true) + .build(); + + PathSegmentEvaluation firstSegmentEvaluation = new PathSegmentEvaluation<>(firstSegment, ctx, result); + firstSegmentEvaluation.evaluate(); + + setEvaluatedAssignmentValidity(ctx, firstSegment); + setEvaluatedAssignmentTarget(firstSegmentEvaluation, firstSegmentEvaluation.ctx); + + LOGGER.trace("Assignment evaluation finished:\n{}", ctx.evalAssignment.debugDumpLazily()); + if (ctx.evalAssignment.getTarget() != null) { + result.addContext("assignmentTargetName", PolyString.getOrig(ctx.evalAssignment.getTarget().getName())); + } + if (trace != null) { + trace.setTextResult(ctx.evalAssignment.debugDump()); + } + result.computeStatusIfUnknown(); + return ctx.evalAssignment; + } catch (Throwable t) { + result.recordFatalError(t.getMessage(), t); + throw t; + } + } + + /** + * Sets evaluatedAssignment.valid property (with unclear semantics) in some strange way, + * mixing validity and relativity mode. FIXME reconsider this. + */ + private void setEvaluatedAssignmentValidity(EvaluationContext ctx, AssignmentPathSegmentImpl segment) { + boolean validityValue = ctx.evalAssignment.isVirtual() || + (segment.isAssignmentValid() && segment.getAssignmentRelativityMode() != null); + ctx.evalAssignment.setValid(validityValue); + } + + private void setEvaluatedAssignmentTarget(PathSegmentEvaluation firstSegmentEvaluation, EvaluationContext ctx) { + assert ctx.evalAssignment.getTarget() == null; + List> targets = firstSegmentEvaluation.getTargets(); + if (targets.size() > 1) { + throw new UnsupportedOperationException("Multiple targets for direct focus assignment are not supported: " + firstSegmentEvaluation.segment.assignment); + } else if (!targets.isEmpty()) { + ctx.evalAssignment.setTarget(targets.get(0)); + } + } + + ExpressionVariables getAssignmentEvaluationVariables() { + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_LOGIN_MODE, loginMode, Boolean.class); + // e.g. AssignmentEvaluator itself, model context, etc (when needed) + return variables; + } + + // TODO revisit this + static 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 AssignmentType getAssignmentBean( + ItemDeltaItem, PrismContainerDefinition> assignmentIdi, + boolean old) { + PrismContainerValue pcv = assignmentIdi.getSingleValue(old); + return pcv != null ? pcv.asContainerable() : null; + } + + private EvaluationOrder getInitialEvaluationOrder( + ItemDeltaItem, PrismContainerDefinition> assignmentIdi, + EvaluationContext ctx) { + AssignmentType assignment = LensUtil.getAssignmentType(assignmentIdi, ctx.evaluateOld); + QName relation = Util.getRelation(assignment, relationRegistry); + return EvaluationOrderImpl.zero(relationRegistry).advance(relation); + } + + @SuppressWarnings("unused") // Can be used from scripts + public boolean isMemberOf(String targetOid) { + return memberOfEngine.isMemberOf(focusOdo, targetOid); + } + + public boolean isMemberOfInvocationResultChanged(DeltaSetTriple> evaluatedAssignmentTriple) { + return memberOfEngine.isMemberOfInvocationResultChanged(evaluatedAssignmentTriple); + } + + public static final class Builder { + private RepositoryService repository; + private ObjectDeltaObject focusOdo; + private LensContext lensContext; + private String channel; + private ObjectResolver objectResolver; + private SystemObjectCache systemObjectCache; + private RelationRegistry relationRegistry; + private PrismContext prismContext; + private MappingFactory mappingFactory; + private ActivationComputer activationComputer; + private XMLGregorianCalendar now; + private ContextLoader contextLoader; + private boolean loginMode = false; + private PrismObject systemConfiguration; + private MappingEvaluator mappingEvaluator; + + public Builder() { + } + + public Builder repository(RepositoryService val) { + repository = val; + return this; + } + + public Builder focusOdo(ObjectDeltaObject val) { + focusOdo = val; + return this; + } + + public Builder lensContext(LensContext val) { + lensContext = val; + return this; + } + + public Builder channel(String val) { + channel = val; + return this; + } + + public Builder objectResolver(ObjectResolver val) { + objectResolver = val; + return this; + } + + public Builder systemObjectCache(SystemObjectCache val) { + systemObjectCache = val; + return this; + } + + public Builder relationRegistry(RelationRegistry val) { + relationRegistry = val; + return this; + } + + public Builder prismContext(PrismContext val) { + prismContext = val; + return this; + } + + public Builder mappingFactory(MappingFactory val) { + mappingFactory = val; + return this; + } + + public Builder activationComputer(ActivationComputer val) { + activationComputer = val; + return this; + } + + public Builder now(XMLGregorianCalendar val) { + now = val; + return this; + } + + public Builder contextLoader(ContextLoader val) { + contextLoader = val; + return this; + } + + public Builder loginMode(boolean val) { + loginMode = val; + return this; + } + + public Builder systemConfiguration(PrismObject val) { + systemConfiguration = val; + return this; + } + + public Builder mappingEvaluator(MappingEvaluator val) { + mappingEvaluator = val; + return this; + } + + public AssignmentEvaluator build() { + return new AssignmentEvaluator<>(this); + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentPathImpl.java similarity index 87% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathImpl.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentPathImpl.java index 230e6e32f29..eeb22e03707 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentPathImpl.java @@ -1,10 +1,10 @@ /* - * Copyright (c) 2010-2017 Evolveum and contributors + * Copyright (c) 2010-2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.model.impl.lens; +package com.evolveum.midpoint.model.impl.lens.assignments; import java.util.ArrayList; import java.util.List; @@ -20,24 +20,22 @@ import com.evolveum.midpoint.schema.RelationRegistry; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentPathType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ExtensionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.OrderConstraintsType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; /** - * @author semancik + * Path from focus object to a given assignment. + * Contains also some (although not complete) information on evaluation of individual segments. * + * @author semancik */ public class AssignmentPathImpl implements AssignmentPath { @NotNull private final List segments = new ArrayList<>(); @NotNull private final PrismContext prismContext; - public AssignmentPathImpl(PrismContext prismContext) { + public AssignmentPathImpl(@NotNull PrismContext prismContext) { this.prismContext = prismContext; } @@ -60,17 +58,21 @@ public void add(AssignmentPathSegmentImpl segment) { segments.add(segment); } - public void removeLast(AssignmentPathSegmentImpl segment) { + void removeLast(AssignmentPathSegmentImpl segment) { AssignmentPathSegmentImpl last = last(); if (last == null) { throw new IllegalStateException("Attempt to remove segment from empty path: " + this + "; segment=" + segment); - } else if (!last.equals(segment)) { + } else if (!last.equalsExceptForTarget(segment)) { throw new IllegalStateException("Attempt to remove wrong segment from the end of path: " + this + "; segment=" + segment); } else { segments.remove(segments.size() - 1); } } + private void replaceLast(AssignmentPathSegmentImpl newLastSegment) { + segments.set(segments.size() - 1, newLastSegment); + } + @Override public AssignmentPathSegmentImpl first() { return segments.get(0); @@ -129,7 +131,7 @@ public int countTargetOccurrences(ObjectType target) { @Override public List getFirstOrderChain() { return segments.stream() - .filter(seg -> seg.isMatchingOrder() && seg.getTarget() != null) + .filter(seg -> seg.isMatchingOrder && seg.getTarget() != null) .map(seg -> seg.getTarget()) .collect(Collectors.toList()); } @@ -138,7 +140,7 @@ public List getFirstOrderChain() { public ObjectType getProtoRole() { ObjectType protoRole = null; for (AssignmentPathSegmentImpl segment: segments) { - if (segment.isMatchingOrder() && segment.getTarget() != null) { + if (segment.isMatchingOrder && segment.getTarget() != null) { protoRole = segment.getTarget(); } } @@ -208,7 +210,7 @@ public void shortDump(StringBuilder sb) { // segment.getEvaluationOrder().shortDump(sb); // sb.append("): "); ObjectType target = segment.getTarget(); - QName relation = segment.getRelation(); + QName relation = segment.relation; if (target != null) { sb.append("--"); if (segment.isAssignment()) { @@ -282,4 +284,12 @@ boolean containsDelegation(boolean evaluateOld, RelationRegistry relationRegistr return segments.stream() .anyMatch(aps -> DeputyUtils.isDelegationAssignment(aps.getAssignment(evaluateOld), relationRegistry)); } + + void replaceLastSegmentWithTargetedOne(@NotNull AssignmentHolderType target) { + AssignmentPathSegmentImpl last = last(); + assert last != null; + AssignmentPathSegmentImpl newLast = last.cloneWithTargetSet(target); + newLast.freeze(); + replaceLast(newLast); + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentPathSegmentImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentPathSegmentImpl.java new file mode 100644 index 00000000000..80402831c0e --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentPathSegmentImpl.java @@ -0,0 +1,877 @@ +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens.assignments; + +import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerable; + +import java.util.List; +import java.util.Objects; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; +import com.evolveum.midpoint.model.api.context.EvaluationOrder; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +/** + * 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 stores some (although not complete) information about assignment evaluation. + * + * Note: we avoid getter methods for some final fields to simplify client code. + * + * @author semancik + */ +public class AssignmentPathSegmentImpl implements AssignmentPathSegment, Freezable { + + private boolean immutable; + + @NotNull private final RelationRegistry relationRegistry; + + @NotNull private final PrismContext prismContext; + + //region Description of the situation: source, assignment, target + /** + * Source object for this assignment path. + * This is the object holding the assignment or inducement. + * + */ + final AssignmentHolderType source; + + /** + * Human readable text describing the source (for error messages) + */ + final String sourceDescription; + + /** + * Item-delta-item form of the assignment. + */ + private final ItemDeltaItem,PrismContainerDefinition> assignmentIdi; + + /** + * Are we evaluating the old or new state of the assignment? + */ + @SuppressWarnings("FieldCanBeLocal") + private final boolean evaluateOld; + + /** + * Pure form of the assignment (new or old): taking "evaluateForOld" into account. + */ + final AssignmentType assignment; + + /** + * Relation of the assignment; normalized. + */ + final QName relation; + + /** + * True if we are referencing "real" assignment; false if this is an inducement. + */ + private final boolean isAssignment; + + /** + * Resolved target object. (Can be null if there's no target or if the target was not resolved yet.) + */ + final ObjectType target; + + /** + * Is this a direct assignment (real or virtual) of the focus? + */ + final boolean direct; + + //endregion + + //region Data related to segment evaluation + /** + * Is the whole path to SOURCE valid? + * + * By validity we mean that lifecycle state and activation (admin status / time validity) + * indicate that the object(s) and respective assignment(s) are active. Conditions are not + * considered here, at least not now. + * + * Also note that the validity of this assignment is not considered. + * + * Validity influences the evaluation process. But look for specific code to learn + * precisely how. + */ + final boolean pathToSourceValid; + + /** + * Is assignment itself considered valid (taking lifecycle, activation, and lifecycle state model + * into account)? Note that we can evaluate parts of assignments even if they are not valid + * in this respect. + * + * The assignment can be considered valid even if its source is not valid! + */ + private boolean assignmentValid; + + /** + * Relativity mode of the source object. + * + * For focus, it is zero. For roles in the chain, it is determined by composing relativity + * modes given by assignment and role conditions. + */ + final PlusMinusZero sourceRelativityMode; + + /** + * Relativity mode of the assignment. It is determined from the relativity mode of source + * and the condition of the assignment. + * + * In short, relativity mode determines where to put constructions and target roles/orgs/services + * (PLUS/MINUS/ZERO/null; null means "nowhere"). + * + * Note also this mode is RELATIVE to the relativity mode of the primary (evaluated) assignment. + * It will be interpreted in the light of its status, i.e. if the assignment is unchanged, being + * added or being removed. + * + * The concept of relativity mode is currently complementary to the concept of validity. + * However, we will probably change the validity from "static" (true/false) point of view + * to "dynamic" (plus, minus, zero) point of view. Then we will probably unify validity + * and relativity mode concepts into one. + */ + private PlusMinusZero assignmentRelativityMode; + + /** + * Evaluation order of this segment regarding the focus. If there were no inducements, + * it would be a collection of relations of assignments on the path. For inducements + * please see {@link TargetInducementEvaluation}. + * + * For evaluation orders please see discussion at the end of this class. + */ + private final EvaluationOrder evaluationOrder; + + /** + * Has this segment matching order regarding the focus? I.e. is its payload applied to the focus? + * I.e. applies its content to the focus? + * + * Normally this value is computed when creating this object. + * But in case of inducements it is set explicitly. + * + * For evaluation order matching please see discussion at the end of this class. + */ + final boolean isMatchingOrder; + + /** + * Evaluation order of this segment regarding the target. If there were no inducements, + * it would be a collection of relations of assignments on the path. For inducements + * please see {@link TargetInducementEvaluation}. + * + * For evaluation orders please see discussion at the end of this class. + */ + private final EvaluationOrder evaluationOrderForTarget; + + /** + * Has this segment matching order regarding the target? I.e. is its payload applied to the target? + * (This is important e.g. for target policy rules.) + * + * Normally this value is computed when creating this object. + * But in case of inducements it is set explicitly. + * + * For evaluation order matching please see discussion at the end of this class. + */ + final boolean isMatchingOrderForTarget; + + /** + * Last segment with the same evaluation order as this one. Used for higher-order + * (summary-rewriting) inducements. See e.g. TestAssignmentProcessor2.test600. + */ + private final Integer lastEqualOrderSegmentIndex; + + // TODO Reconsider this. + final ObjectType varThisObject; + + //endregion + + private AssignmentPathSegmentImpl(Builder builder) { + relationRegistry = builder.relationRegistry; + prismContext = builder.prismContext; + source = builder.source; + sourceDescription = builder.sourceDescription; + assignmentIdi = builder.assignmentIdi; + if (assignmentIdi.getDefinition() == null) { + throw new IllegalArgumentException("Attempt to set segment assignment IDI without a definition"); + } + evaluateOld = builder.evaluateOld; + assignment = getAssignment(assignmentIdi, evaluateOld); + relation = getRelation(assignment, relationRegistry); + isAssignment = builder.isAssignment; + target = builder.target; + direct = builder.direct; + pathToSourceValid = builder.pathToSourceValid; + // assignmentValid not present in builder (it's computed) + sourceRelativityMode = builder.sourceRelativityMode; + // assignmentRelativityMode not present in builder (it's computed) + evaluationOrder = builder.evaluationOrder; + isMatchingOrder = builder.isMatchingOrder != null ? + builder.isMatchingOrder : computeMatchingOrder(evaluationOrder, assignment); + evaluationOrderForTarget = builder.evaluationOrderForTarget; + isMatchingOrderForTarget = builder.isMatchingOrderForTarget != null ? + builder.isMatchingOrderForTarget : computeMatchingOrder(evaluationOrderForTarget, assignment); + lastEqualOrderSegmentIndex = builder.lastEqualOrderSegmentIndex; + varThisObject = builder.varThisObject; + } + + private boolean computeMatchingOrder(EvaluationOrder evaluationOrder, AssignmentType assignment) { + return evaluationOrder.matches(assignment.getOrder(), assignment.getOrderConstraint()); + } + + private AssignmentPathSegmentImpl(AssignmentPathSegmentImpl origin, ObjectType target) { + this.relationRegistry = origin.relationRegistry; + this.prismContext = origin.prismContext; + this.source = origin.source; + this.sourceDescription = origin.sourceDescription; + this.assignmentIdi = origin.assignmentIdi; + this.evaluateOld = origin.evaluateOld; + this.assignment = origin.assignment; + this.relation = origin.relation; + this.isAssignment = origin.isAssignment; + this.target = target; + this.direct = origin.direct; + this.pathToSourceValid = origin.pathToSourceValid; + this.assignmentValid = origin.assignmentValid; + this.sourceRelativityMode = origin.sourceRelativityMode; + this.assignmentRelativityMode = origin.assignmentRelativityMode; + this.evaluationOrder = origin.evaluationOrder; + this.isMatchingOrder = origin.isMatchingOrder; + this.evaluationOrderForTarget = origin.evaluationOrderForTarget; + this.isMatchingOrderForTarget = origin.isMatchingOrderForTarget; + this.lastEqualOrderSegmentIndex = origin.lastEqualOrderSegmentIndex; + this.varThisObject = origin.varThisObject; + } + + @Override + public boolean isAssignment() { + return isAssignment; + } + + public ItemDeltaItem,PrismContainerDefinition> getAssignmentIdi() { + return assignmentIdi; + } + + public AssignmentType getAssignment(boolean evaluateOld) { + return getAssignment(assignmentIdi, evaluateOld); + } + + private static AssignmentType getAssignment( + ItemDeltaItem,PrismContainerDefinition> assignmentIdi, + boolean evaluateOld) { + return asContainerable(assignmentIdi.getSingleValue(evaluateOld)); + } + + private static QName getRelation(AssignmentType assignment, RelationRegistry relationRegistry) { + return assignment != null && assignment.getTargetRef() != null ? + relationRegistry.normalizeRelation(assignment.getTargetRef().getRelation()) : null; + } + + public AssignmentType getAssignment() { + return assignment; + } + + public AssignmentType getAssignmentAny() { + if (assignmentIdi == null) { + return null; + } + return assignmentIdi.getItemNew() != null ? getAssignment(false) : getAssignment(true); + } + + @Override + public AssignmentType getAssignmentNew() { + if (assignmentIdi == null || assignmentIdi.getItemNew() == null || assignmentIdi.getItemNew().isEmpty()) { + return null; + } + return ((PrismContainer) assignmentIdi.getItemNew()).getRealValue(); + } + + @Override + public QName getRelation() { + return relation; + } + + @Override + public ObjectType getTarget() { + return target; + } + + AssignmentPathSegmentImpl cloneWithTargetSet(ObjectType target) { + return new AssignmentPathSegmentImpl(this, target); + } + + @Override + public AssignmentHolderType getSource() { + return source; + } + + String getTargetDescription() { + if (target != null) { + return target + " in " + sourceDescription; + } else { + return "(target) in " + sourceDescription; + } + } + + boolean isAssignmentValid() { + return assignmentValid; + } + + void setAssignmentValid(boolean assignmentValid) { + checkMutable(); + this.assignmentValid = assignmentValid; + } + + boolean isFullPathValid() { + return pathToSourceValid && assignmentValid; + } + + PlusMinusZero getAssignmentRelativityMode() { + return assignmentRelativityMode; + } + + void setAssignmentRelativityMode(PlusMinusZero assignmentRelativityMode) { + checkMutable(); + this.assignmentRelativityMode = assignmentRelativityMode; + } + + boolean isNonNegativeRelativityMode() { + return Util.isNonNegative(assignmentRelativityMode); + } + + public EvaluationOrder getEvaluationOrder() { + return evaluationOrder; + } + + EvaluationOrder getEvaluationOrderForTarget() { + return evaluationOrderForTarget; + } + + ObjectType getOrderOneObject() { + return varThisObject; + } + + @Override + public boolean isMatchingOrder() { + return isMatchingOrder; + } + + @Override + public boolean isMatchingOrderForTarget() { + return isMatchingOrderForTarget; + } + + @Override + public boolean isDelegation() { + return relationRegistry.isDelegation(relation); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((assignmentIdi == null) ? 0 : assignmentIdi.hashCode()); + result = prime * result + ((source == null) ? 0 : source.hashCode()); + result = prime * result + ((target == null) ? 0 : target.hashCode()); + return result; + } + + @SuppressWarnings({ "RedundantIfStatement", "BooleanMethodIsAlwaysInverted" }) + boolean equalsExceptForTarget(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AssignmentPathSegmentImpl other = (AssignmentPathSegmentImpl) obj; + if (assignmentIdi == null) { + if (other.assignmentIdi != null) + return false; + } else if (!assignmentIdi.equals(other.assignmentIdi)) + return false; + if (source == null) { + if (other.source != null) + return false; + } else if (!source.equals(other.source)) + return false; + return true; + } + + @SuppressWarnings({ "RedundantIfStatement", "EqualsWhichDoesntCheckParameterClass" }) + @Override + public boolean equals(Object obj) { + if (!equalsExceptForTarget(obj)) { + return false; + } + AssignmentPathSegmentImpl other = (AssignmentPathSegmentImpl) obj; + if (target == null) { + if (other.target != null) { + return false; + } + } else if (!target.equals(other.target)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("AssignmentPathSegment("); + shortDump(sb); + sb.append(")"); + return sb.toString(); + } + + @Override + public void shortDump(StringBuilder sb) { + evaluationOrder.shortDump(sb); + if (isMatchingOrder) { + sb.append("(match)"); + } + if (isMatchingOrderForTarget) { + sb.append("(match-target)"); + } + sb.append(": "); + sb.append(source).append(" "); + if (!isAssignment) { + sb.append("inducement "); + } + PrismContainer assignment = (PrismContainer) assignmentIdi.getAnyItem(); + AssignmentType assignmentType = assignment != null ? assignment.getRealValue() : null; + if (assignmentType != null) { + sb.append("id:").append(assignmentType.getId()).append(" "); + if (assignmentType.getConstruction() != null) { + sb.append("Constr '").append(assignmentType.getConstruction().getDescription()).append("' "); + } + if (assignmentType.getFocusMappings() != null) { + sb.append("FMappings (").append(assignmentType.getFocusMappings().getMapping().size()).append(") "); + } + if (assignmentType.getPolicyRule() != null) { + sb.append("Rule '").append(assignmentType.getPolicyRule().getName()).append("' "); + } + } + ObjectReferenceType targetRef = assignmentType != null ? assignmentType.getTargetRef() : null; + if (target != null || targetRef != null) { + sb.append("-["); + if (relation != null) { + sb.append(relation.getLocalPart()); + } + sb.append("]-> "); + if (target != null) { + sb.append(target); + } else { + sb.append(ObjectTypeUtil.toShortString(targetRef, true)); + } + } + // TODO eventually remove this + if (lastEqualOrderSegmentIndex != null) { + sb.append(", lastEqualOrder: ").append(lastEqualOrderSegmentIndex); + } + } + + @Override + public String debugDump(int indent) { + StringBuilder sb = new StringBuilder(); + DebugUtil.debugDumpLabel(sb, "AssignmentPathSegment", indent); + sb.append("\n"); + DebugUtil.debugDumpWithLabelLn(sb, "source", source==null?"null":source.toString(), indent + 1); + String assignmentOrInducement = isAssignment ? "assignment" : "inducement"; + if (assignmentIdi != null) { + DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement + " old", String.valueOf(assignmentIdi.getItemOld()), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement + " delta", String.valueOf(assignmentIdi.getDelta()), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement + " new", String.valueOf(assignmentIdi.getItemNew()), indent + 1); + } else { + DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement, "null", indent + 1); + } + 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, "isMatchingOrderForTarget", isMatchingOrderForTarget, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "relation", relation, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "pathToSourceValid", pathToSourceValid, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "assignmentValid", assignmentValid, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "sourceRelativityMode", String.valueOf(sourceRelativityMode), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "assignmentRelativityMode", String.valueOf(assignmentRelativityMode), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "lastEqualOrderSegmentIndex", lastEqualOrderSegmentIndex, indent + 1); + DebugUtil.debugDumpWithLabel(sb, "varThisObject", varThisObject==null?"null":varThisObject.toString(), indent + 1); + return sb.toString(); + } + + @NotNull + @Override + public AssignmentPathSegmentType toAssignmentPathSegmentType(boolean includeAssignmentsContent) { + AssignmentPathSegmentType rv = new AssignmentPathSegmentType(); + if (assignment != null) { + if (includeAssignmentsContent) { + rv.setAssignment(assignment.clone()); + } + rv.setAssignmentId(assignment.getId()); + } + if (source != null) { + rv.setSourceRef(ObjectTypeUtil.createObjectRef(source, prismContext)); + rv.setSourceDisplayName(ObjectTypeUtil.getDisplayName(source)); + } + if (target != null) { + rv.setTargetRef(ObjectTypeUtil.createObjectRef(target, prismContext)); + rv.setTargetDisplayName(ObjectTypeUtil.getDisplayName(target)); + } + rv.setMatchingOrder(isMatchingOrder); + rv.setIsAssignment(isAssignment); + return rv; + } + + Integer getLastEqualOrderSegmentIndex() { + return lastEqualOrderSegmentIndex; + } + + @Override + public boolean matches(@NotNull List orderConstraints) { + return evaluationOrder.matches(null, orderConstraints); + } + + // preliminary implementation; use only to compare segments in paths (pointing to the same target OID) + // that are to be checked for equivalency + @SuppressWarnings("SimplifiableIfStatement") + @Override + public boolean equivalent(AssignmentPathSegment otherSegment) { + if (!prismContext.relationsEquivalent(relation, otherSegment.getRelation())) { + return false; + } + if (target == null && otherSegment.getTarget() == null) { + return true; // TODO reconsider this in general case + } + if (target == null || otherSegment.getTarget() == null) { + return false; + } + return Objects.equals(target.getOid(), otherSegment.getTarget().getOid()); + } + + @Override + public boolean isImmutable() { + return immutable; + } + + @Override + public void freeze() { + immutable = true; + } + + public static final class Builder { + private RelationRegistry relationRegistry; + private PrismContext prismContext; + private AssignmentHolderType source; + private String sourceDescription; + private ItemDeltaItem, PrismContainerDefinition> assignmentIdi; + private boolean evaluateOld; + private boolean isAssignment; + private ObjectType target; + private boolean direct; + private boolean pathToSourceValid; + private PlusMinusZero sourceRelativityMode; + private Integer lastEqualOrderSegmentIndex; + private Boolean isMatchingOrder; + private EvaluationOrder evaluationOrder; + private Boolean isMatchingOrderForTarget; + private EvaluationOrder evaluationOrderForTarget; + private ObjectType varThisObject; + + public Builder() { + } + + public Builder relationRegistry(@NotNull RelationRegistry val) { + relationRegistry = val; + return this; + } + + public Builder prismContext(@NotNull PrismContext val) { + prismContext = val; + return this; + } + + public Builder source(AssignmentHolderType val) { + source = val; + return this; + } + + public Builder sourceDescription(String val) { + sourceDescription = val; + return this; + } + + Builder assignmentIdi(ItemDeltaItem, PrismContainerDefinition> val) { + assignmentIdi = val; + return this; + } + + public Builder assignment(AssignmentType assignment) { + try { + //noinspection unchecked,rawtypes + assignmentIdi = new ItemDeltaItem<>( + LensUtil.createAssignmentSingleValueContainer(assignment), assignment.asPrismContainerValue().getDefinition()); + } catch (SchemaException e) { + // should not really occur! + throw new SystemException("Couldn't create assignment IDI: " + e.getMessage(), e); + } + + return this; + } + + public Builder evaluateOld(boolean val) { + evaluateOld = val; + return this; + } + + public Builder isAssignment(boolean val) { + isAssignment = val; + return this; + } + + public Builder target(ObjectType val) { + target = val; + return this; + } + + public Builder direct(boolean val) { + direct = val; + return this; + } + + public Builder pathToSourceValid(boolean val) { + pathToSourceValid = val; + return this; + } + + public Builder sourceRelativityMode(PlusMinusZero val) { + sourceRelativityMode = val; + return this; + } + + Builder lastEqualOrderSegmentIndex(Integer val) { + lastEqualOrderSegmentIndex = val; + return this; + } + + public Builder isMatchingOrder(Boolean val) { + isMatchingOrder = val; + return this; + } + + public Builder evaluationOrder(EvaluationOrder val) { + evaluationOrder = val; + return this; + } + + Builder isMatchingOrderForTarget(Boolean val) { + isMatchingOrderForTarget = val; + return this; + } + + public Builder evaluationOrderForTarget(EvaluationOrder val) { + evaluationOrderForTarget = val; + return this; + } + + Builder varThisObject(ObjectType val) { + varThisObject = val; + return this; + } + + public AssignmentPathSegmentImpl build() { + return new AssignmentPathSegmentImpl(this); + } + } +} + +/* + * Assignments and inducements can carry constructions, focus mappings, and policy rules. + * We can call these "assignment/inducement payload", or "payload" for short. + * + * When looking at assignments/inducements in assignment path, payload of some assignments/inducements will be collected + * to focus, while payload from others will be not. How we know what to collect? + * + * For assignments/inducements belonging directly to the focus, we take payload from all the assignments. Not from inducements. + * For assignments/inducements belonging to roles (assigned to focus), we take payload from all the inducements of order 1. + * For assignments/inducements belonging to meta-roles (assigned to roles), we take payload from all the inducements of order 2. + * And so on. (It is in fact a bit more complicated, as described below when discussing relations. But OK for the moment.) + * + * To know whether to collect payload from assignment/inducement, i.e. from assignment path segment, we + * define "isMatchingOrder" attribute - and collect only if value of this attribute is true. + * + * How we compute this attribute? + * + * Each assignment path segment has an evaluation order. First assignment has an evaluation order of 1, second + * assignment has an order of 2, etc. Order of a segment can be seen as the number of assignments segments in the path + * (including itself). And, for "real" assignments (i.e. not inducements), we collect payload from assignment segments + * of order 1. + * + * But what about inducements? There are two - somewhat related - questions: + * + * 1. How we compute isMatchingOrder for inducement segments? + * 2. How we compute evaluation order for inducement segments? + * + * As for #1: To compute isMatchingOrder, we must take evaluation order of the _previous_ segment, and compare + * it with the order (or, more generally, order constraints) of the inducement. If they match, we say that inducement + * has matching order. + * + * As for #2: It is not usual that inducements have targets (roles) with another assignments, i.e. that evaluation continues + * after an inducement segment. But it definitely could happen. We can look at it this way: inducement is something like + * a "shortcut" that creates an assignment where no assignment was before. E.g. if we have R1 -A-> MR1 -I-> MR2, + * the "newly created" assignment is R1 -A-> MR2. I.e. as if the " -A-> MR1 -I-> " part was just replaced by " -A-> ". + * If the inducement is of higher order, even more assignments are "cut out". From R1 -A-> MR1 -A-> MMR1 -(I2)-> MR2 + * we have R1 -A-> MR2, i.e. we cut out " -A-> MR1 -A-> MMR1 -(I2)-> " and replaced it by " -A-> ". + * So it looks like that when computing new evaluation order of an inducement, we have to "go back" few steps + * through the assignment path. + * + * Such situation can also easily occur when org structures are used. As depicted in TestAssignmentProcessor2.test500 + * and later, imagine this: + * + * Org1 -----I----+ Org2 -----I----+ + * ^ | (orderConstraints 1..N) ^ | (orderConstraints: manager: 1) + * | | | | + * | V | V + * Org11 Admin Org21 Admin + * ^ ^ + * | (manager) + * | | + * jack jack + * + * In order to resolve such cases, we created the "resetOrder" attribute. It can be applied either to + * summary order, or to one or more specific relations (please, don't specify it for both summary order + * and specific relations!) + * + * In the above examples, we could specify e.g. resetOrder=1 for summary order (for both left and right situation). + * For the right one, we could instead specify resetOrder=0 for org:manager relation; although the former solution + * is preferable. + * + * By simply specifying inducement order greater than 1 (e.g. 5) without any specific order constraints, + * we implicitly provide resetOrder instruction for summary order that points order-1 levels back (i.e. 4 levels back + * for order=5). + * + * Generally, it is preferred to use resetOrder for summary order. It works well with both normal and target + * evaluation order. When resetting individual components, target evaluation order can have problems, as shown + * in TestEvaluationProcessor2.test520. + * + * ---- + * + * Because evaluation order can "increase" and "decrease", it is possible that it goes to zero or below, and then + * increase back to positive numbers. Is that OK? Imagine this: + * + * (Quite an ugly example, but such things might exist.) + * + * Metarole:CrewMember ----I----+ Metarole:Sailors + * A | A + * | | | + * | V | + * Pirate Sailor + * A + * | + * | + * jack + * + * When evaluating jack->Pirate assignment, it is OK to collect from everything (Pirate, CrewMember, Sailor, Sailors). + * + * But when evaluating Pirate as a focal object (forget about jack for the moment), we have Pirate->CrewMember assignment. + * For this assignment we should ignore payload from Sailor (obviously, because the order is not matching), but from + * Metarole:Sailors as well. Payload from Sailors is not connected to Pirate in any meaningful way. (For example, if + * Sailors prescribes an account construction for Sailor, it is of no use to collect this construction when evaluating + * Pirate as focal object!) + * + * Evaluating relations + * ==================== + * + * With the arrival of various kinds of relations (deputy, manager, approver, owner) things got a bit complicated. + * For instance, the deputy relation cannot be used to determine evaluation order in a usual way, because if + * we have this situation: + * + * Pirate -----I-----> Sailor + * A + * | (default) + * | + * jack + * A + * | (deputy) + * | + * barbossa + * + * we obviously want to have barbossa to obtain all payload from roles Pirate and Sailor: exactly as jack does. + * So, the evaluation order of " barbossa -A-> jack -A-> Pirate " should be 1, not two. So deputy is a very special + * kind of relation, that does _not_ increase the traditional evaluation order. But we really want to record + * the fact that the deputy is on the assignment path; therefore, besides traditional "scalar" evaluation order + * (called "summaryOrder") we maintain evaluation orders for each relation separately. In the above example, + * the evaluation orders would be: + * barbossa--->jack summary: 0, deputy: 1, default: 0 + * jack--->Pirate summary: 1, deputy: 1, default: 1 + * Pirate-I->Sailor summary: 1, deputy: 1, default: 1 (because the inducement has a default order of 1) + * + * When we determine matchingOrder for an inducement (question #1 above), we can ask about summary order, + * as well as about individual components (deputy and default, in this case). + * + * Actually, we have three categories of relations (see MID-3581): + * + * - membership relations: apply membership references, payload, authorizations, gui config + * - delegation relations: similar to membership, bud different handling of order + * - other relations: apply membership references but in limited way; payload, authorizations and gui config + * are - by default - not applied. + * + * Currently, membership relations are: default (i.e. member), manager, and meta. + * Delegation: deputy. + * Other: approver, owner, and custom ones. + * + * As for the "other" relations: they bring membership information, but limited to the target that is directly + * assigned. So if jack is an approver for role Landluber, his roleMembershipRef will contain ref to Landluber + * but not e.g. to role Earthworm that is induced by Landluber. In a similar way, payload from targets assigned + * by "other" relations is not collected. + * + * Both of this can be overridden by using specific orderConstraints on particular inducement. + * 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. + * + * Note: authorizations and gui config information are not considered to be a payload of assignment, + * because they are not part of an assignment/inducement - they are payload of a role. In the future we + * might move them into assignments/inducements. + * + * Collecting target policy rules + * ============================== + * + * Special consideration must be given when collecting target policy rules, i.e. rules that are attached to + * assignment targets. Such rules are typically attached to roles that are being assigned. So let's consider this: + * + * rule1 (e.g. assignment approval policy rule) + * A + * | + * | + * Pirate + * A + * | + * | + * jack + * + * 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 isMatchingOrderForTarget + * that marks all segments (assignments/inducements) that contain policy rules relevant to the evaluated assignment's target. + * + * The computation is done by maintaining two evaluationOrders: + * - plain evaluationOrder that is related to the focus object [starts from ZERO.advance(first assignment relation)] + * - special evaluationOrderForTarget that is related to the target of the assignment being evaluated [starts from ZERO] + * + * Finally, how we distinguish between "this target" and "other targets" policy rules? Current approach + * is like this: count the number of times the evaluation order for target reached ZERO level. First encounter with + * that level is on "this target". And we assume that each subsequent marks a target that is among "others". + * + * See PayloadEvaluation.appliesDirectly. + */ diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentSpec.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentSpec.java similarity index 83% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentSpec.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentSpec.java index ffb33586158..4ff4176680b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentSpec.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentSpec.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2010-2017 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.model.impl.lens; +package com.evolveum.midpoint.model.impl.lens.assignments; import com.evolveum.midpoint.prism.delta.PlusMinusZero; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; @@ -16,13 +16,11 @@ /** * A key for assignment:mode => modifications map (for policy state). - * - * @author mederly */ public class AssignmentSpec implements Serializable { @NotNull public final AssignmentType assignment; - @NotNull public final PlusMinusZero mode; // regarding the current object state (not the old one) + @NotNull public final PlusMinusZero mode; // regarding the current object state (not the old one) public AssignmentSpec(@NotNull AssignmentType assignment, @NotNull PlusMinusZero mode) { this.assignment = assignment; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentTargetEvaluationInformation.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentTargetEvaluationInformation.java similarity index 92% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentTargetEvaluationInformation.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentTargetEvaluationInformation.java index 7a188f2aada..19ad111c69e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentTargetEvaluationInformation.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/AssignmentTargetEvaluationInformation.java @@ -1,11 +1,11 @@ /* - * Copyright (c) 2019 Evolveum and contributors + * Copyright (c) 2019-2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.model.impl.lens; +package com.evolveum.midpoint.model.impl.lens.assignments; /** * Information about assignment target evaluation that is stored in the evaluated assignment target cache. diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/ConditionEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/ConditionEvaluator.java new file mode 100644 index 00000000000..5bcdce404e9 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/ConditionEvaluator.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.impl.lens.AssignmentPathVariables; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.prism.OriginType; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingKindType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +/** + * Evaluates assignment/role conditions, resulting in "relativity mode" updates. + */ +class ConditionEvaluator { + + private static final Trace LOGGER = TraceManager.getTrace(ConditionEvaluator.class); + + private static final QName CONDITION_OUTPUT_NAME = new QName(SchemaConstants.NS_C, "condition"); + + private final EvaluationContext ctx; + + ConditionEvaluator(EvaluationContext evaluationContext) { + this.ctx = evaluationContext; + } + + PlusMinusZero computeModeFromCondition(PlusMinusZero initialMode, MappingType condition, + ObjectType source, String description, Object loggingDesc, OperationResult result) + throws SchemaException, CommunicationException, ObjectNotFoundException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + if (condition != null) { + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(condition, + source, assignmentPathVariables, + description, ctx, result); + boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); + boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); + PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); + if (modeFromCondition == null) { + LOGGER.trace("Evaluated condition in {}: {} -> {}: null (not continuing further)", loggingDesc, condOld, condNew); + return null; + } else { + PlusMinusZero updatedMode = PlusMinusZero.compute(initialMode, modeFromCondition); + LOGGER.trace("Evaluated condition in {}: {} -> {}: {} + {} = {}", loggingDesc, condOld, condNew, + initialMode, modeFromCondition, updatedMode); + return updatedMode; + } + } else { + return initialMode; + } + } + + private PrismValueDeltaSetTriple> evaluateCondition(MappingType condition, + ObjectType source, AssignmentPathVariables assignmentPathVariables, String contextDescription, EvaluationContext ctx, + OperationResult result) throws ExpressionEvaluationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { + MappingImpl.Builder, PrismPropertyDefinition> builder = + ctx.ae.mappingFactory.createMappingBuilder(); + builder = builder.mappingType(condition) + .mappingKind(MappingKindType.ASSIGNMENT_CONDITION) + .contextDescription(contextDescription) + .sourceContext(ctx.ae.focusOdo) + .originType(OriginType.ASSIGNMENTS) + .originObject(source) + .defaultTargetDefinition(ctx.ae.prismContext.definitionFactory().createPropertyDefinition(CONDITION_OUTPUT_NAME, DOMUtil.XSD_BOOLEAN)) + .addVariableDefinitions(ctx.ae.getAssignmentEvaluationVariables()) + .rootNode(ctx.ae.focusOdo) + .addVariableDefinition(ExpressionConstants.VAR_FOCUS, ctx.ae.focusOdo) + .addVariableDefinition(ExpressionConstants.VAR_USER, ctx.ae.focusOdo) + .addAliasRegistration(ExpressionConstants.VAR_USER, null) + .addAliasRegistration(ExpressionConstants.VAR_FOCUS, null) + .addVariableDefinition(ExpressionConstants.VAR_SOURCE, source, ObjectType.class) + .addVariableDefinition(ExpressionConstants.VAR_ASSIGNMENT_EVALUATOR, this, AssignmentEvaluator.class); + builder = LensUtil.addAssignmentPathVariables(builder, assignmentPathVariables, ctx.ae.prismContext); + + MappingImpl, PrismPropertyDefinition> mapping = builder.build(); + + ctx.ae.mappingEvaluator.evaluateMapping(mapping, ctx.ae.lensContext, ctx.task, result); + + return mapping.getOutputTriple(); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluatedAssignmentImpl.java similarity index 96% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluatedAssignmentImpl.java index 6a133d4ac1e..ffb166e736d 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluatedAssignmentImpl.java @@ -1,10 +1,10 @@ /* - * Copyright (c) 2010-2019 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.model.impl.lens; +package com.evolveum.midpoint.model.impl.lens.assignments; import java.util.*; import java.util.function.Consumer; @@ -16,6 +16,7 @@ import com.evolveum.midpoint.model.impl.lens.construction.Construction; import com.evolveum.midpoint.model.impl.lens.construction.EvaluatedConstructionImpl; import com.evolveum.midpoint.model.impl.lens.construction.PersonaConstruction; +import com.evolveum.midpoint.model.impl.lens.*; import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; import com.evolveum.midpoint.model.impl.lens.projector.mappings.AssignedFocusMappingEvaluationRequest; import com.evolveum.midpoint.prism.*; @@ -41,6 +42,8 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import com.google.common.annotations.VisibleForTesting; import org.jetbrains.annotations.NotNull; import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerable; @@ -91,13 +94,13 @@ public class EvaluatedAssignmentImpl implements private String tenantOid; private PrismObject target; - private boolean isValid; + private boolean isValid; // TODO define! private boolean wasValid; private boolean forceRecon; // used also to force recomputation of parentOrgRefs @NotNull private final AssignmentOrigin origin; - private Collection policySituations = new HashSet<>(); + private final Collection policySituations = new HashSet<>(); - private PrismContext prismContext; + private final PrismContext prismContext; public EvaluatedAssignmentImpl( @NotNull ItemDeltaItem, PrismContainerDefinition> assignmentIdi, @@ -186,7 +189,8 @@ public DeltaSetTriple getEvaluatedConstructions(@NotNull return (DeltaSetTriple)rv; } - Collection>> getConstructionSet(PlusMinusZero whichSet) { + @VisibleForTesting + public Collection>> getConstructionSet(PlusMinusZero whichSet) { switch (whichSet) { case ZERO: return getConstructionTriple().getZeroSet(); case PLUS: return getConstructionTriple().getPlusSet(); @@ -216,7 +220,7 @@ private void addToTriple(T construction, @NotNull DeltaSetTriple triple, } @NotNull - DeltaSetTriple> getPersonaConstructionTriple() { + public DeltaSetTriple> getPersonaConstructionTriple() { return personaConstructionTriple; } @@ -299,9 +303,6 @@ void addFocusMappingEvaluationRequest(AssignedFocusMappingEvaluationRequest requ this.focusMappingEvaluationRequests.add(request); } - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.impl.lens.EvaluatedAssignment#getTarget() - */ @Override public PrismObject getTarget() { return target; @@ -315,9 +316,6 @@ public boolean isVirtual() { return origin.isVirtual(); } - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.impl.lens.EvaluatedAssignment#isValid() - */ @Override public boolean isValid() { return isValid; @@ -356,7 +354,8 @@ public void evaluateConstructions(ObjectDeltaObject focusOdo, PrismObject focusOdo, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException { + @VisibleForTesting + public void evaluateConstructions(ObjectDeltaObject focusOdo, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException { evaluateConstructions(focusOdo, null, null, task, result); } @@ -545,7 +544,7 @@ public String toString() { + "; " + focusPolicyRules.size()+" rules)"; } - String toHumanReadableString() { + public String toHumanReadableString() { if (target != null) { return "EvaluatedAssignment(" + target + ")"; } else if (!constructionTriple.isEmpty()) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentTargetCache.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluatedAssignmentTargetCache.java similarity index 90% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentTargetCache.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluatedAssignmentTargetCache.java index f2e30594969..f89dbbd7780 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentTargetCache.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluatedAssignmentTargetCache.java @@ -1,10 +1,10 @@ /* - * Copyright (c) 2017 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.model.impl.lens; +package com.evolveum.midpoint.model.impl.lens.assignments; import com.evolveum.midpoint.model.api.context.EvaluationOrder; import com.evolveum.midpoint.prism.delta.DeltaTriple; @@ -25,25 +25,24 @@ * @author semancik * */ -public class EvaluatedAssignmentTargetCache implements DebugDumpable { +class EvaluatedAssignmentTargetCache implements DebugDumpable { private static final Trace LOGGER = TraceManager.getTrace(EvaluatedAssignmentTargetCache.class); // Triple. Target processed for addition is not necessarily reusable for deletion // This is indexed by OID and relation only - private DeltaTriple> processedKeys; + private final DeltaTriple> processedKeys; // Triple. Target processed for addition is not necessarily reusable for deletion // This is indexed by OID, relation and order - private DeltaTriple> processedOrderKeys; - + private final DeltaTriple> processedOrderKeys; EvaluatedAssignmentTargetCache() { processedOrderKeys = new DeltaTriple<>(HashMap::new); processedKeys = new DeltaTriple<>(HashMap::new); } - public void reset() { + void reset() { processedOrderKeys.foreach(Map::clear); processedKeys.foreach(Map::clear); } @@ -85,7 +84,7 @@ private boolean isCacheable(AbstractRoleType targetType) { return idempotence != null && idempotence != IdempotenceType.NONE; } - public boolean canSkip(AssignmentPathSegmentImpl segment, PlusMinusZero mode) { + boolean canSkip(AssignmentPathSegmentImpl segment, PlusMinusZero mode) { ObjectType target = segment.getTarget(); if (!(target instanceof AbstractRoleType)) { // LOGGER.trace("Non-skippable target: {}", target); @@ -96,7 +95,7 @@ public boolean canSkip(AssignmentPathSegmentImpl segment, PlusMinusZero mode) { // LOGGER.trace("Not idempotent target: {}", target); return false; } - if (idempotence == IdempotenceType.CONSERVATIVE && !segment.isMatchingOrder()) { + if (idempotence == IdempotenceType.CONSERVATIVE && !segment.isMatchingOrder) { // this is quite important (and perhaps not too frequent) message, so let's keep it here LOGGER.trace("Conservative idempotent and order is not matching: {}", target); return false; @@ -120,13 +119,13 @@ public boolean canSkip(AssignmentPathSegmentImpl segment, PlusMinusZero mode) { } private class Key { - private String targetOid; - private QName relation; + private final String targetOid; + private final QName relation; - public Key(AssignmentPathSegmentImpl segment) { + Key(AssignmentPathSegmentImpl segment) { super(); this.targetOid = segment.getTarget().getOid(); - this.relation = segment.getRelation(); + this.relation = segment.relation; } @Override @@ -139,6 +138,7 @@ public int hashCode() { return result; } + @SuppressWarnings("RedundantIfStatement") @Override public boolean equals(Object obj) { if (this == obj) { @@ -188,9 +188,9 @@ protected String content() { } private class OrderKey extends Key { - private EvaluationOrder evaluationOrder; + private final EvaluationOrder evaluationOrder; - public OrderKey(AssignmentPathSegmentImpl segment) { + private OrderKey(AssignmentPathSegmentImpl segment) { super(segment); this.evaluationOrder = segment.getEvaluationOrder(); } @@ -204,6 +204,7 @@ public int hashCode() { return result; } + @SuppressWarnings("RedundantIfStatement") @Override public boolean equals(Object obj) { if (this == obj) { 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/assignments/EvaluatedAssignmentTargetImpl.java similarity index 90% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentTargetImpl.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluatedAssignmentTargetImpl.java index 845623a28de..c7d92242884 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/assignments/EvaluatedAssignmentTargetImpl.java @@ -1,10 +1,10 @@ /* - * Copyright (c) 2015-2019 Evolveum and contributors + * Copyright (c) 2015-2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.model.impl.lens; +package com.evolveum.midpoint.model.impl.lens.assignments; import com.evolveum.midpoint.model.api.context.EvaluatedAssignmentTarget; import com.evolveum.midpoint.model.api.context.EvaluationOrder; @@ -24,7 +24,7 @@ */ public class EvaluatedAssignmentTargetImpl implements EvaluatedAssignmentTarget { - final PrismObject target; + @NotNull private final PrismObject target; private final boolean evaluateConstructions; @NotNull private final AssignmentPathImpl assignmentPath; // TODO reconsider (maybe we should store only some lightweight information here) private final AssignmentType assignment; @@ -32,7 +32,7 @@ public class EvaluatedAssignmentTargetImpl implements EvaluatedAssignmentTarget private final boolean isValid; EvaluatedAssignmentTargetImpl( - PrismObject target, boolean evaluateConstructions, + @NotNull PrismObject target, boolean evaluateConstructions, @NotNull AssignmentPathImpl assignmentPath, AssignmentType assignment, boolean isValid) { this.target = target; @@ -43,7 +43,7 @@ public class EvaluatedAssignmentTargetImpl implements EvaluatedAssignmentTarget } @Override - public PrismObject getTarget() { + public @NotNull PrismObject getTarget() { return target; } @@ -54,7 +54,7 @@ public boolean isDirectlyAssigned() { @Override public boolean appliesToFocus() { - return assignmentPath.last().isMatchingOrder(); + return assignmentPath.last().isMatchingOrder; } @Override diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluationContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluationContext.java new file mode 100644 index 00000000000..804410bdf08 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluationContext.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; + +/** + * This is to reduce the number of parameters passed between methods in this class. + * Moreover, it highlights the fact that identity of objects referenced here is fixed for any invocation of the evaluate() method. + * (There is single EvaluationContext instance for any call to evaluate().) + */ + +class EvaluationContext { + + /** + * Evaluated assignment. Here we collect all relevant information. + */ + @NotNull final EvaluatedAssignmentImpl evalAssignment; + + /** + * Assignment path: initially empty. Segments are being added and removed from here. + */ + @NotNull final AssignmentPathImpl assignmentPath; + + /** + * Tells whether the primary assignment was added, removed or it is unchanged. + * + * The primary (direct) assignment is the first assignment in the assignment path, + * i.e. the assignment that is located in the focal object. + */ + final PlusMinusZero primaryAssignmentMode; + + /** + * True if we are evaluating old state of the assignment. + */ + final boolean evaluateOld; + + /** + * The task. + */ + @NotNull final Task task; + + /** + * Assignment evaluator itself. We use this (strangely named) field to access the broader + * evaluation context via "ctx.ae" reference. + */ + @NotNull final AssignmentEvaluator ae; + + /** + * Used to collect membership information. + */ + @NotNull final TargetMembershipCollector membershipCollector; + + /** + * Used to evaluate conditions on assignments and abstract roles (targets). + */ + @NotNull final ConditionEvaluator conditionEvaluator; + + EvaluationContext(@NotNull EvaluatedAssignmentImpl evalAssignment, + @NotNull AssignmentPathImpl assignmentPath, + PlusMinusZero primaryAssignmentMode, boolean evaluateOld, + @NotNull Task task, + @NotNull AssignmentEvaluator assignmentEvaluator) { + this.evalAssignment = evalAssignment; + this.assignmentPath = assignmentPath; + this.primaryAssignmentMode = primaryAssignmentMode; + this.evaluateOld = evaluateOld; + this.task = task; + this.ae = assignmentEvaluator; + this.membershipCollector = new TargetMembershipCollector(this); + this.conditionEvaluator = new ConditionEvaluator(this); + } +} 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/assignments/EvaluationOrderImpl.java similarity index 70% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluationOrderImpl.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/EvaluationOrderImpl.java index bf7d3b24017..71e19a3210b 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/assignments/EvaluationOrderImpl.java @@ -1,10 +1,10 @@ /* - * Copyright (c) 2017 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.model.impl.lens; +package com.evolveum.midpoint.model.impl.lens.assignments; import java.util.*; import java.util.Map.Entry; @@ -13,8 +13,14 @@ import javax.xml.namespace.QName; import com.evolveum.midpoint.model.api.context.EvaluationOrder; +import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.schema.RelationRegistry; 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.OrderConstraintsType; + import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.MultiSet; import org.jetbrains.annotations.NotNull; @@ -25,9 +31,11 @@ */ public class EvaluationOrderImpl implements EvaluationOrder { - public static final EvaluationOrder UNDEFINED = new UndefinedEvaluationOrderImpl(); + private static final Trace LOGGER = TraceManager.getTrace(EvaluationOrderImpl.class); + + static final EvaluationOrder UNDEFINED = new UndefinedEvaluationOrderImpl(); - @NotNull private final HashMap orderMap; // see checkConsistence + @NotNull private final HashMap orderMap; // see checkConsistence @NotNull private final RelationRegistry relationRegistry; public static EvaluationOrder zero(RelationRegistry relationRegistry) { @@ -43,7 +51,7 @@ private void checkConsistence() { throw new IllegalStateException("Null relation in " + this); } if (isNotNormalized(r)) { - throw new IllegalStateException("Unnormalized relation " + r + " in " + this); + throw new IllegalStateException("Un-normalized relation " + r + " in " + this); } if (v == null) { throw new IllegalStateException("Null value in for relation " + r + " in " + this); @@ -230,4 +238,53 @@ public boolean isValid() { public boolean isOrderOne() { return getSummaryOrder() == 1; } + + @Override + public boolean matches(Integer assignmentOrder, List assignmentOrderConstraint) { + boolean rv; + List extraRelations = new ArrayList<>(getExtraRelations()); + if (assignmentOrder == null && assignmentOrderConstraint.isEmpty()) { + // compatibility + rv = getSummaryOrder() == 1; + } else { + rv = true; + if (assignmentOrder != null) { + if (getSummaryOrder() != assignmentOrder) { + rv = false; + } + } + for (OrderConstraintsType orderConstraint : assignmentOrderConstraint) { + if (!isMatchingConstraint(orderConstraint)) { + rv = false; + break; + } + extraRelations.removeIf(r -> QNameUtil.match(r, orderConstraint.getRelation())); + } + } + // TODO this is to be reconsidered -- why do we consider assignment of relation e.g. approver non-matching? + if (!extraRelations.isEmpty()) { + rv = false; + } + LOGGER.trace("computeMatchingOrder => {}, for assignment.order={}, assignment.orderConstraint={}, evaluationOrder={}, remainingExtraRelations={}", + rv, assignmentOrder, assignmentOrderConstraint, this, extraRelations); + return rv; + } + + private boolean isMatchingConstraint(OrderConstraintsType orderConstraint) { + int evaluationOrderInt = getMatchingRelationOrder(orderConstraint.getRelation()); + if (orderConstraint.getOrder() != null) { + return orderConstraint.getOrder() == evaluationOrderInt; + } else { + int orderMin = 1; + int orderMax = 1; + if (orderConstraint.getOrderMin() != null) { + orderMin = XsdTypeMapper.multiplicityToInteger(orderConstraint.getOrderMin()); + } + if (orderConstraint.getOrderMax() != null) { + orderMax = XsdTypeMapper.multiplicityToInteger(orderConstraint.getOrderMax()); + } + return XsdTypeMapper.isMatchingMultiplicity(evaluationOrderInt, orderMin, orderMax); + } + } + } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/MemberOfEngine.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/MemberOfEngine.java new file mode 100644 index 00000000000..279b2539722 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/MemberOfEngine.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * This "isMemberOf iteration" section is an experimental implementation of MID-5366. + *

+ * The main idea: In role/assignment/inducement conditions we test the membership not by querying roleMembershipRef + * on focus object but instead we call assignmentEvaluator.isMemberOf() method. This method - by default - inspects + * roleMembershipRef but also records the check result. Later, when assignment evaluation is complete, AssignmentProcessor + * will ask if all of these check results are still valid. If they are not, it requests re-evaluation of all the assignments, + * using updated check results. + *

+ * This should work unless there are some cyclic dependencies (like "this sentence is a lie" paradox). + */ +@Experimental +class MemberOfEngine { + + private static final Trace LOGGER = TraceManager.getTrace(MemberOfEngine.class); + + private final List memberOfInvocations = new ArrayList<>(); + + boolean isMemberOf(ObjectDeltaObject focusOdo, String targetOid) { + if (targetOid == null) { + throw new IllegalArgumentException("Null targetOid in isMemberOf call"); + } + MemberOfInvocation existingInvocation = findInvocation(targetOid); + if (existingInvocation != null) { + return existingInvocation.result; + } else { + boolean result = computeIsMemberOfDuringEvaluation(focusOdo, targetOid); + memberOfInvocations.add(new MemberOfInvocation(targetOid, result)); + return result; + } + } + + void clearInvocations() { + memberOfInvocations.clear(); + } + + boolean isMemberOfInvocationResultChanged(DeltaSetTriple> evaluatedAssignmentTriple) { + if (!memberOfInvocations.isEmpty()) { + // Similar code is in AssignmentProcessor.processMembershipAndDelegatedRefs -- check that if changing the business logic + List membership = evaluatedAssignmentTriple.getNonNegativeValues().stream() + .filter(EvaluatedAssignmentImpl::isValid) + .flatMap(evaluatedAssignment -> evaluatedAssignment.getMembershipRefVals().stream()) + .map(ref -> ObjectTypeUtil.createObjectRef(ref, false)) + .collect(Collectors.toList()); + LOGGER.trace("Computed new membership: {}", membership); + return updateMemberOfInvocations(membership); + } else { + return false; + } + } + + private MemberOfInvocation findInvocation(String targetOid) { + List matching = memberOfInvocations.stream() + .filter(invocation -> targetOid.equals(invocation.targetOid)) + .collect(Collectors.toList()); + if (matching.isEmpty()) { + return null; + } else if (matching.size() == 1) { + return matching.get(0); + } else { + throw new IllegalStateException("More than one matching MemberOfInvocation for targetOid='" + targetOid + "': " + matching); + } + } + + private boolean computeIsMemberOfDuringEvaluation(ObjectDeltaObject focusOdo, + String targetOid) { + // TODO Or should we consider evaluateOld? + PrismObject focus = focusOdo.getNewObject(); + return focus != null && containsMember(focus.asObjectable().getRoleMembershipRef(), targetOid); + } + + private boolean updateMemberOfInvocations(List newMembership) { + boolean changed = false; + for (MemberOfInvocation invocation : memberOfInvocations) { + boolean newResult = containsMember(newMembership, invocation.targetOid); + if (newResult != invocation.result) { + LOGGER.trace("Invocation result changed for {} - new one is '{}'", invocation, newResult); + invocation.result = newResult; + changed = true; + } + } + return changed; + } + + // todo generalize a bit (e.g. by including relation) + private boolean containsMember(List membership, String targetOid) { + return membership.stream().anyMatch(ref -> targetOid.equals(ref.getOid())); + } + + private static class MemberOfInvocation { + private final String targetOid; + private boolean result; + + private MemberOfInvocation(String targetOid, boolean result) { + this.targetOid = targetOid; + this.result = result; + } + + @Override + public String toString() { + return "MemberOfInvocation{" + + "targetOid='" + targetOid + '\'' + + ", result=" + result + + '}'; + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/PathSegmentEvaluation.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/PathSegmentEvaluation.java new file mode 100644 index 00000000000..2b5de70b38f --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/PathSegmentEvaluation.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.FocusTypeUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.PlusMinusZeroType; + +import java.util.List; + +import static java.util.Collections.emptyList; + +/** + * Carries out and holds assignment evaluation: + * 1) evaluation of the condition + * 2) evaluation of payload (delegates to {@link PayloadEvaluation}) + * 3) evaluation of targets (delegates to {@link TargetsEvaluation}). + */ +class PathSegmentEvaluation extends AbstractEvaluation { + + private static final Trace LOGGER = TraceManager.getTrace(PathSegmentEvaluation.class); + + private static final String OP_EVALUATE = PathSegmentEvaluation.class.getName()+".evaluate"; + + /** + * The segment with the assignment that is being evaluated. + */ + final AssignmentPathSegmentImpl segment; + + /** + * Operation result related to this segment evaluation. + * + * We are using it in quite unorthodox way: it is created in constructor (to make it final, + * along with the trace), and closed in execute() method. This might be changed in the future. + */ + private final OperationResult result; + + /** + * Trace of the segment evaluation. + */ + private final AssignmentSegmentEvaluationTraceType trace; + + /** + * Record of targets evaluation, to be used after the evaluation. + */ + private TargetsEvaluation targetsEvaluation; + + PathSegmentEvaluation(AssignmentPathSegmentImpl segment, EvaluationContext ctx, OperationResult parentResult) { + super(segment, ctx); + this.segment = segment; + this.result = parentResult.subresult(OP_EVALUATE) + .setMinor() + .addParam("segment", segment.shortDump()) + .addContext("segmentSourceName", PolyString.getOrig(segment.source.getName())) + .build(); + this.trace = recordStart(); + } + + /** + * Execution of the evaluation. Can be called only once for this object. + */ + void evaluate() throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, + SecurityViolationException, ConfigurationException, CommunicationException { + try { + checkIfAlreadyEvaluated(); + checkSchema(); + + ctx.assignmentPath.add(segment); + try { + LOGGER.trace("*** Path (with current segment added):\n{}", ctx.assignmentPath.debugDumpLazily()); + + computeValidity(); + computeMode(); + segment.freeze(); + + if (segment.getAssignmentRelativityMode() != null) { + // Note that we evaluate payload and targets for both valid and invalid assignments. + // 1. Validity and relativity mode is recorded to several kinds of payload items (e.g. constructions). + // 2. Other ones (e.g. policy rules) are very important also for invalid assignments. + evaluateSegmentPayloadAndTargets(); + } + } finally { + ctx.assignmentPath.removeLast(segment); + } + recordEnd(); + + } catch (Throwable t) { + result.recordFatalError(t.getMessage(), t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + private void evaluateSegmentPayloadAndTargets() throws CommunicationException, ConfigurationException, SchemaException, + SecurityViolationException, ExpressionEvaluationException, PolicyViolationException, ObjectNotFoundException { + + new PayloadEvaluation<>(segment, ctx).evaluate(); + + if (segment.isAssignmentValid() || segment.direct) { + targetsEvaluation = new TargetsEvaluation<>(segment, ctx, result); + targetsEvaluation.evaluate(); + } + } + + private void computeValidity() { + boolean validity; + if (segment.isMatchingOrder) { + // Validity of segment that is sourced at focus (either directly or indirectly i.e. through inducements) + // is determined using focus lifecycle state model. + AH focusNew = ctx.ae.focusOdo.getNewObject().asObjectable(); + validity = LensUtil.isAssignmentValid(focusNew, segment.assignment, ctx.ae.now, ctx.ae.activationComputer, ctx.ae.focusStateModel); + } else { + // But for other assignments/inducements we consider their validity by regarding their actual source. + // So, even if (e.g.) focus is in "draft" state, only validity of direct assignments should be influenced by this fact. + // Other assignments (e.g. from roles to metaroles) should be considered valid, provided these roles are + // in active lifecycle states. See also MID-6113. + // + // TODO We should consider the assignment source's state model! But we are ignoring it for now. + validity = LensUtil.isAssignmentValid(segment.source, segment.assignment, ctx.ae.now, ctx.ae.activationComputer, null); + } + segment.setAssignmentValid(validity); + LOGGER.trace("Determined validity of segment {} to be {}", segment, validity); + } + + private void computeMode() throws CommunicationException, ObjectNotFoundException, SchemaException, + SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + segment.setAssignmentRelativityMode(determineRelativityMode()); + } + + private AssignmentSegmentEvaluationTraceType recordStart() { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("*** Evaluate segment: {}", segment); + LOGGER.trace("*** Evaluation order - standard: {}, matching: {}", segment.getEvaluationOrder(), segment.isMatchingOrder); + LOGGER.trace("*** Evaluation order - for target: {}, matching: {}", segment.getEvaluationOrderForTarget(), segment.isMatchingOrderForTarget); + LOGGER.trace("*** path to source valid: {}", segment.pathToSourceValid); + } + if (result.isTracingNormal(AssignmentSegmentEvaluationTraceType.class)) { + AssignmentSegmentEvaluationTraceType trace = new AssignmentSegmentEvaluationTraceType(ctx.ae.prismContext) + .segment(segment.toAssignmentPathSegmentType(true)); + result.addTrace(trace); + return trace; + } else { + return null; + } + } + + private void recordEnd() { + if (segment.target != null) { // always null here + result.addContext("segmentTargetName", PolyString.getOrig(segment.getTarget().getName())); + } + result.addReturn("assignmentValid", segment.isAssignmentValid()); + result.addArbitraryObjectAsReturn("mode", segment.getAssignmentRelativityMode()); + //result.addReturn("isValid", valid); + if (trace != null) { + trace.setMode(PlusMinusZeroType.fromValue(segment.getAssignmentRelativityMode())); + trace.setTextResult(segment.debugDump()); + } + } + + private void checkSchema() throws SchemaException { + if (segment.source == null) { + throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+ ctx.evalAssignment +")"); + } + + AssignmentType assignment = segment.assignment; + PrismContainerValue assignmentContainerValue = assignment.asPrismContainerValue(); + PrismContainerable assignmentContainer = assignmentContainerValue.getParent(); + if (assignmentContainer == null) { + throw new SchemaException("The assignment "+assignment+" does not have a parent in "+segment.sourceDescription); + } + if (assignmentContainer.getDefinition() == null) { + throw new SchemaException("The assignment "+assignment+" does not have definition in "+segment.sourceDescription); + } + PrismContainer extensionContainer = assignmentContainerValue.findContainer(AssignmentType.F_EXTENSION); + if (extensionContainer != null) { + if (extensionContainer.getDefinition() == null) { + throw new SchemaException("Extension does not have a definition in assignment "+assignment+" in "+segment.sourceDescription); + } + + if (extensionContainer.getValue().getItems() == null) { + throw new SchemaException("Extension without items in assignment " + assignment + " in " + segment.sourceDescription + ", empty extension tag?"); + } + + for (Item item: extensionContainer.getValue().getItems()) { + if (item == null) { + throw new SchemaException("Null item in extension in assignment "+assignment+" in "+segment.sourceDescription); + } + if (item.getDefinition() == null) { + throw new SchemaException("Item "+item+" has no definition in extension in assignment "+assignment+" in "+segment.sourceDescription); + } + } + } + } + + public List> getTargets() { + return targetsEvaluation != null ? targetsEvaluation.targets : emptyList(); + } + + private PlusMinusZero determineRelativityMode() + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, + ConfigurationException, CommunicationException { + return ctx.conditionEvaluator.computeModeFromCondition( + segment.sourceRelativityMode, + segment.assignment.getCondition(), + segment.source, + "condition in assignment in " + segment.sourceDescription, + FocusTypeUtil.dumpAssignmentLazily(segment.assignment), + result); + } +} + +/* + * + * TODO TODO TODO + * This may happen, e.g. if a condition in an existing assignment turns from false to true. + * In that case the primary assignment mode is ZERO, but the relative mode is PLUS. + * The relative mode always starts at ZERO, even for added or removed assignments. + * + * This depends on the status of conditions. E.g. if condition evaluates 'false -> true' (i.e. in old + * state the value is false, and in new state the value is true), then the mode is PLUS. + * + * This "triples algebra" is based on the following two methods: + * + * @see ExpressionUtil#computeConditionResultMode(boolean, boolean) - Based on condition values "old+new" determines + * into what set (PLUS/MINUS/ZERO/none) should the result be placed. Irrespective of what is the current mode. So, + * in order to determine "real" place where to put it (i.e. the new mode) the following method is used. + *c + * @see PlusMinusZero#compute(PlusMinusZero, PlusMinusZero) - Takes original mode and the mode from recent condition + * and determines the new mode: + * + * PLUS + PLUS/ZERO = PLUS + * MINUS + MINUS/ZERO = MINUS + * ZERO + ZERO = ZERO + * PLUS + MINUS = none + * + * This is quite straightforward, although the last rule deserves a note. If we have an assignment that was originally + * disabled and becomes enabled by the current delta (i.e. PLUS), and that assignment contains an inducement that was originally + * enabled and becomes disabled (i.e. MINUS), the result is that the (e.g.) constructions within the inducement were not + * present in the old state (because assignment was disabled) and are not present in the new state (because inducement is disabled). + * + */ diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/PayloadEvaluation.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/PayloadEvaluation.java new file mode 100644 index 00000000000..e29d587eeb2 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/PayloadEvaluation.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.lens.construction.Construction; +import com.evolveum.midpoint.model.impl.lens.construction.EvaluatedConstructionImpl; +import com.evolveum.midpoint.model.impl.lens.construction.PersonaConstruction; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.AssignedFocusMappingEvaluationRequest; +import com.evolveum.midpoint.prism.OriginType; +import com.evolveum.midpoint.util.exception.SchemaException; +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; + +/** + * Evaluation of assignment payload i.e. constructions (resource/persona), focus mappings + * and policy rules (focus and target). + */ +class PayloadEvaluation extends AbstractEvaluation { + + private static final Trace LOGGER = TraceManager.getTrace(PayloadEvaluation.class); + + PayloadEvaluation(AssignmentPathSegmentImpl segment, EvaluationContext ctx) { + super(segment, ctx); + } + + void evaluate() throws SchemaException { + assert ctx.assignmentPath.last() == segment; + assert segment.getAssignmentRelativityMode() != null; + checkIfAlreadyEvaluated(); + + if (ctx.ae.loginMode) { + // In login mode we are interested only in authorization and GUI configuration data + // that is present in roles. + LOGGER.trace("Skipping evaluation of payload of assignment {} because of login mode", segment.assignment); + } else if (!segment.isAssignmentValid() && !segment.direct) { + LOGGER.trace("Skipping evaluation of payload of assignment {} because it is not valid and it's not the directly assigned one", segment.assignment); + } else { + // Directly assigned assignments are visited even if they are not valid (i.e. effectively disabled) - see below + + if (segment.isMatchingOrder) { + collectConstruction(); // constructions (from invalid direct assignments) are collected + collectPersonaConstruction(); // constructions (from invalid direct assignments) are collected + if (segment.isFullPathValid()) { + collectFocusMappings(); // but mappings from invalid direct assignments are not + } + if (segment.isNonNegativeRelativityMode()) { + collectFocusPolicyRule(); // focus policy rules from invalid assignments are collected (why?) but only if non-negative (why?) + } + } + + if (segment.isMatchingOrderForTarget) { + // Target policy rules from non-valid direct assignments are collected because of e.g. approvals or SoD. + // But we consider only non-negative ones (why?) + if (segment.isNonNegativeRelativityMode()) { + collectTargetPolicyRule(); + } + } + } + } + + private void collectConstruction() { + ConstructionType constructionBean = segment.assignment.getConstruction(); + if (constructionBean != null) { + + LOGGER.trace("Preparing construction '{}' in {}", constructionBean.getDescription(), segment.source); + + Construction> construction = new Construction<>(constructionBean, segment.source); + // We have to clone here as the path is constantly changing during evaluation + construction.setAssignmentPath(ctx.assignmentPath.clone()); + construction.setFocusOdo(ctx.ae.focusOdo); + construction.setLensContext(ctx.ae.lensContext); + construction.setObjectResolver(ctx.ae.objectResolver); + construction.setPrismContext(ctx.ae.prismContext); + construction.setMappingFactory(ctx.ae.mappingFactory); + construction.setMappingEvaluator(ctx.ae.mappingEvaluator); + construction.setNow(ctx.ae.now); + construction.setContextLoader(ctx.ae.contextLoader); + construction.setOriginType(OriginType.ASSIGNMENTS); + construction.setChannel(ctx.ae.channel); + construction.setOrderOneObject(segment.varThisObject); + construction.setValid(segment.isFullPathValid()); + construction.setRelativityMode(segment.getAssignmentRelativityMode()); + + // Do not evaluate the construction here. We will do it in the second pass. Just prepare everything to be evaluated. + ctx.evalAssignment.addConstruction(construction, segment.getAssignmentRelativityMode()); + } + } + + private void collectPersonaConstruction() { + PersonaConstructionType constructionBean = segment.assignment.getPersonaConstruction(); + if (constructionBean != null) { + + LOGGER.trace("Preparing persona construction '{}' in {}", constructionBean.getDescription(), segment.source); + + PersonaConstruction construction = new PersonaConstruction<>(constructionBean, segment.source); + // We have to clone here as the path is constantly changing during evaluation + construction.setAssignmentPath(ctx.assignmentPath.clone()); + construction.setFocusOdo(ctx.ae.focusOdo); + construction.setLensContext(ctx.ae.lensContext); + construction.setObjectResolver(ctx.ae.objectResolver); + construction.setPrismContext(ctx.ae.prismContext); + construction.setOriginType(OriginType.ASSIGNMENTS); + construction.setChannel(ctx.ae.channel); + construction.setValid(segment.isFullPathValid()); + construction.setRelativityMode(segment.getAssignmentRelativityMode()); + + ctx.evalAssignment.addPersonaConstruction(construction, segment.getAssignmentRelativityMode()); + } + } + + private void collectFocusMappings() throws SchemaException { + MappingsType mappingsBean = segment.assignment.getFocusMappings(); + if (mappingsBean != null) { + + LOGGER.trace("Request evaluation of focus mappings '{}' in {} ({} mappings)", + mappingsBean.getDescription(), segment.source, mappingsBean.getMapping().size()); + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + + for (MappingType mappingBean : mappingsBean.getMapping()) { + AssignedFocusMappingEvaluationRequest request = new AssignedFocusMappingEvaluationRequest(mappingBean, segment.source, + ctx.evalAssignment, segment.getAssignmentRelativityMode(), assignmentPathVariables, segment.sourceDescription); + ctx.evalAssignment.addFocusMappingEvaluationRequest(request); + } + } + } + + private void collectFocusPolicyRule() { + PolicyRuleType policyRuleBean = segment.assignment.getPolicyRule(); + if (policyRuleBean != null) { + LOGGER.trace("Collecting focus policy rule '{}' in {}", policyRuleBean.getName(), segment.source); + ctx.evalAssignment.addFocusPolicyRule(createEvaluatedPolicyRule(policyRuleBean)); + } + } + + private void collectTargetPolicyRule() { + PolicyRuleType policyRuleBean = segment.assignment.getPolicyRule(); + if (policyRuleBean != null) { + LOGGER.trace("Collecting target policy rule '{}' in {}", policyRuleBean.getName(), segment.source); + + EvaluatedPolicyRuleImpl policyRule = createEvaluatedPolicyRule(policyRuleBean); + if (appliesDirectly(ctx.assignmentPath)) { + ctx.evalAssignment.addThisTargetPolicyRule(policyRule); + } else { + ctx.evalAssignment.addOtherTargetPolicyRule(policyRule); + } + } + } + + @NotNull + private EvaluatedPolicyRuleImpl createEvaluatedPolicyRule(PolicyRuleType policyRuleBean) { + return new EvaluatedPolicyRuleImpl(policyRuleBean.clone(), ctx.assignmentPath.clone(), ctx.ae.prismContext); + } + + private boolean appliesDirectly(AssignmentPathImpl assignmentPath) { + assert !assignmentPath.isEmpty(); + // TODO what about deputy relation which does not increase summaryOrder? + long zeroOrderCount = assignmentPath.getSegments().stream() + .filter(seg -> seg.getEvaluationOrderForTarget().getSummaryOrder() == 0) + .count(); + return zeroOrderCount == 1; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetAssignmentEvaluation.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetAssignmentEvaluation.java new file mode 100644 index 00000000000..ee8b1afe8a9 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetAssignmentEvaluation.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import com.evolveum.midpoint.model.api.context.EvaluationOrder; +import com.evolveum.midpoint.model.impl.lens.assignments.TargetEvaluation.TargetValidity; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.FocusTypeUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import javax.xml.namespace.QName; + +import static com.evolveum.midpoint.model.impl.lens.assignments.AssignmentEvaluator.getOrderOneObject; +import static com.evolveum.midpoint.model.impl.lens.assignments.Util.isAllowedByLimitations; + +/** + * Evaluates an assignment of a target (assignment holder): basically, creates a new assignment path + * segment and requests its evaluation. + */ +class TargetAssignmentEvaluation extends AbstractEvaluation { + + private static final Trace LOGGER = TraceManager.getTrace(TargetAssignmentEvaluation.class); + + private final PlusMinusZero targetRelativityMode; + private final TargetValidity targetValidity; + private final OperationResult result; + + private final AssignmentType nextAssignment; + + TargetAssignmentEvaluation(AssignmentPathSegmentImpl segment, PlusMinusZero targetRelativityMode, TargetValidity targetValidity, + EvaluationContext ctx, OperationResult result, AssignmentType nextAssignment) { + super(segment, ctx); + this.targetRelativityMode = targetRelativityMode; + this.result = result; + this.targetValidity = targetValidity; + this.nextAssignment = nextAssignment; + } + + void evaluate() throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, + SecurityViolationException, ConfigurationException, CommunicationException { + + assert ctx.assignmentPath.last() == segment; + assert segment.isAssignmentValid() || segment.direct; + assert targetValidity.targetValid || segment.direct; + assert targetRelativityMode != null; + checkIfAlreadyEvaluated(); + + // TODO reconsider this + ObjectType orderOneObject = getOrderOneObject(segment); + + if (ctx.ae.relationRegistry.isDelegation(segment.relation)) { + // We have to handle assignments as though they were inducements here. + if (!isAllowedByLimitations(segment, nextAssignment, ctx)) { + LOGGER.trace("Skipping application of delegated assignment {} because it is limited in the delegation", + FocusTypeUtil.dumpAssignmentLazily(nextAssignment)); + return; + } + } + QName nextRelation = Util.getRelation(nextAssignment, ctx.ae.relationRegistry); + EvaluationOrder nextEvaluationOrder = segment.getEvaluationOrder().advance(nextRelation); + EvaluationOrder nextEvaluationOrderForTarget = segment.getEvaluationOrderForTarget().advance(nextRelation); + LOGGER.trace("orig EO({}): follow assignment {} {}; new EO({})", segment.getEvaluationOrder().shortDumpLazily(), + segment.target, FocusTypeUtil.dumpAssignmentLazily(nextAssignment), nextEvaluationOrder); + + AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl.Builder() + .source((AssignmentHolderType) segment.target) + .sourceDescription(segment.target+" in "+segment.sourceDescription) + .assignment(nextAssignment) + .isAssignment(true) + .relationRegistry(ctx.ae.relationRegistry) + .prismContext(ctx.ae.prismContext) + .evaluationOrder(nextEvaluationOrder) + .evaluationOrderForTarget(nextEvaluationOrderForTarget) + .varThisObject(orderOneObject) + .pathToSourceValid(targetValidity.pathAndTargetValid) + .sourceRelativityMode(targetRelativityMode) + .build(); + new PathSegmentEvaluation<>(nextSegment, ctx, result).evaluate(); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetEvaluation.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetEvaluation.java new file mode 100644 index 00000000000..4888a448a41 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetEvaluation.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import static com.evolveum.midpoint.model.impl.lens.assignments.Util.isNonNegative; + +import org.apache.commons.lang.BooleanUtils; +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.model.common.ArchetypeManager; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.schema.internals.InternalMonitor; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +/** + * Evaluates resolved assignment target: its payload (authorizations, GUI config) and assignments/inducements. + */ +class TargetEvaluation extends AbstractEvaluation { + + private static final Trace LOGGER = TraceManager.getTrace(TargetEvaluation.class); + + private final OperationResult result; + + /** + * The resolved target. + */ + @NotNull private final AssignmentHolderType target; + + /** + * Relativity mode of this segment. It is modified by target (role) condition + * into targetRelativityMode. + */ + private final PlusMinusZero assignmentRelativityMode; + + /** + * Relativity mode after application of target (role) condition. + */ + private PlusMinusZero targetRelativityMode; + + /** + * Aggregated validity of target and the whole path. + */ + private TargetValidity targetValidity; + + TargetEvaluation(AssignmentPathSegmentImpl segment, EvaluationContext ctx, OperationResult result) { + super(segment, ctx); + this.result = result; + + this.assignmentRelativityMode = segment.getAssignmentRelativityMode(); + this.target = (AssignmentHolderType) segment.getTarget(); + } + + void evaluate() throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, + SecurityViolationException, ConfigurationException, CommunicationException { + + assert ctx.assignmentPath.last() == segment; + assert assignmentRelativityMode != null; + assert segment.isAssignmentValid() || segment.direct; + checkIfAlreadyEvaluated(); + + if (ctx.ae.evaluatedAssignmentTargetCache.canSkip(segment, ctx.primaryAssignmentMode)) { + LOGGER.trace("Skipping evaluation of segment {} because it is idempotent and we have seen the target before", segment); + InternalMonitor.recordRoleEvaluationSkip(target, true); + return; + } + + LOGGER.trace("Evaluating segment TARGET:\n{}", segment.debugDumpLazily(1)); + LOGGER.debug("Evaluating RBAC [{}]", ctx.assignmentPath.shortDumpLazily()); + + checkRelationWithTarget(target); + determineValidity(); + + InternalMonitor.recordRoleEvaluation(target, true); + + AssignmentTargetEvaluationInformation targetEvaluationInformation; + if (targetValidity.pathAndTargetValid) { + // Cache it immediately, even before evaluation. So if there is a cycle in the role path + // then we can detect it and skip re-evaluation of aggressively idempotent roles. + targetEvaluationInformation = ctx.ae.evaluatedAssignmentTargetCache.recordProcessing(segment, ctx.primaryAssignmentMode); + } else { + targetEvaluationInformation = null; + } + + int targetPolicyRulesOnEntry = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); + try { + if (targetValidity.targetValid) { + // TODO why only for valid targets? This is how it was implemented in original AssignmentEvaluator. + targetRelativityMode = determineTargetRelativityMode(); + } else { + targetRelativityMode = assignmentRelativityMode; + } + + if (targetRelativityMode != null) { + evaluateInternal(); + } + } finally { + int targetPolicyRulesOnExit = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); + LOGGER.trace("Evaluating segment target DONE for {}; target policy rules: {} -> {}", segment, + targetPolicyRulesOnEntry, targetPolicyRulesOnExit); + if (targetEvaluationInformation != null) { + targetEvaluationInformation.setBringsTargetPolicyRules(targetPolicyRulesOnExit > targetPolicyRulesOnEntry); + } + } + } + + private void evaluateInternal() + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, + ConfigurationException, CommunicationException, PolicyViolationException { + assert targetRelativityMode != null; + + collectEvaluatedAssignmentTarget(); + + // we need to evaluate assignments also for non-valid targets, because of target policy rules + // ... but only for direct ones! + if (targetValidity.targetValid || segment.direct) { + evaluateAssignments(); + } + + // We need to collect membership also for disabled targets (provided the assignment itself is enabled): MID-4127. + // It is quite logical: if a user is member of a disabled role, it still needs to have roleMembershipRef + // pointing to that role. + if (isNonNegative(targetRelativityMode) && Util.shouldCollectMembership(segment)) { + ctx.membershipCollector.collect(target, segment.relation); + } + + if (targetValidity.targetValid) { + + // TODO In a way analogous to the membership info above: shouldn't we collect tenantRef information + // also for disabled tenants? + if (isNonNegative(targetRelativityMode)) { + collectTenantRef(target, 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. + evaluateInducements(); + + // Respective conditions are evaluated inside + evaluateTargetPayload(); + + } else { + LOGGER.trace("Not collecting payload from target of {} as it is not valid", segment); + } + } + + private void evaluateAssignments() throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + for (AssignmentType assignment : target.getAssignment()) { + new TargetAssignmentEvaluation<>(segment, targetRelativityMode, targetValidity, ctx, result, assignment) + .evaluate(); + } + } + + private void evaluateInducements() throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + if (target instanceof AbstractRoleType) { + for (AssignmentType inducement : ((AbstractRoleType) target).getInducement()) { + new TargetInducementEvaluation<>(segment, targetRelativityMode, targetValidity, ctx, result, inducement) + .evaluate(); + } + } + } + + private void evaluateTargetPayload() { + new TargetPayloadEvaluation<>(segment, targetRelativityMode, targetValidity, ctx).evaluate(); + } + + private PlusMinusZero determineTargetRelativityMode() + throws SchemaException, CommunicationException, ObjectNotFoundException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + MappingType condition = target instanceof AbstractRoleType ? ((AbstractRoleType) target).getCondition() : null; + // TODO why we use "segment.source" as source object for condition evaluation even if + // we evaluate condition in target role? We should probably use the role itself as the source here. + AssignmentHolderType source = segment.source; + return ctx.conditionEvaluator.computeModeFromCondition( + assignmentRelativityMode, condition, + source, + "condition in " + segment.getTargetDescription(), target, + result); + } + + private void collectEvaluatedAssignmentTarget() { + EvaluatedAssignmentTargetImpl evalAssignmentTarget = new EvaluatedAssignmentTargetImpl( + target.asPrismObject(), + segment.isMatchingOrder, // evaluateConstructions: exact meaning of this is to be revised + ctx.assignmentPath.clone(), + segment.assignment, + targetValidity.pathAndTargetValid); + ctx.evalAssignment.addRole(evalAssignmentTarget, targetRelativityMode); + } + + private void determineValidity() throws ConfigurationException { + // FIXME Target state model does not reflect its archetype! + LifecycleStateModelType targetStateModel = ArchetypeManager.determineLifecycleModel(target.asPrismObject(), ctx.ae.systemConfiguration); + boolean targetValid = LensUtil.isFocusValid(target, ctx.ae.now, ctx.ae.activationComputer, targetStateModel); + boolean pathAndTargetValid = segment.isFullPathValid() && targetValid; + targetValidity = new TargetValidity(targetValid, pathAndTargetValid); + } + + private void checkRelationWithTarget(AssignmentHolderType target) + throws SchemaException { + if (target instanceof AbstractRoleType || target instanceof TaskType) { //TODO: + // OK, just go on + } else if (target instanceof UserType) { + if (!ctx.ae.relationRegistry.isDelegation(segment.relation)) { + throw new SchemaException("Unsupported relation " + segment.relation + " for assignment of target type " + target + " in " + segment.sourceDescription); + } + } else { + throw new SchemaException("Unknown assignment target type " + target + " in " + segment.sourceDescription); + } + } + + private void collectTenantRef(AssignmentHolderType targetToSet, EvaluationContext ctx) { + if (targetToSet instanceof OrgType) { + if (BooleanUtils.isTrue(((OrgType) targetToSet).isTenant()) && ctx.evalAssignment.getTenantOid() == null) { + if (ctx.assignmentPath.hasOnlyOrgs()) { + ctx.evalAssignment.setTenantOid(targetToSet.getOid()); + } + } + } + } + + static class TargetValidity { + final boolean targetValid; + final boolean pathAndTargetValid; + + private TargetValidity(boolean targetValid, boolean pathAndTargetValid) { + this.targetValid = targetValid; + this.pathAndTargetValid = pathAndTargetValid; + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetInducementEvaluation.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetInducementEvaluation.java new file mode 100644 index 00000000000..7558e8b7cd6 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetInducementEvaluation.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import com.evolveum.midpoint.model.api.context.EvaluationOrder; +import com.evolveum.midpoint.model.impl.lens.assignments.TargetEvaluation.TargetValidity; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.FocusTypeUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OrderConstraintsType; + +import org.apache.commons.collections4.CollectionUtils; +import org.jetbrains.annotations.NotNull; + +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.evolveum.midpoint.model.impl.lens.assignments.AssignmentEvaluator.getOrderOneObject; +import static com.evolveum.midpoint.model.impl.lens.assignments.Util.isAllowedByLimitations; + +/** + * Evaluates an inducement of a target (abstract role): basically, creates a new assignment path + * segment and requests its evaluation. The tricky part is computing evaluation order (focus/target) + * of the new segment. + */ +class TargetInducementEvaluation extends AbstractEvaluation { + + private static final Trace LOGGER = TraceManager.getTrace(TargetInducementEvaluation.class); + + private final PlusMinusZero targetRelativityMode; + @NotNull private final TargetValidity targetValidity; + private final OperationResult result; + private final AssignmentType inducement; + + TargetInducementEvaluation(AssignmentPathSegmentImpl segment, + PlusMinusZero targetRelativityMode, @NotNull TargetValidity targetValidity, + EvaluationContext ctx, OperationResult result, AssignmentType inducement) { + super(segment, ctx); + this.targetRelativityMode = targetRelativityMode; + this.targetValidity = targetValidity; + this.result = result; + this.inducement = inducement; + } + + void evaluate() throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + assert ctx.assignmentPath.last() == segment; + assert segment.isAssignmentValid() || segment.direct; + assert targetRelativityMode != null; + assert targetValidity.targetValid; + + checkIfAlreadyEvaluated(); + + // TODO reconsider this + ObjectType orderOneObject = getOrderOneObject(segment); + + if (!isInducementApplicableToFocusType(inducement.getFocusType())) { + LOGGER.trace("Skipping application of inducement {} because the focusType does not match (specified: {}, actual: {})", + FocusTypeUtil.dumpAssignmentLazily(inducement), inducement.getFocusType(), ctx.ae.lensContext.getFocusClass()); + return; + } + + if (!isAllowedByLimitations(segment, inducement, ctx)) { + LOGGER.trace("Skipping application of inducement {} because it is limited", FocusTypeUtil.dumpAssignmentLazily(inducement)); + return; + } + + boolean nextIsMatchingOrder = segment.getEvaluationOrder().matches(inducement.getOrder(), inducement.getOrderConstraint()); + boolean nextIsMatchingOrderForTarget = segment.getEvaluationOrderForTarget().matches(inducement.getOrder(), inducement.getOrderConstraint()); + + OrderAdjustment adjustment = computeOrderAdjustment(); + + String nextSourceDescription = segment.target+" in "+segment.sourceDescription; + AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl.Builder() + .source((AssignmentHolderType) segment.target) + .sourceDescription(nextSourceDescription) + .assignment(inducement) + .isAssignment(false) + .relationRegistry(ctx.ae.relationRegistry) + .prismContext(ctx.ae.prismContext) + .varThisObject(orderOneObject) + .pathToSourceValid(targetValidity.pathAndTargetValid) + .sourceRelativityMode(targetRelativityMode) + .evaluationOrder(adjustment.evaluationOrder) + .evaluationOrderForTarget(adjustment.targetEvaluationOrder) + .isMatchingOrder(nextIsMatchingOrder) + .isMatchingOrderForTarget(nextIsMatchingOrderForTarget) + .lastEqualOrderSegmentIndex(adjustment.lastEqualOrderSegmentIndex) + .build(); + + // 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(), segment.target, FocusTypeUtil.dumpInducementConstraints(inducement), + FocusTypeUtil.dumpAssignment(inducement), adjustment.evaluationOrder.shortDump()); + } + + new PathSegmentEvaluation<>(nextSegment, ctx, result).evaluate(); + } + + private boolean isInducementApplicableToFocusType(QName inducementFocusType) throws SchemaException { + if (inducementFocusType == null) { + return true; + } + Class inducementFocusClass = ctx.ae.prismContext.getSchemaRegistry().determineCompileTimeClass(inducementFocusType); + if (inducementFocusClass == null) { + throw new SchemaException("Could not determine class for " + inducementFocusType); + } + if (ctx.ae.lensContext.getFocusClass() == null) { + // should not occur; it would be probably safe to throw an exception here + LOGGER.error("No focus class in lens context; inducement targeted at focus type {} will not be applied:\n{}", + inducementFocusType, ctx.ae.lensContext.debugDump()); + return false; + } + return inducementFocusClass.isAssignableFrom(ctx.ae.lensContext.getFocusClass()); + } + + private static class OrderAdjustment { + private final EvaluationOrder evaluationOrder; + private final EvaluationOrder targetEvaluationOrder; + private final Integer lastEqualOrderSegmentIndex; + + private OrderAdjustment(EvaluationOrder evaluationOrder, EvaluationOrder targetEvaluationOrder, Integer lastEqualOrderSegmentIndex) { + this.evaluationOrder = evaluationOrder; + this.targetEvaluationOrder = targetEvaluationOrder; + this.lastEqualOrderSegmentIndex = lastEqualOrderSegmentIndex; + } + + private OrderAdjustment(EvaluationOrder evaluationOrder, EvaluationOrder targetEvaluationOrder) { + this.evaluationOrder = evaluationOrder; + this.targetEvaluationOrder = targetEvaluationOrder; + this.lastEqualOrderSegmentIndex = null; + } + + private static OrderAdjustment undefined() { + return new OrderAdjustment(EvaluationOrderImpl.UNDEFINED, EvaluationOrderImpl.UNDEFINED); + } + } + + /** + * Tries to compute correct evaluation order after application of an inducement. Terrible method. + * For some background please see comments at the end of {@link AssignmentPathSegmentImpl} class. + */ + private OrderAdjustment computeOrderAdjustment() { + EvaluationOrder currentOrder = segment.getEvaluationOrder(); + EvaluationOrder currentTargetOrder = segment.getEvaluationOrderForTarget(); + List constraints = new ArrayList<>(inducement.getOrderConstraint()); + Integer order = inducement.getOrder(); + + if (constraints.isEmpty()) { + if (order == null || order == 1) { + return new OrderAdjustment(currentOrder, currentTargetOrder); + } else if (order <= 0) { + throw new IllegalStateException("Wrong inducement order: it must be positive but it is " + order + " instead"); + } + // converting legacy -> new specification + int currentSummary = currentOrder.getSummaryOrder(); + if (order > currentSummary) { + LOGGER.trace("order of the inducement ({}) is greater than the current evaluation order ({}), marking as undefined", + order, currentOrder); + return OrderAdjustment.undefined(); + } else { + // i.e. currentOrder >= order, i.e. currentOrder > order-1 + int newOrder = currentSummary - (order - 1); + assert newOrder > 0; + constraints.add(new OrderConstraintsType(ctx.ae.prismContext) + .order(order) + .resetOrder(newOrder)); + } + } + + OrderConstraintsType summaryConstraints = getConstraintWithoutRelation(constraints); + Integer resetSummaryTo = summaryConstraints != null && summaryConstraints.getResetOrder() != null ? + summaryConstraints.getResetOrder() : null; + + OrderAdjustment adjustment; + if (resetSummaryTo != null) { + adjustment = applyResetSummary(currentOrder, currentTargetOrder, constraints, resetSummaryTo); + } else { + adjustment = applyResetForRelations(currentOrder, currentTargetOrder, constraints); + } + + if (!adjustment.evaluationOrder.isDefined()) { + return adjustment; + } + + if (adjustment.evaluationOrder.getSummaryOrder() <= 0) { + return OrderAdjustment.undefined(); + } + + if (!adjustment.evaluationOrder.isValid()) { + throw new AssertionError("Resulting evaluation order path is invalid: " + adjustment.evaluationOrder); + } + + if (adjustment.targetEvaluationOrder.isValid()) { + return adjustment; + } else { + // some extreme cases like the one described in TestAssignmentProcessor2.test520 + return new OrderAdjustment(adjustment.evaluationOrder, EvaluationOrderImpl.UNDEFINED, adjustment.lastEqualOrderSegmentIndex); + } + } + + private OrderAdjustment applyResetForRelations(EvaluationOrder currentOrder, EvaluationOrder currentTargetOrder, + List constraints) { + EvaluationOrder updatedOrder = currentOrder; + for (OrderConstraintsType constraint : constraints) { + if (constraint.getResetOrder() != null) { + assert constraint.getRelation() != null; // already processed above + int currentOrderForRelation = updatedOrder.getMatchingRelationOrder(constraint.getRelation()); + int newOrderForRelation = constraint.getResetOrder(); + if (newOrderForRelation > currentOrderForRelation) { + LOGGER.warn("Cannot increase evaluation order for {} from {} to {}: {}", constraint.getRelation(), + currentOrderForRelation, newOrderForRelation, constraint); + } else if (newOrderForRelation < currentOrderForRelation) { + updatedOrder = updatedOrder.resetOrder(constraint.getRelation(), newOrderForRelation); + LOGGER.trace("Reset order for {} from {} to {} -> {}", constraint.getRelation(), currentOrderForRelation, newOrderForRelation, updatedOrder); + } else { + LOGGER.trace("Keeping order for {} at {} -> {}", constraint.getRelation(), currentOrderForRelation, updatedOrder); + } + } + } + Map difference = currentOrder.diff(updatedOrder); + EvaluationOrder updatedTargetOrder = currentTargetOrder.applyDifference(difference); + return new OrderAdjustment(updatedOrder, updatedTargetOrder); + } + + private OrderAdjustment applyResetSummary(EvaluationOrder currentOrder, EvaluationOrder currentTargetOrder, + List constraints, Integer resetSummaryTo) { + OrderAdjustment adjustment; + int summaryBackwards = currentOrder.getSummaryOrder() - resetSummaryTo; + if (summaryBackwards < 0) { + // or should we throw an exception? + LOGGER.warn("Cannot move summary order backwards to a negative value ({}). Current order: {}, requested order: {}", + summaryBackwards, currentOrder.getSummaryOrder(), resetSummaryTo); + return OrderAdjustment.undefined(); + } else if (summaryBackwards > 0) { + int assignmentsSeen = 0; + int i = ctx.assignmentPath.size()-1; + while (assignmentsSeen < summaryBackwards) { + if (i < 0) { + LOGGER.trace("Cannot move summary order backwards by {}; only {} assignments segment seen: {}", + summaryBackwards, assignmentsSeen, ctx.assignmentPath); + return OrderAdjustment.undefined(); + } + AssignmentPathSegmentImpl segment = ctx.assignmentPath.getSegments().get(i); + if (segment.isAssignment()) { + if (!ctx.ae.relationRegistry.isDelegation(segment.relation)) { + assignmentsSeen++; + LOGGER.trace("Going back {}: relation at assignment -{} (position -{}): {}", summaryBackwards, + assignmentsSeen, ctx.assignmentPath.size() - i, segment.relation); + } + } else { + AssignmentType inducement = segment.getAssignment(ctx.evaluateOld); // for i>0 returns value regardless of evaluateOld + for (OrderConstraintsType constraint : inducement.getOrderConstraint()) { + if (constraint.getResetOrder() != null && constraint.getRelation() != null) { + LOGGER.debug("Going back {}: an inducement with non-summary resetting constraint found" + + " in the chain (at position -{}): {} in {}", summaryBackwards, ctx.assignmentPath.size()-i, + constraint, segment); + return OrderAdjustment.undefined(); + } + } + if (segment.getLastEqualOrderSegmentIndex() != null) { + i = segment.getLastEqualOrderSegmentIndex(); + continue; + } + } + i--; + } + adjustment = new OrderAdjustment( + ctx.assignmentPath.getSegments().get(i).getEvaluationOrder(), + ctx.assignmentPath.getSegments().get(i).getEvaluationOrderForTarget(), + i); + } else { + // summaryBackwards is 0 - nothing to change + adjustment = new OrderAdjustment(currentOrder, currentTargetOrder); + } + if (adjustment.evaluationOrder.isDefined()) { + for (OrderConstraintsType constraint : constraints) { + if (constraint.getRelation() != null && constraint.getResetOrder() != null) { + LOGGER.warn("Ignoring resetOrder (with a value of {} for {}) because summary order was already moved backwards by {} to {}: {}", + constraint.getResetOrder(), constraint.getRelation(), summaryBackwards, + adjustment.evaluationOrder.getSummaryOrder(), constraint); + } + } + } + return adjustment; + } + + private OrderConstraintsType getConstraintWithoutRelation(List constraints) { + return CollectionUtils.emptyIfNull(constraints).stream() + .filter(c -> c.getRelation() == null) + .findFirst().orElse(null); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetMembershipCollector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetMembershipCollector.java new file mode 100644 index 00000000000..7ddf896008e --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetMembershipCollector.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +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 javax.xml.namespace.QName; +import java.util.Collection; + +/** + * Responsible for collecting role membership (and similar) information to EvaluatedAssignment. + */ +class TargetMembershipCollector { + + private static final Trace LOGGER = TraceManager.getTrace(TargetMembershipCollector.class); + + private final EvaluationContext ctx; + + TargetMembershipCollector(EvaluationContext ctx) { + this.ctx = ctx; + } + + void collect(AssignmentHolderType targetToAdd, QName relation) { + PrismReferenceValue valueToAdd = ctx.ae.prismContext.itemFactory().createReferenceValue(); + valueToAdd.setObject(targetToAdd.asPrismObject()); + valueToAdd.setTargetType(ObjectTypes.getObjectType(targetToAdd.getClass()).getTypeQName()); + valueToAdd.setRelation(relation); + valueToAdd.setTargetName(targetToAdd.getName().toPolyString()); + + collect(valueToAdd, targetToAdd.getClass(), relation, targetToAdd, ctx); + } + + void collect(ObjectReferenceType referenceToAdd, QName relation) { + PrismReferenceValue valueToAdd = ctx.ae.prismContext.itemFactory().createReferenceValue(); + valueToAdd.setOid(referenceToAdd.getOid()); + valueToAdd.setTargetType(referenceToAdd.getType()); + valueToAdd.setRelation(relation); + valueToAdd.setTargetName(referenceToAdd.getTargetName()); + + Class targetClass = ObjectTypes.getObjectTypeFromTypeQName(referenceToAdd.getType()).getClassDefinition(); + collect(valueToAdd, targetClass, relation, referenceToAdd, ctx); + } + + private void collect(PrismReferenceValue valueToAdd, Class targetClass, QName relation, + Object targetDesc, EvaluationContext ctx) { + if (ctx.assignmentPath.containsDelegation(ctx.evaluateOld, ctx.ae.relationRegistry)) { + addIfNotThere(ctx.evalAssignment.getDelegationRefVals(), valueToAdd, "delegationRef", targetDesc); + } else { + if (AbstractRoleType.class.isAssignableFrom(targetClass)) { + addIfNotThere(ctx.evalAssignment.getMembershipRefVals(), valueToAdd, "membershipRef", targetDesc); + } + } + if (OrgType.class.isAssignableFrom(targetClass) && ctx.ae.relationRegistry.isStoredIntoParentOrgRef(relation)) { + addIfNotThere(ctx.evalAssignment.getOrgRefVals(), valueToAdd, "orgRef", targetDesc); + } + if (ArchetypeType.class.isAssignableFrom(targetClass)) { + addIfNotThere(ctx.evalAssignment.getArchetypeRefVals(), valueToAdd, "archetypeRef", targetDesc); + } + } + + private void addIfNotThere(Collection collection, PrismReferenceValue valueToAdd, String collectionName, + Object targetDesc) { + if (!collection.contains(valueToAdd)) { + LOGGER.trace("Adding target {} to {}", targetDesc, collectionName); + collection.add(valueToAdd); + } else { + LOGGER.trace("Would add target {} to {}, but it's already there", targetDesc, collectionName); + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetPayloadEvaluation.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetPayloadEvaluation.java new file mode 100644 index 00000000000..2ce64508b8f --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetPayloadEvaluation.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import com.evolveum.midpoint.model.impl.lens.assignments.TargetEvaluation.TargetValidity; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.security.api.Authorization; +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 static com.evolveum.midpoint.model.impl.lens.assignments.Util.isNonNegative; + +/** + * Collects target (i.e. role) payload: authorizations, GUI configuration. + */ +class TargetPayloadEvaluation extends AbstractEvaluation { + + private static final Trace LOGGER = TraceManager.getTrace(TargetPayloadEvaluation.class); + + private final PlusMinusZero targetRelativityMode; + + @NotNull private final TargetValidity targetValidity; + + TargetPayloadEvaluation(@NotNull AssignmentPathSegmentImpl segment, PlusMinusZero targetRelativityMode, + @NotNull TargetValidity targetValidity, + @NotNull EvaluationContext ctx) { + super(segment, ctx); + this.targetRelativityMode = targetRelativityMode; + this.targetValidity = targetValidity; + } + + public void evaluate() { + assert segment.isAssignmentValid() || segment.direct; + assert targetValidity.targetValid; + assert targetRelativityMode != null; + checkIfAlreadyEvaluated(); + + ObjectType target = segment.getTarget(); + if (target instanceof AbstractRoleType) { + if (!segment.isMatchingOrder) { + LOGGER.trace("Not collecting payload from target of {} as it is of not matching order: {}", segment, segment.getEvaluationOrder()); + } else if (!isNonNegative(targetRelativityMode)) { + LOGGER.trace("Not collecting payload from target of {} as the target relativity mode is not non-negative: {}", segment, targetRelativityMode); + } else { + for (AuthorizationType authorizationBean : ((AbstractRoleType) target).getAuthorization()) { + Authorization authorization = createAuthorization(authorizationBean, target.toString()); + if (!ctx.evalAssignment.getAuthorizations().contains(authorization)) { + ctx.evalAssignment.addAuthorization(authorization); + } + } + AdminGuiConfigurationType adminGuiConfiguration = ((AbstractRoleType) target).getAdminGuiConfiguration(); + if (adminGuiConfiguration != null && + !ctx.evalAssignment.getAdminGuiConfigurations().contains(adminGuiConfiguration)) { + ctx.evalAssignment.addAdminGuiConfiguration(adminGuiConfiguration); + } + } + } + } + + private Authorization createAuthorization(AuthorizationType authorizationBean, String sourceDesc) { + Authorization authorization = new Authorization(authorizationBean); + authorization.setSourceDescription(sourceDesc); + return authorization; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetsEvaluation.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetsEvaluation.java new file mode 100644 index 00000000000..2ff49247943 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/TargetsEvaluation.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import static com.evolveum.midpoint.model.impl.lens.assignments.Util.isChanged; +import static com.evolveum.midpoint.prism.util.PrismTestUtil.getPrismContext; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.AssignmentPathVariables; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.FocusTypeUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; + +/** + * Evaluates assignment target(s) - if there are any. + */ +class TargetsEvaluation extends AbstractEvaluation { + + private static final Trace LOGGER = TraceManager.getTrace(TargetsEvaluation.class); + + /** + * Number of times any given target is allowed to occur in the assignment path. + * After exceeding it is not evaluated. + */ + private static final int MAX_TARGET_OCCURRENCES = 2; + + private final OperationResult result; + + /** + * Resolved target objects. + */ + final List> targets = new ArrayList<>(); + + TargetsEvaluation(AssignmentPathSegmentImpl segment, EvaluationContext ctx, OperationResult result) { + super(segment, ctx); + this.result = result; + } + + void evaluate() throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException, PolicyViolationException, ObjectNotFoundException { + assert ctx.assignmentPath.last() == segment; + assert segment.getAssignmentRelativityMode() != null; + assert segment.isAssignmentValid() || segment.direct; + + checkIfAlreadyEvaluated(); + + if (segment.assignment.getTargetRef() == null) { + LOGGER.trace("No targetRef for {}, nothing to evaluate", segment); + return; + } + + if (ctx.ae.loginMode && !ctx.ae.relationRegistry.isProcessedOnLogin(segment.relation)) { + LOGGER.trace("Skipping processing of assignment target {} because relation {} is configured for login skip", segment.assignment.getTargetRef().getOid(), segment.relation); + // Skip - to optimize logging-in, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) + // We want to make this configurable in the future MID-3581 + return; + } + + if (!ctx.ae.loginMode && !isChanged(ctx.primaryAssignmentMode) && + !ctx.ae.relationRegistry.isProcessedOnRecompute(segment.relation) && !shouldEvaluateAllAssignmentRelationsOnRecompute()) { + LOGGER.debug("Skipping processing of assignment target for {} because relation {} is configured for recompute skip (primary assignment mode={})", segment, segment.relation, ctx.primaryAssignmentMode); + // Skip - to optimize recompute, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) + // Never skip this if assignment has changed. We want to process this, e.g. to enforce min/max assignee rules. + // We want to make this configurable in the future MID-3581 + // TODO but what if the assignment itself has not changed but some of the conditions have? + addSkippedTargetsToMembershipLists(); + return; + } + + targets.addAll(getTargets()); + LOGGER.trace("Targets in {}, assignment ID {}: {}", segment.source, segment.assignment.getId(), targets); + for (PrismObject target : targets) { + if (hasCycle(target)) { + continue; + } + if (isDelegationToNonDelegableTarget(target)) { + continue; + } + AssignmentHolderType targetBean = (AssignmentHolderType) target.asObjectable(); + ctx.assignmentPath.replaceLastSegmentWithTargetedOne(targetBean); + new TargetEvaluation<>(ctx.assignmentPath.last(), ctx, result).evaluate(); + } + } + + private void addSkippedTargetsToMembershipLists() + throws SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, + SecurityViolationException { + boolean resolvedTargets = false; + // TODO CLEAN THIS UP + // Important: but we still want this to be reflected in roleMembershipRef + if (segment.isNonNegativeRelativityMode() && Util.shouldCollectMembership(segment)) { + if (segment.assignment.getTargetRef().getOid() != null) { + ctx.membershipCollector.collect(segment.assignment.getTargetRef(), segment.relation); + } else { + // no OID, so we have to resolve the filter + resolvedTargets = true; + targets.addAll(getTargets()); + for (PrismObject targetObject : targets) { + ObjectType target = targetObject.asObjectable(); + if (target instanceof FocusType) { + ctx.membershipCollector.collect((FocusType) target, segment.relation); + } + } + } + } + + // We have to know targets for direct assignments + if (segment.direct && !resolvedTargets) { + targets.addAll(getTargets()); + } + } + + private boolean hasCycle(@NotNull PrismObject target) throws PolicyViolationException { + // TODO reconsider this + if (target.getOid().equals(segment.source.getOid())) { + throw new PolicyViolationException("The "+segment.source+" refers to itself in assignment/inducement"); + } + int count = ctx.assignmentPath.countTargetOccurrences(target.asObjectable()); + if (count >= MAX_TARGET_OCCURRENCES) { + LOGGER.debug("Max # of target occurrences ({}) detected for target {} in {} - stopping evaluation here", + MAX_TARGET_OCCURRENCES, ObjectTypeUtil.toShortString(target), ctx.assignmentPath); + return true; + } else { + return false; + } + } + + private boolean isDelegationToNonDelegableTarget(@NotNull PrismObject target) { + AssignmentPathSegment previousSegment = ctx.assignmentPath.beforeLast(1); + if (previousSegment == null || !previousSegment.isDelegation() || !target.canRepresent(AbstractRoleType.class)) { + return false; + } + if (!Boolean.TRUE.equals(((AbstractRoleType)target.asObjectable()).isDelegable())) { + LOGGER.trace("Skipping evaluation of {} because it delegates to a non-delegable target {}", + FocusTypeUtil.dumpAssignmentLazily(segment.assignment), target); + return true; + } else { + return false; + } + } + + private boolean shouldEvaluateAllAssignmentRelationsOnRecompute() { + return ModelExecuteOptions.isEvaluateAllAssignmentRelationsOnRecompute(ctx.ae.lensContext.getOptions()); + } + + @NotNull + private List> getTargets() throws SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + try { + return resolveTargets(segment, ctx, result); + } catch (ObjectNotFoundException ex) { + // Do not throw an exception. We don't have referential integrity. Therefore if a role is deleted then throwing + // an exception would prohibit any operations with the users that have the role, including removal of the reference. + // The failure is recorded in the result and we will log it. It should be enough. + LOGGER.error(ex.getMessage()+" in assignment target reference in "+segment.sourceDescription,ex); + // For OrgType references we trigger the reconciliation (see MID-2242) + ctx.evalAssignment.setForceRecon(true); + return Collections.emptyList(); + } + } + + @NotNull + private List> resolveTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, + OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, SecurityViolationException { + ObjectReferenceType targetRef = segment.assignment.getTargetRef(); + String oid = targetRef.getOid(); + + // Target is referenced, need to fetch it + Class targetClass; + if (targetRef.getType() != null) { + targetClass = ctx.ae.prismContext.getSchemaRegistry().determineCompileTimeClass(targetRef.getType()); + if (targetClass == null) { + throw new SchemaException("Cannot determine type from " + targetRef.getType() + " in target reference in " + segment.assignment + " in " + segment.sourceDescription); + } + } else { + throw new SchemaException("Missing type in target reference in " + segment.assignment + " in " + segment.sourceDescription); + } + + if (oid == null) { + LOGGER.trace("Resolving dynamic target ref"); + if (targetRef.getFilter() == null) { + throw new SchemaException("The OID and filter are both null in assignment targetRef in "+segment.source); + } + return resolveTargetsFromFilter(targetClass, targetRef.getFilter(), segment, ctx, result); + } else { + LOGGER.trace("Resolving target {}:{} from repository", targetClass.getSimpleName(), oid); + PrismObject target; + try { + target = ctx.ae.repository.getObject(targetClass, oid, null, result); + } catch (SchemaException e) { + throw new SchemaException(e.getMessage() + " in " + segment.sourceDescription, e); + } + // Not handling object not found exception here. Caller will handle that. + if (target == null) { + throw new IllegalArgumentException("Got null target from repository, oid:"+oid+", class:"+targetClass+" (should not happen, probably a bug) in "+segment.sourceDescription); + } + return Collections.singletonList(target); + } + } + + @NotNull + private List> resolveTargetsFromFilter(Class targetClass, + SearchFilterType filter, AssignmentPathSegmentImpl segment, + EvaluationContext ctx, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException{ + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(ctx.ae.lensContext, null, ctx.task, result)); + try { + PrismObject systemConfiguration = ctx.ae.systemObjectCache.getSystemConfiguration(result); + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(segment.source, null, null, systemConfiguration.asObjectable(), ctx.ae.prismContext); + variables.put(ExpressionConstants.VAR_SOURCE, segment.getOrderOneObject(), ObjectType.class); + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + if (assignmentPathVariables != null) { + ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, variables, getPrismContext()); + } + variables.addVariableDefinitions(ctx.ae.getAssignmentEvaluationVariables()); + ObjectFilter origFilter = ctx.ae.prismContext.getQueryConverter().parseFilter(filter, targetClass); + // TODO: expression profile should be determined from the holding object archetype + ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); + ObjectFilter evaluatedFilter = ExpressionUtil.evaluateFilterExpressions(origFilter, variables, expressionProfile, ctx.ae.mappingFactory.getExpressionFactory(), ctx.ae.prismContext, " evaluating resource filter expression ", ctx.task, result); + if (evaluatedFilter == null) { + throw new SchemaException("The OID is null and filter could not be evaluated in assignment targetRef in "+segment.source); + } + + return ctx.ae.repository.searchObjects(targetClass, ctx.ae.prismContext.queryFactory().createQuery(evaluatedFilter), null, result); + // we don't check for no targets here; as we don't care for referential integrity + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/UndefinedEvaluationOrderImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/UndefinedEvaluationOrderImpl.java similarity index 86% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/UndefinedEvaluationOrderImpl.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/UndefinedEvaluationOrderImpl.java index 73e2cd2f9eb..94aac697a4e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/UndefinedEvaluationOrderImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/UndefinedEvaluationOrderImpl.java @@ -1,20 +1,19 @@ /* - * Copyright (c) 2010-2017 Evolveum and contributors + * Copyright (c) 2020 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.model.impl.lens; +package com.evolveum.midpoint.model.impl.lens.assignments; import com.evolveum.midpoint.model.api.context.EvaluationOrder; import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OrderConstraintsType; + import org.apache.commons.collections4.MultiSet; import javax.xml.namespace.QName; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * @author semancik @@ -107,4 +106,9 @@ public Collection getExtraRelations() { public boolean isOrderOne() { return false; // TODO } + + @Override + public boolean matches(Integer assignmentOrder, List assignmentOrderConstraint) { + return false; + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/Util.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/Util.java new file mode 100644 index 00000000000..7c1f8bb8713 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/Util.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.assignments; + +import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.util.FocusTypeUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentSelectorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; + +import org.apache.commons.lang.BooleanUtils; +import org.jetbrains.annotations.Nullable; + +import javax.xml.namespace.QName; + +class Util { + + static 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. + return mode == PlusMinusZero.ZERO || mode == PlusMinusZero.PLUS; + } + + static boolean isChanged(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. + return mode == PlusMinusZero.PLUS || mode == PlusMinusZero.MINUS; + } + + static boolean isAllowedByLimitations(AssignmentPathSegment segment, AssignmentType nextAssignment, EvaluationContext ctx) { + AssignmentType currentAssignment = segment.getAssignment(ctx.evaluateOld); + AssignmentSelectorType targetLimitation = currentAssignment.getLimitTargetContent(); + if (isDeputyDelegation(nextAssignment, ctx.ae.relationRegistry)) { // delegation of delegation + return targetLimitation != null && BooleanUtils.isTrue(targetLimitation.isAllowTransitive()); + } else { + // As for the case of targetRef==null: we want to pass target-less assignments (focus mappings, policy rules etc) + // from the delegator to delegatee. To block them we should use order constraints (but also for assignments?). + return targetLimitation == null || nextAssignment.getTargetRef() == null || + FocusTypeUtil.selectorMatches(targetLimitation, nextAssignment, ctx.ae.prismContext); + } + } + + private static boolean isDeputyDelegation(AssignmentType assignmentType, RelationRegistry relationRegistry) { + ObjectReferenceType targetRef = assignmentType.getTargetRef(); + return targetRef != null && relationRegistry.isDelegation(targetRef.getRelation()); + } + + @Nullable + static QName getRelation(AssignmentType assignmentType, RelationRegistry relationRegistry) { + return assignmentType.getTargetRef() != null ? + relationRegistry.normalizeRelation(assignmentType.getTargetRef().getRelation()) : null; + } + + static boolean shouldCollectMembership(AssignmentPathSegmentImpl segment) { + /* + * We obviously want to process membership from the segment if it's of matching order. + * + * But we want to do that also for targets obtained via delegations. The current (approximate) approach is to + * collect membership from all assignments of any user that we find on the assignment path. + * + * TODO: does this work for invalid (effectiveStatus = disabled) assignments? + */ + return segment.direct || segment.isMatchingOrder || segment.source instanceof UserType; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/AbstractConstruction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/AbstractConstruction.java index c89df53b23f..91501a28628 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/AbstractConstruction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/AbstractConstruction.java @@ -8,7 +8,7 @@ import java.io.Serializable; -import com.evolveum.midpoint.model.impl.lens.AssignmentPathImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentPathImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.prism.OriginType; import com.evolveum.midpoint.prism.PrismContext; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/ConstructionCollector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/ConstructionCollector.java index dfe9489790a..ebcd31f0f0b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/ConstructionCollector.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/ConstructionCollector.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.function.Function; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractConstructionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConstructionProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConstructionProcessor.java index 71fa98e25ba..4a2a53ee55a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConstructionProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConstructionProcessor.java @@ -17,6 +17,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.FailableLensFunction; +import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.prism.delta.DeltaMapTriple; import com.evolveum.midpoint.prism.delta.DeltaSetTriple; import com.evolveum.midpoint.util.HumanReadableDescribable; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java index f08092894c1..9c65c578648 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java @@ -45,6 +45,13 @@ import com.evolveum.midpoint.model.common.SystemObjectCache; import com.evolveum.midpoint.model.common.mapping.MappingImpl; import com.evolveum.midpoint.model.common.mapping.MappingFactory; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentEvaluator; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.ItemValueWithOrigin; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.prism.path.UniformItemPath; import com.evolveum.midpoint.prism.util.ObjectDeltaObject; import com.evolveum.midpoint.provisioning.api.ProvisioningService; @@ -188,6 +195,7 @@ private void processAssig AssignmentTripleEvaluator assignmentTripleEvaluator = new AssignmentTripleEvaluator<>(); assignmentTripleEvaluator.setActivationComputer(activationComputer); assignmentTripleEvaluator.setAssignmentEvaluator(assignmentEvaluator); + assignmentTripleEvaluator.setObjectResolver(objectResolver); assignmentTripleEvaluator.setContext(context); assignmentTripleEvaluator.setNow(now); assignmentTripleEvaluator.setPrismContext(prismContext); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentTripleEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentTripleEvaluator.java index 9ad8b7b64b4..996ab9f4d2d 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentTripleEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentTripleEvaluator.java @@ -12,13 +12,14 @@ import javax.xml.datatype.XMLGregorianCalendar; import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.repo.common.ObjectResolver; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.midpoint.common.ActivationComputer; import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.impl.lens.AssignmentEvaluator; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentEvaluator; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; @@ -55,7 +56,6 @@ * Evaluates all assignments and sorts them to triple: added, removed and untouched assignments. * * @author semancik - * */ public class AssignmentTripleEvaluator { @@ -67,6 +67,7 @@ public class AssignmentTripleEvaluator { private AssignmentHolderType source; private AssignmentEvaluator assignmentEvaluator; private ActivationComputer activationComputer; + private ObjectResolver objectResolver; private PrismContext prismContext; private XMLGregorianCalendar now; private Task task; @@ -96,50 +97,30 @@ public void setSource(AssignmentHolderType source) { this.source = source; } - public AssignmentEvaluator getAssignmentEvaluator() { - return assignmentEvaluator; - } - public void setAssignmentEvaluator(AssignmentEvaluator assignmentEvaluator) { this.assignmentEvaluator = assignmentEvaluator; } - public ActivationComputer getActivationComputer() { - return activationComputer; + public void setObjectResolver(ObjectResolver objectResolver) { + this.objectResolver = objectResolver; } public void setActivationComputer(ActivationComputer activationComputer) { this.activationComputer = activationComputer; } - public PrismContext getPrismContext() { - return prismContext; - } - public void setPrismContext(PrismContext prismContext) { this.prismContext = prismContext; } - public XMLGregorianCalendar getNow() { - return now; - } - public void setNow(XMLGregorianCalendar now) { this.now = now; } - public Task getTask() { - return task; - } - public void setTask(Task task) { this.task = task; } - public OperationResult getResult() { - return result; - } - public void setResult(OperationResult result) { this.result = result; } @@ -148,39 +129,9 @@ public void reset(boolean alsoMemberOfInvocations) { assignmentEvaluator.reset(alsoMemberOfInvocations); } -// public DeltaSetTriple> preProcessAssignments(PrismObject taskType) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { -// -//// LensFocusContext focusContext = context.getFocusContext(); -// -// SmartAssignmentCollection assignmentCollection = new SmartAssignmentCollection<>(); -// -//// Collection forcedAssignments = LensUtil.getForcedAssignments(focusContext.getLifecycleModel(), -//// getNewObjectLifecycleState(focusContext), assignmentEvaluator.getObjectResolver(), -//// prismContext, task, result); -// -// assignmentCollection.collectAssignmentsForPreprocessing(taskType.findContainer(TaskType.F_ASSIGNMENT), null); -// -// -// if (LOGGER.isTraceEnabled()) { -// LOGGER.trace("Assignment collection:\n{}", assignmentCollection.debugDump(1)); -// } -// -// // Iterate over all the assignments. I mean really all. This is a union of the existing and changed assignments -// // therefore it contains all three types of assignments (plus, minus and zero). As it is an union each assignment -// // will be processed only once. Inside the loop we determine whether it was added, deleted or remains unchanged. -// // This is a first step of the processing. It takes all the account constructions regardless of the resource and -// // account type (intent). Therefore several constructions for the same resource and intent may appear in the resulting -// // sets. This is not good as we want only a single account for each resource/intent combination. But that will be -// // sorted out later. -// DeltaSetTriple> evaluatedAssignmentTriple = prismContext.deltaFactory().createDeltaSetTriple(); -// for (SmartAssignmentElement assignmentElement : assignmentCollection) { -// processAssignment(evaluatedAssignmentTriple, null, null, assignmentElement); -// } -// -// return evaluatedAssignmentTriple; -// } - - public DeltaSetTriple> processAllAssignments() throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + DeltaSetTriple> processAllAssignments() throws ObjectNotFoundException, SchemaException, + ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, + CommunicationException { LensFocusContext focusContext = context.getFocusContext(); @@ -189,12 +140,12 @@ public DeltaSetTriple> processAllAssignments() throw ContainerDelta assignmentDelta = getExecutionWaveAssignmentDelta(focusContext); assignmentDelta.expand(focusContext.getObjectCurrent(), LOGGER); - LOGGER.trace("Assignment delta:\n{}", assignmentDelta.debugDump()); + LOGGER.trace("Assignment delta:\n{}", assignmentDelta.debugDumpLazily()); SmartAssignmentCollection assignmentCollection = new SmartAssignmentCollection<>(); Collection forcedAssignments = LensUtil.getForcedAssignments(focusContext.getLifecycleModel(), - getNewObjectLifecycleState(focusContext), assignmentEvaluator.getObjectResolver(), + getNewObjectLifecycleState(focusContext), objectResolver, prismContext, task, result); LOGGER.trace("Task for process: {}", task.debugDumpLazily()); @@ -361,9 +312,7 @@ private void processAssignment(DeltaSetTriple> evalu // Entirely new assignment is added if (assignmentElement.isCurrent() && assignmentElement.isOld()) { // Phantom add: adding assignment that is already there - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Processing changed assignment, phantom add: {}", SchemaDebugUtil.prettyPrint(assignmentCVal)); - } + LOGGER.trace("Processing changed assignment, phantom add: {}", SchemaDebugUtil.prettyPrintLazily(assignmentCVal)); EvaluatedAssignmentImpl evaluatedAssignment = evaluateAssignment(createAssignmentIdiNoChange(assignmentCVal), PlusMinusZero.ZERO, false, assignmentPlacementDesc, assignmentElement); if (evaluatedAssignment == null) { return; @@ -371,9 +320,7 @@ private void processAssignment(DeltaSetTriple> evalu evaluatedAssignment.setWasValid(evaluatedAssignment.isValid()); collectToZero(evaluatedAssignmentTriple, evaluatedAssignment, forceRecon); } else { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Processing changed assignment, add: {}", SchemaDebugUtil.prettyPrint(assignmentCVal)); - } + LOGGER.trace("Processing changed assignment, add: {}", SchemaDebugUtil.prettyPrintLazily(assignmentCVal)); EvaluatedAssignmentImpl evaluatedAssignment = evaluateAssignment(createAssignmentIdiAdd(assignmentCVal), PlusMinusZero.PLUS, false, assignmentPlacementDesc, assignmentElement); if (evaluatedAssignment == null) { return; @@ -384,9 +331,7 @@ private void processAssignment(DeltaSetTriple> evalu } else if (isDelete && !isAdd) { // Existing assignment is removed - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Processing changed assignment, delete: {}", SchemaDebugUtil.prettyPrint(assignmentCVal)); - } + LOGGER.trace("Processing changed assignment, delete: {}", SchemaDebugUtil.prettyPrintLazily(assignmentCVal)); EvaluatedAssignmentImpl evaluatedAssignment = evaluateAssignment(createAssignmentIdiDelete(assignmentCVal), PlusMinusZero.MINUS, true, assignmentPlacementDesc, assignmentElement); if (evaluatedAssignment == null) { return; @@ -410,10 +355,8 @@ private void processAssignment(DeltaSetTriple> evalu if (isValid == isValidOld) { // No change in validity -> right to the zero set // The change is not significant for assignment applicability. Recon will sort out the details. - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Processing changed assignment, minor change (add={}, delete={}, valid={}): {}", - isAdd, isDelete, isValid, SchemaDebugUtil.prettyPrint(assignmentCVal)); - } + LOGGER.trace("Processing changed assignment, minor change (add={}, delete={}, valid={}): {}", + isAdd, isDelete, isValid, SchemaDebugUtil.prettyPrintLazily(assignmentCVal)); EvaluatedAssignmentImpl evaluatedAssignment = evaluateAssignment(assignmentIdi, PlusMinusZero.ZERO, false, assignmentPlacementDesc, assignmentElement); if (evaluatedAssignment == null) { return; @@ -422,10 +365,8 @@ private void processAssignment(DeltaSetTriple> evalu collectToZero(evaluatedAssignmentTriple, evaluatedAssignment, true); } else if (isValid) { // Assignment became valid. We need to place it in plus set to initiate provisioning - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Processing changed assignment, assignment becomes valid (add={}, delete={}): {}", - isAdd, isDelete, SchemaDebugUtil.prettyPrint(assignmentCVal)); - } + LOGGER.trace("Processing changed assignment, assignment becomes valid (add={}, delete={}): {}", + isAdd, isDelete, SchemaDebugUtil.prettyPrintLazily(assignmentCVal)); EvaluatedAssignmentImpl evaluatedAssignment = evaluateAssignment(assignmentIdi, PlusMinusZero.PLUS, false, assignmentPlacementDesc, assignmentElement); if (evaluatedAssignment == null) { return; @@ -434,10 +375,8 @@ private void processAssignment(DeltaSetTriple> evalu collectToPlus(evaluatedAssignmentTriple, evaluatedAssignment, true); } else { // Assignment became invalid. We need to place is in minus set to initiate deprovisioning - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Processing changed assignment, assignment becomes invalid (add={}, delete={}): {}", - isAdd, isDelete, SchemaDebugUtil.prettyPrint(assignmentCVal)); - } + LOGGER.trace("Processing changed assignment, assignment becomes invalid (add={}, delete={}): {}", + isAdd, isDelete, SchemaDebugUtil.prettyPrintLazily(assignmentCVal)); EvaluatedAssignmentImpl evaluatedAssignment = evaluateAssignment(assignmentIdi, PlusMinusZero.MINUS, false, assignmentPlacementDesc, assignmentElement); if (evaluatedAssignment == null) { return; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java index b197acdb1fc..18f6c7ff52c 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; import com.evolveum.midpoint.common.ActivationComputer; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AssignedFocusMappingEvaluationRequest.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AssignedFocusMappingEvaluationRequest.java index e3a59ac7c07..b7739caa059 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AssignedFocusMappingEvaluationRequest.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AssignedFocusMappingEvaluationRequest.java @@ -8,7 +8,7 @@ package com.evolveum.midpoint.model.impl.lens.projector.mappings; import com.evolveum.midpoint.model.impl.lens.AssignmentPathVariables; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.prism.delta.PlusMinusZero; import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingKindType; import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/AssignmentPolicyRuleEvaluationContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/AssignmentPolicyRuleEvaluationContext.java index 5720dc3a074..f78b9642071 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/AssignmentPolicyRuleEvaluationContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/AssignmentPolicyRuleEvaluationContext.java @@ -9,7 +9,7 @@ import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.prism.delta.DeltaSetTriple; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java index 99436caf9d6..dee92ec5776 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java @@ -10,6 +10,8 @@ import com.evolveum.midpoint.model.common.mapping.MappingImpl; import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentTargetImpl; import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleScriptExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleScriptExecutor.java index 12be677cde6..9d4ecf4cc47 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleScriptExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleScriptExecutor.java @@ -6,57 +6,64 @@ */ package com.evolveum.midpoint.model.impl.lens.projector.policy; -import com.evolveum.midpoint.model.api.ScriptExecutionException; +import com.evolveum.midpoint.model.api.ModelService; import com.evolveum.midpoint.model.api.context.*; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.scripting.ScriptingExpressionEvaluator; +import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.expression.VariablesMap; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ValueListType; + +import org.apache.commons.collections4.CollectionUtils; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; /** * Executes scripts defined in scriptExecution policy action. * Designed to be called during FINAL stage, just like notification action. * * HIGHLY EXPERIMENTAL - * - * @author mederly - * */ @Component public class PolicyRuleScriptExecutor { private static final Trace LOGGER = TraceManager.getTrace(PolicyRuleScriptExecutor.class); - private static final String EXECUTE_SCRIPT_OPERATION = PolicyRuleScriptExecutor.class.getName() + ".executeScript"; + private static final String OP_EXECUTE_SCRIPT = PolicyRuleScriptExecutor.class.getName() + ".executeScript"; + @Autowired private ModelService modelService; + @Autowired private PrismContext prismContext; @Autowired private ScriptingExpressionEvaluator scriptingExpressionEvaluator; + @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; - public void execute(@NotNull ModelContext context, Task task, OperationResult result) { - LensFocusContext focusContext = (LensFocusContext) context.getFocusContext(); + public void execute(@NotNull LensContext context, Task task, OperationResult result) { + LensFocusContext focusContext = context.getFocusContext(); if (focusContext != null) { for (EvaluatedPolicyRule rule : focusContext.getPolicyRules()) { executeRuleScriptingActions(rule, context, task, result); } - DeltaSetTriple> triple = ((LensContext) context).getEvaluatedAssignmentTriple(); + DeltaSetTriple> triple = context.getEvaluatedAssignmentTriple(); if (triple != null) { // We need to apply rules from all the assignments - even those that were deleted. for (EvaluatedAssignment assignment : triple.getAllValues()) { @@ -68,7 +75,7 @@ public void execute(@NotNull ModelContext context, Tas } } - private void executeRuleScriptingActions(EvaluatedPolicyRule rule, ModelContext context, Task task, OperationResult result) { + private void executeRuleScriptingActions(EvaluatedPolicyRule rule, LensContext context, Task task, OperationResult result) { if (rule.isTriggered()) { for (ScriptExecutionPolicyActionType action : rule.getEnabledActions(ScriptExecutionPolicyActionType.class)) { executeScriptingAction(action, rule, context, task, result); @@ -76,7 +83,7 @@ private void executeRuleScriptingActions(EvaluatedPolicyRule rule, ModelContext< } } - private void executeScriptingAction(ScriptExecutionPolicyActionType action, EvaluatedPolicyRule rule, ModelContext context, Task task, OperationResult parentResult) { + private void executeScriptingAction(ScriptExecutionPolicyActionType action, EvaluatedPolicyRule rule, LensContext context, Task task, OperationResult parentResult) { LOGGER.debug("Executing policy action scripts ({}) in action: {}\non rule:{}", action.getExecuteScript().size(), action, rule.debugDumpLazily()); List executeScript = action.getExecuteScript(); @@ -85,31 +92,153 @@ private void executeScriptingAction(ScriptExecutionPolicyActionType action, Eval } } - private void executeScript(ScriptExecutionPolicyActionType action, EvaluatedPolicyRule rule, ModelContext context, - Task task, OperationResult parentResult, ExecuteScriptType executeScriptBean) { - OperationResult result = parentResult.createSubresult(EXECUTE_SCRIPT_OPERATION); + private void executeScript(ScriptExecutionPolicyActionType action, EvaluatedPolicyRule rule, LensContext context, + Task task, OperationResult parentResult, ExecuteScriptType specifiedExecuteScriptBean) { + OperationResult result = parentResult.createSubresult(OP_EXECUTE_SCRIPT); try { - VariablesMap initialVariables = createInitialVariables(action, rule, context); - if (executeScriptBean.getInput() == null && context.getFocusContext() != null) { - PrismObject objectAny = ((LensFocusContext) context.getFocusContext()).getObjectAny(); - if (objectAny != null) { - ValueListType input = new ValueListType(); - input.getValue().add(objectAny.getValue().clone()); - executeScriptBean.setInput(input); - } + ExecuteScriptType realExecuteScriptBean; + if (specifiedExecuteScriptBean.getInput() == null && context.getFocusContext() != null) { + ValueListType input = createScriptInput(action, rule, context, context.getFocusContext(), task, result); + realExecuteScriptBean = specifiedExecuteScriptBean.clone().input(input); + } else { + realExecuteScriptBean = specifiedExecuteScriptBean; } - scriptingExpressionEvaluator.evaluateExpression(executeScriptBean, initialVariables, false, task, result); - } catch (ScriptExecutionException | RuntimeException e) { - result.recordFatalError("Couldn't execute script policy action: " + e.getMessage(), e); + VariablesMap initialVariables = createInitialVariables(action, rule, context); + scriptingExpressionEvaluator.evaluateExpression(realExecuteScriptBean, initialVariables, false, task, result); + } catch (Throwable t) { + result.recordFatalError("Couldn't execute script policy action: " + t.getMessage(), t); LoggingUtils.logUnexpectedException(LOGGER, "Couldn't execute script with id={} in scriptExecution policy action '{}' (rule '{}'): {}", - e, action.getId(), action.getName(), rule.getName(), e.getMessage()); + t, action.getId(), action.getName(), rule.getName(), t.getMessage()); } finally { result.computeStatusIfUnknown(); } } + private ValueListType createScriptInput(ScriptExecutionPolicyActionType action, EvaluatedPolicyRule rule, + LensContext context, LensFocusContext focusContext, Task task, OperationResult result) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + ScriptExecutionObjectType object = action.getObject(); + if (object == null) { + return createInput(MiscUtil.singletonOrEmptyList(focusContext.getObjectAny())); + } else { + Map> objectsMap = new HashMap<>(); // use OID-keyed map to avoid duplicates + if (!object.getAssigned().isEmpty()) { + List> assigned = getAssigned(context); + LOGGER.trace("Assigned objects (all): {}", assigned); + List> filtered = filterObjects(assigned, object.getAssigned()); + LOGGER.trace("Assigned objects (filtered on selectors): {}", filtered); + addObjects(objectsMap, filtered); + } + if (!object.getAssignedMatchingPolicyConstraints().isEmpty()) { + List> assignedMatchingConstraints = getAssignedMatchingConstraints(rule); + LOGGER.trace("Assigned objects matching policy constraints (all): {}", assignedMatchingConstraints); + List> filtered = filterObjects(assignedMatchingConstraints, object.getAssignedMatchingPolicyConstraints()); + LOGGER.trace("Assigned objects matching policy constraints (filtered on selectors): {}", filtered); + addObjects(objectsMap, filtered); + } + if (!object.getAssignedOnPath().isEmpty()) { + List> assignedOnPath = getAssignedOnPath(rule); + LOGGER.trace("Assigned objects on respective assignment path (all): {}", assignedOnPath); + List> filtered = filterObjects(assignedOnPath, object.getAssignedOnPath()); + LOGGER.trace("Assigned objects on respective assignment path (filtered on selectors): {}", filtered); + addObjects(objectsMap, filtered); + } + if (!object.getAssignee().isEmpty()) { + List> assignees = getAssignees(focusContext.getOid(), task, result); + LOGGER.trace("Assignee objects (all): {}", assignees); + List> filtered = filterObjects(assignees, object.getAssignee()); + LOGGER.trace("Assignee objects (filtered on selectors): {}", filtered); + addObjects(objectsMap, filtered); + } + return createInput(objectsMap.values()); + } + } + + private List> getAssignees(String focusOid, Task task, OperationResult result) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + if (focusOid == null) { + LOGGER.warn("No focus object OID, no assignees can be found"); + return Collections.emptyList(); + } else { + ObjectQuery query = prismContext.queryFor(AssignmentHolderType.class) + .item(AssignmentHolderType.F_ROLE_MEMBERSHIP_REF).ref(focusOid) + .build(); + //noinspection unchecked + return (List) modelService.searchObjects(AssignmentHolderType.class, query, null, task, result); + } + } + + private void addObjects(Map> objectsMap, List> objects) { + objects.forEach(o -> objectsMap.put(o.getOid(), o)); + } + + private List> filterObjects(List> objects, List selectors) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + List> all = new ArrayList<>(); + for (ObjectSelectorType selector : selectors) { + all.addAll(filterObjects(objects, selector)); + } + return all; + } + + private List> filterObjects(List> objects, ObjectSelectorType selector) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + List> matching = new ArrayList<>(); + for (PrismObject object : objects) { + //noinspection unchecked + if (repositoryService.selectorMatches(selector, (PrismObject) object, + null, LOGGER, "script object evaluation")) { + matching.add(object); + } + } + return matching; + } + + private List> getAssignedOnPath(EvaluatedPolicyRule rule) { + AssignmentPath assignmentPath = rule.getAssignmentPath(); + if (assignmentPath != null) { + return assignmentPath.getFirstOrderChain().stream() + .map(ObjectType::asPrismObject) + .collect(Collectors.toList()); + } else { + LOGGER.warn("No assignment path for {} (but assignedOnPath object specification is present)", rule); + return Collections.emptyList(); + } + } + + private List> getAssignedMatchingConstraints(EvaluatedPolicyRule rule) { + List> rv = new ArrayList<>(); + addFromTriggers(rv, rule.getAllTriggers()); + return rv; + } + + private void addFromTriggers(List> rv, @NotNull Collection> triggers) { + for (EvaluatedPolicyRuleTrigger trigger : triggers) { + rv.addAll(trigger.getTargetObjects()); + addFromTriggers(rv, trigger.getInnerTriggers()); + } + } + + private List> getAssigned(LensContext context) { + List> rv = new ArrayList<>(); + for (EvaluatedAssignmentImpl evaluatedAssignment : context.getEvaluatedAssignmentTriple().getAllValues()) { + CollectionUtils.addIgnoreNull(rv, evaluatedAssignment.getTarget()); + } + return rv; + } + + private ValueListType createInput(Collection> objects) { + ValueListType input = new ValueListType(); + objects.forEach(o -> input.getValue().add(o.getValue().clone())); + return input; + } + private VariablesMap createInitialVariables(ScriptExecutionPolicyActionType action, EvaluatedPolicyRule rule, - ModelContext context) { + LensContext context) { VariablesMap rv = new VariablesMap(); rv.put(ExpressionConstants.VAR_POLICY_ACTION, action, ScriptExecutionPolicyActionType.class); rv.put(ExpressionConstants.VAR_POLICY_RULE, rule, EvaluatedPolicyRule.class); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyStateRecorder.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyStateRecorder.java index cd0ad885ed2..84d6f292712 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyStateRecorder.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyStateRecorder.java @@ -8,7 +8,7 @@ import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; import com.evolveum.midpoint.model.api.context.PolicyRuleExternalizationOptions; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.prism.*; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentModificationConstraintEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentModificationConstraintEvaluator.java index 5e5f4d1b824..e9b98e5c353 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentModificationConstraintEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/AssignmentModificationConstraintEvaluator.java @@ -79,7 +79,8 @@ public EvaluatedModificationTrigger evaluate( } // TODO check modifications - EvaluatedModificationTrigger rv = new EvaluatedModificationTrigger(PolicyConstraintKindType.ASSIGNMENT_MODIFICATION, constraint, + EvaluatedModificationTrigger rv = new EvaluatedModificationTrigger(PolicyConstraintKindType.ASSIGNMENT_MODIFICATION, + constraint, ctx.evaluatedAssignment.getTarget(), createMessage(constraintElement, ctx, result), createShortMessage(constraintElement, ctx, result)); result.addReturn("trigger", rv.toDiagShortcut()); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ExclusionConstraintEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ExclusionConstraintEvaluator.java index 8950358bff4..83b726343bf 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ExclusionConstraintEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ExclusionConstraintEvaluator.java @@ -12,9 +12,8 @@ import com.evolveum.midpoint.model.api.context.AssignmentPath; import com.evolveum.midpoint.model.api.context.EvaluatedExclusionTrigger; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentTargetImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentTargetImpl; import com.evolveum.midpoint.model.impl.lens.projector.policy.AssignmentPolicyRuleEvaluationContext; import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEvaluationContext; import com.evolveum.midpoint.prism.PrismContext; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/HasAssignmentConstraintEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/HasAssignmentConstraintEvaluator.java index f900252157b..7a6a4c85d86 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/HasAssignmentConstraintEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/HasAssignmentConstraintEvaluator.java @@ -8,8 +8,8 @@ package com.evolveum.midpoint.model.impl.lens.projector.policy.evaluators; import com.evolveum.midpoint.model.api.context.EvaluatedHasAssignmentTrigger; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentTargetImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentTargetImpl; import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; import com.evolveum.midpoint.model.impl.lens.projector.policy.ObjectState; import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEvaluationContext; @@ -40,6 +40,8 @@ import java.util.Collections; import java.util.List; +import static java.util.Collections.emptySet; + @Component public class HasAssignmentConstraintEvaluator implements PolicyConstraintEvaluator { @@ -78,6 +80,7 @@ public EvaluatedHasAssignmentTrigger evaluate( boolean allowEnabled = !Boolean.FALSE.equals(constraint.isEnabled()); boolean allowDisabled = !Boolean.TRUE.equals(constraint.isEnabled()); + List> matchingTargets = new ArrayList<>(); for (EvaluatedAssignmentImpl evaluatedAssignment : evaluatedAssignmentTriple.getNonNegativeValues()) { AssignmentOrigin origin = evaluatedAssignment.getOrigin(); boolean assignmentIsAdded = origin.isBeingAdded(); @@ -111,16 +114,24 @@ public EvaluatedHasAssignmentTrigger evaluate( if (ExclusionConstraintEvaluator .oidMatches(constraint.getTargetRef(), target, prismContext, matchingRuleRegistry, "hasAssignment constraint")) { - if (shouldExist) { - // TODO more specific trigger, containing information on matching assignment; see ExclusionConstraintEvaluator - return new EvaluatedHasAssignmentTrigger(PolicyConstraintKindType.HAS_ASSIGNMENT, constraint, - createPositiveMessage(constraintElement, ctx, target.getTarget(), result), - createPositiveShortMessage(constraintElement, ctx, target.getTarget(), result)); - } + // TODO more specific trigger, containing information on matching assignment; see ExclusionConstraintEvaluator + matchingTargets.add(target.getTarget()); } } } - return createTriggerIfShouldNotExist(shouldExist, constraintElement, ctx, result); + if (!matchingTargets.isEmpty()) { + if (shouldExist) { + PrismObject anyTargetObject = matchingTargets.get(0); + return new EvaluatedHasAssignmentTrigger(PolicyConstraintKindType.HAS_ASSIGNMENT, constraint, matchingTargets, + createPositiveMessage(constraintElement, ctx, anyTargetObject, result), + createPositiveShortMessage(constraintElement, ctx, anyTargetObject, result)); + } else { + // we matched something but the constraint was "has no assignment" + return null; + } + } else { + return createTriggerIfShouldNotExist(shouldExist, constraintElement, ctx, result); + } } catch (Throwable t) { result.recordFatalError(t.getMessage(), t); throw t; @@ -182,7 +193,7 @@ private EvaluatedHasAssignmentTrigger createTriggerIfShouldNotExist(boolean shou if (shouldExist) { return null; } else { - return new EvaluatedHasAssignmentTrigger(PolicyConstraintKindType.HAS_NO_ASSIGNMENT, constraint, + return new EvaluatedHasAssignmentTrigger(PolicyConstraintKindType.HAS_NO_ASSIGNMENT, constraint, emptySet(), createNegativeMessage(constraintElement, ctx, constraint.getTargetRef().getType(), constraint.getTargetRef().getOid(), result), createNegativeShortMessage(constraintElement, ctx, constraint.getTargetRef().getType(), constraint.getTargetRef().getOid(), result)); // targetName seems to be always null, even if specified in the policy rule diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ObjectModificationConstraintEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ObjectModificationConstraintEvaluator.java index 54cf85b940f..4128225b591 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ObjectModificationConstraintEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/ObjectModificationConstraintEvaluator.java @@ -72,7 +72,7 @@ public EvaluatedModificationTrigger evaluate(@ LocalizableMessage message = createMessage(constraint, rctx, result); LocalizableMessage shortMessage = createShortMessage(constraint, rctx, result); return new EvaluatedModificationTrigger(PolicyConstraintKindType.OBJECT_MODIFICATION, constraint.getValue(), - message, shortMessage); + null, message, shortMessage); } else { LOGGER.trace("No operation matches."); return null; @@ -130,17 +130,20 @@ private boolean modificationConstraintMatches( return false; } if (!ctx.focusContext.hasAnyDelta()) { + LOGGER.trace("Focus context has no delta (primary nor secondary)"); return false; } if (!constraint.getItem().isEmpty()) { //noinspection unchecked ObjectDelta summaryDelta = ObjectDeltaCollectionsUtil.union(ctx.focusContext.getPrimaryDelta(), ctx.focusContext.getSecondaryDelta()); if (summaryDelta == null) { + LOGGER.trace("Summary delta is null"); return false; } boolean exactPathMatch = isTrue(constraint.isExactPathMatch()); for (ItemPathType path : constraint.getItem()) { if (!pathMatches(summaryDelta, ctx.focusContext.getObjectOld(), prismContext.toPath(path), exactPathMatch)) { + LOGGER.trace("Path {} does not match the constraint", path); return false; } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/PolicySituationConstraintEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/PolicySituationConstraintEvaluator.java index 2f81db8e3ba..c7d9862c3a5 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/PolicySituationConstraintEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/evaluators/PolicySituationConstraintEvaluator.java @@ -9,7 +9,7 @@ import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; import com.evolveum.midpoint.model.api.context.EvaluatedSituationTrigger; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.projector.policy.AssignmentPolicyRuleEvaluationContext; import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEvaluationContext; import com.evolveum.midpoint.schema.constants.SchemaConstants; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/PipelineData.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/PipelineData.java index ca2d4c432c1..128f9923254 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/PipelineData.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/PipelineData.java @@ -165,10 +165,12 @@ private Collection resolveQuery(Class static @NotNull PipelineData parseFrom(ValueListType input, VariablesMap frozenInitialVariables, PrismContext prismContext) { PipelineData rv = new PipelineData(); if (input != null) { - for (Object o : input.getValue()) { - if (o instanceof RawType) { + for (Object object : input.getValue()) { + if (object instanceof PrismValue) { + rv.addValue((PrismValue) object, frozenInitialVariables); + } else if (object instanceof RawType) { // a bit of hack: this should have been solved by the parser (we'll fix this later) - RawType raw = (RawType) o; + RawType raw = (RawType) object; PrismValue prismValue = raw.getAlreadyParsedValue(); if (prismValue != null) { rv.addValue(prismValue, frozenInitialVariables); @@ -177,12 +179,12 @@ private Collection resolveQuery(Class // TODO attempt to parse it somehow (e.g. by passing to the pipeline and then parsing based on expected type) } } else { - if (o instanceof Containerable) { - rv.addValue(((Containerable) o).asPrismContainerValue(), frozenInitialVariables); - } else if (o instanceof Referencable) { - rv.addValue(((Referencable) o).asReferenceValue(), frozenInitialVariables); + if (object instanceof Containerable) { + rv.addValue(((Containerable) object).asPrismContainerValue(), frozenInitialVariables); + } else if (object instanceof Referencable) { + rv.addValue(((Referencable) object).asReferenceValue(), frozenInitialVariables); } else { - rv.addValue(prismContext.itemFactory().createPropertyValue(o), frozenInitialVariables); + rv.addValue(prismContext.itemFactory().createPropertyValue(object), frozenInitialVariables); } } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptingExpressionEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptingExpressionEvaluator.java index e38d366093a..379be981f2c 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptingExpressionEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptingExpressionEvaluator.java @@ -159,6 +159,9 @@ private ExecutionContext evaluateExpression(@NotNull ExecuteScriptType executeSc } catch (ExpressionEvaluationException | SchemaException | ObjectNotFoundException | RuntimeException | CommunicationException | ConfigurationException | SecurityViolationException e) { result.recordFatalError("Couldn't execute script", e); throw new ScriptExecutionException("Couldn't execute script: " + e.getMessage(), e); + } catch (Throwable t) { + result.recordFatalError("Couldn't execute script", t); + throw t; } } @@ -177,26 +180,33 @@ public PipelineData evaluateExpression(ScriptingExpressionType value, PipelineDa CommunicationException, SecurityViolationException, ExpressionEvaluationException { context.checkTaskStop(); OperationResult globalResult = parentResult.createMinorSubresult(DOT_CLASS + "evaluateExpression"); - PipelineData output; - if (value instanceof ExpressionPipelineType) { - output = executePipeline((ExpressionPipelineType) value, input, context, globalResult); - } else if (value instanceof ExpressionSequenceType) { - output = executeSequence((ExpressionSequenceType) value, input, context, globalResult); - } else if (value instanceof SelectExpressionType) { - output = selectEvaluator.evaluate((SelectExpressionType) value, input, context, globalResult); - } else if (value instanceof FilterContentExpressionType) { - output = filterContentEvaluator.evaluate((FilterContentExpressionType) value, input, context, globalResult); - } else if (value instanceof SearchExpressionType) { - output = searchEvaluator.evaluate((SearchExpressionType) value, input, context, globalResult); - } else if (value instanceof ActionExpressionType) { - output = executeAction((ActionExpressionType) value, input, context, globalResult); - } else { - throw new IllegalArgumentException("Unsupported expression type: " + (value==null?"(null)":value.getClass())); + try { + PipelineData output; + if (value instanceof ExpressionPipelineType) { + output = executePipeline((ExpressionPipelineType) value, input, context, globalResult); + } else if (value instanceof ExpressionSequenceType) { + output = executeSequence((ExpressionSequenceType) value, input, context, globalResult); + } else if (value instanceof SelectExpressionType) { + output = selectEvaluator.evaluate((SelectExpressionType) value, input, context, globalResult); + } else if (value instanceof FilterContentExpressionType) { + output = filterContentEvaluator.evaluate((FilterContentExpressionType) value, input, context, globalResult); + } else if (value instanceof SearchExpressionType) { + output = searchEvaluator.evaluate((SearchExpressionType) value, input, context, globalResult); + } else if (value instanceof ActionExpressionType) { + output = executeAction((ActionExpressionType) value, input, context, globalResult); + } else { + throw new IllegalArgumentException("Unsupported expression type: " + (value == null ? "(null)" : value.getClass())); + } + globalResult.computeStatusIfUnknown(); + globalResult.setSummarizeSuccesses(true); + globalResult.summarize(); + return output; + } catch (Throwable t) { + globalResult.recordFatalError(t); + throw t; + } finally { + globalResult.computeStatusIfUnknown(); } - globalResult.computeStatusIfUnknown(); - globalResult.setSummarizeSuccesses(true); - globalResult.summarize(); - return output; } private PipelineData executeAction(@NotNull ActionExpressionType action, PipelineData input, ExecutionContext context, diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/GuiProfileCompiler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/GuiProfileCompiler.java index 90b4d5079a8..53a17a8af9e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/GuiProfileCompiler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/GuiProfileCompiler.java @@ -1,558 +1,558 @@ -/* - * Copyright (c) 2018-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.security; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipal; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; -import com.evolveum.midpoint.model.api.authentication.CompiledGuiProfile; -import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; -import com.evolveum.midpoint.model.api.context.EvaluatedAssignmentTarget; -import com.evolveum.midpoint.model.api.util.DeputyUtils; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.impl.controller.CollectionProcessor; -import com.evolveum.midpoint.model.impl.lens.AssignmentCollector; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.common.ObjectResolver; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.security.api.Authorization; -import com.evolveum.midpoint.security.api.AuthorizationTransformer; -import com.evolveum.midpoint.security.api.DelegatorWithOtherPrivilegesLimitations; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.QNameUtil; -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.ObjectNotFoundException; -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; - -/** - * Compiles user interface profile for a particular user. The profile contains essential information needed to efficiently render - * user interface pages for specified user. - * - * This methods in this component may be quite costly to invoke. Therefore it should NOT be invoked for every request. - * The methods are supposed to be invoked once (or several times) during user's session. The result of this method should be - * cached in web session (in principal). - * - * @author Radovan semancik - */ -@Component -public class GuiProfileCompiler { - - private static final Trace LOGGER = TraceManager.getTrace(GuiProfileCompiler.class); - - @Autowired private SecurityHelper securityHelper; - @Autowired private SystemObjectCache systemObjectCache; - @Autowired private RelationRegistry relationRegistry; - @Autowired private PrismContext prismContext; - @Autowired private CollectionProcessor collectionProcessor; - @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; - - @Autowired private AssignmentCollector assignmentCollector; - - - - @Autowired - @Qualifier("cacheRepositoryService") - private RepositoryService repositoryService; - - public void compileUserProfile(GuiProfiledPrincipal principal, PrismObject systemConfiguration, AuthorizationTransformer authorizationTransformer, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - - principal.setApplicableSecurityPolicy(securityHelper.locateSecurityPolicy(principal.getFocus().asPrismObject(), systemConfiguration, task, result)); - - List adminGuiConfigurations = new ArrayList<>(); - collect(adminGuiConfigurations, principal, authorizationTransformer, task, result); - - CompiledGuiProfile compiledGuiProfile = compileUserProfile(adminGuiConfigurations, systemConfiguration, task, result); - principal.setCompiledGuiProfile(compiledGuiProfile); - } - - private void collect(List adminGuiConfigurations, GuiProfiledPrincipal principal, AuthorizationTransformer authorizationTransformer, Task task, OperationResult result) throws SchemaException { - FocusType focusType = principal.getFocus(); - - Collection> evaluatedAssignments = assignmentCollector.collect(focusType.asPrismObject(), true, task, result); - Collection authorizations = principal.getAuthorities(); - for (EvaluatedAssignment assignment : evaluatedAssignments) { - if (assignment.isValid()) { - addAuthorizations(authorizations, assignment.getAuthorizations(), authorizationTransformer); - adminGuiConfigurations.addAll(assignment.getAdminGuiConfigurations()); - } - for (EvaluatedAssignmentTarget target : assignment.getRoles().getNonNegativeValues()) { - if (target.isValid() && target.getTarget() != null && target.getTarget().asObjectable() instanceof UserType - && DeputyUtils.isDelegationPath(target.getAssignmentPath(), relationRegistry)) { - List limitations = DeputyUtils.extractLimitations(target.getAssignmentPath()); - principal.addDelegatorWithOtherPrivilegesLimitations(new DelegatorWithOtherPrivilegesLimitations( - (UserType) target.getTarget().asObjectable(), limitations)); - } - } - } - - if (focusType instanceof UserType && ((UserType)focusType).getAdminGuiConfiguration() != null) { - // config from the user object should go last (to be applied as the last one) - adminGuiConfigurations.add(((UserType)focusType).getAdminGuiConfiguration()); - } - - } - - private void addAuthorizations(Collection targetCollection, Collection sourceCollection, AuthorizationTransformer authorizationTransformer) { - if (sourceCollection == null) { - return; - } - for (Authorization autz: sourceCollection) { - if (authorizationTransformer == null) { - targetCollection.add(autz); - } else { - Collection transformedAutzs = authorizationTransformer.transform(autz); - if (transformedAutzs != null) { - targetCollection.addAll(transformedAutzs); - } - } - } - } - - public CompiledGuiProfile compileUserProfile(@NotNull List adminGuiConfigurations, - PrismObject systemConfiguration, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - - AdminGuiConfigurationType globalAdminGuiConfig = null; - if (systemConfiguration != null) { - globalAdminGuiConfig = systemConfiguration.asObjectable().getAdminGuiConfiguration(); - } - // if there's no admin config at all, return null (to preserve original behavior) - if (adminGuiConfigurations.isEmpty() && globalAdminGuiConfig == null) { - return null; - } - - CompiledGuiProfile composite = new CompiledGuiProfile(); - if (globalAdminGuiConfig != null) { - applyAdminGuiConfiguration(composite, globalAdminGuiConfig, task, result); - } - for (AdminGuiConfigurationType adminGuiConfiguration: adminGuiConfigurations) { - applyAdminGuiConfiguration(composite, adminGuiConfiguration, task, result); - } - return composite; - } - - private void applyAdminGuiConfiguration(CompiledGuiProfile composite, AdminGuiConfigurationType adminGuiConfiguration, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - if (adminGuiConfiguration == null) { - return; - } - adminGuiConfiguration.getAdditionalMenuLink().forEach(additionalMenuLink -> composite.getAdditionalMenuLink().add(additionalMenuLink.clone())); - adminGuiConfiguration.getUserDashboardLink().forEach(userDashboardLink -> composite.getUserDashboardLink().add(userDashboardLink.clone())); - if (adminGuiConfiguration.getDefaultTimezone() != null) { - composite.setDefaultTimezone(adminGuiConfiguration.getDefaultTimezone()); - } - if (adminGuiConfiguration.getPreferredDataLanguage() != null) { - composite.setPreferredDataLanguage(adminGuiConfiguration.getPreferredDataLanguage()); - } - if (adminGuiConfiguration.isEnableExperimentalFeatures() != null) { - composite.setEnableExperimentalFeatures(adminGuiConfiguration.isEnableExperimentalFeatures()); - } - if (adminGuiConfiguration.getDefaultExportSettings() != null) { - composite.setDefaultExportSettings(adminGuiConfiguration.getDefaultExportSettings().clone()); - } - if (adminGuiConfiguration.getDisplayFormats() != null){ - composite.setDisplayFormats(adminGuiConfiguration.getDisplayFormats().clone()); - } - - applyViews(composite, adminGuiConfiguration.getObjectLists(), task, result); // Compatibility, deprecated - applyViews(composite, adminGuiConfiguration.getObjectCollectionViews(), task, result); - - if (adminGuiConfiguration.getObjectForms() != null) { - if (composite.getObjectForms() == null) { - composite.setObjectForms(adminGuiConfiguration.getObjectForms().clone()); - } else { - for (ObjectFormType objectForm: adminGuiConfiguration.getObjectForms().getObjectForm()) { - joinForms(composite.getObjectForms(), objectForm.clone()); - } - } - } - if (adminGuiConfiguration.getObjectDetails() != null) { - if (composite.getObjectDetails() == null) { - composite.setObjectDetails(adminGuiConfiguration.getObjectDetails().clone()); - } else { - for (GuiObjectDetailsPageType objectDetails: adminGuiConfiguration.getObjectDetails().getObjectDetailsPage()) { - joinObjectDetails(composite.getObjectDetails(), objectDetails); - } - } - } - if (adminGuiConfiguration.getUserDashboard() != null) { - if (composite.getUserDashboard() == null) { - composite.setUserDashboard(adminGuiConfiguration.getUserDashboard().clone()); - } else { - for (DashboardWidgetType widget: adminGuiConfiguration.getUserDashboard().getWidget()) { - mergeWidget(composite, widget); - } - } - } - for (UserInterfaceFeatureType feature: adminGuiConfiguration.getFeature()) { - mergeFeature(composite, feature.clone()); - } - - - if (adminGuiConfiguration.getFeedbackMessagesHook() != null) { - composite.setFeedbackMessagesHook(adminGuiConfiguration.getFeedbackMessagesHook().clone()); - } - - if (adminGuiConfiguration.getRoleManagement() != null && - adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit() != null) { - if (composite.getRoleManagement() != null && composite.getRoleManagement().getAssignmentApprovalRequestLimit() != null) { - // the greater value wins (so it is possible to give an exception to selected users) - Integer newValue = Math.max( - adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit(), - composite.getRoleManagement().getAssignmentApprovalRequestLimit()); - composite.getRoleManagement().setAssignmentApprovalRequestLimit(newValue); - } else { - if (composite.getRoleManagement() == null) { - composite.setRoleManagement(new AdminGuiConfigurationRoleManagementType()); - } - composite.getRoleManagement().setAssignmentApprovalRequestLimit( - adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit()); - } - } - } - - private void applyViews(CompiledGuiProfile composite, GuiObjectListViewsType viewsType, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - if (viewsType == null) { - return; - } - - if (viewsType.getDefault() != null) { - if (composite.getDefaultObjectCollectionView() == null) { - composite.setDefaultObjectCollectionView(new CompiledObjectCollectionView()); - } - compileView(composite.getDefaultObjectCollectionView(), viewsType.getDefault(), task, result); - } - - for (GuiObjectListViewType objectCollectionView : viewsType.getObjectList()) { // Compatibility, legacy - applyView(composite, objectCollectionView, task, result); - } - - for (GuiObjectListViewType objectCollectionView : viewsType.getObjectCollectionView()) { - applyView(composite, objectCollectionView, task, result); - } - } - - private void applyView(CompiledGuiProfile composite, GuiObjectListViewType objectListViewType, Task task, OperationResult result) { - try { - CompiledObjectCollectionView existingView = findOrCreateMatchingView(composite, objectListViewType); - compileView(existingView, objectListViewType, task, result); - } catch (Throwable e) { - // Do not let any error stop processing here. This code is used during user login. An error here can stop login procedure. We do not - // want that. E.g. wrong adminGuiConfig may prohibit login on administrator, therefore ruining any chance of fixing the situation. - // This is also handled somewhere up the call stack. But we want to handle it also here. Otherwise an error in one collection would - // mean that entire configuration processing will be stopped. We do not want that. We want to skip processing of just that one wrong view. - LOGGER.error("Error compiling user profile, view '{}': {}", determineViewIdentifier(objectListViewType), e.getMessage(), e); - } - } - - - private CompiledObjectCollectionView findOrCreateMatchingView(CompiledGuiProfile composite, GuiObjectListViewType objectListViewType) { - QName objectType = objectListViewType.getType(); - String viewIdentifier = determineViewIdentifier(objectListViewType); - CompiledObjectCollectionView existingView = composite.findObjectCollectionView(objectType, viewIdentifier); - if (existingView == null) { - existingView = new CompiledObjectCollectionView(objectType, viewIdentifier); - composite.getObjectCollectionViews().add(existingView); - } - return existingView; - } - - private String determineViewIdentifier(GuiObjectListViewType objectListViewType) { - String viewIdentifier = objectListViewType.getIdentifier(); - if (viewIdentifier != null) { - return viewIdentifier; - } - String viewName = objectListViewType.getName(); - if (viewName != null) { - // legacy, deprecated - return viewName; - } - CollectionRefSpecificationType collection = objectListViewType.getCollection(); - if (collection == null) { - return objectListViewType.getType().getLocalPart(); - } - ObjectReferenceType collectionRef = collection.getCollectionRef(); - if (collectionRef == null) { - return objectListViewType.getType().getLocalPart(); - } - return collectionRef.getOid(); - } - - private void compileView(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - compileActions(existingView, objectListViewType); - compileAdditionalPanels(existingView, objectListViewType); - compileColumns(existingView, objectListViewType); - compileDisplay(existingView, objectListViewType); - compileDistinct(existingView, objectListViewType); - compileSorting(existingView, objectListViewType); - compileCounting(existingView, objectListViewType); - compileDisplayOrder(existingView, objectListViewType); - compileSearchBox(existingView, objectListViewType); - compileCollection(existingView, objectListViewType, task, result); - compileRefreshInterval(existingView, objectListViewType); - } - - private void compileActions(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - List newActions = objectListViewType.getAction(); - for (GuiActionType newAction: newActions) { - // TODO: check for action duplication/override - existingView.getActions().add(newAction); // No need to clone, CompiledObjectCollectionView is not prism - } - - } - - private void compileAdditionalPanels(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - GuiObjectListViewAdditionalPanelsType newAdditionalPanels = objectListViewType.getAdditionalPanels(); - if (newAdditionalPanels == null) { - return; - } - // TODO: later: merge additional panel definitions - existingView.setAdditionalPanels(newAdditionalPanels); - } - - private void compileCollection(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - CollectionRefSpecificationType collectionSpec = objectListViewType.getCollection(); - if (collectionSpec == null) { - ObjectReferenceType collectionRef = objectListViewType.getCollectionRef(); - if (collectionRef == null) { - return; - } - // Legacy, deprecated - collectionSpec = new CollectionRefSpecificationType(); - collectionSpec.setCollectionRef(collectionRef.clone()); - } - if (existingView.getCollection() != null) { - LOGGER.debug("Redefining collection in view {}", existingView.getViewIdentifier()); - } - existingView.setCollection(collectionSpec); - - compileCollection(existingView, collectionSpec, task, result); - } - - private void compileCollection(CompiledObjectCollectionView existingView, CollectionRefSpecificationType collectionSpec, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - - QName targetObjectType = existingView.getObjectType(); - Class targetTypeClass = ObjectType.class; - if (targetObjectType != null) { - targetTypeClass = ObjectTypes.getObjectTypeFromTypeQName(targetObjectType).getClassDefinition(); - } - collectionProcessor.compileObjectCollectionView(existingView, collectionSpec, targetTypeClass, task, result); - } - - private void compileColumns(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - List newColumns = objectListViewType.getColumn(); - if (newColumns == null || newColumns.isEmpty()) { - return; - } - // Not very efficient algorithm. But must do for now. - List existingColumns = existingView.getColumns(); - existingColumns.addAll(newColumns); - List orderedList = MiscSchemaUtil.orderCustomColumns(existingColumns); - existingColumns.clear(); - existingColumns.addAll(orderedList); - } - - private void compileDisplay(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - DisplayType newDisplay = objectListViewType.getDisplay(); - if (newDisplay == null) { - return; - } - if (existingView.getDisplay() == null) { - existingView.setDisplay(newDisplay); - } - MiscSchemaUtil.mergeDisplay(existingView.getDisplay(), newDisplay); - } - - private void compileDistinct(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - DistinctSearchOptionType newDistinct = objectListViewType.getDistinct(); - if (newDistinct == null) { - return; - } - existingView.setDistinct(newDistinct); - } - - private void compileSorting(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - Boolean newDisableSorting = objectListViewType.isDisableSorting(); - if (newDisableSorting != null) { - existingView.setDisableSorting(newDisableSorting); - } - } - - private void compileRefreshInterval(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - Integer refreshInterval = objectListViewType.getRefreshInterval(); - if (refreshInterval != null) { - existingView.setRefreshInterval(refreshInterval); - } - } - - private void compileCounting(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - Boolean newDisableCounting = objectListViewType.isDisableCounting(); - if (newDisableCounting != null) { - existingView.setDisableCounting(newDisableCounting); - } - } - - private void compileDisplayOrder(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType){ - Integer newDisplayOrder = objectListViewType.getDisplayOrder(); - if (newDisplayOrder != null){ - existingView.setDisplayOrder(newDisplayOrder); - } - } - - private void compileSearchBox(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - SearchBoxConfigurationType newSearchBoxConfig = objectListViewType.getSearchBoxConfiguration(); - if (newSearchBoxConfig == null) { - return; - } - // TODO: merge - existingView.setSearchBoxConfiguration(newSearchBoxConfig); - } - - private void joinForms(ObjectFormsType objectForms, ObjectFormType newForm) { - objectForms.getObjectForm().removeIf(currentForm -> isTheSameObjectForm(currentForm, newForm)); - objectForms.getObjectForm().add(newForm.clone().id(null)); - } - - private void joinObjectDetails(GuiObjectDetailsSetType objectDetailsSet, GuiObjectDetailsPageType newObjectDetails) { - objectDetailsSet.getObjectDetailsPage().removeIf(currentDetails -> isTheSameObjectType(currentDetails, newObjectDetails)); - objectDetailsSet.getObjectDetailsPage().add(newObjectDetails.clone()); - } - - private boolean isTheSameObjectType(AbstractObjectTypeConfigurationType oldConf, AbstractObjectTypeConfigurationType newConf) { - return QNameUtil.match(oldConf.getType(), newConf.getType()); - } - - private boolean isTheSameObjectForm(ObjectFormType oldForm, ObjectFormType newForm){ - if (!isTheSameObjectType(oldForm,newForm)) { - return false; - } - if (oldForm.isIncludeDefaultForms() != null && - newForm.isIncludeDefaultForms() != null){ - return true; - } - if (oldForm.getFormSpecification() == null && newForm.getFormSpecification() == null) { - String oldFormPanelUri = oldForm.getFormSpecification().getPanelUri(); - String newFormPanelUri = newForm.getFormSpecification().getPanelUri(); - if (oldFormPanelUri != null && oldFormPanelUri.equals(newFormPanelUri)) { - return true; - } - - String oldFormPanelClass = oldForm.getFormSpecification().getPanelClass(); - String newFormPanelClass = newForm.getFormSpecification().getPanelClass(); - if (oldFormPanelClass != null && oldFormPanelClass.equals(newFormPanelClass)) { - return true; - } - - String oldFormRefOid = oldForm.getFormSpecification().getFormRef() == null ? - null : oldForm.getFormSpecification().getFormRef().getOid(); - String newFormRefOid = newForm.getFormSpecification().getFormRef() == null ? - null : newForm.getFormSpecification().getFormRef().getOid(); - if (oldFormRefOid != null && oldFormRefOid.equals(newFormRefOid)) { - return true; - } - } - return false; - } - - private void mergeWidget(CompiledGuiProfile composite, DashboardWidgetType newWidget) { - String newWidgetIdentifier = newWidget.getIdentifier(); - DashboardWidgetType compositeWidget = composite.findUserDashboardWidget(newWidgetIdentifier); - if (compositeWidget == null) { - composite.getUserDashboard().getWidget().add(newWidget.clone()); - } else { - mergeWidget(compositeWidget, newWidget); - } - } - - private void mergeWidget(DashboardWidgetType compositeWidget, DashboardWidgetType newWidget) { - mergeFeature(compositeWidget, newWidget, UserInterfaceElementVisibilityType.VACANT); - // merge other widget properties (in the future) - } - - private void mergeFeature(CompiledGuiProfile composite, UserInterfaceFeatureType newFeature) { - String newIdentifier = newFeature.getIdentifier(); - UserInterfaceFeatureType compositeFeature = composite.findFeature(newIdentifier); - if (compositeFeature == null) { - composite.getFeatures().add(newFeature.clone()); - } else { - mergeFeature(compositeFeature, newFeature, UserInterfaceElementVisibilityType.AUTOMATIC); - } - } - - private void mergeFeature(T compositeFeature, T newFeature, UserInterfaceElementVisibilityType defaultVisibility) { - UserInterfaceElementVisibilityType newCompositeVisibility = mergeVisibility(compositeFeature.getVisibility(), newFeature.getVisibility(), defaultVisibility); - compositeFeature.setVisibility(newCompositeVisibility); - } - - private UserInterfaceElementVisibilityType mergeVisibility( - UserInterfaceElementVisibilityType compositeVisibility, UserInterfaceElementVisibilityType newVisibility, UserInterfaceElementVisibilityType defaultVisibility) { - if (compositeVisibility == null) { - compositeVisibility = defaultVisibility; - } - if (newVisibility == null) { - newVisibility = defaultVisibility; - } - if (compositeVisibility == UserInterfaceElementVisibilityType.HIDDEN || newVisibility == UserInterfaceElementVisibilityType.HIDDEN) { - return UserInterfaceElementVisibilityType.HIDDEN; - } - if (compositeVisibility == UserInterfaceElementVisibilityType.VISIBLE || newVisibility == UserInterfaceElementVisibilityType.VISIBLE) { - return UserInterfaceElementVisibilityType.VISIBLE; - } - if (compositeVisibility == UserInterfaceElementVisibilityType.AUTOMATIC || newVisibility == UserInterfaceElementVisibilityType.AUTOMATIC) { - return UserInterfaceElementVisibilityType.AUTOMATIC; - } - return UserInterfaceElementVisibilityType.VACANT; - } - - public CompiledGuiProfile getGlobalCompiledGuiProfile(Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(parentResult); - if (systemConfiguration == null) { - return null; - } - List adminGuiConfigurations = new ArrayList<>(); - CompiledGuiProfile compiledGuiProfile = compileUserProfile(adminGuiConfigurations, systemConfiguration, task, parentResult); - // TODO: cache compiled profile - return compiledGuiProfile; - } - - -} +/* + * Copyright (c) 2018-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.security; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipal; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; +import com.evolveum.midpoint.model.api.authentication.CompiledGuiProfile; +import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; +import com.evolveum.midpoint.model.api.context.EvaluatedAssignmentTarget; +import com.evolveum.midpoint.model.api.util.DeputyUtils; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.impl.controller.CollectionProcessor; +import com.evolveum.midpoint.model.impl.lens.AssignmentCollector; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.security.api.Authorization; +import com.evolveum.midpoint.security.api.AuthorizationTransformer; +import com.evolveum.midpoint.security.api.DelegatorWithOtherPrivilegesLimitations; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.QNameUtil; +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.ObjectNotFoundException; +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; + +/** + * Compiles user interface profile for a particular user. The profile contains essential information needed to efficiently render + * user interface pages for specified user. + * + * This methods in this component may be quite costly to invoke. Therefore it should NOT be invoked for every request. + * The methods are supposed to be invoked once (or several times) during user's session. The result of this method should be + * cached in web session (in principal). + * + * @author Radovan semancik + */ +@Component +public class GuiProfileCompiler { + + private static final Trace LOGGER = TraceManager.getTrace(GuiProfileCompiler.class); + + @Autowired private SecurityHelper securityHelper; + @Autowired private SystemObjectCache systemObjectCache; + @Autowired private RelationRegistry relationRegistry; + @Autowired private PrismContext prismContext; + @Autowired private CollectionProcessor collectionProcessor; + @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; + + @Autowired private AssignmentCollector assignmentCollector; + + + + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; + + public void compileUserProfile(GuiProfiledPrincipal principal, PrismObject systemConfiguration, AuthorizationTransformer authorizationTransformer, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + + principal.setApplicableSecurityPolicy(securityHelper.locateSecurityPolicy(principal.getFocus().asPrismObject(), systemConfiguration, task, result)); + + List adminGuiConfigurations = new ArrayList<>(); + collect(adminGuiConfigurations, principal, authorizationTransformer, task, result); + + CompiledGuiProfile compiledGuiProfile = compileUserProfile(adminGuiConfigurations, systemConfiguration, task, result); + principal.setCompiledGuiProfile(compiledGuiProfile); + } + + private void collect(List adminGuiConfigurations, GuiProfiledPrincipal principal, AuthorizationTransformer authorizationTransformer, Task task, OperationResult result) throws SchemaException { + FocusType focusType = principal.getFocus(); + + Collection> evaluatedAssignments = assignmentCollector.collect(focusType.asPrismObject(), true, task, result); + Collection authorizations = principal.getAuthorities(); + for (EvaluatedAssignment assignment : evaluatedAssignments) { + if (assignment.isValid()) { + addAuthorizations(authorizations, assignment.getAuthorizations(), authorizationTransformer); + adminGuiConfigurations.addAll(assignment.getAdminGuiConfigurations()); + } + for (EvaluatedAssignmentTarget target : assignment.getRoles().getNonNegativeValues()) { + if (target.isValid() && target.getTarget().asObjectable() instanceof UserType + && DeputyUtils.isDelegationPath(target.getAssignmentPath(), relationRegistry)) { + List limitations = DeputyUtils.extractLimitations(target.getAssignmentPath()); + principal.addDelegatorWithOtherPrivilegesLimitations(new DelegatorWithOtherPrivilegesLimitations( + (UserType) target.getTarget().asObjectable(), limitations)); + } + } + } + + if (focusType instanceof UserType && ((UserType)focusType).getAdminGuiConfiguration() != null) { + // config from the user object should go last (to be applied as the last one) + adminGuiConfigurations.add(((UserType)focusType).getAdminGuiConfiguration()); + } + + } + + private void addAuthorizations(Collection targetCollection, Collection sourceCollection, AuthorizationTransformer authorizationTransformer) { + if (sourceCollection == null) { + return; + } + for (Authorization autz: sourceCollection) { + if (authorizationTransformer == null) { + targetCollection.add(autz); + } else { + Collection transformedAutzs = authorizationTransformer.transform(autz); + if (transformedAutzs != null) { + targetCollection.addAll(transformedAutzs); + } + } + } + } + + public CompiledGuiProfile compileUserProfile(@NotNull List adminGuiConfigurations, + PrismObject systemConfiguration, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + + AdminGuiConfigurationType globalAdminGuiConfig = null; + if (systemConfiguration != null) { + globalAdminGuiConfig = systemConfiguration.asObjectable().getAdminGuiConfiguration(); + } + // if there's no admin config at all, return null (to preserve original behavior) + if (adminGuiConfigurations.isEmpty() && globalAdminGuiConfig == null) { + return null; + } + + CompiledGuiProfile composite = new CompiledGuiProfile(); + if (globalAdminGuiConfig != null) { + applyAdminGuiConfiguration(composite, globalAdminGuiConfig, task, result); + } + for (AdminGuiConfigurationType adminGuiConfiguration: adminGuiConfigurations) { + applyAdminGuiConfiguration(composite, adminGuiConfiguration, task, result); + } + return composite; + } + + private void applyAdminGuiConfiguration(CompiledGuiProfile composite, AdminGuiConfigurationType adminGuiConfiguration, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + if (adminGuiConfiguration == null) { + return; + } + adminGuiConfiguration.getAdditionalMenuLink().forEach(additionalMenuLink -> composite.getAdditionalMenuLink().add(additionalMenuLink.clone())); + adminGuiConfiguration.getUserDashboardLink().forEach(userDashboardLink -> composite.getUserDashboardLink().add(userDashboardLink.clone())); + if (adminGuiConfiguration.getDefaultTimezone() != null) { + composite.setDefaultTimezone(adminGuiConfiguration.getDefaultTimezone()); + } + if (adminGuiConfiguration.getPreferredDataLanguage() != null) { + composite.setPreferredDataLanguage(adminGuiConfiguration.getPreferredDataLanguage()); + } + if (adminGuiConfiguration.isEnableExperimentalFeatures() != null) { + composite.setEnableExperimentalFeatures(adminGuiConfiguration.isEnableExperimentalFeatures()); + } + if (adminGuiConfiguration.getDefaultExportSettings() != null) { + composite.setDefaultExportSettings(adminGuiConfiguration.getDefaultExportSettings().clone()); + } + if (adminGuiConfiguration.getDisplayFormats() != null){ + composite.setDisplayFormats(adminGuiConfiguration.getDisplayFormats().clone()); + } + + applyViews(composite, adminGuiConfiguration.getObjectLists(), task, result); // Compatibility, deprecated + applyViews(composite, adminGuiConfiguration.getObjectCollectionViews(), task, result); + + if (adminGuiConfiguration.getObjectForms() != null) { + if (composite.getObjectForms() == null) { + composite.setObjectForms(adminGuiConfiguration.getObjectForms().clone()); + } else { + for (ObjectFormType objectForm: adminGuiConfiguration.getObjectForms().getObjectForm()) { + joinForms(composite.getObjectForms(), objectForm.clone()); + } + } + } + if (adminGuiConfiguration.getObjectDetails() != null) { + if (composite.getObjectDetails() == null) { + composite.setObjectDetails(adminGuiConfiguration.getObjectDetails().clone()); + } else { + for (GuiObjectDetailsPageType objectDetails: adminGuiConfiguration.getObjectDetails().getObjectDetailsPage()) { + joinObjectDetails(composite.getObjectDetails(), objectDetails); + } + } + } + if (adminGuiConfiguration.getUserDashboard() != null) { + if (composite.getUserDashboard() == null) { + composite.setUserDashboard(adminGuiConfiguration.getUserDashboard().clone()); + } else { + for (DashboardWidgetType widget: adminGuiConfiguration.getUserDashboard().getWidget()) { + mergeWidget(composite, widget); + } + } + } + for (UserInterfaceFeatureType feature: adminGuiConfiguration.getFeature()) { + mergeFeature(composite, feature.clone()); + } + + + if (adminGuiConfiguration.getFeedbackMessagesHook() != null) { + composite.setFeedbackMessagesHook(adminGuiConfiguration.getFeedbackMessagesHook().clone()); + } + + if (adminGuiConfiguration.getRoleManagement() != null && + adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit() != null) { + if (composite.getRoleManagement() != null && composite.getRoleManagement().getAssignmentApprovalRequestLimit() != null) { + // the greater value wins (so it is possible to give an exception to selected users) + Integer newValue = Math.max( + adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit(), + composite.getRoleManagement().getAssignmentApprovalRequestLimit()); + composite.getRoleManagement().setAssignmentApprovalRequestLimit(newValue); + } else { + if (composite.getRoleManagement() == null) { + composite.setRoleManagement(new AdminGuiConfigurationRoleManagementType()); + } + composite.getRoleManagement().setAssignmentApprovalRequestLimit( + adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit()); + } + } + } + + private void applyViews(CompiledGuiProfile composite, GuiObjectListViewsType viewsType, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + if (viewsType == null) { + return; + } + + if (viewsType.getDefault() != null) { + if (composite.getDefaultObjectCollectionView() == null) { + composite.setDefaultObjectCollectionView(new CompiledObjectCollectionView()); + } + compileView(composite.getDefaultObjectCollectionView(), viewsType.getDefault(), task, result); + } + + for (GuiObjectListViewType objectCollectionView : viewsType.getObjectList()) { // Compatibility, legacy + applyView(composite, objectCollectionView, task, result); + } + + for (GuiObjectListViewType objectCollectionView : viewsType.getObjectCollectionView()) { + applyView(composite, objectCollectionView, task, result); + } + } + + private void applyView(CompiledGuiProfile composite, GuiObjectListViewType objectListViewType, Task task, OperationResult result) { + try { + CompiledObjectCollectionView existingView = findOrCreateMatchingView(composite, objectListViewType); + compileView(existingView, objectListViewType, task, result); + } catch (Throwable e) { + // Do not let any error stop processing here. This code is used during user login. An error here can stop login procedure. We do not + // want that. E.g. wrong adminGuiConfig may prohibit login on administrator, therefore ruining any chance of fixing the situation. + // This is also handled somewhere up the call stack. But we want to handle it also here. Otherwise an error in one collection would + // mean that entire configuration processing will be stopped. We do not want that. We want to skip processing of just that one wrong view. + LOGGER.error("Error compiling user profile, view '{}': {}", determineViewIdentifier(objectListViewType), e.getMessage(), e); + } + } + + + private CompiledObjectCollectionView findOrCreateMatchingView(CompiledGuiProfile composite, GuiObjectListViewType objectListViewType) { + QName objectType = objectListViewType.getType(); + String viewIdentifier = determineViewIdentifier(objectListViewType); + CompiledObjectCollectionView existingView = composite.findObjectCollectionView(objectType, viewIdentifier); + if (existingView == null) { + existingView = new CompiledObjectCollectionView(objectType, viewIdentifier); + composite.getObjectCollectionViews().add(existingView); + } + return existingView; + } + + private String determineViewIdentifier(GuiObjectListViewType objectListViewType) { + String viewIdentifier = objectListViewType.getIdentifier(); + if (viewIdentifier != null) { + return viewIdentifier; + } + String viewName = objectListViewType.getName(); + if (viewName != null) { + // legacy, deprecated + return viewName; + } + CollectionRefSpecificationType collection = objectListViewType.getCollection(); + if (collection == null) { + return objectListViewType.getType().getLocalPart(); + } + ObjectReferenceType collectionRef = collection.getCollectionRef(); + if (collectionRef == null) { + return objectListViewType.getType().getLocalPart(); + } + return collectionRef.getOid(); + } + + private void compileView(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + compileActions(existingView, objectListViewType); + compileAdditionalPanels(existingView, objectListViewType); + compileColumns(existingView, objectListViewType); + compileDisplay(existingView, objectListViewType); + compileDistinct(existingView, objectListViewType); + compileSorting(existingView, objectListViewType); + compileCounting(existingView, objectListViewType); + compileDisplayOrder(existingView, objectListViewType); + compileSearchBox(existingView, objectListViewType); + compileCollection(existingView, objectListViewType, task, result); + compileRefreshInterval(existingView, objectListViewType); + } + + private void compileActions(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + List newActions = objectListViewType.getAction(); + for (GuiActionType newAction: newActions) { + // TODO: check for action duplication/override + existingView.getActions().add(newAction); // No need to clone, CompiledObjectCollectionView is not prism + } + + } + + private void compileAdditionalPanels(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + GuiObjectListViewAdditionalPanelsType newAdditionalPanels = objectListViewType.getAdditionalPanels(); + if (newAdditionalPanels == null) { + return; + } + // TODO: later: merge additional panel definitions + existingView.setAdditionalPanels(newAdditionalPanels); + } + + private void compileCollection(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + CollectionRefSpecificationType collectionSpec = objectListViewType.getCollection(); + if (collectionSpec == null) { + ObjectReferenceType collectionRef = objectListViewType.getCollectionRef(); + if (collectionRef == null) { + return; + } + // Legacy, deprecated + collectionSpec = new CollectionRefSpecificationType(); + collectionSpec.setCollectionRef(collectionRef.clone()); + } + if (existingView.getCollection() != null) { + LOGGER.debug("Redefining collection in view {}", existingView.getViewIdentifier()); + } + existingView.setCollection(collectionSpec); + + compileCollection(existingView, collectionSpec, task, result); + } + + private void compileCollection(CompiledObjectCollectionView existingView, CollectionRefSpecificationType collectionSpec, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + + QName targetObjectType = existingView.getObjectType(); + Class targetTypeClass = ObjectType.class; + if (targetObjectType != null) { + targetTypeClass = ObjectTypes.getObjectTypeFromTypeQName(targetObjectType).getClassDefinition(); + } + collectionProcessor.compileObjectCollectionView(existingView, collectionSpec, targetTypeClass, task, result); + } + + private void compileColumns(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + List newColumns = objectListViewType.getColumn(); + if (newColumns == null || newColumns.isEmpty()) { + return; + } + // Not very efficient algorithm. But must do for now. + List existingColumns = existingView.getColumns(); + existingColumns.addAll(newColumns); + List orderedList = MiscSchemaUtil.orderCustomColumns(existingColumns); + existingColumns.clear(); + existingColumns.addAll(orderedList); + } + + private void compileDisplay(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + DisplayType newDisplay = objectListViewType.getDisplay(); + if (newDisplay == null) { + return; + } + if (existingView.getDisplay() == null) { + existingView.setDisplay(newDisplay); + } + MiscSchemaUtil.mergeDisplay(existingView.getDisplay(), newDisplay); + } + + private void compileDistinct(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + DistinctSearchOptionType newDistinct = objectListViewType.getDistinct(); + if (newDistinct == null) { + return; + } + existingView.setDistinct(newDistinct); + } + + private void compileSorting(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + Boolean newDisableSorting = objectListViewType.isDisableSorting(); + if (newDisableSorting != null) { + existingView.setDisableSorting(newDisableSorting); + } + } + + private void compileRefreshInterval(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + Integer refreshInterval = objectListViewType.getRefreshInterval(); + if (refreshInterval != null) { + existingView.setRefreshInterval(refreshInterval); + } + } + + private void compileCounting(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + Boolean newDisableCounting = objectListViewType.isDisableCounting(); + if (newDisableCounting != null) { + existingView.setDisableCounting(newDisableCounting); + } + } + + private void compileDisplayOrder(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType){ + Integer newDisplayOrder = objectListViewType.getDisplayOrder(); + if (newDisplayOrder != null){ + existingView.setDisplayOrder(newDisplayOrder); + } + } + + private void compileSearchBox(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + SearchBoxConfigurationType newSearchBoxConfig = objectListViewType.getSearchBoxConfiguration(); + if (newSearchBoxConfig == null) { + return; + } + // TODO: merge + existingView.setSearchBoxConfiguration(newSearchBoxConfig); + } + + private void joinForms(ObjectFormsType objectForms, ObjectFormType newForm) { + objectForms.getObjectForm().removeIf(currentForm -> isTheSameObjectForm(currentForm, newForm)); + objectForms.getObjectForm().add(newForm.clone().id(null)); + } + + private void joinObjectDetails(GuiObjectDetailsSetType objectDetailsSet, GuiObjectDetailsPageType newObjectDetails) { + objectDetailsSet.getObjectDetailsPage().removeIf(currentDetails -> isTheSameObjectType(currentDetails, newObjectDetails)); + objectDetailsSet.getObjectDetailsPage().add(newObjectDetails.clone()); + } + + private boolean isTheSameObjectType(AbstractObjectTypeConfigurationType oldConf, AbstractObjectTypeConfigurationType newConf) { + return QNameUtil.match(oldConf.getType(), newConf.getType()); + } + + private boolean isTheSameObjectForm(ObjectFormType oldForm, ObjectFormType newForm){ + if (!isTheSameObjectType(oldForm,newForm)) { + return false; + } + if (oldForm.isIncludeDefaultForms() != null && + newForm.isIncludeDefaultForms() != null){ + return true; + } + if (oldForm.getFormSpecification() == null && newForm.getFormSpecification() == null) { + String oldFormPanelUri = oldForm.getFormSpecification().getPanelUri(); + String newFormPanelUri = newForm.getFormSpecification().getPanelUri(); + if (oldFormPanelUri != null && oldFormPanelUri.equals(newFormPanelUri)) { + return true; + } + + String oldFormPanelClass = oldForm.getFormSpecification().getPanelClass(); + String newFormPanelClass = newForm.getFormSpecification().getPanelClass(); + if (oldFormPanelClass != null && oldFormPanelClass.equals(newFormPanelClass)) { + return true; + } + + String oldFormRefOid = oldForm.getFormSpecification().getFormRef() == null ? + null : oldForm.getFormSpecification().getFormRef().getOid(); + String newFormRefOid = newForm.getFormSpecification().getFormRef() == null ? + null : newForm.getFormSpecification().getFormRef().getOid(); + if (oldFormRefOid != null && oldFormRefOid.equals(newFormRefOid)) { + return true; + } + } + return false; + } + + private void mergeWidget(CompiledGuiProfile composite, DashboardWidgetType newWidget) { + String newWidgetIdentifier = newWidget.getIdentifier(); + DashboardWidgetType compositeWidget = composite.findUserDashboardWidget(newWidgetIdentifier); + if (compositeWidget == null) { + composite.getUserDashboard().getWidget().add(newWidget.clone()); + } else { + mergeWidget(compositeWidget, newWidget); + } + } + + private void mergeWidget(DashboardWidgetType compositeWidget, DashboardWidgetType newWidget) { + mergeFeature(compositeWidget, newWidget, UserInterfaceElementVisibilityType.VACANT); + // merge other widget properties (in the future) + } + + private void mergeFeature(CompiledGuiProfile composite, UserInterfaceFeatureType newFeature) { + String newIdentifier = newFeature.getIdentifier(); + UserInterfaceFeatureType compositeFeature = composite.findFeature(newIdentifier); + if (compositeFeature == null) { + composite.getFeatures().add(newFeature.clone()); + } else { + mergeFeature(compositeFeature, newFeature, UserInterfaceElementVisibilityType.AUTOMATIC); + } + } + + private void mergeFeature(T compositeFeature, T newFeature, UserInterfaceElementVisibilityType defaultVisibility) { + UserInterfaceElementVisibilityType newCompositeVisibility = mergeVisibility(compositeFeature.getVisibility(), newFeature.getVisibility(), defaultVisibility); + compositeFeature.setVisibility(newCompositeVisibility); + } + + private UserInterfaceElementVisibilityType mergeVisibility( + UserInterfaceElementVisibilityType compositeVisibility, UserInterfaceElementVisibilityType newVisibility, UserInterfaceElementVisibilityType defaultVisibility) { + if (compositeVisibility == null) { + compositeVisibility = defaultVisibility; + } + if (newVisibility == null) { + newVisibility = defaultVisibility; + } + if (compositeVisibility == UserInterfaceElementVisibilityType.HIDDEN || newVisibility == UserInterfaceElementVisibilityType.HIDDEN) { + return UserInterfaceElementVisibilityType.HIDDEN; + } + if (compositeVisibility == UserInterfaceElementVisibilityType.VISIBLE || newVisibility == UserInterfaceElementVisibilityType.VISIBLE) { + return UserInterfaceElementVisibilityType.VISIBLE; + } + if (compositeVisibility == UserInterfaceElementVisibilityType.AUTOMATIC || newVisibility == UserInterfaceElementVisibilityType.AUTOMATIC) { + return UserInterfaceElementVisibilityType.AUTOMATIC; + } + return UserInterfaceElementVisibilityType.VACANT; + } + + public CompiledGuiProfile getGlobalCompiledGuiProfile(Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(parentResult); + if (systemConfiguration == null) { + return null; + } + List adminGuiConfigurations = new ArrayList<>(); + CompiledGuiProfile compiledGuiProfile = compileUserProfile(adminGuiConfigurations, systemConfiguration, task, parentResult); + // TODO: cache compiled profile + return compiledGuiProfile; + } + + +} diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/AbstractLensTest.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/AbstractLensTest.java index a732a323f30..7bff233c700 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/AbstractLensTest.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/AbstractLensTest.java @@ -12,6 +12,7 @@ import java.util.*; import java.util.function.Consumer; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAbstractAssignmentEvaluator.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAbstractAssignmentEvaluator.java index 64200ed799b..d0fb687a5d7 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAbstractAssignmentEvaluator.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAbstractAssignmentEvaluator.java @@ -17,6 +17,9 @@ import java.util.*; import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentEvaluator; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; + import com.evolveum.midpoint.model.impl.lens.construction.Construction; import com.evolveum.midpoint.model.impl.lens.construction.EvaluatedConstructionImpl; import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor.java index 6e42b66aaf8..c720509d26c 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.model.impl.lens.construction.EvaluatedConstructionImpl; import org.jetbrains.annotations.NotNull; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import org.springframework.beans.factory.annotation.Autowired; import org.testng.annotations.Test; 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 deaaa763759..dda39c34924 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 @@ -12,6 +12,7 @@ import com.evolveum.midpoint.model.api.context.EvaluationOrder; import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluationContext; import com.evolveum.midpoint.model.common.mapping.MappingFactory; +import com.evolveum.midpoint.model.impl.lens.assignments.*; import com.evolveum.midpoint.model.impl.lens.construction.AbstractConstruction; import com.evolveum.midpoint.model.impl.lens.construction.Construction; import com.evolveum.midpoint.model.impl.lens.construction.EvaluatedConstructionImpl; diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules.java index 510e1218222..77ee7effe9e 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPolicyRules.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.model.impl.lens; import com.evolveum.midpoint.model.api.context.*; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.util.RecordingProgressListener; import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismObject; diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestProjectorPersona.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestProjectorPersona.java index 81bab10bf7d..01dacc5624d 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestProjectorPersona.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestProjectorPersona.java @@ -8,6 +8,7 @@ import static org.testng.AssertJUnit.*; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.construction.PersonaConstruction; import org.springframework.test.annotation.DirtiesContext; diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestLinkedObjects.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestLinkedObjects.java index 1d9d1ae699e..add0f5344ff 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestLinkedObjects.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestLinkedObjects.java @@ -6,25 +6,23 @@ */ package com.evolveum.midpoint.model.intest; -import static com.evolveum.midpoint.util.MiscUtil.extractSingleton; - import java.io.File; +import com.evolveum.midpoint.test.PredefinedTestMethodTracing; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.apache.bcel.generic.RETURN; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; import org.testng.annotations.Test; import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.TestResource; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ArchetypeType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ServiceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; /** * Various tests related to navigation the links between objects. @@ -36,24 +34,52 @@ public class TestLinkedObjects extends AbstractEmptyModelIntegrationTest { public static final File TEST_DIR = new File("src/test/resources/linked"); + private static final File SYSTEM_CONFIGURATION_FILE = new File(TEST_DIR, "system-configuration.xml"); + + private static final TestResource TEMPLATE_USER = new TestResource<>(TEST_DIR, "template-user.xml", "241afdcc-26eb-4417-a4df-a8c1add06e84"); + private static final TestResource TEMPLATE_DEVICE = new TestResource<>(TEST_DIR, "template-device.xml", "e0d5d585-da74-4523-b4f5-78cb54c0dccd"); + + private static final TestResource ARCHETYPE_USER = new TestResource<>(TEST_DIR, "archetype-user.xml", "c46b1bcc-af43-44ee-a107-71f36e952cc5"); private static final TestResource ARCHETYPE_TOKEN = new TestResource<>(TEST_DIR, "archetype-token.xml", "e7bff8d1-cebd-4fbe-b935-64cfc2f22f52"); + private static final TestResource ARCHETYPE_DEVICE = new TestResource<>(TEST_DIR, "archetype-device.xml", "d6d90e2c-ad25-4f7f-a0e1-2f5fac03b402"); + private static final TestResource SERVICE_MEDALLION = new TestResource<>(TEST_DIR, "service-medallion.xml", "8734f795-f6b4-4cc5-843b-6307aaf88f9d"); + private static final TestResource SERVICE_SWORD = new TestResource<>(TEST_DIR, "service-sword.xml", "c64ee819-6dcd-4ad2-a91a-303fb0aed29e"); + private static final TestResource SERVICE_AXE = new TestResource<>(TEST_DIR, "service-axe.xml", "90a3a6a0-07ea-4b2d-b800-ccdf4e7dea78"); + private static final TestResource USER_CAVIN = new TestResource<>(TEST_DIR, "user-cavin.xml", "04753be2-f0f1-4292-8f24-48b0eedfcce3"); private static final TestResource USER_ZUMMI = new TestResource<>(TEST_DIR, "user-zummi.xml", "3224fccd-27fa-45b5-8cf3-497a0d2dd892"); private static final TestResource USER_GRUFFY = new TestResource<>(TEST_DIR, "user-gruffy.xml", "30b59b40-2875-410d-8731-482743eb6de2"); private static final TestResource USER_GRAMMI = new TestResource<>(TEST_DIR, "user-grammi.xml", "041d0c03-c322-4e0d-89ba-a2d49b732674"); + private static final TestResource USER_CUBBY = new TestResource<>(TEST_DIR, "user-cubby.xml", "7b8f2e00-a49e-40ff-a4bd-11b70bac89d3"); @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); + addObject(TEMPLATE_USER, initTask, initResult); + addObject(TEMPLATE_DEVICE, initTask, initResult); + + addObject(ARCHETYPE_USER, initTask, initResult); addObject(ARCHETYPE_TOKEN, initTask, initResult); + addObject(ARCHETYPE_DEVICE, initTask, initResult); + addObject(SERVICE_MEDALLION, initTask, initResult); + addObject(SERVICE_SWORD, initTask, initResult); + addObject(SERVICE_AXE, initTask, initResult); addObject(USER_CAVIN, initTask, initResult); - repoAdd(USER_ZUMMI, initResult); - repoAdd(USER_GRUFFY, initResult); - repoAdd(USER_GRAMMI, initResult); + addObject(USER_ZUMMI, initTask, initResult); + addObject(USER_GRUFFY, initTask, initResult); + addObject(USER_GRAMMI, initTask, initResult); + addObject(USER_CUBBY, initTask, initResult); + +// predefinedTestMethodTracing = PredefinedTestMethodTracing.MODEL_LOGGING; + } + + @Override + protected File getSystemConfigurationFile() { + return SYSTEM_CONFIGURATION_FILE; } @Test @@ -64,6 +90,12 @@ public void test000Sanity() throws Exception { assertService(SERVICE_MEDALLION.oid, "after init") .assertDescription("Not held") .display(); + assertService(SERVICE_SWORD.oid, "after init") + .assertDescription("Not used") + .display(); + assertService(SERVICE_AXE.oid, "after init") + .assertDescription("Not used") + .display(); } /** @@ -77,22 +109,43 @@ public void test100GiveMedallionToCavin() throws Exception { OperationResult result = task.getResult(); when(); - // @formatter:off + assign(USER_CAVIN, SERVICE_MEDALLION, SchemaConstants.ORG_DEFAULT, null, task, result); + + then(); + assertSuccess(result); + + assertUserAfter(USER_CAVIN.oid) + .assertOrganizationalUnits("medallion holders"); + + assertServiceAfter(SERVICE_MEDALLION.oid) + .assertDescription("Held by cavin (Cavin)"); + } + + /** + * Cavin is being entitled. + * We should observe update on the medallion. + */ + @Test + public void test110EntitleCavin() throws Exception { + given(); + Task task = getTestTask(); + OperationResult result = task.getResult(); + + when(); ObjectDelta delta = deltaFor(UserType.class) - .item(UserType.F_ASSIGNMENT) - .add(ObjectTypeUtil.createAssignmentTo(SERVICE_MEDALLION.object, SchemaConstants.ORG_DEFAULT)) + .item(UserType.F_FULL_NAME).replace(PolyString.fromOrig("Sir Cavin")) .asObjectDelta(USER_CAVIN.oid); - // @formatter:on - executeChanges(delta, null, task, result); then(); + assertSuccess(result); + assertUserAfter(USER_CAVIN.oid) + .assertFullName("Sir Cavin") .assertOrganizationalUnits("medallion holders"); - recomputeFocus(ServiceType.class, SERVICE_MEDALLION.oid, task, result); // should occur automatically assertServiceAfter(SERVICE_MEDALLION.oid) - .assertDescription("Held by cavin (Cavin)"); + .assertDescription("Held by cavin (Sir Cavin)"); } /** @@ -100,29 +153,22 @@ public void test100GiveMedallionToCavin() throws Exception { * Phase one is that it is unassigned from Cavin. */ @Test - public void test110PassMedallionToZummiPhaseOne() throws Exception { + public void test120PassMedallionToZummiPhaseOne() throws Exception { given(); Task task = getTestTask(); OperationResult result = task.getResult(); refresh(USER_CAVIN, task, result); - AssignmentType medallionAssignment = extractSingleton(USER_CAVIN.getObjectable().getAssignment()); when(); - // @formatter:off - ObjectDelta delta = deltaFor(UserType.class) - .item(UserType.F_ASSIGNMENT) - .delete(medallionAssignment.clone()) - .asObjectDelta(USER_CAVIN.oid); - // @formatter:on - - executeChanges(delta, null, task, result); + unassignIfSingle(USER_CAVIN, SERVICE_MEDALLION, SchemaConstants.ORG_DEFAULT, null, task, result); then(); + assertSuccess(result); + assertUserAfter(USER_CAVIN.oid) .assertOrganizationalUnits(); - recomputeFocus(ServiceType.class, SERVICE_MEDALLION.oid, task, result); // should occur automatically assertServiceAfter(SERVICE_MEDALLION.oid) .assertDescription("Not held"); } @@ -132,28 +178,162 @@ public void test110PassMedallionToZummiPhaseOne() throws Exception { * Phase two is that it is assigned to Zummi. */ @Test - public void test120PassMedallionToZummiPhaseTwo() throws Exception { + public void test130PassMedallionToZummiPhaseTwo() throws Exception { given(); Task task = getTestTask(); OperationResult result = task.getResult(); when(); - // @formatter:off - ObjectDelta delta = deltaFor(UserType.class) - .item(UserType.F_ASSIGNMENT) - .add(ObjectTypeUtil.createAssignmentTo(SERVICE_MEDALLION.object, SchemaConstants.ORG_DEFAULT)) - .asObjectDelta(USER_ZUMMI.oid); - // @formatter:on - - executeChanges(delta, null, task, result); + assign(USER_ZUMMI, SERVICE_MEDALLION, SchemaConstants.ORG_DEFAULT, null, task, result); then(); + assertSuccess(result); + assertUserAfter(USER_ZUMMI.oid) .assertOrganizationalUnits("medallion holders"); - recomputeFocus(ServiceType.class, SERVICE_MEDALLION.oid, task, result); // should occur automatically assertServiceAfter(SERVICE_MEDALLION.oid) .assertDescription("Held by zummi (Zummi Gummi)"); } + /** + * Medallion is renamed. So its holder should be updated. + */ + @Test + public void test140RenameMedallion() throws Exception { + given(); + Task task = getTestTask(); + OperationResult result = task.getResult(); + + when(); + ObjectDelta delta = deltaFor(ServiceType.class) + .item(ServiceType.F_NAME).replace(PolyString.fromOrig("gummi-medallion")) + .asObjectDelta(SERVICE_MEDALLION.oid); + executeChanges(delta, null, task, result); + + then(); + assertSuccess(result); + + assertUserAfter(USER_ZUMMI.oid) + .assertOrganizationalUnits("gummi-medallion holders"); + } + + /** + * Give axe to Gruffy. + */ + @Test + public void test150GiveAxeToGruffy() throws Exception { + given(); + Task task = getTestTask(); + OperationResult result = task.getResult(); + + when(); + assign(USER_GRUFFY, SERVICE_AXE, SchemaConstants.ORG_DEFAULT, null, task, result); + + then(); + assertSuccess(result); + + assertUserAfter(USER_GRUFFY.oid) + .assertOrganizations("axe users"); + + recomputeFocus(ServiceType.class, SERVICE_AXE.oid, task, result); // todo eliminate + assertServiceAfter(SERVICE_AXE.oid) + .assertDescription("Used by gruffy (Gruffy Gummi)"); + } + + /** + * Weird case: In a user -> token link, the token is deactivated. + * User should be no longer marked as token holder. + */ + @Test + public void test200DeactivateMedallion() throws Exception { + given(); + Task task = getTestTask(); + OperationResult result = task.getResult(); + + when(); + ObjectDelta delta = deltaFor(ServiceType.class) + .item(ServiceType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS).replace(ActivationStatusType.DISABLED) + .asObjectDelta(SERVICE_MEDALLION.oid); + executeChanges(delta, null, task, result); + + then(); + assertSuccess(result); + recomputeUser(USER_ZUMMI.oid); // temporary + + assertUserAfter(USER_ZUMMI.oid) + .assertOrganizationalUnits(); + } + /** + * Assign disabled device. + */ + @Test + public void test210AssignDisabledSword() throws Exception { + given(); + Task task = getTestTask(); + OperationResult result = task.getResult(); + + when(); + assign(USER_CUBBY, SERVICE_SWORD, SchemaConstants.ORG_DEFAULT, null, task, result); + + then(); + assertSuccess(result); + + assertUserAfter(USER_CUBBY.oid) + .assertOrganizations("sword users"); + + assertServiceAfter(SERVICE_SWORD.oid) + .assertDescription("Used by cubby (Cubby Gummi)"); + } + + /** + * Sword is renamed. So its holder should be updated, even if the sword is disabled. + */ + @Test + public void test220RenameDisabledSword() throws Exception { + given(); + Task task = getTestTask(); + OperationResult result = task.getResult(); + + when(); + ObjectDelta delta = deltaFor(ServiceType.class) + .item(ServiceType.F_NAME).replace(PolyString.fromOrig("wooden-sword")) + .asObjectDelta(SERVICE_SWORD.oid); + executeChanges(delta, null, task, result); + + then(); + assertSuccess(result); + + assertUserAfter(USER_CUBBY.oid) + .assertOrganizations("wooden-sword users"); + + assertServiceAfter(SERVICE_SWORD.oid) + .assertDescription("Used by cubby (Cubby Gummi)"); + } + + /** + * Sword user is renamed. So it should be updated, even if it is disabled. + */ + @Test + public void test230RenameCubbyUsingDisabledSword() throws Exception { + given(); + Task task = getTestTask(); + OperationResult result = task.getResult(); + + when(); + ObjectDelta delta = deltaFor(UserType.class) + .item(UserType.F_FULL_NAME).replace(PolyString.fromOrig("Little Cubby Gummi")) + .asObjectDelta(USER_CUBBY.oid); + executeChanges(delta, null, task, result); + + then(); + assertSuccess(result); + + assertUserAfter(USER_CUBBY.oid) + .assertFullName("Little Cubby Gummi") + .assertOrganizations("wooden-sword users"); + + assertServiceAfter(SERVICE_SWORD.oid) + .assertDescription("Used by cubby (Little Cubby Gummi)"); + } } diff --git a/model/model-intest/src/test/resources/linked/archetype-device.xml b/model/model-intest/src/test/resources/linked/archetype-device.xml new file mode 100644 index 00000000000..b18a2336d3e --- /dev/null +++ b/model/model-intest/src/test/resources/linked/archetype-device.xml @@ -0,0 +1,112 @@ + + + + device + A device has some special characteristics: + + 1. It is held by at most single user at given time. + + 2. It gives its holder some properties, e.g. the `organizational` obtains value of + `D users` for any device D held. + + 3. On the other hand, it knows who uses it by storing the user's `name` and `fullName` in + its `description` property. + + Note that points 2 and 3 are implemented by object templates (point 2 by template for users + and point 3 by template for services). This means that they work for both active and + inactive devices. For a different approach please see the "token" archetype. + + + + + + + + + + + + + + Recomputes a user when device name is changed. (Note that user is recomputed + automatically when device is assigned or unassigned.) + + + + name + + + + + + + + + + + + + + 1 + + + + + + + + Recomputes a device when it is assigned to (or unassigned from) the user or when the assignment + is modified. But what we would need, actually, is triggering device recompute when the effective membership + is changed. + + + + + + + + + + + + + + + + 1 + + + + + Recomputes a device when user's name or fullName changes. + + + + name + + + fullName + + + + + + + + + + + + + + + 2 + + diff --git a/model/model-intest/src/test/resources/linked/archetype-token.xml b/model/model-intest/src/test/resources/linked/archetype-token.xml index 466910f7ce6..35be73ebab4 100644 --- a/model/model-intest/src/test/resources/linked/archetype-token.xml +++ b/model/model-intest/src/test/resources/linked/archetype-token.xml @@ -6,9 +6,10 @@ --> token - A token has some special characteristics: + A magic token has some special characteristics: 1. It is held by at most single user at given time. @@ -17,10 +18,24 @@ 3. On the other hand, it knows who holds it by storing the holder's `name` and `fullName` in its `description` property. + + Note that points 2 and 3 are implemented by induced mappings for this particular archetype. + This means that they work only for active tokens. This is fully in accord with the magical + character of the token: if it's inactive (e.g. disabled) it loses all its powers. + + (But what about triggering the changes? They obviously will stop for disabled tokens.) + + For a different approach please see the "device" archetype. + + + + + + From token to user: putting 'T holders' into organizationalUnit. strong + + + organizationalUnit + + all + + + + + + diff --git a/model/model-intest/src/test/resources/linked/service-axe.xml b/model/model-intest/src/test/resources/linked/service-axe.xml new file mode 100644 index 00000000000..c3322402608 --- /dev/null +++ b/model/model-intest/src/test/resources/linked/service-axe.xml @@ -0,0 +1,14 @@ + + + + axe + + + + diff --git a/model/model-intest/src/test/resources/linked/service-sword.xml b/model/model-intest/src/test/resources/linked/service-sword.xml new file mode 100644 index 00000000000..d77ec188486 --- /dev/null +++ b/model/model-intest/src/test/resources/linked/service-sword.xml @@ -0,0 +1,17 @@ + + + + sword + + + + + disabled + + diff --git a/model/model-intest/src/test/resources/linked/system-configuration.xml b/model/model-intest/src/test/resources/linked/system-configuration.xml new file mode 100644 index 00000000000..b98b2d3515c --- /dev/null +++ b/model/model-intest/src/test/resources/linked/system-configuration.xml @@ -0,0 +1,25 @@ + + + + + + + SystemConfiguration + + UserType + + + diff --git a/model/model-intest/src/test/resources/linked/template-device.xml b/model/model-intest/src/test/resources/linked/template-device.xml new file mode 100644 index 00000000000..e2112c4f92b --- /dev/null +++ b/model/model-intest/src/test/resources/linked/template-device.xml @@ -0,0 +1,29 @@ + + + + template-device + + description + + From user to device: putting 'Used by ...' into device description. + strong + + + + + description + + + + diff --git a/model/model-intest/src/test/resources/linked/template-user.xml b/model/model-intest/src/test/resources/linked/template-user.xml new file mode 100644 index 00000000000..4e2c5936a14 --- /dev/null +++ b/model/model-intest/src/test/resources/linked/template-user.xml @@ -0,0 +1,37 @@ + + + + template-user + + organization + + afterAssignments + strong + + + + + + all + + + + + diff --git a/model/model-intest/src/test/resources/linked/user-cavin.xml b/model/model-intest/src/test/resources/linked/user-cavin.xml index 075965fba79..587dbbea247 100644 --- a/model/model-intest/src/test/resources/linked/user-cavin.xml +++ b/model/model-intest/src/test/resources/linked/user-cavin.xml @@ -8,5 +8,8 @@ cavin + + + Cavin diff --git a/model/model-intest/src/test/resources/linked/user-cubby.xml b/model/model-intest/src/test/resources/linked/user-cubby.xml new file mode 100644 index 00000000000..b1b729e7bfe --- /dev/null +++ b/model/model-intest/src/test/resources/linked/user-cubby.xml @@ -0,0 +1,15 @@ + + + + cubby + + + + Cubby Gummi + diff --git a/model/model-intest/src/test/resources/linked/user-grammi.xml b/model/model-intest/src/test/resources/linked/user-grammi.xml index c790aae3f1a..114acf3da05 100644 --- a/model/model-intest/src/test/resources/linked/user-grammi.xml +++ b/model/model-intest/src/test/resources/linked/user-grammi.xml @@ -8,5 +8,8 @@ grammi + + + Grammi Gummi diff --git a/model/model-intest/src/test/resources/linked/user-gruffy.xml b/model/model-intest/src/test/resources/linked/user-gruffy.xml index 411594f57f3..8479e6ad626 100644 --- a/model/model-intest/src/test/resources/linked/user-gruffy.xml +++ b/model/model-intest/src/test/resources/linked/user-gruffy.xml @@ -8,5 +8,8 @@ gruffy + + + Gruffy Gummi diff --git a/model/model-intest/src/test/resources/linked/user-zummi.xml b/model/model-intest/src/test/resources/linked/user-zummi.xml index 92168c30d11..8d1957e5fa7 100644 --- a/model/model-intest/src/test/resources/linked/user-zummi.xml +++ b/model/model-intest/src/test/resources/linked/user-zummi.xml @@ -8,5 +8,8 @@ zummi + + + Zummi Gummi diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index 5b36fd5763e..8a7c56c3de8 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -78,8 +78,8 @@ - - + + @@ -141,6 +141,7 @@ + diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/NotificationHook.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/NotificationHook.java index 0e84c821a06..1f71226ebfd 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/NotificationHook.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/NotificationHook.java @@ -14,7 +14,7 @@ import com.evolveum.midpoint.model.api.hooks.ChangeHook; import com.evolveum.midpoint.model.api.hooks.HookOperationMode; import com.evolveum.midpoint.model.api.hooks.HookRegistry; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.notifications.api.NotificationManager; diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/TestResource.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/TestResource.java index 3ffc1c64b52..363b05941dd 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/TestResource.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/TestResource.java @@ -39,4 +39,9 @@ public T getObjectable() { public Class getObjectClass() { return object.getCompileTimeClass(); } + + @Override + public String toString() { + return object != null ? object.toString() : file + " (" + oid + ")"; + } } diff --git a/testing/story/src/test/resources/logback-test.xml b/testing/story/src/test/resources/logback-test.xml index f87bfe1b0ed..287501b5c4e 100644 --- a/testing/story/src/test/resources/logback-test.xml +++ b/testing/story/src/test/resources/logback-test.xml @@ -43,8 +43,8 @@ - - + +