diff --git a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/PolicyEngineImpl.java b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/PolicyEngineImpl.java index ef84f6fa4de..43d16643517 100644 --- a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/PolicyEngineImpl.java +++ b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/PolicyEngineImpl.java @@ -15,12 +15,13 @@ package org.eclipse.edc.policy.engine; import org.eclipse.edc.policy.engine.plan.PolicyEvaluationPlanner; -import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction; -import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction; +import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction; +import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction; import org.eclipse.edc.policy.engine.spi.PolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.engine.spi.PolicyRuleFunction; import org.eclipse.edc.policy.engine.spi.PolicyValidatorFunction; -import org.eclipse.edc.policy.engine.spi.RuleFunction; +import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule; import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; import org.eclipse.edc.policy.engine.validation.PolicyValidator; import org.eclipse.edc.policy.engine.validation.RuleValidator; @@ -31,18 +32,17 @@ import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.Prohibition; import org.eclipse.edc.policy.model.Rule; +import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.result.Result; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; import java.util.function.BiFunction; +import java.util.function.Predicate; -import static java.util.stream.Collectors.toList; import static org.eclipse.edc.spi.result.Result.failure; import static org.eclipse.edc.spi.result.Result.success; @@ -53,13 +53,15 @@ public class PolicyEngineImpl implements PolicyEngine { public static final String ALL_SCOPES_DELIMITED = ALL_SCOPES + DELIMITER; - private final Map>> constraintFunctions = new TreeMap<>(); + private final Map> scopes = new HashMap<>(); - private final List> dynamicConstraintFunctions = new ArrayList<>(); + private final List> constraintFunctions = new ArrayList<>(); + private final List> dynamicConstraintFunctions = new ArrayList<>(); + private final List> ruleFunctions = new ArrayList<>(); + + private final List> preValidators = new ArrayList<>(); + private final List> postValidators = new ArrayList<>(); - private final Map>> ruleFunctions = new TreeMap<>(); - private final Map> preValidators = new HashMap<>(); - private final Map> postValidators = new HashMap<>(); private final ScopeFilter scopeFilter; private final RuleValidator ruleValidator; @@ -78,66 +80,88 @@ public Policy filter(Policy policy, String scope) { } @Override - public Result evaluate(String scope, Policy policy, PolicyContext context) { - var delimitedScope = scope + "."; + public Result evaluate(Policy policy, C context) { + Predicate> isScoped = entry -> entry.contextType().isAssignableFrom(context.getClass()); - var scopedPreValidators = preValidators.entrySet().stream().filter(entry -> scopeFilter(entry.getKey(), delimitedScope)).flatMap(l -> l.getValue().stream()).toList(); - for (var validator : scopedPreValidators) { - if (!validator.apply(policy, context)) { - return failValidator("Pre-validator", validator, context); - } + var preValidationFailure = preValidators.stream() + .filter(isScoped) + .map(it -> (PolicyValidatorRule) it.rule()) + .filter(it -> !it.apply(policy, context)) + .findFirst(); + + if (preValidationFailure.isPresent()) { + return failValidator("Pre-validator", preValidationFailure.get(), context); } var evalBuilder = PolicyEvaluator.Builder.newInstance(); - ruleFunctions.entrySet().stream().filter(entry -> scopeFilter(entry.getKey(), delimitedScope)).flatMap(entry -> entry.getValue().stream()).forEach(entry -> { - if (Duty.class.isAssignableFrom(entry.type)) { - evalBuilder.dutyRuleFunction((rule) -> entry.function.evaluate(rule, context)); - } else if (Permission.class.isAssignableFrom(entry.type)) { - evalBuilder.permissionRuleFunction((rule) -> entry.function.evaluate(rule, context)); - } else if (Prohibition.class.isAssignableFrom(entry.type)) { - evalBuilder.prohibitionRuleFunction((rule) -> entry.function.evaluate(rule, context)); - } - }); - - constraintFunctions.entrySet().stream().filter(entry -> scopeFilter(entry.getKey(), delimitedScope)).flatMap(entry -> entry.getValue().stream()).forEach(entry -> { - if (Duty.class.isAssignableFrom(entry.type)) { - evalBuilder.dutyFunction(entry.key, (operator, value, duty) -> entry.function.evaluate(operator, value, duty, context)); - } else if (Permission.class.isAssignableFrom(entry.type)) { - evalBuilder.permissionFunction(entry.key, (operator, value, permission) -> entry.function.evaluate(operator, value, permission, context)); - } else if (Prohibition.class.isAssignableFrom(entry.type)) { - evalBuilder.prohibitionFunction(entry.key, (operator, value, prohibition) -> entry.function.evaluate(operator, value, prohibition, context)); - } - }); - - dynamicConstraintFunctions.stream().filter(entry -> scopeFilter(entry.scope, delimitedScope)).forEach(entry -> { - if (Duty.class.isAssignableFrom(entry.type)) { - evalBuilder.dynamicDutyFunction(entry.function::canHandle, (key, operator, value, duty) -> entry.function.evaluate(key, operator, value, duty, context)); - } else if (Permission.class.isAssignableFrom(entry.type)) { - evalBuilder.dynamicPermissionFunction(entry.function::canHandle, (key, operator, value, permission) -> entry.function.evaluate(key, operator, value, permission, context)); - } else if (Prohibition.class.isAssignableFrom(entry.type)) { - evalBuilder.dynamicProhibitionFunction(entry.function::canHandle, (key, operator, value, prohibition) -> entry.function.evaluate(key, operator, value, prohibition, context)); - } - }); + + ruleFunctions.stream() + .filter(isScoped) + .forEach(entry -> { + if (Duty.class.isAssignableFrom(entry.type)) { + evalBuilder.dutyRuleFunction((rule) -> + ((PolicyRuleFunction) entry.function).evaluate(rule, context)); + } else if (Permission.class.isAssignableFrom(entry.type)) { + evalBuilder.permissionRuleFunction((rule) -> + ((PolicyRuleFunction) entry.function).evaluate(rule, context)); + } else if (Prohibition.class.isAssignableFrom(entry.type)) { + evalBuilder.prohibitionRuleFunction((rule) -> + ((PolicyRuleFunction) entry.function).evaluate(rule, context)); + } + }); + + constraintFunctions.stream() + .filter(isScoped) + .forEach(entry -> { + if (Duty.class.isAssignableFrom(entry.type)) { + evalBuilder.dutyFunction(entry.key, (operator, value, duty) -> + ((AtomicConstraintRuleFunction) entry.function).evaluate(operator, value, duty, context)); + } else if (Permission.class.isAssignableFrom(entry.type)) { + evalBuilder.permissionFunction(entry.key, (operator, value, permission) -> + ((AtomicConstraintRuleFunction) entry.function).evaluate(operator, value, permission, context)); + } else if (Prohibition.class.isAssignableFrom(entry.type)) { + evalBuilder.prohibitionFunction(entry.key, (operator, value, prohibition) -> + ((AtomicConstraintRuleFunction) entry.function).evaluate(operator, value, prohibition, context)); + } + }); + + dynamicConstraintFunctions.stream() + .filter(isScoped) + .forEach(entry -> { + if (Duty.class.isAssignableFrom(entry.type)) { + evalBuilder.dynamicDutyFunction(entry.function::canHandle, (key, operator, value, duty) -> + ((DynamicAtomicConstraintRuleFunction) entry.function).evaluate(key, operator, value, duty, context)); + } else if (Permission.class.isAssignableFrom(entry.type)) { + evalBuilder.dynamicPermissionFunction(entry.function::canHandle, (key, operator, value, permission) -> + ((DynamicAtomicConstraintRuleFunction) entry.function).evaluate(key, operator, value, permission, context)); + } else if (Prohibition.class.isAssignableFrom(entry.type)) { + evalBuilder.dynamicProhibitionFunction(entry.function::canHandle, (key, operator, value, prohibition) -> + ((DynamicAtomicConstraintRuleFunction) entry.function).evaluate(key, operator, value, prohibition, context)); + } + }); var evaluator = evalBuilder.build(); - var filteredPolicy = scopeFilter.applyScope(policy, scope); + var filteredPolicy = scopeFilter.applyScope(policy, context.scope()); var result = evaluator.evaluate(filteredPolicy); if (result.valid()) { - var scopedPostValidators = postValidators.entrySet().stream().filter(entry -> scopeFilter(entry.getKey(), delimitedScope)).flatMap(l -> l.getValue().stream()).toList(); - for (var validator : scopedPostValidators) { - if (!validator.apply(policy, context)) { - return failValidator("Post-validator", validator, context); - } + var postValidationFailure = postValidators.stream() + .filter(isScoped) + .map(it -> (PolicyValidatorRule) it.rule()) + .filter(it -> !it.apply(policy, context)) + .findFirst(); + + if (postValidationFailure.isPresent()) { + return failValidator("Post-validator", postValidationFailure.get(), context); } return success(); } else { - return failure(result.getProblems().stream().map(RuleProblem::getDescription).collect(toList())); + return failure("Policy in scope %s not fulfilled: %s".formatted(context.scope(), result.getProblems().stream().map(RuleProblem::getDescription).toList())); } } @@ -146,10 +170,7 @@ public Result validate(Policy policy) { var validatorBuilder = PolicyValidator.Builder.newInstance() .ruleValidator(ruleValidator); - constraintFunctions.values().stream() - .flatMap(Collection::stream) - .forEach(entry -> validatorBuilder.evaluationFunction(entry.key, entry.type, entry.function)); - + constraintFunctions.forEach(entry -> validatorBuilder.evaluationFunction(entry.key, entry.type, entry.function)); dynamicConstraintFunctions.forEach(entry -> validatorBuilder.dynamicEvaluationFunction(entry.type, entry.function)); return validatorBuilder.build().validate(policy); @@ -158,100 +179,130 @@ public Result validate(Policy policy) { @Override public PolicyEvaluationPlan createEvaluationPlan(String scope, Policy policy) { var planner = PolicyEvaluationPlanner.Builder.newInstance(scope).ruleValidator(ruleValidator); + Predicate> isScoped = entry -> entry.contextType().isAssignableFrom(contextType(scope)); - preValidators.forEach(planner::preValidators); - postValidators.forEach(planner::postValidators); + preValidators.stream().filter(isScoped).map(ValidatorRuleEntry::rule) + .forEach(planner::preValidator); + postValidators.stream().filter(isScoped).map(ValidatorRuleEntry::rule) + .forEach(planner::postValidator); - constraintFunctions.forEach((functionScope, entry) -> entry.forEach(constraintEntry -> { - planner.evaluationFunction(functionScope, constraintEntry.key, constraintEntry.type, constraintEntry.function); - })); + constraintFunctions.stream().filter(isScoped) + .forEach(entry -> planner.evaluationFunction(entry.key, entry.type, entry.function)); + dynamicConstraintFunctions.stream().filter(isScoped) + .forEach(entry -> planner.evaluationFunction(entry.type, entry.function)); + ruleFunctions.stream().filter(isScoped) + .forEach(entry -> planner.evaluationFunction(entry.type, entry.function)); - dynamicConstraintFunctions.forEach(dynFunctions -> - planner.evaluationFunction(dynFunctions.scope, dynFunctions.type, dynFunctions.function) - ); + return policy.accept(planner.build()); + } - ruleFunctions.forEach((functionScope, entry) -> entry.forEach(functionEntry -> { - planner.evaluationFunction(functionScope, functionEntry.type, functionEntry.function); - })); + @Override + public void registerScope(String scope, Class contextType) { + scopes.put(scope, contextType); + } - return policy.accept(planner.build()); + @Override + public void registerFunction(Class contextType, Class type, String key, AtomicConstraintRuleFunction function) { + constraintFunctions.add(new ConstraintFunctionEntry(contextType, type, key, function)); } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) - public void registerFunction(String scope, Class type, String key, AtomicConstraintFunction function) { - constraintFunctions.computeIfAbsent(scope + ".", k -> new ArrayList<>()).add(new ConstraintFunctionEntry(type, key, function)); + public void registerFunction(String scope, Class type, String key, AtomicConstraintRuleFunction function) { + constraintFunctions.add(new ConstraintFunctionEntry(contextType(scope), type, key, function)); + } + + @Override + public void registerFunction(Class contextType, Class type, DynamicAtomicConstraintRuleFunction function) { + dynamicConstraintFunctions.add(new DynamicConstraintFunctionEntry(contextType, type, function)); } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) - public void registerFunction(String scope, Class type, DynamicAtomicConstraintFunction function) { - dynamicConstraintFunctions.add(new DynamicConstraintFunctionEntry(type, scope + DELIMITER, function)); + public void registerFunction(String scope, Class type, DynamicAtomicConstraintRuleFunction function) { + dynamicConstraintFunctions.add(new DynamicConstraintFunctionEntry(contextType(scope), type, function)); + } + + @Override + public void registerFunction(Class contextType, Class type, PolicyRuleFunction function) { + ruleFunctions.add(new RuleFunctionEntry(contextType, type, function)); } @Override @SuppressWarnings({ "unchecked", "rawtypes" }) - public void registerFunction(String scope, Class type, RuleFunction function) { - ruleFunctions.computeIfAbsent(scope + ".", k -> new ArrayList<>()).add(new RuleFunctionEntry(type, function)); + public void registerFunction(String scope, Class type, PolicyRuleFunction function) { + ruleFunctions.add(new RuleFunctionEntry(contextType(scope), type, function)); + } + + @Override + public void registerPreValidator(Class contextType, PolicyValidatorRule validator) { + preValidators.add(new ValidatorRuleEntry(contextType, validator)); + } + + @Override + public void registerPostValidator(Class contextType, PolicyValidatorRule validator) { + postValidators.add(new ValidatorRuleEntry(contextType, validator)); } @Override public void registerPreValidator(String scope, BiFunction validator) { - registerPreValidator(scope, new PolicyValidatorFunctionWrapper(validator)); + registerPreValidator(PolicyContext.class, new PolicyValidatorFunctionWrapper(validator)); } @Override public void registerPreValidator(String scope, PolicyValidatorFunction validator) { - preValidators.computeIfAbsent(scope + DELIMITER, k -> new ArrayList<>()).add(validator); + registerPreValidator(PolicyContext.class, validator); } @Override public void registerPostValidator(String scope, BiFunction validator) { - registerPostValidator(scope, new PolicyValidatorFunctionWrapper(validator)); + registerPostValidator(PolicyContext.class, new PolicyValidatorFunctionWrapper(validator)); } @Override public void registerPostValidator(String scope, PolicyValidatorFunction validator) { - postValidators.computeIfAbsent(scope + DELIMITER, k -> new ArrayList<>()).add(validator); + registerPostValidator(PolicyContext.class, validator); } @NotNull - private Result failValidator(String type, PolicyValidatorFunction validator, PolicyContext context) { - return failure(context.hasProblems() ? context.getProblems() : List.of(type + " failed: " + validator.getClass().getName())); - } - - private static class ConstraintFunctionEntry { - Class type; - String key; - AtomicConstraintFunction function; - - ConstraintFunctionEntry(Class type, String key, AtomicConstraintFunction function) { - this.type = type; - this.key = key; - this.function = function; - } + private Result failValidator(String type, PolicyValidatorRule validator, PolicyContext context) { + return failure(context.hasProblems() ? context.getProblems() : List.of(type + " failed: " + validator.name())); } - private static class DynamicConstraintFunctionEntry { - Class type; - String scope; - DynamicAtomicConstraintFunction function; - - DynamicConstraintFunctionEntry(Class type, String scope, DynamicAtomicConstraintFunction function) { - this.type = type; - this.scope = scope; - this.function = function; - } + private record ConstraintFunctionEntry( + Class contextType, + Class type, + String key, + AtomicConstraintRuleFunction function + ) implements FunctionEntry { } + + private record DynamicConstraintFunctionEntry( + Class contextType, + Class type, + DynamicAtomicConstraintRuleFunction function + ) implements FunctionEntry { } + + private record RuleFunctionEntry( + Class contextType, + Class type, + PolicyRuleFunction function + ) implements FunctionEntry { } + + private record ValidatorRuleEntry( + Class contextType, + PolicyValidatorRule rule + ) implements FunctionEntry { } + + private interface FunctionEntry { + Class contextType(); } - private static class RuleFunctionEntry { - Class type; - RuleFunction function; - - RuleFunctionEntry(Class type, RuleFunction function) { - this.type = type; - this.function = function; + private @NotNull Class contextType(String scope) { + var contextType = scopes.get(scope); + if (contextType == null) { + throw new EdcException("Scope %s not registered".formatted(scope)); } + return contextType; } private record PolicyValidatorFunctionWrapper( diff --git a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/plan/PolicyEvaluationPlanner.java b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/plan/PolicyEvaluationPlanner.java index c0f9c3626c6..b47dc82e6c3 100644 --- a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/plan/PolicyEvaluationPlanner.java +++ b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/plan/PolicyEvaluationPlanner.java @@ -14,11 +14,11 @@ package org.eclipse.edc.policy.engine.plan; -import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction; -import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction; +import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction; +import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction; import org.eclipse.edc.policy.engine.spi.PolicyContext; -import org.eclipse.edc.policy.engine.spi.PolicyValidatorFunction; -import org.eclipse.edc.policy.engine.spi.RuleFunction; +import org.eclipse.edc.policy.engine.spi.PolicyRuleFunction; +import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule; import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; import org.eclipse.edc.policy.engine.spi.plan.step.AndConstraintStep; import org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep; @@ -37,14 +37,12 @@ import org.eclipse.edc.policy.model.Constraint; import org.eclipse.edc.policy.model.Duty; import org.eclipse.edc.policy.model.MultiplicityConstraint; -import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.policy.model.OrConstraint; import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.Prohibition; import org.eclipse.edc.policy.model.Rule; import org.eclipse.edc.policy.model.XoneConstraint; -import org.eclipse.edc.spi.result.Result; import java.util.ArrayList; import java.util.List; @@ -54,17 +52,16 @@ import java.util.TreeMap; import java.util.stream.Collectors; -import static org.eclipse.edc.policy.engine.PolicyEngineImpl.scopeFilter; import static org.eclipse.edc.policy.engine.spi.PolicyEngine.DELIMITER; public class PolicyEvaluationPlanner implements Policy.Visitor, Rule.Visitor>, Constraint.Visitor { private final Stack ruleContext = new Stack<>(); - private final List preValidators = new ArrayList<>(); - private final List postValidators = new ArrayList<>(); - private final Map>> constraintFunctions = new TreeMap<>(); - private final List> dynamicConstraintFunctions = new ArrayList<>(); - private final List> ruleFunctions = new ArrayList<>(); + private final List> preValidators = new ArrayList<>(); + private final List> postValidators = new ArrayList<>(); + private final Map>> constraintFunctions = new TreeMap<>(); + private final List> dynamicConstraintFunctions = new ArrayList<>(); + private final List> ruleFunctions = new ArrayList<>(); private final String delimitedScope; private final String scope; @@ -98,7 +95,7 @@ public XoneConstraintStep visitXoneConstraint(XoneConstraint constraint) { public AtomicConstraintStep visitAtomicConstraint(AtomicConstraint constraint) { var currentRule = currentRule(); var leftValue = constraint.getLeftExpression().accept(s -> s.getValue().toString()); - var function = getFunctions(leftValue, currentRule.getClass()); + var functionName = getFunctionName(leftValue, currentRule.getClass()); var filteringReasons = new ArrayList(); @@ -106,11 +103,11 @@ public AtomicConstraintStep visitAtomicConstraint(AtomicConstraint constraint) { filteringReasons.add("leftOperand '%s' is not bound to scope '%s'".formatted(leftValue, scope)); } - if (function == null) { + if (functionName == null) { filteringReasons.add("leftOperand '%s' is not bound to any function within scope '%s'".formatted(leftValue, scope)); } - return new AtomicConstraintStep(constraint, filteringReasons, currentRule, function); + return new AtomicConstraintStep(constraint, filteringReasons, currentRule, functionName); } @Override @@ -161,17 +158,17 @@ public DutyStep visitDuty(Duty duty) { return prohibitionStepBuilder.build(); } - private AtomicConstraintFunction getFunctions(String key, Class ruleKind) { + private String getFunctionName(String key, Class ruleKind) { return constraintFunctions.getOrDefault(key, new ArrayList<>()) .stream() .filter(entry -> ruleKind.isAssignableFrom(entry.type())) - .map(entry -> entry.function) + .map(entry -> entry.function.name()) .findFirst() .or(() -> dynamicConstraintFunctions .stream() .filter(f -> ruleKind.isAssignableFrom(f.type)) .filter(f -> f.function.canHandle(key)) - .map(entry -> wrapDynamicFunction(key, entry.function)) + .map(f -> f.function.name()) .findFirst()) .orElse(null); } @@ -215,43 +212,19 @@ private List validateMultiplicityConstraint(MultiplicityConstrai .collect(Collectors.toList()); } - private AtomicConstraintFunction wrapDynamicFunction(String key, DynamicAtomicConstraintFunction function) { - return new AtomicConstraintFunctionWrapper<>(key, function); - } - - private record ConstraintFunctionEntry( + private record ConstraintFunctionEntry( Class type, - AtomicConstraintFunction function) { + AtomicConstraintRuleFunction function) { } - private record DynamicAtomicConstraintFunctionEntry( + private record DynamicAtomicConstraintFunctionEntry( Class type, - DynamicAtomicConstraintFunction function) { + DynamicAtomicConstraintRuleFunction function) { } - private record RuleFunctionFunctionEntry( + private record RuleFunctionFunctionEntry( Class type, - RuleFunction function) { - } - - private record AtomicConstraintFunctionWrapper( - String leftOperand, - DynamicAtomicConstraintFunction inner) implements AtomicConstraintFunction { - - @Override - public boolean evaluate(Operator operator, Object rightValue, R rule, PolicyContext context) { - return inner.evaluate(leftOperand, operator, rightValue, rule, context); - } - - @Override - public Result validate(Operator operator, Object rightValue, R rule) { - return inner.validate(leftOperand, operator, rightValue, rule); - } - - @Override - public String name() { - return inner.name(); - } + PolicyRuleFunction function) { } public static class Builder { @@ -270,55 +243,33 @@ public Builder ruleValidator(RuleValidator ruleValidator) { return this; } - public Builder preValidator(String scope, PolicyValidatorFunction validator) { - - if (scopeFilter(scope, planner.delimitedScope)) { - planner.preValidators.add(validator); - } + public Builder preValidator(PolicyValidatorRule validator) { + planner.preValidators.add(validator); return this; } - public Builder preValidators(String scope, List validators) { - validators.forEach(validator -> preValidator(scope, validator)); - return this; - } - - public Builder postValidator(String scope, PolicyValidatorFunction validator) { - if (scopeFilter(scope, planner.delimitedScope)) { - planner.postValidators.add(validator); - } - return this; - } - - public Builder postValidators(String scope, List validators) { - validators.forEach(validator -> postValidator(scope, validator)); + public Builder postValidator(PolicyValidatorRule validator) { + planner.postValidators.add(validator); return this; } @SuppressWarnings({ "unchecked", "rawtypes" }) - public Builder evaluationFunction(String scope, String key, Class ruleKind, AtomicConstraintFunction function) { - - if (scopeFilter(scope, planner.delimitedScope)) { - planner.constraintFunctions.computeIfAbsent(key, k -> new ArrayList<>()) - .add(new ConstraintFunctionEntry(ruleKind, function)); - } + public Builder evaluationFunction(String key, Class ruleKind, AtomicConstraintRuleFunction function) { + planner.constraintFunctions.computeIfAbsent(key, k -> new ArrayList<>()) + .add(new ConstraintFunctionEntry(ruleKind, function)); return this; } @SuppressWarnings({ "unchecked", "rawtypes" }) - public Builder evaluationFunction(String scope, Class ruleKind, DynamicAtomicConstraintFunction function) { - if (scopeFilter(scope, planner.delimitedScope)) { - planner.dynamicConstraintFunctions.add(new DynamicAtomicConstraintFunctionEntry(ruleKind, function)); - } + public Builder evaluationFunction(Class ruleKind, DynamicAtomicConstraintRuleFunction function) { + planner.dynamicConstraintFunctions.add(new DynamicAtomicConstraintFunctionEntry(ruleKind, function)); return this; } @SuppressWarnings({ "unchecked", "rawtypes" }) - public Builder evaluationFunction(String scope, Class ruleKind, RuleFunction function) { - if (scopeFilter(scope, planner.delimitedScope)) { - planner.ruleFunctions.add(new RuleFunctionFunctionEntry(ruleKind, function)); - } + public Builder evaluationFunction(Class ruleKind, PolicyRuleFunction function) { + planner.ruleFunctions.add(new RuleFunctionFunctionEntry(ruleKind, function)); return this; } diff --git a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/validation/PolicyValidator.java b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/validation/PolicyValidator.java index ef4eb6fb142..669bec470da 100644 --- a/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/validation/PolicyValidator.java +++ b/core/common/lib/policy-engine-lib/src/main/java/org/eclipse/edc/policy/engine/validation/PolicyValidator.java @@ -15,7 +15,9 @@ package org.eclipse.edc.policy.engine.validation; import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction; +import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction; import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction; +import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction; import org.eclipse.edc.policy.engine.spi.PolicyContext; import org.eclipse.edc.policy.model.AndConstraint; import org.eclipse.edc.policy.model.AtomicConstraint; @@ -39,7 +41,6 @@ import java.util.Objects; import java.util.Stack; import java.util.TreeMap; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -59,8 +60,8 @@ public class PolicyValidator implements Policy.Visitor>, Rule.Visit private final Stack ruleContext = new Stack<>(); - private final Map>> constraintFunctions = new TreeMap<>(); - private final List> dynamicConstraintFunctions = new ArrayList<>(); + private final Map>> constraintFunctions = new TreeMap<>(); + private final List> dynamicConstraintFunctions = new ArrayList<>(); private RuleValidator ruleValidator; public Result validate(Policy policy) { @@ -133,12 +134,12 @@ private Result validateLeftExpression(Rule rule, String leftOperand) { } private Result validateConstraint(String leftOperand, Operator operator, Object rightOperand, Rule rule) { - var functions = getFunctions(leftOperand, rule.getClass()); + var functions = getValidations(leftOperand, rule.getClass()); if (functions.isEmpty()) { return Result.failure("left operand '%s' is not bound to any functions: Rule { %s }".formatted(leftOperand, rule)); } else { return functions.stream() - .map(f -> f.validate(operator, rightOperand, rule)) + .map(f -> f.validate(leftOperand, operator, rightOperand, rule)) .reduce(Result.success(), Result::merge); } } @@ -164,29 +165,30 @@ private Result validateAction(Rule rule) { } } - private List> getFunctions(String key, Class ruleKind) { + private List getValidations(String key, Class ruleKind) { // first look-up for an exact match var functions = constraintFunctions.getOrDefault(key, new ArrayList<>()) .stream() .filter(entry -> ruleKind.isAssignableFrom(entry.type())) - .map(entry -> entry.function) - .collect(Collectors.toList()); + .map(entry -> (PolicyValidation) (leftOperand, operator, rightOperand, rule) -> + entry.function.validate(operator, rightOperand, rule)) + .toList(); // if not found inspect the dynamic functions if (functions.isEmpty()) { - functions = dynamicConstraintFunctions + return dynamicConstraintFunctions .stream() - .filter(f -> ruleKind.isAssignableFrom(f.type)) - .filter(f -> f.function.canHandle(key)) - .map(entry -> wrapDynamicFunction(key, entry.function)) + .filter(entry -> ruleKind.isAssignableFrom(entry.type)) + .filter(entry -> entry.function.canHandle(key)) + .map(entry -> (PolicyValidation) entry.function::validate) .toList(); } return functions; } - private AtomicConstraintFunction wrapDynamicFunction(String key, DynamicAtomicConstraintFunction function) { - return new AtomicConstraintFunctionWrapper<>(key, function); + private interface PolicyValidation { + Result validate(String leftOperand, Operator operator, Object rightOperand, Rule rule); } private Rule currentRule() { @@ -210,14 +212,14 @@ public Builder ruleValidator(RuleValidator ruleValidator) { } @SuppressWarnings({ "unchecked", "rawtypes" }) - public Builder evaluationFunction(String key, Class ruleKind, AtomicConstraintFunction function) { + public Builder evaluationFunction(String key, Class ruleKind, AtomicConstraintRuleFunction function) { validator.constraintFunctions.computeIfAbsent(key, k -> new ArrayList<>()) .add(new ConstraintFunctionEntry(ruleKind, function)); return this; } @SuppressWarnings({ "unchecked", "rawtypes" }) - public Builder dynamicEvaluationFunction(Class ruleKind, DynamicAtomicConstraintFunction function) { + public Builder dynamicEvaluationFunction(Class ruleKind, DynamicAtomicConstraintRuleFunction function) { validator.dynamicConstraintFunctions.add(new DynamicAtomicConstraintFunctionEntry(ruleKind, function)); return this; } @@ -229,28 +231,13 @@ public PolicyValidator build() { } - private record ConstraintFunctionEntry( + private record ConstraintFunctionEntry( Class type, - AtomicConstraintFunction function) { + AtomicConstraintRuleFunction function) { } - private record DynamicAtomicConstraintFunctionEntry( + private record DynamicAtomicConstraintFunctionEntry( Class type, - DynamicAtomicConstraintFunction function) { - } - - private record AtomicConstraintFunctionWrapper( - String leftOperand, - DynamicAtomicConstraintFunction inner) implements AtomicConstraintFunction { - - @Override - public boolean evaluate(Operator operator, Object rightValue, R rule, PolicyContext context) { - throw new UnsupportedOperationException("Evaluation is not supported"); - } - - @Override - public Result validate(Operator operator, Object rightValue, R rule) { - return inner.validate(leftOperand, operator, rightValue, rule); - } + DynamicAtomicConstraintRuleFunction function) { } } diff --git a/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplPlannerTest.java b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplPlannerTest.java index 2ba0f92fd4f..0d70d4c0a8b 100644 --- a/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplPlannerTest.java +++ b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplPlannerTest.java @@ -14,12 +14,13 @@ package org.eclipse.edc.policy.engine; -import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction; +import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction; import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; import org.eclipse.edc.policy.engine.spi.PolicyEngine; -import org.eclipse.edc.policy.engine.spi.PolicyValidatorFunction; +import org.eclipse.edc.policy.engine.spi.PolicyRuleFunction; +import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule; import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; -import org.eclipse.edc.policy.engine.spi.RuleFunction; import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; import org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep; import org.eclipse.edc.policy.engine.spi.plan.step.MultiplicityConstraintStep; @@ -31,12 +32,14 @@ import org.eclipse.edc.policy.model.AtomicConstraint; import org.eclipse.edc.policy.model.Duty; import org.eclipse.edc.policy.model.LiteralExpression; +import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.policy.model.OrConstraint; import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.Prohibition; import org.eclipse.edc.policy.model.Rule; import org.eclipse.edc.policy.model.XoneConstraint; +import org.eclipse.edc.spi.EdcException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -47,12 +50,10 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import java.util.List; -import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.policy.engine.spi.PolicyEngine.ALL_SCOPES; import static org.eclipse.edc.policy.model.Operator.EQ; import static org.junit.jupiter.params.provider.Arguments.of; import static org.mockito.ArgumentMatchers.any; @@ -80,6 +81,7 @@ private static AtomicConstraint atomicConstraint(String key, String value) { @BeforeEach void setUp() { policyEngine = new PolicyEngineImpl(new ScopeFilter(bindingRegistry), new RuleValidator(bindingRegistry)); + policyEngine.registerScope("test", TestContext.class); } @Nested @@ -92,7 +94,7 @@ void withRule(Policy policy, Class ruleClass, String action, String key, F bindingRegistry.bind(action, TEST_SCOPE); bindingRegistry.bind(key, TEST_SCOPE); - policyEngine.registerFunction(TEST_SCOPE, ruleClass, key, (op, rv, r, ctx) -> true); + policyEngine.registerFunction(TestContext.class, ruleClass, key, (op, rv, r, ctx) -> true); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, policy); @@ -105,7 +107,7 @@ void withRule(Policy policy, Class ruleClass, String action, String key, F .first() .isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> { assertThat(constraintStep.isFiltered()).isFalse(); - assertThat(constraintStep.function()).isNotNull(); + assertThat(constraintStep.functionName()).isNotNull(); assertThat(constraintStep.constraint()).isNotNull(); assertThat(constraintStep.rule()).isInstanceOf(ruleClass); }); @@ -116,14 +118,23 @@ void withRule(Policy policy, Class ruleClass, String action, String key, F @ArgumentsSource(SimplePolicyProvider.class) void withRuleAndDynFunction(Policy policy, Class ruleClass, String action, String key, Function>> stepsProvider) { - DynamicAtomicConstraintFunction function = mock(); + var function = new DynamicAtomicConstraintRuleFunction() { - when(function.canHandle(key)).thenReturn(true); + @Override + public boolean evaluate(Object leftValue, Operator operator, Object rightValue, Rule rule, TestContext context) { + throw new EdcException("should not pass here"); + } + + @Override + public boolean canHandle(Object leftValue) { + return true; + } + }; bindingRegistry.bind(action, TEST_SCOPE); bindingRegistry.bind(key, TEST_SCOPE); - policyEngine.registerFunction(TEST_SCOPE, ruleClass, function); + policyEngine.registerFunction(TestContext.class, ruleClass, function); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, policy); @@ -136,7 +147,7 @@ void withRuleAndDynFunction(Policy policy, Class ruleClass, String action, .first() .isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> { assertThat(constraintStep.isFiltered()).isFalse(); - assertThat(constraintStep.function()).isNotNull(); + assertThat(constraintStep.functionName()).isNotNull(); assertThat(constraintStep.constraint()).isNotNull(); assertThat(constraintStep.rule()).isInstanceOf(ruleClass); }); @@ -148,13 +159,13 @@ void withRuleAndDynFunction(Policy policy, Class ruleClass, String action, @ArgumentsSource(SimplePolicyProvider.class) void withRuleAndRuleFunction(Policy policy, Class ruleClass, String action, String key, Function>> stepsProvider) { - RuleFunction function = mock(); + PolicyRuleFunction function = mock(); bindingRegistry.bind(action, TEST_SCOPE); bindingRegistry.bind(key, TEST_SCOPE); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, function); - policyEngine.registerFunction(TEST_SCOPE, ruleClass, function); + policyEngine.registerFunction(TestContext.class, ruleClass, function); + policyEngine.registerFunction(TestContext.class, ruleClass, function); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, policy); @@ -167,7 +178,7 @@ void withRuleAndRuleFunction(Policy policy, Class ruleClass, String action .first() .isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> { assertThat(constraintStep.isFiltered()).isTrue(); - assertThat(constraintStep.function()).isNull(); + assertThat(constraintStep.functionName()).isNull(); assertThat(constraintStep.constraint()).isNotNull(); assertThat(constraintStep.rule()).isInstanceOf(ruleClass); }); @@ -178,12 +189,11 @@ void withRuleAndRuleFunction(Policy policy, Class ruleClass, String action @ArgumentsSource(SimplePolicyProvider.class) void withRuleAndRuleFunctionNotBound(Policy policy, Class ruleClass, String action, String key, Function>> stepsProvider) { - RuleFunction function = mock(); - bindingRegistry.bind(action, TEST_SCOPE); bindingRegistry.bind(key, TEST_SCOPE); - policyEngine.registerFunction("invalidScope", ruleClass, function); + PolicyRuleFunction function = mock(); + policyEngine.registerFunction(UnboundedContext.class, ruleClass, function); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, policy); @@ -209,7 +219,7 @@ void withPermissionContainingDuty() { bindingRegistry.bind(actionType, TEST_SCOPE); bindingRegistry.bind(key, TEST_SCOPE); - policyEngine.registerFunction(ALL_SCOPES, Duty.class, key, (op, rv, r, ctx) -> true); + policyEngine.registerFunction(TestContext.class, Duty.class, key, (op, rv, r, ctx) -> true); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, policy); @@ -223,7 +233,7 @@ void withPermissionContainingDuty() { .first() .isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> { assertThat(constraintStep.isFiltered()).isTrue(); - assertThat(constraintStep.function()).isNull(); + assertThat(constraintStep.functionName()).isNull(); assertThat(constraintStep.constraint()).isNotNull(); assertThat(constraintStep.rule()).isInstanceOf(Permission.class); }); @@ -271,7 +281,7 @@ void shouldIgnorePermissionStep_whenActionNotBound() { var permission = Permission.Builder.newInstance().action(Action.Builder.newInstance().type("action").build()).constraint(constraint).build(); var policy = Policy.Builder.newInstance().permission(permission).build(); - policyEngine.registerFunction(ALL_SCOPES, Permission.class, "foo", (op, rv, r, ctx) -> true); + policyEngine.registerFunction(TestContext.class, Permission.class, "foo", (op, rv, r, ctx) -> true); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, policy); @@ -339,7 +349,7 @@ void shouldIgnoreAtomicConstraintStep_whenLeftExpressionNotFunctionBound() { void shouldIgnoreAtomicConstraintStep_whenLeftExpressionNotDynFunctionBound() { - DynamicAtomicConstraintFunction function = mock(); + DynamicAtomicConstraintRuleFunction function = mock(); when(function.canHandle(any())).thenReturn(true); @@ -349,7 +359,7 @@ void shouldIgnoreAtomicConstraintStep_whenLeftExpressionNotDynFunctionBound() { var constraint = atomicConstraint("foo", "bar"); var permission = Permission.Builder.newInstance().action(Action.Builder.newInstance().type("action").build()).constraint(constraint).build(); var policy = Policy.Builder.newInstance().permission(permission).build(); - policyEngine.registerFunction(ALL_SCOPES, Duty.class, function); + policyEngine.registerFunction(TestContext.class, Duty.class, function); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, policy); @@ -361,7 +371,7 @@ void shouldIgnoreAtomicConstraintStep_whenLeftExpressionNotDynFunctionBound() { .first() .isInstanceOfSatisfying(AtomicConstraintStep.class, constraintStep -> { assertThat(constraintStep.isFiltered()).isTrue(); - assertThat(constraintStep.function()).isNull(); + assertThat(constraintStep.functionName()).isNull(); }); }); } @@ -374,11 +384,10 @@ class MultiplicityConstraints { @ParameterizedTest @ArgumentsSource(MultiplicityPolicyProvider.class) void shouldEvaluate_withMultiplicityConstraint(Policy policy, Class ruleClass, String action, String key, Function>> stepsProvider) { - bindingRegistry.bind(key, TEST_SCOPE); bindingRegistry.bind(action, TEST_SCOPE); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, key, (op, rv, r, ctx) -> true); + policyEngine.registerFunction(TestContext.class, ruleClass, key, (op, rv, r, ctx) -> true); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, policy); @@ -436,10 +445,11 @@ class Validator { @Test void shouldEvaluate_withNoValidators() { + policyEngine.registerScope("another.scope", PolicyContext.class); var emptyPolicy = Policy.Builder.newInstance().build(); - policyEngine.registerPreValidator("foo", (policy, policyContext) -> true); + policyEngine.registerPreValidator(TestContext.class, (policy, policyContext) -> true); - var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, emptyPolicy); + var plan = policyEngine.createEvaluationPlan("another.scope", emptyPolicy); assertThat(plan.getPostValidators()).isEmpty(); assertThat(plan.getPreValidators()).isEmpty(); @@ -449,9 +459,9 @@ void shouldEvaluate_withNoValidators() { void shouldEvaluate_withFunctionalValidators() { var emptyPolicy = Policy.Builder.newInstance().build(); - BiFunction function = (policy, policyContext) -> true; - policyEngine.registerPreValidator(TEST_SCOPE, function); - policyEngine.registerPostValidator(TEST_SCOPE, function); + PolicyValidatorRule function = (policy, policyContext) -> true; + policyEngine.registerPreValidator(TestContext.class, function); + policyEngine.registerPostValidator(TestContext.class, function); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, emptyPolicy); @@ -468,8 +478,8 @@ void shouldEvaluate_withFunctionalValidators() { @Test void shouldEvaluate_withValidators() { var emptyPolicy = Policy.Builder.newInstance().build(); - policyEngine.registerPreValidator(TEST_SCOPE, new MyValidatorFunction()); - policyEngine.registerPostValidator(TEST_SCOPE, new MyValidatorFunction()); + policyEngine.registerPreValidator(TestContext.class, new MyValidatorFunction()); + policyEngine.registerPostValidator(TestContext.class, new MyValidatorFunction()); var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, emptyPolicy); @@ -482,10 +492,10 @@ void shouldEvaluate_withValidators() { } - static class MyValidatorFunction implements PolicyValidatorFunction { + static class MyValidatorFunction implements PolicyValidatorRule { @Override - public Boolean apply(Policy policy, PolicyContext policyContext) { + public Boolean apply(Policy policy, TestContext policyContext) { return true; } @@ -496,4 +506,20 @@ public String name() { } } + static class TestContext extends PolicyContextImpl { + + @Override + public String scope() { + return TEST_SCOPE; + } + } + + private static class UnboundedContext extends PolicyContextImpl { + + @Override + public String scope() { + return "unbounded"; + } + } + } diff --git a/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplScenariosTest.java b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplScenariosTest.java index 43cc162de45..c189f002b70 100644 --- a/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplScenariosTest.java +++ b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplScenariosTest.java @@ -41,6 +41,7 @@ */ public class PolicyEngineImplScenariosTest { private static final String TEST_SCOPE = "test"; + private static final String TEST_AGENT_SCOPE = "test.agent"; private static final String ABS_SPATIAL_CONSTRAINT = "absoluteSpatialPosition"; private static final String CONNECTOR_CONSTRAINT = "connector"; private static final Action USE_ACTION = Action.Builder.newInstance().type("use").build(); @@ -61,9 +62,9 @@ void verifyUnrestrictedUse() { bindingRegistry.bind(USE_ACTION.getType(), ALL_SCOPES); var usePermission = Permission.Builder.newInstance().action(USE_ACTION).build(); var policy = Policy.Builder.newInstance().permission(usePermission).build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isSucceeded(); } @@ -74,12 +75,12 @@ void verifyUnrestrictedUse() { @Test void verifyNoUse() { bindingRegistry.bind(USE_ACTION.getType(), ALL_SCOPES); - policyEngine.registerFunction(ALL_SCOPES, Prohibition.class, (rule, ctx) -> rule.getAction().getType().equals(USE_ACTION.getType())); + policyEngine.registerFunction(TestContext.class, Prohibition.class, (rule, ctx) -> rule.getAction().getType().equals(USE_ACTION.getType())); var prohibition = Prohibition.Builder.newInstance().action(USE_ACTION).build(); var policy = Policy.Builder.newInstance().prohibition(prohibition).build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result.succeeded()).isFalse(); } @@ -93,10 +94,11 @@ void verifySpatialLocation() { bindingRegistry.bind(ABS_SPATIAL_CONSTRAINT, ALL_SCOPES); // function that verifies the EU region - policyEngine.registerFunction(ALL_SCOPES, Permission.class, ABS_SPATIAL_CONSTRAINT, (operator, value, permission, context) -> { - var claims = context.getContextData(ParticipantAgent.class).getClaims(); - return claims.containsKey("region") && claims.get("region").equals(value); - }); + policyEngine.registerFunction(TestAgentContext.class, Permission.class, ABS_SPATIAL_CONSTRAINT, + (operator, rightValue, rule, context) -> { + var claims = context.participantAgent().getClaims(); + return claims.containsKey("region") && claims.get("region").equals(rightValue); + }); var left = new LiteralExpression(ABS_SPATIAL_CONSTRAINT); var right = new LiteralExpression("eu"); @@ -104,16 +106,12 @@ void verifySpatialLocation() { var usePermission = Permission.Builder.newInstance().action(USE_ACTION).constraint(spatialConstraint).build(); var policy = Policy.Builder.newInstance().permission(usePermission).build(); - var euContext = PolicyContextImpl.Builder.newInstance() - .additional(ParticipantAgent.class, new ParticipantAgent(Map.of("region", "eu"), emptyMap())) - .build(); - var euResult = policyEngine.evaluate(TEST_SCOPE, policy, euContext); + var euContext = new TestAgentContext(new ParticipantAgent(Map.of("region", "eu"), emptyMap())); + var euResult = policyEngine.evaluate(policy, euContext); assertThat(euResult).isSucceeded(); - var noRegionContext = PolicyContextImpl.Builder.newInstance() - .additional(ParticipantAgent.class, new ParticipantAgent(emptyMap(), emptyMap())) - .build(); - var noRegionResult = policyEngine.evaluate(TEST_SCOPE, policy, noRegionContext); + var noRegionContext = new TestAgentContext(new ParticipantAgent(emptyMap(), emptyMap())); + var noRegionResult = policyEngine.evaluate(policy, noRegionContext); assertThat(noRegionResult).isFailed(); } @@ -122,7 +120,7 @@ void verifySpatialLocation() { */ @Test void verifyConnectorUse() { - policyEngine.registerFunction(ALL_SCOPES, Permission.class, CONNECTOR_CONSTRAINT, (operator, value, permission, context) -> { + policyEngine.registerFunction(TestContext.class, Permission.class, CONNECTOR_CONSTRAINT, (operator, value, permission, context) -> { if (!(value instanceof List)) { context.reportProblem("Unsupported right operand type: " + value.getClass().getName()); return false; @@ -136,11 +134,36 @@ void verifyConnectorUse() { var connectorConstraint = AtomicConstraint.Builder.newInstance().leftExpression(left).operator(IN).rightExpression(right).build(); var usePermission = Permission.Builder.newInstance().action(USE_ACTION).constraint(connectorConstraint).build(); var policy = Policy.Builder.newInstance().permission(usePermission).build(); - var context = PolicyContextImpl.Builder.newInstance().build(); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, new TestContext()); assertThat(result.succeeded()).isTrue(); } + private static class TestContext extends PolicyContextImpl { + + @Override + public String scope() { + return TEST_SCOPE; + } + } + + private static class TestAgentContext extends PolicyContextImpl { + + private final ParticipantAgent participantAgent; + + TestAgentContext(ParticipantAgent participantAgent) { + this.participantAgent = participantAgent; + } + + public ParticipantAgent participantAgent() { + return participantAgent; + } + + @Override + public String scope() { + return TEST_AGENT_SCOPE; + } + } + } diff --git a/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplTest.java b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplTest.java index cd1c56fcffa..2d030e94495 100644 --- a/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplTest.java +++ b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplTest.java @@ -14,10 +14,13 @@ package org.eclipse.edc.policy.engine; -import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction; +import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction; +import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction; import org.eclipse.edc.policy.engine.spi.PolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.engine.spi.PolicyRuleFunction; +import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule; import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; import org.eclipse.edc.policy.engine.validation.RuleValidator; import org.eclipse.edc.policy.model.Action; @@ -28,8 +31,8 @@ import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.Prohibition; import org.eclipse.edc.policy.model.Rule; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -39,16 +42,18 @@ import org.junit.jupiter.params.provider.ValueSource; import java.util.Set; -import java.util.function.BiFunction; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.policy.engine.spi.PolicyEngine.ALL_SCOPES; import static org.eclipse.edc.policy.model.Operator.EQ; +import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.params.provider.Arguments.of; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -57,32 +62,45 @@ class PolicyEngineImplTest { private static final String TEST_SCOPE = "test"; + private static final String PARENT_SCOPE = "parent"; + private static final String CHILD_SCOPE = "parent.child"; + private static final String FOO_SCOPE = "foo"; private final RuleBindingRegistry bindingRegistry = new RuleBindingRegistryImpl(); private PolicyEngine policyEngine; - @BeforeEach void setUp() { policyEngine = new PolicyEngineImpl(new ScopeFilter(bindingRegistry), new RuleValidator(bindingRegistry)); + policyEngine.registerScope(TEST_SCOPE, TestContext.class); + policyEngine.registerScope(PARENT_SCOPE, ParentContext.class); + policyEngine.registerScope(CHILD_SCOPE, ChildContext.class); + policyEngine.registerScope(FOO_SCOPE, ChildContext.class); } @Test void validateEmptyPolicy() { - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); var emptyPolicy = Policy.Builder.newInstance().build(); // No explicit rule specified, policy should evaluate to true - var result = policyEngine.evaluate(TEST_SCOPE, emptyPolicy, context); + var result = policyEngine.evaluate(emptyPolicy, context); assertThat(result).isSucceeded(); } + @Deprecated(since = "0.10.0") + @Test + void shouldThrowException_whenRegisterWithScopeNotRegistered() { + assertThatThrownBy(() -> policyEngine.registerFunction(ALL_SCOPES, Rule.class, "key", mock())); + assertThatThrownBy(() -> policyEngine.registerFunction("unregistered.scope", Rule.class, "key", mock())); + } + @Test void validateUnsatisfiedDuty() { - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); bindingRegistry.bind("foo", ALL_SCOPES); - policyEngine.registerFunction(ALL_SCOPES, Duty.class, "foo", (op, rv, duty, ctx) -> false); + policyEngine.registerFunction(TestContext.class, Duty.class, "foo", (op, rv, duty, ctx) -> false); var left = new LiteralExpression("foo"); var right = new LiteralExpression("bar"); @@ -91,7 +109,7 @@ void validateUnsatisfiedDuty() { var policy = Policy.Builder.newInstance().duty(duty).build(); // The duty is not satisfied, so the policy should evaluate to false - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isFailed(); } @@ -101,7 +119,7 @@ void validateRuleOutOfScope() { // Verifies that a rule will be filtered if its action is not registered. The constraint is registered but should be filtered since it is contained in the permission. // If the permission is not properly filtered, the constraint will not be fulfilled and raise an exception. bindingRegistry.bind("foo", ALL_SCOPES); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); var left = new LiteralExpression("foo"); var right = new LiteralExpression("bar"); @@ -112,17 +130,17 @@ void validateRuleOutOfScope() { var policy = Policy.Builder.newInstance().permission(permission).build(); // the permission containing the unfulfilled constraint should be filtered, resulting in the policy evaluation succeeding - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isSucceeded(); } @Test - void validateUngrantedPermission() { + void validateNotGrantedPermission() { bindingRegistry.bind("foo", ALL_SCOPES); - policyEngine.registerFunction(ALL_SCOPES, Permission.class, "foo", (op, rv, duty, context) -> false); - var context = PolicyContextImpl.Builder.newInstance().build(); + policyEngine.registerFunction(TestContext.class, Permission.class, "foo", (op, rv, duty, context) -> false); + var context = new TestContext(); var left = new LiteralExpression("foo"); var right = new LiteralExpression("bar"); @@ -131,7 +149,7 @@ void validateUngrantedPermission() { var policy = Policy.Builder.newInstance().permission(permission).build(); // The permission is not granted, so the policy should evaluate to false - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isFailed(); } @@ -140,13 +158,13 @@ void validateUngrantedPermission() { void validateTriggeredProhibition() { bindingRegistry.bind("foo", ALL_SCOPES); - policyEngine.registerFunction(ALL_SCOPES, Prohibition.class, "foo", (op, rv, duty, context) -> true); - var context = PolicyContextImpl.Builder.newInstance().build(); + policyEngine.registerFunction(PolicyContext.class, Prohibition.class, "foo", (op, rv, duty, context) -> true); + var context = new TestContext(); var policy = createTestPolicy(); // The prohibition is triggered (it is true), so the policy should evaluate to false - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isFailed(); } @@ -155,45 +173,14 @@ void validateTriggeredProhibition() { void validateConstraintFunctionOutOfScope() { bindingRegistry.bind("foo", ALL_SCOPES); - policyEngine.registerFunction("foo", Prohibition.class, "foo", (op, rv, duty, context) -> Assertions.fail("Foo prohibition should be out of scope")); - policyEngine.registerFunction("bar", Prohibition.class, "foo", (op, rv, duty, context) -> true); - var context = PolicyContextImpl.Builder.newInstance().build(); - - var policy = createTestPolicy(); - - // The bar-scoped prohibition is triggered (it is true), so the policy should evaluate to false - var result = policyEngine.evaluate("bar", policy, context); - - assertThat(result).isFailed(); - } - - @Test - void validateChildScopeNotVisible() { - bindingRegistry.bind("foo", ALL_SCOPES); - - policyEngine.registerFunction("bar", Prohibition.class, "foo", (op, rv, duty, context) -> true); - policyEngine.registerFunction("bar.child", Prohibition.class, "foo", (op, rv, duty, context) -> Assertions.fail("Child prohibition should be out of scope")); - var context = PolicyContextImpl.Builder.newInstance().build(); - - var policy = createTestPolicy(); - - // The bar-scoped prohibition is triggered (it is true), so the policy should evaluate to false - var result = policyEngine.evaluate("bar", policy, context); - - assertThat(result).isFailed(); - } - - @Test - void validateScopeIsInheritedByChildren() { - bindingRegistry.bind("foo", ALL_SCOPES); - - policyEngine.registerFunction("bar", Prohibition.class, "foo", (op, rv, duty, context) -> true); - var context = PolicyContextImpl.Builder.newInstance().build(); + policyEngine.registerFunction(FooContext.class, Prohibition.class, "foo", (op, rv, duty, context) -> fail("Foo prohibition should be out of scope")); + policyEngine.registerFunction(TestContext.class, Prohibition.class, "foo", (op, rv, duty, context) -> true); + var context = new TestContext(); var policy = createTestPolicy(); // The bar-scoped prohibition is triggered (it is true), so the policy should evaluate to false - var result = policyEngine.evaluate("bar.child", policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isFailed(); } @@ -208,24 +195,27 @@ void validateRuleFunctionOutOfScope() { var policy = Policy.Builder.newInstance().permission(permission).build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); + + policyEngine.registerFunction(FooContext.class, Permission.class, (rule, ctx) -> fail("Foo permission should be out of scope")); + policyEngine.registerFunction(TestContext.class, Permission.class, (rule, ctx) -> rule.getAction().getType().equals(action.getType())); - policyEngine.registerFunction("foo", Permission.class, (rule, ctx) -> Assertions.fail("Foo permission should be out of scope")); - policyEngine.registerFunction("bar", Permission.class, (rule, ctx) -> rule.getAction().getType().equals(action.getType())); - assertThat(policyEngine.evaluate("bar", policy, context).succeeded()).isTrue(); + var result = policyEngine.evaluate(policy, context); + + assertThat(result).isSucceeded(); } @Test void validateAllScopesPreFunctionalValidator() { bindingRegistry.bind("foo", ALL_SCOPES); - BiFunction function = (policy, context) -> false; - policyEngine.registerPreValidator(ALL_SCOPES, function); + PolicyValidatorRule function = (policy, context) -> false; + policyEngine.registerPreValidator(PolicyContext.class, function); var policy = Policy.Builder.newInstance().build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isFailed(); } @@ -234,13 +224,13 @@ void validateAllScopesPreFunctionalValidator() { void validateAllScopesPostFunctionalValidator() { bindingRegistry.bind("foo", ALL_SCOPES); - BiFunction function = (policy, context) -> false; - policyEngine.registerPostValidator(ALL_SCOPES, function); + PolicyValidatorRule function = (policy, context) -> false; + policyEngine.registerPostValidator(PolicyContext.class, function); var policy = Policy.Builder.newInstance().build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isFailed(); } @@ -252,14 +242,14 @@ void validateAllScopesPrePostValidator(boolean preValidation) { bindingRegistry.bind("foo", ALL_SCOPES); if (preValidation) { - policyEngine.registerPreValidator(ALL_SCOPES, (policy, context) -> false); + policyEngine.registerPreValidator(PolicyContext.class, (policy, context) -> false); } else { - policyEngine.registerPostValidator(ALL_SCOPES, (policy, context) -> false); + policyEngine.registerPostValidator(PolicyContext.class, (policy, context) -> false); } var policy = Policy.Builder.newInstance().build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isFailed(); } @@ -270,15 +260,15 @@ void validateScopedPrePostValidator(boolean preValidation) { bindingRegistry.bind("foo", TEST_SCOPE); if (preValidation) { - policyEngine.registerPreValidator(TEST_SCOPE, (policy, context) -> false); + policyEngine.registerPreValidator(TestContext.class, (policy, context) -> false); } else { - policyEngine.registerPostValidator(TEST_SCOPE, (policy, context) -> false); + policyEngine.registerPostValidator(TestContext.class, (policy, context) -> false); } var policy = Policy.Builder.newInstance().build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isFailed(); } @@ -289,15 +279,15 @@ void validateOutOfScopedPrePostValidator(boolean preValidation) { bindingRegistry.bind("foo", TEST_SCOPE); if (preValidation) { - policyEngine.registerPreValidator("random.scope", (policy, context) -> false); + policyEngine.registerPreValidator(FooContext.class, (policy, context) -> false); } else { - policyEngine.registerPostValidator("random.scope", (policy, context) -> false); + policyEngine.registerPostValidator(FooContext.class, (policy, context) -> false); } var policy = Policy.Builder.newInstance().build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new TestContext(); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result.succeeded()).isTrue(); } @@ -309,15 +299,15 @@ void validateHierarchicalScopedNotFiredPrePostValidator(boolean preValidation) { bindingRegistry.bind("foo", TEST_SCOPE); if (preValidation) { - policyEngine.registerPreValidator(TEST_SCOPE + ".test", (policy, context) -> false); + policyEngine.registerPreValidator(ChildContext.class, (policy, context) -> false); } else { - policyEngine.registerPostValidator(TEST_SCOPE + ".test", (policy, context) -> false); + policyEngine.registerPostValidator(ChildContext.class, (policy, context) -> false); } var policy = Policy.Builder.newInstance().build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new ParentContext(); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result.succeeded()).isTrue(); } @@ -326,18 +316,18 @@ void validateHierarchicalScopedNotFiredPrePostValidator(boolean preValidation) { @ParameterizedTest @ValueSource(booleans = { true, false }) void validateHierarchicalScopedFiredPrePostValidator(boolean preValidation) { - bindingRegistry.bind("foo", TEST_SCOPE); + bindingRegistry.bind("foo", PARENT_SCOPE); if (preValidation) { - policyEngine.registerPreValidator(TEST_SCOPE, (policy, context) -> false); + policyEngine.registerPreValidator(ParentContext.class, (policy, context) -> false); } else { - policyEngine.registerPostValidator(TEST_SCOPE, (policy, context) -> false); + policyEngine.registerPostValidator(ParentContext.class, (policy, context) -> false); } var policy = Policy.Builder.newInstance().build(); - var context = PolicyContextImpl.Builder.newInstance().build(); + var context = new ChildContext(); - var result = policyEngine.evaluate(TEST_SCOPE + ".test", policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result).isFailed(); } @@ -347,14 +337,14 @@ void validateHierarchicalScopedFiredPrePostValidator(boolean preValidation) { void shouldTriggerDynamicFunction_whenWildcardScope(Policy policy, Class ruleClass, boolean evaluateReturn) { bindingRegistry.dynamicBind((key) -> Set.of(TEST_SCOPE)); - var context = PolicyContextImpl.Builder.newInstance().build(); - DynamicAtomicConstraintFunction function = mock(DynamicAtomicConstraintFunction.class); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, function); + var context = new TestContext(); + DynamicAtomicConstraintRuleFunction function = mock(); + policyEngine.registerFunction(PolicyContext.class, ruleClass, function); when(function.canHandle(any())).thenReturn(true); when(function.evaluate(any(), any(), any(), any(), eq(context))).thenReturn(evaluateReturn); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result.succeeded()).isTrue(); @@ -367,14 +357,14 @@ void shouldTriggerDynamicFunction_whenWildcardScope(Policy policy, Class r void shouldTriggerDynamicFunction_whenExplicitScope(Policy policy, Class ruleClass, boolean evaluateReturn) { bindingRegistry.dynamicBind((key) -> Set.of(TEST_SCOPE)); - var context = PolicyContextImpl.Builder.newInstance().build(); - DynamicAtomicConstraintFunction function = mock(DynamicAtomicConstraintFunction.class); - policyEngine.registerFunction(TEST_SCOPE, ruleClass, function); + var context = new TestContext(); + DynamicAtomicConstraintRuleFunction function = mock(); + policyEngine.registerFunction(TestContext.class, ruleClass, function); when(function.canHandle(any())).thenReturn(true); when(function.evaluate(any(), any(), any(), any(), eq(context))).thenReturn(evaluateReturn); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result.succeeded()).isTrue(); @@ -386,14 +376,14 @@ void shouldTriggerDynamicFunction_whenExplicitScope(Policy policy, Class r @ArgumentsSource(PolicyProvider.class) void shouldNotTriggerDynamicFunction_whenBindAlreadyAvailable(Policy policy, Class ruleClass) { bindingRegistry.bind("foo", ALL_SCOPES); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, "foo", (op, rv, duty, context) -> !ruleClass.isAssignableFrom(Prohibition.class)); + policyEngine.registerFunction(PolicyContext.class, ruleClass, "foo", (op, rv, duty, context) -> !ruleClass.isAssignableFrom(Prohibition.class)); bindingRegistry.dynamicBind((key) -> Set.of(TEST_SCOPE)); - var context = PolicyContextImpl.Builder.newInstance().build(); - DynamicAtomicConstraintFunction function = mock(DynamicAtomicConstraintFunction.class); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, function); + var context = new TestContext(); + DynamicAtomicConstraintRuleFunction function = mock(); + policyEngine.registerFunction(PolicyContext.class, ruleClass, function); - var result = policyEngine.evaluate(TEST_SCOPE, policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result.succeeded()).isTrue(); @@ -405,20 +395,157 @@ void shouldNotTriggerDynamicFunction_whenBindAlreadyAvailable(Policy policy, Cla void shouldNotTriggerDynamicFunction_whenDifferentScope(Policy policy, Class ruleClass, boolean evaluateReturn) { bindingRegistry.dynamicBind((key) -> Set.of(TEST_SCOPE)); - var context = PolicyContextImpl.Builder.newInstance().build(); - DynamicAtomicConstraintFunction function = mock(DynamicAtomicConstraintFunction.class); - policyEngine.registerFunction(TEST_SCOPE, ruleClass, function); + var context = new FooContext(); + DynamicAtomicConstraintRuleFunction function = mock(); + policyEngine.registerFunction(TestContext.class, ruleClass, function); when(function.canHandle(any())).thenReturn(true); - when(function.evaluate(any(), any(), any(), any(), eq(context))).thenReturn(evaluateReturn); + when(function.evaluate(any(), any(), any(), any(), any())).thenReturn(evaluateReturn); - var result = policyEngine.evaluate("randomScope", policy, context); + var result = policyEngine.evaluate(policy, context); assertThat(result.succeeded()).isTrue(); verifyNoInteractions(function); } + @Nested + class TypedContext { + + @Test + void shouldUseTypedContextOnAtomicConstraintFunction() { + bindingRegistry.bind("foo", ALL_SCOPES); + + var left = new LiteralExpression("foo"); + var right = new LiteralExpression("bar"); + var constraint = AtomicConstraint.Builder.newInstance().leftExpression(left).operator(EQ).rightExpression(right).build(); + var duty = Duty.Builder.newInstance().constraint(constraint).build(); + var policy = Policy.Builder.newInstance().duty(duty).build(); + + AtomicConstraintRuleFunction function = mock(); + when(function.evaluate(any(), any(), any(), any())).thenReturn(false); + policyEngine.registerFunction(TestContext.class, Duty.class, "foo", function); + + var context = new TestContext(); + + var result = policyEngine.evaluate(policy, context); + + assertThat(result).isFailed(); + verify(function).evaluate(any(), any(), any(), same(context)); + } + + @Test + void shouldUseTypedContextOnDynamicConstraintFunction() { + bindingRegistry.bind("foo", ALL_SCOPES); + + var left = new LiteralExpression("foo"); + var right = new LiteralExpression("bar"); + var constraint = AtomicConstraint.Builder.newInstance().leftExpression(left).operator(EQ).rightExpression(right).build(); + var duty = Duty.Builder.newInstance().constraint(constraint).build(); + var policy = Policy.Builder.newInstance().duty(duty).build(); + + DynamicAtomicConstraintRuleFunction function = mock(); + when(function.canHandle(any())).thenReturn(true); + + policyEngine.registerFunction(TestContext.class, Duty.class, function); + + var context = new TestContext(); + + var result = policyEngine.evaluate(policy, context); + + assertThat(result).isFailed(); + verify(function).evaluate(any(), any(), any(), any(), same(context)); + } + + @Test + void shouldUseTypedContextOnRuleFunction() { + bindingRegistry.bind("foo", ALL_SCOPES); + + var left = new LiteralExpression("foo"); + var right = new LiteralExpression("bar"); + var constraint = AtomicConstraint.Builder.newInstance().leftExpression(left).operator(EQ).rightExpression(right).build(); + var duty = Duty.Builder.newInstance().constraint(constraint).build(); + var policy = Policy.Builder.newInstance().duty(duty).build(); + + PolicyRuleFunction function = mock(); + + policyEngine.registerFunction(TestContext.class, Duty.class, function); + + var context = new TestContext(); + + var result = policyEngine.evaluate(policy, context); + + assertThat(result).isFailed(); + verify(function).evaluate(any(), same(context)); + } + + @Test + void validateChildScopeNotVisible() { + bindingRegistry.bind("foo", ALL_SCOPES); + + AtomicConstraintRuleFunction parentFunction = mock(); + when(parentFunction.evaluate(any(), any(), any(), any())).thenReturn(true); + AtomicConstraintRuleFunction childFunction = mock(); + when(childFunction.evaluate(any(), any(), any(), any())).thenReturn(true); + policyEngine.registerFunction(ParentContext.class, Prohibition.class, "foo", parentFunction); + policyEngine.registerFunction(ChildContext.class, Prohibition.class, "foo", childFunction); + var context = new ParentContext(); + + var policy = createTestPolicy(); + + var result = policyEngine.evaluate(policy, context); + + assertThat(result).isFailed(); + verify(parentFunction).evaluate(any(), any(), any(), same(context)); + verifyNoInteractions(childFunction); + } + + @Test + void validateScopeIsInheritedByChildren() { + bindingRegistry.bind("foo", ALL_SCOPES); + AtomicConstraintRuleFunction parentFunction = mock(); + when(parentFunction.evaluate(any(), any(), any(), any())).thenReturn(true); + policyEngine.registerFunction(ParentContext.class, Prohibition.class, "foo", parentFunction); + + var context = new ChildContext(); + var policy = createTestPolicy(); + + var result = policyEngine.evaluate(policy, context); + + assertThat(result).isFailed(); + verify(parentFunction).evaluate(any(), any(), any(), same(context)); + } + + } + + private static class TestContext extends PolicyContextImpl { + @Override + public String scope() { + return TEST_SCOPE; + } + } + + private static class FooContext extends PolicyContextImpl { + @Override + public String scope() { + return FOO_SCOPE; + } + } + + private static class ChildContext extends ParentContext { + @Override + public String scope() { + return CHILD_SCOPE; + } + } + + private static class ParentContext extends PolicyContextImpl { + @Override + public String scope() { + return PARENT_SCOPE; + } + } + private Policy createTestPolicy() { var left = new LiteralExpression("foo"); var right = new LiteralExpression("bar"); diff --git a/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplValidationTest.java b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplValidationTest.java index b1a8201b1f6..1314967a59e 100644 --- a/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplValidationTest.java +++ b/core/common/lib/policy-engine-lib/src/test/java/org/eclipse/edc/policy/engine/PolicyEngineImplValidationTest.java @@ -16,6 +16,8 @@ import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction; import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintFunction; +import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction; +import org.eclipse.edc.policy.engine.spi.PolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; import org.eclipse.edc.policy.engine.validation.RuleValidator; @@ -55,7 +57,6 @@ class PolicyEngineImplValidationTest { private final RuleBindingRegistry bindingRegistry = new RuleBindingRegistryImpl(); private PolicyEngine policyEngine; - @BeforeEach void setUp() { policyEngine = new PolicyEngineImpl(new ScopeFilter(bindingRegistry), new RuleValidator(bindingRegistry)); @@ -78,8 +79,8 @@ void validate_whenKeyNotBoundInTheRegistryAndToFunctions() { var constraint = AtomicConstraint.Builder.newInstance().leftExpression(left).operator(EQ).rightExpression(right).build(); var permission = Permission.Builder.newInstance().constraint(constraint).build(); var policy = Policy.Builder.newInstance().permission(permission).build(); - policyEngine.registerFunction(ALL_SCOPES, Duty.class, "foo", (op, rv, r, ctx) -> true); - policyEngine.registerFunction(ALL_SCOPES, Prohibition.class, "foo", (op, rv, r, ctx) -> true); + policyEngine.registerFunction(PolicyContext.class, Duty.class, "foo", (op, rv, r, ctx) -> true); + policyEngine.registerFunction(PolicyContext.class, Prohibition.class, "foo", (op, rv, r, ctx) -> true); var result = policyEngine.validate(policy); @@ -94,7 +95,7 @@ void validate_whenKeyNotBoundInTheRegistryAndToFunctions() { @ArgumentsSource(PolicyProvider.class) void validate_whenKeyIsNotBoundInTheRegistry(Policy policy, Class ruleClass, String key) { - policyEngine.registerFunction(ALL_SCOPES, ruleClass, key, (op, rv, duty, ctx) -> true); + policyEngine.registerFunction(PolicyContext.class, ruleClass, key, (op, rv, duty, ctx) -> true); var result = policyEngine.validate(policy); @@ -110,7 +111,7 @@ void validate(Policy policy, Class ruleClass, String key) { bindingRegistry.bind(key, ALL_SCOPES); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, key, (op, rv, duty, ctx) -> true); + policyEngine.registerFunction(PolicyContext.class, ruleClass, key, (op, rv, duty, ctx) -> true); var result = policyEngine.validate(policy); @@ -129,14 +130,14 @@ void validate_shouldFail_withDynamicFunction() { var policy = Policy.Builder.newInstance().permission(permission).build(); - DynamicAtomicConstraintFunction function = mock(); + DynamicAtomicConstraintRuleFunction function = mock(); when(function.canHandle(leftOperand)).thenReturn(true); when(function.validate(any(), any(), any(), any())).thenReturn(Result.success()); bindingRegistry.dynamicBind(s -> Set.of(ALL_SCOPES)); - policyEngine.registerFunction(ALL_SCOPES, Duty.class, function); + policyEngine.registerFunction(PolicyContext.class, Duty.class, function); var result = policyEngine.validate(policy); @@ -157,7 +158,7 @@ void validate_withDynamicFunction(Policy policy, Class ruleClass, String k when(function.validate(any(), any(), any(), any())).thenReturn(Result.success()); bindingRegistry.dynamicBind(s -> Set.of(ALL_SCOPES)); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, function); + policyEngine.registerFunction(PolicyContext.class, ruleClass, function); var result = policyEngine.validate(policy); @@ -174,7 +175,7 @@ void validate_shouldFail_whenSkippingDynamicFunction(Policy policy, Class when(function.canHandle(key)).thenReturn(false); bindingRegistry.dynamicBind(s -> Set.of(ALL_SCOPES)); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, function); + policyEngine.registerFunction(PolicyContext.class, ruleClass, function); var result = policyEngine.validate(policy); @@ -195,7 +196,7 @@ void validate_shouldFails_withDynamicFunction(Policy policy, Class ruleCla when(function.validate(any(), any(), any(), any())).thenReturn(Result.failure("Dynamic function validation failure")); bindingRegistry.dynamicBind(s -> Set.of(ALL_SCOPES)); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, function); + policyEngine.registerFunction(PolicyContext.class, ruleClass, function); var result = policyEngine.validate(policy); @@ -213,7 +214,7 @@ void validate_shouldFail_whenFunctionValidationFails(Policy policy, Class when(function.validate(any(), any(), any())).thenReturn(Result.failure("Function validation failure")); bindingRegistry.bind(key, ALL_SCOPES); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, key, function); + policyEngine.registerFunction(PolicyContext.class, ruleClass, key, function); var result = policyEngine.validate(policy); @@ -236,7 +237,7 @@ void validate_shouldFail_whenActionIsNotBound() { when(function.validate(any(), any(), any())).thenReturn(Result.success()); bindingRegistry.bind("foo", ALL_SCOPES); - policyEngine.registerFunction(ALL_SCOPES, Permission.class, "foo", function); + policyEngine.registerFunction(PolicyContext.class, Permission.class, "foo", function); var result = policyEngine.validate(policy); @@ -252,7 +253,7 @@ void validate_withMultiplicityConstraints(Policy policy, Class ruleClass, for (var key : keys) { bindingRegistry.bind(key, ALL_SCOPES); - policyEngine.registerFunction(ALL_SCOPES, ruleClass, key, (op, rv, duty, ctx) -> true); + policyEngine.registerFunction(PolicyContext.class, ruleClass, key, (op, rv, duty, ctx) -> true); } diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/ControlPlaneServicesExtension.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/ControlPlaneServicesExtension.java index 4d4bac6de11..efc6101d440 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/ControlPlaneServicesExtension.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/ControlPlaneServicesExtension.java @@ -64,6 +64,10 @@ import org.eclipse.edc.connector.controlplane.transfer.spi.store.TransferProcessStore; import org.eclipse.edc.connector.secret.spi.observe.SecretObservableImpl; import org.eclipse.edc.connector.spi.service.SecretService; +import org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestVersionPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; @@ -83,6 +87,11 @@ import java.time.Clock; +import static org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext.CATALOGING_REQUEST_SCOPE; +import static org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext.CONTRACT_NEGOTIATION_REQUEST_SCOPE; +import static org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext.TRANSFER_PROCESS_REQUEST_SCOPE; +import static org.eclipse.edc.policy.context.request.spi.RequestVersionPolicyContext.VERSION_REQUEST_SCOPE; + @Extension(ControlPlaneServicesExtension.NAME) public class ControlPlaneServicesExtension implements ServiceExtension { @@ -178,6 +187,14 @@ public String name() { return NAME; } + @Override + public void initialize(ServiceExtensionContext context) { + policyEngine.registerScope(TRANSFER_PROCESS_REQUEST_SCOPE, RequestTransferProcessPolicyContext.class); + policyEngine.registerScope(CONTRACT_NEGOTIATION_REQUEST_SCOPE, RequestContractNegotiationPolicyContext.class); + policyEngine.registerScope(CATALOGING_REQUEST_SCOPE, RequestCatalogPolicyContext.class); + policyEngine.registerScope(VERSION_REQUEST_SCOPE, RequestVersionPolicyContext.class); + } + @Provider public AssetService assetService() { var assetObservable = new AssetObservableImpl(); diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/catalog/CatalogProtocolServiceImpl.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/catalog/CatalogProtocolServiceImpl.java index fdc5da202e5..82a3cd58b63 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/catalog/CatalogProtocolServiceImpl.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/catalog/CatalogProtocolServiceImpl.java @@ -21,7 +21,7 @@ import org.eclipse.edc.connector.controlplane.catalog.spi.DatasetResolver; import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogProtocolService; import org.eclipse.edc.connector.controlplane.services.spi.protocol.ProtocolTokenValidator; -import org.eclipse.edc.policy.engine.spi.PolicyScope; +import org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.transaction.spi.TransactionContext; @@ -31,9 +31,6 @@ public class CatalogProtocolServiceImpl implements CatalogProtocolService { - @PolicyScope - public static final String CATALOGING_REQUEST_SCOPE = "request.catalog"; - private final DatasetResolver datasetResolver; private final DataServiceRegistry dataServiceRegistry; private final String participantId; @@ -56,7 +53,7 @@ public CatalogProtocolServiceImpl(DatasetResolver datasetResolver, @Override @NotNull public ServiceResult getCatalog(CatalogRequestMessage message, TokenRepresentation tokenRepresentation) { - return transactionContext.execute(() -> protocolTokenValidator.verify(tokenRepresentation, CATALOGING_REQUEST_SCOPE, message) + return transactionContext.execute(() -> protocolTokenValidator.verify(tokenRepresentation, RequestCatalogPolicyContext::new, message) .map(agent -> { try (var datasets = datasetResolver.query(agent, message.getQuerySpec())) { var dataServices = dataServiceRegistry.getDataServices(); @@ -73,7 +70,7 @@ public ServiceResult getCatalog(CatalogRequestMessage message, TokenRep @Override public @NotNull ServiceResult getDataset(String datasetId, TokenRepresentation tokenRepresentation) { - return transactionContext.execute(() -> protocolTokenValidator.verify(tokenRepresentation, CATALOGING_REQUEST_SCOPE) + return transactionContext.execute(() -> protocolTokenValidator.verify(tokenRepresentation, RequestCatalogPolicyContext::new) .map(agent -> datasetResolver.getById(agent, datasetId)) .compose(dataset -> { if (dataset == null) { diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImpl.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImpl.java index d32fba19783..2504010f9e1 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImpl.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImpl.java @@ -32,7 +32,7 @@ import org.eclipse.edc.connector.controlplane.contract.spi.validation.ValidatedConsumerOffer; import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationProtocolService; import org.eclipse.edc.connector.controlplane.services.spi.protocol.ProtocolTokenValidator; -import org.eclipse.edc.policy.engine.spi.PolicyScope; +import org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.agent.ParticipantAgent; import org.eclipse.edc.spi.iam.TokenRepresentation; @@ -52,9 +52,6 @@ public class ContractNegotiationProtocolServiceImpl implements ContractNegotiationProtocolService { - @PolicyScope - public static final String CONTRACT_NEGOTIATION_REQUEST_SCOPE = "request.contract.negotiation"; - private final ContractNegotiationStore store; private final TransactionContext transactionContext; private final ContractValidationService validationService; @@ -318,7 +315,7 @@ private ServiceResult getAndLeaseNegotiation(String negotia } private ServiceResult verifyRequest(TokenRepresentation tokenRepresentation, Policy policy, RemoteMessage message) { - return protocolTokenValidator.verify(tokenRepresentation, CONTRACT_NEGOTIATION_REQUEST_SCOPE, policy, message) + return protocolTokenValidator.verify(tokenRepresentation, RequestContractNegotiationPolicyContext::new, policy, message) .onFailure(failure -> monitor.debug(() -> "Verification Failed: %s".formatted(failure.getFailureDetail()))); } diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/protocol/ProtocolTokenValidatorImpl.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/protocol/ProtocolTokenValidatorImpl.java index 0fb08166efe..4b90a2ce650 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/protocol/ProtocolTokenValidatorImpl.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/protocol/ProtocolTokenValidatorImpl.java @@ -15,7 +15,7 @@ package org.eclipse.edc.connector.controlplane.services.protocol; import org.eclipse.edc.connector.controlplane.services.spi.protocol.ProtocolTokenValidator; -import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; +import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.agent.ParticipantAgent; @@ -50,8 +50,16 @@ public ProtocolTokenValidatorImpl(IdentityService identityService, PolicyEngine } @Override - public ServiceResult verify(TokenRepresentation tokenRepresentation, String policyScope, Policy policy, RemoteMessage message) { - var tokenValidation = identityService.verifyJwtToken(tokenRepresentation, createVerificationContext(policyScope, policy, message)); + public ServiceResult verify(TokenRepresentation tokenRepresentation, RequestPolicyContext.Provider policyContextProvider, Policy policy, RemoteMessage message) { + var requestScopeBuilder = RequestScope.Builder.newInstance(); + var requestContext = RequestContext.Builder.newInstance().message(message).direction(RequestContext.Direction.Ingress).build(); + var policyContext = policyContextProvider.instantiate(requestContext, requestScopeBuilder); + policyEngine.evaluate(policy, policyContext); + var verificationContext = VerificationContext.Builder.newInstance() + .policy(policy) + .scopes(policyContext.requestScopeBuilder().build().getScopes()) + .build(); + var tokenValidation = identityService.verifyJwtToken(tokenRepresentation, verificationContext); if (tokenValidation.failed()) { monitor.debug(() -> "Unauthorized: %s".formatted(tokenValidation.getFailureDetail())); return ServiceResult.unauthorized("Unauthorized"); @@ -62,18 +70,4 @@ public ServiceResult verify(TokenRepresentation tokenRepresent return ServiceResult.success(participantAgent); } - private VerificationContext createVerificationContext(String scope, Policy policy, RemoteMessage message) { - var requestScopeBuilder = RequestScope.Builder.newInstance(); - var requestContext = RequestContext.Builder.newInstance().message(message).direction(RequestContext.Direction.Ingress).build(); - var policyContext = PolicyContextImpl.Builder.newInstance() - .additional(RequestContext.class, requestContext) - .additional(RequestScope.Builder.class, requestScopeBuilder) - .build(); - policyEngine.evaluate(scope, policy, policyContext); - return VerificationContext.Builder.newInstance() - .policy(policy) - .scopes(requestScopeBuilder.build().getScopes()) - .build(); - } - } diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/protocol/VersionProtocolServiceImpl.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/protocol/VersionProtocolServiceImpl.java index 32154dac0a0..7ee5f62d398 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/protocol/VersionProtocolServiceImpl.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/protocol/VersionProtocolServiceImpl.java @@ -18,15 +18,12 @@ import org.eclipse.edc.connector.controlplane.services.spi.protocol.ProtocolVersionRegistry; import org.eclipse.edc.connector.controlplane.services.spi.protocol.ProtocolVersions; import org.eclipse.edc.connector.controlplane.services.spi.protocol.VersionProtocolService; -import org.eclipse.edc.policy.engine.spi.PolicyScope; +import org.eclipse.edc.policy.context.request.spi.RequestVersionPolicyContext; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.result.ServiceResult; public class VersionProtocolServiceImpl implements VersionProtocolService { - @PolicyScope - public static final String VERSIONING_REQUEST_SCOPE = "request.version"; - private final ProtocolVersionRegistry registry; private final ProtocolTokenValidator tokenValidator; @@ -37,7 +34,7 @@ public VersionProtocolServiceImpl(ProtocolVersionRegistry registry, ProtocolToke @Override public ServiceResult getAll(TokenRepresentation tokenRepresentation) { - return tokenValidator.verify(tokenRepresentation, VERSIONING_REQUEST_SCOPE) + return tokenValidator.verify(tokenRepresentation, RequestVersionPolicyContext::new) .map(it -> registry.getAll()); } } diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/transferprocess/TransferProcessProtocolServiceImpl.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/transferprocess/TransferProcessProtocolServiceImpl.java index f8a700e2750..6b06004aec9 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/transferprocess/TransferProcessProtocolServiceImpl.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/transferprocess/TransferProcessProtocolServiceImpl.java @@ -33,7 +33,7 @@ import org.eclipse.edc.connector.controlplane.transfer.spi.types.protocol.TransferStartMessage; import org.eclipse.edc.connector.controlplane.transfer.spi.types.protocol.TransferSuspensionMessage; import org.eclipse.edc.connector.controlplane.transfer.spi.types.protocol.TransferTerminationMessage; -import org.eclipse.edc.policy.engine.spi.PolicyScope; +import org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext; import org.eclipse.edc.spi.agent.ParticipantAgent; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.monitor.Monitor; @@ -57,9 +57,6 @@ public class TransferProcessProtocolServiceImpl implements TransferProcessProtocolService { - @PolicyScope - public static final String TRANSFER_PROCESS_REQUEST_SCOPE = "request.transfer.process"; - private final TransferProcessStore transferProcessStore; private final TransactionContext transactionContext; private final ContractNegotiationStore negotiationStore; @@ -278,7 +275,7 @@ private ServiceResult fetchRequestContext(T i } private ServiceResult verifyRequest(TokenRepresentation tokenRepresentation, TransferRequestMessageContext context, RemoteMessage message) { - var result = protocolTokenValidator.verify(tokenRepresentation, TRANSFER_PROCESS_REQUEST_SCOPE, context.agreement().getPolicy(), message); + var result = protocolTokenValidator.verify(tokenRepresentation, RequestTransferProcessPolicyContext::new, context.agreement().getPolicy(), message); if (result.failed()) { monitor.debug(() -> "Verification Failed: %s".formatted(result.getFailureDetail())); return ServiceResult.notFound("Not found"); diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/catalog/CatalogProtocolServiceImplTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/catalog/CatalogProtocolServiceImplTest.java index 92fcd11db73..d989b218c85 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/catalog/CatalogProtocolServiceImplTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/catalog/CatalogProtocolServiceImplTest.java @@ -38,7 +38,6 @@ import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.connector.controlplane.services.catalog.CatalogProtocolServiceImpl.CATALOGING_REQUEST_SCOPE; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.spi.result.ServiceFailure.Reason.NOT_FOUND; import static org.eclipse.edc.spi.result.ServiceFailure.Reason.UNAUTHORIZED; @@ -87,7 +86,7 @@ void shouldReturnCatalogWithConnectorDataServiceAndItsDataset() { var participantAgent = createParticipantAgent(); var dataService = DataService.Builder.newInstance().build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CATALOGING_REQUEST_SCOPE), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(dataServiceRegistry.getDataServices()).thenReturn(List.of(dataService)); when(datasetResolver.query(any(), any())).thenReturn(Stream.of(createDataset())); @@ -108,7 +107,7 @@ void shouldFail_whenTokenValidationFails() { var message = CatalogRequestMessage.Builder.newInstance().protocol("protocol").querySpec(querySpec).build(); var tokenRepresentation = createTokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CATALOGING_REQUEST_SCOPE), eq(message))).thenReturn(ServiceResult.unauthorized("unauthorized")); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), eq(message))).thenReturn(ServiceResult.unauthorized("unauthorized")); var result = service.getCatalog(message, tokenRepresentation); @@ -125,7 +124,7 @@ void shouldReturnDataset() { var participantAgent = createParticipantAgent(); var dataset = createDataset(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CATALOGING_REQUEST_SCOPE))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any())).thenReturn(ServiceResult.success(participantAgent)); when(datasetResolver.getById(any(), any())).thenReturn(dataset); var result = service.getDataset("datasetId", tokenRepresentation); @@ -140,7 +139,7 @@ void shouldFail_whenDatasetIsNull() { var participantAgent = createParticipantAgent(); var tokenRepresentation = createTokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CATALOGING_REQUEST_SCOPE))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any())).thenReturn(ServiceResult.success(participantAgent)); when(datasetResolver.getById(any(), any())).thenReturn(null); var result = service.getDataset("datasetId", tokenRepresentation); @@ -152,7 +151,7 @@ void shouldFail_whenDatasetIsNull() { void shouldFail_whenTokenValidationFails() { var tokenRepresentation = createTokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CATALOGING_REQUEST_SCOPE))).thenReturn(ServiceResult.unauthorized("unauthorized")); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any())).thenReturn(ServiceResult.unauthorized("unauthorized")); var result = service.getDataset("datasetId", tokenRepresentation); diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImplTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImplTest.java index 4ef4df25abb..201dd5a96c5 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImplTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/contractnegotiation/ContractNegotiationProtocolServiceImplTest.java @@ -72,7 +72,6 @@ import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.REQUESTED; import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.TERMINATED; import static org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiationStates.VERIFIED; -import static org.eclipse.edc.connector.controlplane.services.contractnegotiation.ContractNegotiationProtocolServiceImpl.CONTRACT_NEGOTIATION_REQUEST_SCOPE; import static org.eclipse.edc.connector.controlplane.services.contractnegotiation.ContractNegotiationProtocolServiceImplTest.TestFunctions.contractOffer; import static org.eclipse.edc.connector.controlplane.services.contractnegotiation.ContractNegotiationProtocolServiceImplTest.TestFunctions.createPolicy; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; @@ -128,7 +127,7 @@ void notifyAccepted_shouldTransitionToAccepted() { .type(ContractNegotiationEventMessage.Type.ACCEPTED) .policy(Policy.Builder.newInstance().build()) .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent)); when(store.findById(any())).thenReturn(contractNegotiation); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(contractNegotiation)); @@ -161,7 +160,7 @@ void notifyAgreed_shouldTransitionToAgreed() { .contractAgreement(contractAgreement) .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById(any())).thenReturn(negotiationConsumerRequested); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiationConsumerRequested)); when(validationService.validateConfirmed(eq(participantAgent), eq(contractAgreement), any(ContractOffer.class))).thenReturn(Result.success()); @@ -196,7 +195,7 @@ void notifyVerified_shouldTransitionToVerified() { .providerPid("providerPid") .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById(any())).thenReturn(negotiation); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiation)); when(validationService.validateRequest(any(ParticipantAgent.class), any(ContractNegotiation.class))).thenReturn(Result.success()); @@ -227,7 +226,7 @@ void notifyFinalized_shouldTransitionToFinalized() { .build(); var tokenRepresentation = tokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(negotiation); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiation)); @@ -258,7 +257,7 @@ void notifyTerminated_shouldTransitionToTerminated() { .build(); var tokenRepresentation = tokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(negotiation); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiation)); @@ -283,7 +282,7 @@ void findById_shouldReturnNegotiation_whenValidCounterParty() { var contractOffer = contractOffer(); var negotiation = contractNegotiationBuilder().id(id).type(PROVIDER).contractOffer(contractOffer).state(VERIFIED.code()).build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), isNull())) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), isNull())) .thenReturn(ServiceResult.success(participantAgent)); when(store.findById(id)).thenReturn(negotiation); when(validationService.validateRequest(participantAgent, negotiation)).thenReturn(Result.success()); @@ -298,7 +297,7 @@ void findById_shouldReturnNegotiation_whenValidCounterParty() { @Test void findById_shouldReturnNotFound_whenNegotiationNotFound() { var tokenRepresentation = tokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any())) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any())) .thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(null); @@ -317,7 +316,7 @@ void findById_shouldReturnBadRequest_whenCounterPartyUnauthorized() { var tokenRepresentation = tokenRepresentation(); var contractOffer = contractOffer(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), isNull())).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), isNull())).thenReturn(ServiceResult.success(participantAgent)); var negotiation = contractNegotiationBuilder().id(id).type(PROVIDER).contractOffer(contractOffer).state(VERIFIED.code()).build(); @@ -336,7 +335,7 @@ void findById_shouldReturnBadRequest_whenCounterPartyUnauthorized() { @ArgumentsSource(NotifyArguments.class) void notify_shouldReturnNotFound_whenNotFound(MethodCall methodCall, M message) { var tokenRepresentation = tokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent())); when(store.findByIdAndLease(any())).thenReturn(StoreResult.notFound("not found")); @@ -358,7 +357,7 @@ void notify_shouldReturnBadRequest_whenValidationFails when(validatableOffer.getContractPolicy()).thenReturn(createPolicy()); when(consumerOfferResolver.resolveOffer(any())).thenReturn(ServiceResult.success(validatableOffer)); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(createContractNegotiationOffered()); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(createContractNegotiationOffered())); @@ -382,7 +381,7 @@ void notify_shouldReturnUnauthorized_whenTokenValidati when(validatableOffer.getContractPolicy()).thenReturn(createPolicy()); when(consumerOfferResolver.resolveOffer(any())).thenReturn(ServiceResult.success(validatableOffer)); when(store.findById(any())).thenReturn(createContractNegotiationOffered()); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.unauthorized("unauthorized")); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.unauthorized("unauthorized")); var result = methodCall.call(service, message, tokenRepresentation); @@ -533,7 +532,7 @@ void shouldInitiateNegotiation_whenNegotiationDoesNotExist() { when(validatableOffer.getContractPolicy()).thenReturn(createPolicy()); when(consumerOfferResolver.resolveOffer(contractOffer.getId())).thenReturn(ServiceResult.success(validatableOffer)); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findByIdAndLease(any())).thenReturn(StoreResult.notFound("not found")); when(validationService.validateInitialOffer(participantAgent, validatableOffer)).thenReturn(Result.success(validatedOffer)); @@ -576,7 +575,7 @@ void shouldTransitionToRequested_whenNegotiationFound() { when(validatableOffer.getContractPolicy()).thenReturn(createPolicy()); when(consumerOfferResolver.resolveOffer(contractOffer.getId())).thenReturn(ServiceResult.success(validatableOffer)); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById(any())).thenReturn(negotiation); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiation)); when(validationService.validateInitialOffer(participantAgent, validatableOffer)).thenReturn(Result.success(validatedOffer)); @@ -637,7 +636,7 @@ void shouldInitiateNegotiation_whenNegotiationDoesNotExist() { .contractOffer(contractOffer) .providerPid("providerPid") .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent())); var result = service.notifyOffered(message, tokenRepresentation); @@ -677,7 +676,7 @@ void shouldTransitionToOffered_whenNegotiationAlreadyExist() { .build(); var negotiation = createContractNegotiationRequested(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent)); when(store.findById(processId)).thenReturn(negotiation); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiation)); @@ -705,7 +704,7 @@ void shouldReturnNotFound_whenOfferNotFound() { .consumerPid("consumerPid") .providerPid("providerPid") .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent())); when(store.findByIdAndLease(any())).thenReturn(StoreResult.notFound("not found")); @@ -732,7 +731,7 @@ void notify_shouldStoreReceivedMessageId(Method when(validatableOffer.getContractPolicy()).thenReturn(createPolicy()); when(consumerOfferResolver.resolveOffer(any())).thenReturn(ServiceResult.success(validatableOffer)); - when(protocolTokenValidator.verify(any(), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(any(), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(negotiation); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiation)); @@ -762,7 +761,7 @@ void notify_shouldIgnoreMessage_whenAlreadyRece when(validatableOffer.getContractPolicy()).thenReturn(createPolicy()); when(consumerOfferResolver.resolveOffer(any())).thenReturn(ServiceResult.success(validatableOffer)); - when(protocolTokenValidator.verify(any(), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(any(), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(negotiation); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiation)); @@ -788,7 +787,7 @@ void notify_shouldIgnoreMessage_whenFinalState( when(validatableOffer.getContractPolicy()).thenReturn(createPolicy()); when(consumerOfferResolver.resolveOffer(any())).thenReturn(ServiceResult.success(validatableOffer)); - when(protocolTokenValidator.verify(any(), eq(CONTRACT_NEGOTIATION_REQUEST_SCOPE), any(), eq(message))) + when(protocolTokenValidator.verify(any(), any(), any(), eq(message))) .thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(negotiation); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(negotiation)); diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/protocol/ProtocolTokenValidatorImplTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/protocol/ProtocolTokenValidatorImplTest.java index db3b57a881b..3463a98719e 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/protocol/ProtocolTokenValidatorImplTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/protocol/ProtocolTokenValidatorImplTest.java @@ -14,6 +14,7 @@ package org.eclipse.edc.connector.controlplane.services.protocol; +import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.agent.ParticipantAgent; @@ -21,6 +22,7 @@ import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.iam.RequestContext; +import org.eclipse.edc.spi.iam.RequestScope; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceFailure; @@ -30,9 +32,10 @@ import static java.util.Collections.emptyMap; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.spi.result.ServiceFailure.Reason.UNAUTHORIZED; +import static org.mockito.AdditionalMatchers.and; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -54,14 +57,14 @@ void shouldVerifyToken() { when(identityService.verifyJwtToken(any(), any())).thenReturn(Result.success(claimToken)); when(agentService.createFor(any())).thenReturn(participantAgent); - var result = validator.verify(tokenRepresentation, "scope", policy, new TestMessage()); + var result = validator.verify(tokenRepresentation, TestRequestPolicyContext::new, policy, new TestMessage()); assertThat(result).isSucceeded().isSameAs(participantAgent); verify(agentService).createFor(claimToken); - verify(policyEngine).evaluate(eq("scope"), same(policy), argThat(ctx -> { - var reqContext = ctx.getContextData(RequestContext.class); + verify(policyEngine).evaluate(same(policy), and(isA(RequestPolicyContext.class), argThat(ctx -> { + var reqContext = ctx.requestContext(); return reqContext.getMessage().getClass().equals(TestMessage.class) && reqContext.getDirection().equals(RequestContext.Direction.Ingress); - })); + }))); verify(identityService).verifyJwtToken(same(tokenRepresentation), any()); } @@ -69,11 +72,17 @@ void shouldVerifyToken() { void shouldReturnUnauthorized_whenTokenIsNotValid() { when(identityService.verifyJwtToken(any(), any())).thenReturn(Result.failure("failure")); - var result = validator.verify(TokenRepresentation.Builder.newInstance().build(), "scope", Policy.Builder.newInstance().build(), new TestMessage()); + var result = validator.verify(TokenRepresentation.Builder.newInstance().build(), TestRequestPolicyContext::new, Policy.Builder.newInstance().build(), new TestMessage()); assertThat(result).isFailed().extracting(ServiceFailure::getReason).isEqualTo(UNAUTHORIZED); } + private RequestPolicyContext policyContext() { + var requestScopeBuilder = RequestScope.Builder.newInstance(); + var requestContext = RequestContext.Builder.newInstance().build(); + return new TestRequestPolicyContext(requestContext, requestScopeBuilder); + } + static class TestMessage implements RemoteMessage { @Override public String getProtocol() { @@ -90,4 +99,16 @@ public String getCounterPartyId() { return null; } } + + private static class TestRequestPolicyContext extends RequestPolicyContext { + + TestRequestPolicyContext(RequestContext requestContext, RequestScope.Builder requestScopeBuilder) { + super(requestContext, requestScopeBuilder); + } + + @Override + public String scope() { + return "request.test"; + } + } } diff --git a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/transferprocess/TransferProcessProtocolServiceImplTest.java b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/transferprocess/TransferProcessProtocolServiceImplTest.java index f90c2351ec3..d4bcc28f659 100644 --- a/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/transferprocess/TransferProcessProtocolServiceImplTest.java +++ b/core/control-plane/control-plane-aggregate-services/src/test/java/org/eclipse/edc/connector/controlplane/services/transferprocess/TransferProcessProtocolServiceImplTest.java @@ -64,7 +64,6 @@ import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.connector.controlplane.services.transferprocess.TransferProcessProtocolServiceImpl.TRANSFER_PROCESS_REQUEST_SCOPE; import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess.Type.CONSUMER; import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess.Type.PROVIDER; import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates.COMPLETED; @@ -131,7 +130,7 @@ void notifyRequested_validAgreement_shouldInitiateTransfer() { .dataDestination(DataAddress.Builder.newInstance().type("any").build()) .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); when(validationService.validateAgreement(any(ParticipantAgent.class), any())).thenReturn(Result.success(null)); when(dataAddressValidator.validateDestination(any())).thenReturn(ValidationResult.success()); @@ -162,7 +161,7 @@ void notifyRequested_doNothingIfProcessAlreadyExist() { var participantAgent = participantAgent(); var tokenRepresentation = tokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); when(validationService.validateAgreement(any(ParticipantAgent.class), any())).thenReturn(Result.success(null)); when(dataAddressValidator.validateDestination(any())).thenReturn(ValidationResult.success()); @@ -187,7 +186,7 @@ void notifyRequested_invalidAgreement_shouldNotInitiateTransfer() { var participantAgent = participantAgent(); var tokenRepresentation = tokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); when(validationService.validateAgreement(any(ParticipantAgent.class), any())).thenReturn(Result.failure("error")); when(dataAddressValidator.validateDestination(any())).thenReturn(ValidationResult.success()); @@ -212,7 +211,7 @@ void notifyRequested_invalidDestination_shouldNotInitiateTransfer() { .build(); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(dataAddressValidator.validateDestination(any())).thenReturn(ValidationResult.failure(violation("invalid data address", "path"))); var result = service.notifyRequested(message, tokenRepresentation); @@ -237,7 +236,7 @@ void notifyCompleted_shouldTransitionToCompleted() { var transferProcess = transferProcess(STARTED, "transferProcessId"); when(store.findById("correlationId")).thenReturn(transferProcess); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(participantAgent, agreement)).thenReturn(Result.success()); @@ -265,7 +264,7 @@ void notifyCompleted_shouldReturnConflict_whenStatusIsNotValid() { .build(); var agreement = contractAgreement(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -294,7 +293,7 @@ void notifyCompleted_shouldReturnBadRequest_whenCounterPartyUnauthorized() { var agreement = contractAgreement(); var transferProcess = transferProcess(STARTED, "transferProcessId"); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -327,7 +326,7 @@ void notifyTerminated_shouldTransitionToTerminated() { var agreement = contractAgreement(); var transferProcess = transferProcess(STARTED, "transferProcessId"); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -357,7 +356,7 @@ void notifyTerminated_shouldReturnConflict_whenTransferProcessCannotBeTerminated .reason("TestReason") .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -387,7 +386,7 @@ void notifyTerminated_shouldReturnBadRequest_whenCounterPartyUnauthorized() { .reason("TestReason") .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -412,7 +411,7 @@ void findById_shouldReturnTransferProcess_whenValidCounterParty() { var transferProcess = transferProcess(INITIAL, processId); var agreement = contractAgreement(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), isNull())).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), isNull())).thenReturn(ServiceResult.success(participantAgent)); when(store.findById(processId)).thenReturn(transferProcess); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(participantAgent, agreement)).thenReturn(Result.success()); @@ -429,7 +428,7 @@ void findById_shouldReturnNotFound_whenNegotiationNotFound() { var participantAgent = participantAgent(); var tokenRepresentation = tokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any())).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any())).thenReturn(ServiceResult.success(participantAgent)); when(store.findById(any())).thenReturn(null); var result = service.findById("invalidId", tokenRepresentation); @@ -448,7 +447,7 @@ void findById_shouldReturnBadRequest_whenCounterPartyUnauthorized() { var tokenRepresentation = tokenRepresentation(); var agreement = contractAgreement(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), isNull())).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), isNull())).thenReturn(ServiceResult.success(participantAgent)); when(store.findById(processId)).thenReturn(transferProcess); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); when(validationService.validateRequest(participantAgent, agreement)).thenReturn(Result.failure("error")); @@ -467,7 +466,7 @@ void notify_shouldFail_whenTransferProcessNotFound(Met var participantAgent = participantAgent(); var tokenRepresentation = tokenRepresentation(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any())).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any())).thenReturn(ServiceResult.success(participantAgent)); when(store.findByIdAndLease(any())).thenReturn(StoreResult.notFound("not found")); var result = methodCall.call(service, message, tokenRepresentation); @@ -485,7 +484,7 @@ void notify_shouldFail_whenTokenValidationFails(Method when(store.findById(any())).thenReturn(transferProcessBuilder().build()); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(transferProcessBuilder().build())); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.unauthorized("unauthorized")); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.unauthorized("unauthorized")); var result = methodCall.call(service, message, tokenRepresentation); @@ -582,7 +581,7 @@ void shouldTransitionToStarted() { var agreement = contractAgreement(); var transferProcess = transferProcess(STARTED, "transferProcessId"); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -615,7 +614,7 @@ void shouldReturnConflict_whenTransferCannotBeStarted() { .build(); var agreement = contractAgreement(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -644,7 +643,7 @@ void shouldReturnBadRequest_whenCounterPartyUnauthorized() { var agreement = contractAgreement(); var transferProcess = transferProcess(REQUESTED, "transferProcessId"); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -681,7 +680,7 @@ void shouldTransitionToStartedAndStartDataFlow_whenProvider() { var transferProcess = transferProcessBuilder().id("transferProcessId") .state(SUSPENDED.code()).type(PROVIDER).build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -715,7 +714,7 @@ void shouldReturnError_whenStatusIsNotSuspendedAndTypeProvider() { var dataFlowResponse = DataFlowResponse.Builder.newInstance().dataPlaneId("dataPlaneId").build(); when(dataFlowManager.start(any(), any())).thenReturn(StatusResult.success(dataFlowResponse)); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -745,7 +744,7 @@ void consumer_shouldTransitionToSuspended() { var agreement = contractAgreement(); var transferProcess = transferProcessBuilder().state(STARTED.code()).type(CONSUMER).build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -774,7 +773,7 @@ void provider_shouldSuspendDataFlowAndTransitionToSuspended() { var agreement = contractAgreement(); var transferProcess = transferProcessBuilder().state(STARTED.code()).type(PROVIDER).build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -805,7 +804,7 @@ void provider_shouldReturnConflict_whenDataFlowCannotBeSuspended() { var agreement = contractAgreement(); var transferProcess = transferProcessBuilder().state(STARTED.code()).type(PROVIDER).build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -834,7 +833,7 @@ void shouldReturnConflict_whenTransferProcessCannotBeSuspended() { .reason("TestReason") .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -864,7 +863,7 @@ void shouldReturnBadRequest_whenCounterPartyUnauthorized() { .reason("TestReason") .build(); - when(protocolTokenValidator.verify(eq(tokenRepresentation), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); + when(protocolTokenValidator.verify(eq(tokenRepresentation), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent)); when(store.findById("correlationId")).thenReturn(transferProcess); when(store.findByIdAndLease("correlationId")).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(agreement); @@ -891,7 +890,7 @@ void notify_shouldStoreReceivedMessageId(Method TransferProcess.Type type, TransferProcessStates currentState) { var transferProcess = transferProcessBuilder().state(currentState.code()).type(type).build(); - when(protocolTokenValidator.verify(any(), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent())); + when(protocolTokenValidator.verify(any(), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(transferProcess); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); @@ -915,7 +914,7 @@ void notify_shouldIgnoreMessage_whenAlreadyRece TransferProcessStates currentState) { var transferProcess = transferProcessBuilder().state(currentState.code()).type(type).build(); transferProcess.protocolMessageReceived(message.getId()); - when(protocolTokenValidator.verify(any(), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent())); + when(protocolTokenValidator.verify(any(), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(transferProcess); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); @@ -934,7 +933,7 @@ void notify_shouldIgnoreMessage_whenAlreadyRece void notify_shouldIgnoreMessage_whenFinalState(MethodCall methodCall, M message, TransferProcess.Type type) { var transferProcess = transferProcessBuilder().state(COMPLETED.code()).type(type).build(); - when(protocolTokenValidator.verify(any(), eq(TRANSFER_PROCESS_REQUEST_SCOPE), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent())); + when(protocolTokenValidator.verify(any(), any(), any(), eq(message))).thenReturn(ServiceResult.success(participantAgent())); when(store.findById(any())).thenReturn(transferProcess); when(store.findByIdAndLease(any())).thenReturn(StoreResult.success(transferProcess)); when(negotiationStore.findContractAgreement(any())).thenReturn(contractAgreement()); diff --git a/core/control-plane/control-plane-catalog/src/main/java/org/eclipse/edc/connector/controlplane/catalog/CatalogCoreExtension.java b/core/control-plane/control-plane-catalog/src/main/java/org/eclipse/edc/connector/controlplane/catalog/CatalogCoreExtension.java index 249a66301ca..3bde3348e9d 100644 --- a/core/control-plane/control-plane-catalog/src/main/java/org/eclipse/edc/connector/controlplane/catalog/CatalogCoreExtension.java +++ b/core/control-plane/control-plane-catalog/src/main/java/org/eclipse/edc/connector/controlplane/catalog/CatalogCoreExtension.java @@ -17,24 +17,24 @@ import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; import org.eclipse.edc.connector.controlplane.catalog.spi.DatasetResolver; import org.eclipse.edc.connector.controlplane.catalog.spi.DistributionResolver; +import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext; import org.eclipse.edc.connector.controlplane.contract.spi.offer.store.ContractDefinitionStore; import org.eclipse.edc.connector.controlplane.policy.spi.store.PolicyDefinitionStore; import org.eclipse.edc.policy.engine.spi.PolicyEngine; -import org.eclipse.edc.policy.engine.spi.PolicyScope; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.spi.query.CriterionOperatorRegistry; import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +import static org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext.CATALOG_SCOPE; @Extension(CatalogCoreExtension.NAME) public class CatalogCoreExtension implements ServiceExtension { public static final String NAME = "Catalog Core"; - @PolicyScope - public static final String CATALOG_SCOPE = "catalog"; - @Inject private AssetIndex assetIndex; @@ -58,6 +58,11 @@ public String name() { return NAME; } + @Override + public void initialize(ServiceExtensionContext context) { + policyEngine.registerScope(CATALOG_SCOPE, CatalogPolicyContext.class); + } + @Provider public DatasetResolver datasetResolver() { var contractDefinitionResolver = new ContractDefinitionResolverImpl(contractDefinitionStore, policyEngine, policyDefinitionStore); diff --git a/core/control-plane/control-plane-catalog/src/main/java/org/eclipse/edc/connector/controlplane/catalog/ContractDefinitionResolverImpl.java b/core/control-plane/control-plane-catalog/src/main/java/org/eclipse/edc/connector/controlplane/catalog/ContractDefinitionResolverImpl.java index 6397a64d42c..05eb33cdc78 100644 --- a/core/control-plane/control-plane-catalog/src/main/java/org/eclipse/edc/connector/controlplane/catalog/ContractDefinitionResolverImpl.java +++ b/core/control-plane/control-plane-catalog/src/main/java/org/eclipse/edc/connector/controlplane/catalog/ContractDefinitionResolverImpl.java @@ -16,11 +16,11 @@ import org.eclipse.edc.connector.controlplane.catalog.spi.ContractDefinitionResolver; import org.eclipse.edc.connector.controlplane.catalog.spi.ResolvedContractDefinitions; +import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext; import org.eclipse.edc.connector.controlplane.contract.spi.offer.store.ContractDefinitionStore; import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; import org.eclipse.edc.connector.controlplane.policy.spi.store.PolicyDefinitionStore; -import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.agent.ParticipantAgent; @@ -31,7 +31,6 @@ import java.util.Optional; import static java.lang.String.format; -import static org.eclipse.edc.connector.controlplane.catalog.CatalogCoreExtension.CATALOG_SCOPE; /** * Determines the contract definitions applicable to a {@link ParticipantAgent} by evaluating the access control and @@ -54,14 +53,13 @@ public ResolvedContractDefinitions resolveFor(ParticipantAgent agent) { var policies = new HashMap(); var definitions = definitionStore.findAll(QuerySpec.max()) .filter(definition -> { - var policyContext = PolicyContextImpl.Builder.newInstance().additional(ParticipantAgent.class, agent).build(); var accessResult = Optional.of(definition.getAccessPolicyId()) .map(policyId -> policies.computeIfAbsent(policyId, key -> Optional.ofNullable(policyStore.findById(key)) .map(PolicyDefinition::getPolicy) .orElse(null)) ) - .map(policy -> policyEngine.evaluate(CATALOG_SCOPE, policy, policyContext)) + .map(policy -> policyEngine.evaluate(policy, new CatalogPolicyContext(agent))) .orElse(Result.failure(format("Policy %s not found", definition.getAccessPolicyId()))); return accessResult.succeeded(); diff --git a/core/control-plane/control-plane-catalog/src/test/java/org/eclipse/edc/connector/controlplane/catalog/ContractDefinitionResolverImplTest.java b/core/control-plane/control-plane-catalog/src/test/java/org/eclipse/edc/connector/controlplane/catalog/ContractDefinitionResolverImplTest.java index 320e094f036..162a96f8334 100644 --- a/core/control-plane/control-plane-catalog/src/test/java/org/eclipse/edc/connector/controlplane/catalog/ContractDefinitionResolverImplTest.java +++ b/core/control-plane/control-plane-catalog/src/test/java/org/eclipse/edc/connector/controlplane/catalog/ContractDefinitionResolverImplTest.java @@ -15,6 +15,7 @@ package org.eclipse.edc.connector.controlplane.catalog; +import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext; import org.eclipse.edc.connector.controlplane.contract.spi.offer.store.ContractDefinitionStore; import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; @@ -32,7 +33,6 @@ import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; -import static org.eclipse.edc.connector.controlplane.catalog.CatalogCoreExtension.CATALOG_SCOPE; import static org.mockito.AdditionalMatchers.and; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; @@ -59,7 +59,7 @@ void shouldReturnDefinition_whenAccessPolicySatisfied() { var agent = new ParticipantAgent(emptyMap(), emptyMap()); var def = PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).build(); when(policyStore.findById(any())).thenReturn(def); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.success()); when(definitionStore.findAll(any())).thenReturn(Stream.of(createContractDefinition())); var result = resolver.resolveFor(agent); @@ -67,9 +67,8 @@ void shouldReturnDefinition_whenAccessPolicySatisfied() { assertThat(result.contractDefinitions()).hasSize(1); assertThat(result.policies()).hasSize(1); verify(policyEngine, atLeastOnce()).evaluate( - eq(CATALOG_SCOPE), eq(def.getPolicy()), - and(isA(PolicyContext.class), argThat(c -> c.getContextData(ParticipantAgent.class).equals(agent))) + and(isA(CatalogPolicyContext.class), argThat(c -> c.agent().equals(agent))) ); verify(definitionStore).findAll(any()); } @@ -80,7 +79,7 @@ void shouldNotReturnDefinition_whenAccessPolicyNotSatisfied() { var definition = PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).id("access").build(); when(policyStore.findById(any())).thenReturn(definition); var contractDefinition = createContractDefinition(); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.failure("invalid")); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.failure("invalid")); when(definitionStore.findAll(any())).thenReturn(Stream.of(contractDefinition)); var result = resolver.resolveFor(agent); @@ -94,7 +93,7 @@ void shouldNotReturnDefinition_whenAccessPolicyNotSatisfied() { void shouldNotReturnDefinition_whenAccessPolicyDoesNotExist() { var agent = new ParticipantAgent(emptyMap(), emptyMap()); when(policyStore.findById(any())).thenReturn(null); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.success()); when(definitionStore.findAll(QuerySpec.max())).thenReturn(Stream.of(createContractDefinition())); var result = resolver.resolveFor(agent); @@ -111,7 +110,7 @@ void shouldQueryPolicyOnce_whenDifferentDefinitionsHaveSamePolicy() { var policy = Policy.Builder.newInstance().build(); var policyDefinition = PolicyDefinition.Builder.newInstance().policy(policy).build(); when(policyStore.findById(any())).thenReturn(policyDefinition); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.success()); when(definitionStore.findAll(any())).thenReturn(Stream.of(contractDefinition1, contractDefinition2)); var result = resolver.resolveFor(new ParticipantAgent(emptyMap(), emptyMap())); diff --git a/core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/ContractCoreExtension.java b/core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/ContractCoreExtension.java index 1a0415585d7..34d3924a357 100644 --- a/core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/ContractCoreExtension.java +++ b/core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/ContractCoreExtension.java @@ -18,6 +18,7 @@ package org.eclipse.edc.connector.controlplane.contract; import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; +import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext; import org.eclipse.edc.connector.controlplane.contract.listener.ContractNegotiationEventListener; import org.eclipse.edc.connector.controlplane.contract.negotiation.ConsumerContractNegotiationManagerImpl; import org.eclipse.edc.connector.controlplane.contract.negotiation.ProviderContractNegotiationManagerImpl; @@ -28,13 +29,14 @@ import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.ProviderContractNegotiationManager; import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.observe.ContractNegotiationObservable; import org.eclipse.edc.connector.controlplane.contract.spi.negotiation.store.ContractNegotiationStore; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext; import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.controlplane.contract.spi.validation.ContractValidationService; import org.eclipse.edc.connector.controlplane.contract.validation.ContractValidationServiceImpl; import org.eclipse.edc.connector.controlplane.policy.contract.ContractExpiryCheckFunction; import org.eclipse.edc.connector.controlplane.policy.spi.store.PolicyDefinitionStore; import org.eclipse.edc.policy.engine.spi.PolicyEngine; -import org.eclipse.edc.policy.engine.spi.PolicyScope; import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.runtime.metamodel.annotation.CoreExtension; @@ -57,7 +59,9 @@ import java.time.Clock; -import static org.eclipse.edc.connector.controlplane.contract.spi.validation.ContractValidationService.TRANSFER_SCOPE; +import static org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext.CATALOG_SCOPE; +import static org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext.NEGOTIATION_SCOPE; +import static org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext.TRANSFER_SCOPE; import static org.eclipse.edc.connector.controlplane.policy.contract.ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_USE_ACTION_ATTRIBUTE; import static org.eclipse.edc.statemachine.AbstractStateEntityManager.DEFAULT_BATCH_SIZE; @@ -96,9 +100,6 @@ public class ContractCoreExtension implements ServiceExtension { @Setting(value = "The base delay for the provider negotiation retry mechanism in millisecond", type = "long", defaultValue = DEFAULT_SEND_RETRY_BASE_DELAY + "") private static final String NEGOTIATION_PROVIDER_SEND_RETRY_BASE_DELAY_MS = "edc.negotiation.provider.send.retry.base-delay.ms"; - @PolicyScope - public static final String CATALOG_SCOPE = "catalog"; - private ConsumerContractNegotiationManagerImpl consumerNegotiationManager; private ProviderContractNegotiationManagerImpl providerNegotiationManager; @@ -156,6 +157,9 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { typeManager.registerTypes(ContractNegotiation.class); + policyEngine.registerScope(CATALOG_SCOPE, CatalogPolicyContext.class); + policyEngine.registerScope(NEGOTIATION_SCOPE, ContractNegotiationPolicyContext.class); + policyEngine.registerScope(TRANSFER_SCOPE, TransferProcessPolicyContext.class); registerServices(context); } @@ -186,8 +190,9 @@ private void registerServices(ServiceExtensionContext context) { // bind/register rule to evaluate contract expiry ruleBindingRegistry.bind(ODRL_USE_ACTION_ATTRIBUTE, TRANSFER_SCOPE); ruleBindingRegistry.bind(CONTRACT_EXPIRY_EVALUATION_KEY, TRANSFER_SCOPE); - var function = new ContractExpiryCheckFunction(); - policyEngine.registerFunction(TRANSFER_SCOPE, Permission.class, CONTRACT_EXPIRY_EVALUATION_KEY, function); + + policyEngine.registerFunction(TransferProcessPolicyContext.class, Permission.class, CONTRACT_EXPIRY_EVALUATION_KEY, + new ContractExpiryCheckFunction<>()); var iterationWaitMillis = context.getSetting(NEGOTIATION_STATE_MACHINE_ITERATION_WAIT_MILLIS, DEFAULT_ITERATION_WAIT); var waitStrategy = context.hasService(NegotiationWaitStrategy.class) ? context.getService(NegotiationWaitStrategy.class) : new ExponentialWaitStrategy(iterationWaitMillis); diff --git a/core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImpl.java b/core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImpl.java index 24ec40a1985..97ebec61ab6 100644 --- a/core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImpl.java +++ b/core/control-plane/control-plane-contract/src/main/java/org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImpl.java @@ -18,15 +18,17 @@ import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; +import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext; import org.eclipse.edc.connector.controlplane.contract.policy.PolicyEquality; import org.eclipse.edc.connector.controlplane.contract.spi.ContractOfferId; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext; import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractOffer; import org.eclipse.edc.connector.controlplane.contract.spi.validation.ContractValidationService; import org.eclipse.edc.connector.controlplane.contract.spi.validation.ValidatableConsumerOffer; import org.eclipse.edc.connector.controlplane.contract.spi.validation.ValidatedConsumerOffer; -import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.agent.ParticipantAgent; @@ -40,7 +42,6 @@ import java.util.Optional; import static java.lang.String.format; -import static org.eclipse.edc.connector.controlplane.contract.ContractCoreExtension.CATALOG_SCOPE; import static org.eclipse.edc.spi.result.Result.failure; import static org.eclipse.edc.spi.result.Result.success; @@ -75,12 +76,8 @@ public ContractValidationServiceImpl(AssetIndex assetIndex, return failure("Invalid provider credentials"); } - var policyContext = PolicyContextImpl.Builder.newInstance() - .additional(ParticipantAgent.class, agent) - .additional(ContractAgreement.class, agreement) - .additional(Instant.class, Instant.now()) - .build(); - var policyResult = policyEngine.evaluate(TRANSFER_SCOPE, agreement.getPolicy(), policyContext); + var policyContext = new TransferProcessPolicyContext(agent, agreement, Instant.now()); + var policyResult = policyEngine.evaluate(agreement.getPolicy(), policyContext); if (!policyResult.succeeded()) { return failure(format("Policy does not fulfill the agreement %s, policy evaluation %s", agreement.getId(), policyResult.getFailureDetail())); } @@ -128,10 +125,10 @@ private Result validateInitialOffer(ValidatableConsumerOffer consumerOff return failure("Invalid consumer identity"); } - var accessPolicyResult = evaluatePolicy(consumerOffer.getAccessPolicy(), CATALOG_SCOPE, agent, consumerOffer.getOfferId()); + var accessPolicyResult = policyEngine.evaluate(consumerOffer.getAccessPolicy(), new CatalogPolicyContext(agent)); if (accessPolicyResult.failed()) { - return accessPolicyResult; + return accessPolicyResult.mapFailure(); } // verify the target asset exists @@ -148,16 +145,8 @@ private Result validateInitialOffer(ValidatableConsumerOffer consumerOff } var contractPolicy = consumerOffer.getContractPolicy().withTarget(consumerOffer.getOfferId().assetIdPart()); - return evaluatePolicy(contractPolicy, NEGOTIATION_SCOPE, agent, consumerOffer.getOfferId()); - } - - private Result evaluatePolicy(Policy policy, String scope, ParticipantAgent agent, ContractOfferId offerId) { - var policyContext = PolicyContextImpl.Builder.newInstance().additional(ParticipantAgent.class, agent).build(); - var policyResult = policyEngine.evaluate(scope, policy, policyContext); - if (policyResult.failed()) { - return failure(format("Policy in scope %s not fulfilled for offer %s, policy evaluation %s", scope, offerId.toString(), policyResult.getFailureDetail())); - } - return Result.success(policy); + return policyEngine.evaluate(contractPolicy, new ContractNegotiationPolicyContext(agent)) + .map(v -> contractPolicy); } @NotNull diff --git a/core/control-plane/control-plane-contract/src/test/java/org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImplTest.java b/core/control-plane/control-plane-contract/src/test/java/org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImplTest.java index 37bb04fea41..8f2f10d5c26 100644 --- a/core/control-plane/control-plane-contract/src/test/java/org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImplTest.java +++ b/core/control-plane/control-plane-contract/src/test/java/org/eclipse/edc/connector/controlplane/contract/validation/ContractValidationServiceImplTest.java @@ -19,8 +19,11 @@ import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; import org.eclipse.edc.connector.controlplane.asset.spi.index.AssetIndex; +import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext; import org.eclipse.edc.connector.controlplane.contract.policy.PolicyEquality; import org.eclipse.edc.connector.controlplane.contract.spi.ContractOfferId; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext; import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; @@ -48,9 +51,6 @@ import static java.time.Instant.MIN; import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.connector.controlplane.contract.ContractCoreExtension.CATALOG_SCOPE; -import static org.eclipse.edc.connector.controlplane.contract.spi.validation.ContractValidationService.NEGOTIATION_SCOPE; -import static org.eclipse.edc.connector.controlplane.contract.spi.validation.ContractValidationService.TRANSFER_SCOPE; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.spi.agent.ParticipantAgent.PARTICIPANT_IDENTITY; import static org.mockito.AdditionalMatchers.and; @@ -99,8 +99,8 @@ void verifyContractOfferValidation() { var asset = Asset.Builder.newInstance().id("1").build(); when(assetIndex.findById("1")).thenReturn(asset); - when(policyEngine.evaluate(eq(CATALOG_SCOPE), any(), isA(PolicyContext.class))).thenReturn(Result.success()); - when(policyEngine.evaluate(eq(NEGOTIATION_SCOPE), any(), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(CatalogPolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(ContractNegotiationPolicyContext.class))).thenReturn(Result.success()); var validatableOffer = createValidatableConsumerOffer(asset, originalPolicy); @@ -114,14 +114,12 @@ void verifyContractOfferValidation() { verify(assetIndex).findById("1"); verify(policyEngine).evaluate( - eq(CATALOG_SCOPE), eq(newPolicy), - and(isA(PolicyContext.class), argThat(c -> c.getContextData(ParticipantAgent.class).equals(participantAgent))) + and(isA(CatalogPolicyContext.class), argThat(c -> c.agent().equals(participantAgent))) ); verify(policyEngine).evaluate( - eq(NEGOTIATION_SCOPE), eq(newPolicy), - and(isA(PolicyContext.class), argThat(c -> c.getContextData(ParticipantAgent.class).equals(participantAgent))) + and(isA(ContractNegotiationPolicyContext.class), argThat(c -> c.agent().equals(participantAgent))) ); } @@ -174,9 +172,8 @@ void validate_failsIfOfferedPolicyIsNotTheEqualToTheStoredOne() { void verifyContractAgreementValidation() { var newPolicy = Policy.Builder.newInstance().build(); var participantAgent = new ParticipantAgent(emptyMap(), Map.of(PARTICIPANT_IDENTITY, CONSUMER_ID)); - var captor = ArgumentCaptor.forClass(PolicyContext.class); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), any())).thenReturn(Result.success()); var agreement = createContractAgreement() .contractSigningDate(now.getEpochSecond()) @@ -187,10 +184,10 @@ void verifyContractAgreementValidation() { assertThat(isValid.succeeded()).isTrue(); - verify(policyEngine).evaluate(eq(TRANSFER_SCOPE), eq(newPolicy), captor.capture()); - + var captor = ArgumentCaptor.forClass(TransferProcessPolicyContext.class); + verify(policyEngine).evaluate(eq(newPolicy), captor.capture()); var context = captor.getValue(); - assertThat(context.getContextData(ContractAgreement.class)).isNotNull().isInstanceOf(ContractAgreement.class); + assertThat(context.contractAgreement()).isNotNull().isInstanceOf(ContractAgreement.class); } @ParameterizedTest @@ -326,7 +323,7 @@ void validateInitialOffer_assetInOfferNotReferencedByDefinition_shouldFail() { var validatableOffer = createValidatableConsumerOffer(); var participantAgent = new ParticipantAgent(emptyMap(), Map.of(PARTICIPANT_IDENTITY, CONSUMER_ID)); - when(policyEngine.evaluate(eq(CATALOG_SCOPE), any(), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(CatalogPolicyContext.class))).thenReturn(Result.success()); when(assetIndex.findById(anyString())).thenReturn(Asset.Builder.newInstance().build()); when(assetIndex.countAssets(anyList())).thenReturn(0L); @@ -341,15 +338,14 @@ void validateInitialOffer_fails_whenContractPolicyEvaluationFails() { var validatableOffer = createValidatableConsumerOffer(); var participantAgent = new ParticipantAgent(emptyMap(), Map.of(PARTICIPANT_IDENTITY, CONSUMER_ID)); - when(policyEngine.evaluate(eq(CATALOG_SCOPE), any(), isA(PolicyContext.class))).thenReturn(Result.success()); - when(policyEngine.evaluate(eq(NEGOTIATION_SCOPE), any(), isA(PolicyContext.class))).thenReturn(Result.failure("evaluation failure")); + when(policyEngine.evaluate(any(), isA(CatalogPolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(ContractNegotiationPolicyContext.class))).thenReturn(Result.failure("evaluation failure")); when(assetIndex.findById(anyString())).thenReturn(Asset.Builder.newInstance().build()); when(assetIndex.countAssets(anyList())).thenReturn(1L); var result = validationService.validateInitialOffer(participantAgent, validatableOffer); assertThat(result).isFailed().detail() - .startsWith("Policy in scope %s not fulfilled for offer %s, policy evaluation".formatted(NEGOTIATION_SCOPE, validatableOffer.getOfferId().toString())) .contains("evaluation failure"); } @@ -375,7 +371,7 @@ void validateConsumerRequest_failsInvalidCredentials(String counterPartyId) { @Test void validateAgreement_failWhenOutsideInForcePeriod_fixed() { var participantAgent = new ParticipantAgent(emptyMap(), Map.of(PARTICIPANT_IDENTITY, CONSUMER_ID)); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.failure("test-failure")); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.failure("test-failure")); var agreement = createContractAgreement() .id(ContractOfferId.create("1", "2").toString()) @@ -387,7 +383,7 @@ void validateAgreement_failWhenOutsideInForcePeriod_fixed() { } private Result validateAgreementDate(long signingDate) { - when(policyEngine.evaluate(eq(NEGOTIATION_SCOPE), isA(Policy.class), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(isA(Policy.class), isA(ContractNegotiationPolicyContext.class))).thenReturn(Result.success()); var agreement = createContractAgreement() .id(ContractOfferId.create("1", "2").toString()) diff --git a/core/control-plane/control-plane-transfer/src/main/java/org/eclipse/edc/connector/controlplane/transfer/TransferProcessDefaultServicesExtension.java b/core/control-plane/control-plane-transfer/src/main/java/org/eclipse/edc/connector/controlplane/transfer/TransferProcessDefaultServicesExtension.java index 78ace47c641..3e20e8105fa 100644 --- a/core/control-plane/control-plane-transfer/src/main/java/org/eclipse/edc/connector/controlplane/transfer/TransferProcessDefaultServicesExtension.java +++ b/core/control-plane/control-plane-transfer/src/main/java/org/eclipse/edc/connector/controlplane/transfer/TransferProcessDefaultServicesExtension.java @@ -23,6 +23,7 @@ import org.eclipse.edc.connector.controlplane.transfer.spi.flow.DataFlowManager; import org.eclipse.edc.connector.controlplane.transfer.spi.flow.TransferTypeParser; import org.eclipse.edc.connector.controlplane.transfer.spi.observe.TransferProcessObservable; +import org.eclipse.edc.connector.controlplane.transfer.spi.policy.ProvisionManifestVerifyPolicyContext; import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ProvisionManager; import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ResourceManifestGenerator; import org.eclipse.edc.policy.engine.spi.PolicyEngine; @@ -32,6 +33,8 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import static org.eclipse.edc.connector.controlplane.transfer.spi.policy.ProvisionManifestVerifyPolicyContext.MANIFEST_VERIFICATION_SCOPE; + @Extension(value = TransferProcessDefaultServicesExtension.NAME) public class TransferProcessDefaultServicesExtension implements ServiceExtension { @@ -45,6 +48,11 @@ public String name() { return NAME; } + @Override + public void initialize(ServiceExtensionContext context) { + policyEngine.registerScope(MANIFEST_VERIFICATION_SCOPE, ProvisionManifestVerifyPolicyContext.class); + } + @Provider public DataFlowManager dataFlowManager(ServiceExtensionContext context) { return new DataFlowManagerImpl(context.getMonitor()); diff --git a/core/control-plane/control-plane-transfer/src/main/java/org/eclipse/edc/connector/controlplane/transfer/provision/ResourceManifestGeneratorImpl.java b/core/control-plane/control-plane-transfer/src/main/java/org/eclipse/edc/connector/controlplane/transfer/provision/ResourceManifestGeneratorImpl.java index 4ce77ec2c59..a65b972b065 100644 --- a/core/control-plane/control-plane-transfer/src/main/java/org/eclipse/edc/connector/controlplane/transfer/provision/ResourceManifestGeneratorImpl.java +++ b/core/control-plane/control-plane-transfer/src/main/java/org/eclipse/edc/connector/controlplane/transfer/provision/ResourceManifestGeneratorImpl.java @@ -15,14 +15,13 @@ package org.eclipse.edc.connector.controlplane.transfer.provision; +import org.eclipse.edc.connector.controlplane.transfer.spi.policy.ProvisionManifestVerifyPolicyContext; import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ConsumerResourceDefinitionGenerator; import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ProviderResourceDefinitionGenerator; import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ResourceManifestContext; import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ResourceManifestGenerator; import org.eclipse.edc.connector.controlplane.transfer.spi.types.ResourceManifest; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; -import org.eclipse.edc.policy.engine.spi.PolicyContext; -import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.result.Result; @@ -69,12 +68,13 @@ public Result generateConsumerResourceManifest(TransferProcess var manifestContext = new ResourceManifestContext(manifest); // Create additional context information for policy engine to make manifest context available - var policyContext = PolicyContextImpl.Builder.newInstance() - .additional(ResourceManifestContext.class, manifestContext) - .build(); + var policyContext = new ProvisionManifestVerifyPolicyContext(manifestContext); - return policyEngine.evaluate(MANIFEST_VERIFICATION_SCOPE, policy, policyContext) - .map(a -> extractModifiedManifest(policyContext)); + return policyEngine.evaluate(policy, policyContext) + .map(a -> ResourceManifest.Builder.newInstance() + .definitions(policyContext.resourceManifestContext().getDefinitions()) + .build() + ); } @Override @@ -87,8 +87,4 @@ public ResourceManifest generateProviderResourceManifest(TransferProcess transfe return ResourceManifest.Builder.newInstance().definitions(definitions).build(); } - private ResourceManifest extractModifiedManifest(PolicyContext policyContext) { - var manifestContext = policyContext.getContextData(ResourceManifestContext.class); - return ResourceManifest.Builder.newInstance().definitions(manifestContext.getDefinitions()).build(); - } } diff --git a/core/control-plane/control-plane-transfer/src/test/java/org/eclipse/edc/connector/controlplane/transfer/provision/ResourceManifestGeneratorImplTest.java b/core/control-plane/control-plane-transfer/src/test/java/org/eclipse/edc/connector/controlplane/transfer/provision/ResourceManifestGeneratorImplTest.java index 63416b75ef9..73ef4804c2b 100644 --- a/core/control-plane/control-plane-transfer/src/test/java/org/eclipse/edc/connector/controlplane/transfer/provision/ResourceManifestGeneratorImplTest.java +++ b/core/control-plane/control-plane-transfer/src/test/java/org/eclipse/edc/connector/controlplane/transfer/provision/ResourceManifestGeneratorImplTest.java @@ -56,7 +56,7 @@ void shouldGenerateResourceManifestForConsumerManagedTransferProcess() { var resourceDefinition = TestResourceDefinition.Builder.newInstance().id(UUID.randomUUID().toString()).build(); when(consumerGenerator.canGenerate(any(), any())).thenReturn(true); when(consumerGenerator.generate(any(), any())).thenReturn(resourceDefinition); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.success()); var result = generator.generateConsumerResourceManifest(transferProcess, policy); @@ -69,7 +69,7 @@ void shouldGenerateResourceManifestForConsumerManagedTransferProcess() { void shouldGenerateEmptyResourceManifestForNotGeneratedFilter() { var transferProcess = TransferProcess.Builder.newInstance().dataDestination(dataDestination()).build(); when(consumerGenerator.canGenerate(any(), any())).thenReturn(false); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.success()); var result = generator.generateConsumerResourceManifest(transferProcess, policy); @@ -82,7 +82,7 @@ void shouldReturnFailedResultForConsumerWhenPolicyEvaluationFailed() { var transferProcess = TransferProcess.Builder.newInstance().dataDestination(dataDestination()).build(); var resourceDefinition = TestResourceDefinition.Builder.newInstance().id(UUID.randomUUID().toString()).build(); when(consumerGenerator.generate(any(), any())).thenReturn(resourceDefinition); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.failure("error")); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.failure("error")); var result = generator.generateConsumerResourceManifest(transferProcess, policy); diff --git a/core/control-plane/lib/control-plane-policies-lib/src/main/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunction.java b/core/control-plane/lib/control-plane-policies-lib/src/main/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunction.java index e9a88545132..0b3bf4f1a8d 100644 --- a/core/control-plane/lib/control-plane-policies-lib/src/main/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunction.java +++ b/core/control-plane/lib/control-plane-policies-lib/src/main/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunction.java @@ -14,9 +14,8 @@ package org.eclipse.edc.connector.controlplane.policy.contract; -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction; -import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.AgreementPolicyContext; +import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction; import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.spi.EdcException; @@ -26,7 +25,7 @@ import java.time.format.DateTimeParseException; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; -import java.util.Objects; +import java.util.Optional; import java.util.regex.Pattern; import static java.lang.String.format; @@ -62,48 +61,42 @@ * * Please note that all {@link Operator}s except {@link Operator#IN} are supported. */ -public class ContractExpiryCheckFunction implements AtomicConstraintFunction { +public class ContractExpiryCheckFunction implements AtomicConstraintRuleFunction { public static final String CONTRACT_EXPIRY_EVALUATION_KEY = EDC_NAMESPACE + "inForceDate"; + private static final String EXPRESSION_REGEX = "(contract[A,a]greement)\\+(-?[0-9]+)(s|m|h|d)"; private static final int REGEX_GROUP_NUMERIC = 2; private static final int REGEX_GROUP_UNIT = 3; - @Override - public boolean evaluate(Operator operator, Object rightValue, Permission rule, PolicyContext context) { - if (!(rightValue instanceof String)) { - context.reportProblem("Right-value expected to be String but was " + rightValue.getClass()); + public boolean evaluate(Operator operator, Object rightValue, Permission rule, C context) { + if (rightValue == null) { + context.reportProblem("Right-value is null."); return false; } - try { - var now = getContextData(Instant.class, context); - - var rightValueStr = (String) rightValue; - var bound = asInstant(rightValueStr); - if (bound != null) { - return checkFixedPeriod(now, operator, bound); - } - - var duration = asDuration(rightValueStr); - if (duration != null) { - var agreement = getContextData(ContractAgreement.class, context); - var signingDate = Instant.ofEpochSecond(agreement.getContractSigningDate()); - return checkFixedPeriod(now, operator, signingDate.plus(duration)); - } - - context.reportProblem(format("Unsupported right-value, expected either an ISO-8061 String or a expression matching '%s', but got '%s'", - CONTRACT_EXPIRY_EVALUATION_KEY, rightValueStr)); - - } catch (NullPointerException | DateTimeParseException ex) { - context.reportProblem(ex.getMessage()); + if (!(rightValue instanceof String rightValueStr)) { + context.reportProblem("Right-value expected to be String but was " + rightValue.getClass()); + return false; } - return false; + + return Optional.ofNullable(asInstant(rightValueStr)) + .or(() -> Optional.ofNullable(asDuration(rightValueStr)) + .map(duration -> Instant.ofEpochSecond(context.contractAgreement().getContractSigningDate()) + .plus(duration) + ) + ).map(bound -> checkFixedPeriod(context.now(), operator, bound)) + .orElseGet(() -> { + var message = "Unsupported right-value, expected either an ISO-8061 String or a expression matching '%s', but got '%s'" + .formatted(ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY, rightValueStr); + context.reportProblem(message); + return false; + }); } /** - * Checks whether an input string fits the regex {@link ContractExpiryCheckFunction#EXPRESSION_REGEX}, e.g. "contractAgreement+50m" + * Checks whether an input string fits the regex {@link #EXPRESSION_REGEX}, e.g. "contractAgreement+50m" * and parses that string into a {@link Duration} if successful. * * @param rightValueStr A string potentially containing a duration expression. @@ -157,9 +150,4 @@ private Instant asInstant(String isoString) { return null; } } - - private R getContextData(Class clazz, PolicyContext context) { - return Objects.requireNonNull(context.getContextData(clazz), clazz.getSimpleName()); - } - } diff --git a/core/control-plane/lib/control-plane-policies-lib/src/test/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunctionEvaluationTest.java b/core/control-plane/lib/control-plane-policies-lib/src/test/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunctionEvaluationTest.java deleted file mode 100644 index 05c01021c67..00000000000 --- a/core/control-plane/lib/control-plane-policies-lib/src/test/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunctionEvaluationTest.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation - * - */ - -package org.eclipse.edc.connector.controlplane.policy.contract; - -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.junit.annotations.ComponentTest; -import org.eclipse.edc.junit.assertions.AbstractResultAssert; -import org.eclipse.edc.policy.engine.PolicyEngineImpl; -import org.eclipse.edc.policy.engine.RuleBindingRegistryImpl; -import org.eclipse.edc.policy.engine.ScopeFilter; -import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; -import org.eclipse.edc.policy.engine.spi.PolicyEngine; -import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; -import org.eclipse.edc.policy.engine.validation.RuleValidator; -import org.eclipse.edc.policy.model.Action; -import org.eclipse.edc.policy.model.AndConstraint; -import org.eclipse.edc.policy.model.AtomicConstraint; -import org.eclipse.edc.policy.model.LiteralExpression; -import org.eclipse.edc.policy.model.Operator; -import org.eclipse.edc.policy.model.Permission; -import org.eclipse.edc.policy.model.Policy; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.junit.jupiter.params.provider.ValueSource; - -import java.time.Instant; -import java.util.UUID; -import java.util.stream.Stream; - -import static java.time.Duration.ofDays; -import static java.time.Duration.ofSeconds; -import static java.time.Instant.now; -import static org.eclipse.edc.connector.controlplane.policy.contract.ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY; -import static org.eclipse.edc.policy.model.Operator.EQ; -import static org.eclipse.edc.policy.model.Operator.GEQ; -import static org.eclipse.edc.policy.model.Operator.GT; -import static org.eclipse.edc.policy.model.Operator.LEQ; -import static org.eclipse.edc.policy.model.Operator.LT; -import static org.eclipse.edc.policy.model.Operator.NEQ; -import static org.junit.jupiter.params.provider.Arguments.of; - -@ComponentTest -class ContractExpiryCheckFunctionEvaluationTest { - private static final String TRANSFER_SCOPE = "transfer.process"; - - private static final Instant NOW = now(); - private final ContractExpiryCheckFunction function = new ContractExpiryCheckFunction(); - private final RuleBindingRegistry bindingRegistry = new RuleBindingRegistryImpl(); - private PolicyEngine policyEngine; - - @BeforeEach - void setup() { - // bind/register rule to evaluate contract expiry - bindingRegistry.bind("use", TRANSFER_SCOPE); - bindingRegistry.bind(CONTRACT_EXPIRY_EVALUATION_KEY, TRANSFER_SCOPE); - policyEngine = new PolicyEngineImpl(new ScopeFilter(bindingRegistry), new RuleValidator(bindingRegistry)); - policyEngine.registerFunction(TRANSFER_SCOPE, Permission.class, CONTRACT_EXPIRY_EVALUATION_KEY, function); - } - - @ParameterizedTest - @ArgumentsSource(ValidTimeProvider.class) - void evaluate_fixed_isValid(Operator startOp, Instant start, Operator endOp, Instant end) { - var policy = createInForcePolicy(startOp, start, endOp, end); - var context = PolicyContextImpl.Builder.newInstance().additional(Instant.class, NOW).build(); - - var result = policyEngine.evaluate(TRANSFER_SCOPE, policy, context); - - AbstractResultAssert.assertThat(result).isSucceeded(); - } - - @ParameterizedTest - @ArgumentsSource(InvalidTimeProvider.class) - void evaluate_fixed_isInvalid(Operator startOp, Instant start, Operator endOp, Instant end) { - var policy = createInForcePolicy(startOp, start, endOp, end); - var context = PolicyContextImpl.Builder.newInstance().additional(Instant.class, NOW).build(); - - var result = policyEngine.evaluate(TRANSFER_SCOPE, policy, context); - - AbstractResultAssert.assertThat(result) - .isFailed() - .detail().contains(CONTRACT_EXPIRY_EVALUATION_KEY); - } - - @ParameterizedTest - @ValueSource(strings = { "100d", "25h", "2m", "67s" }) - void evaluate_durationAsEnd_isValid(String numeric) { - var policy = createInForcePolicy(GEQ, NOW.minusSeconds(60), LEQ, "contractAgreement+" + numeric); - var context = PolicyContextImpl.Builder.newInstance() - .additional(Instant.class, NOW) - .additional(ContractAgreement.class, createAgreement("test-agreement", NOW)) - .build(); - - var result = policyEngine.evaluate(TRANSFER_SCOPE, policy, context); - - AbstractResultAssert.assertThat(result).isSucceeded(); - } - - @ParameterizedTest - @ValueSource(strings = { "-100d", "-25h", "-2m", "-67s" }) - void evaluate_durationAsStart_isValid(String numeric) { - var policy = createInForcePolicy(GEQ, "contractAgreement+" + numeric, LEQ, NOW.plusSeconds(60)); - var context = PolicyContextImpl.Builder.newInstance() - .additional(Instant.class, NOW) - .additional(ContractAgreement.class, createAgreement("test-agreement", NOW)) - .build(); - - var result = policyEngine.evaluate(TRANSFER_SCOPE, policy, context); - - AbstractResultAssert.assertThat(result).isSucceeded(); - } - - @ParameterizedTest - @ValueSource(strings = { "d", "*25h", "2ms" }) - void evaluate_duration_invalidExpression(String numeric) { - var policy = createInForcePolicy(GEQ, "contractAgreement+" + numeric, LEQ, NOW.plusSeconds(60)); - var context = PolicyContextImpl.Builder.newInstance() - .additional(Instant.class, NOW) - .additional(ContractAgreement.class, createAgreement("test-agreement", NOW)) - .build(); - - var result = policyEngine.evaluate(TRANSFER_SCOPE, policy, context); - - AbstractResultAssert.assertThat(result).isFailed(); - } - - private Policy createInForcePolicy(Operator operatorStart, Object startDate, Operator operatorEnd, Object endDate) { - var fixedInForceTimeConstraint = AndConstraint.Builder.newInstance() - .constraint(AtomicConstraint.Builder.newInstance() - .leftExpression(new LiteralExpression(CONTRACT_EXPIRY_EVALUATION_KEY)) - .operator(operatorStart) - .rightExpression(new LiteralExpression(startDate.toString())) - .build()) - .constraint(AtomicConstraint.Builder.newInstance() - .leftExpression(new LiteralExpression(CONTRACT_EXPIRY_EVALUATION_KEY)) - .operator(operatorEnd) - .rightExpression(new LiteralExpression(endDate.toString())) - .build()) - .build(); - var permission = Permission.Builder.newInstance() - .action(Action.Builder.newInstance().type("use").build()) - .constraint(fixedInForceTimeConstraint).build(); - - return Policy.Builder.newInstance() - .permission(permission) - .build(); - } - - private ContractAgreement createAgreement(String agreementId) { - return createAgreement(agreementId, NOW); - } - - private ContractAgreement createAgreement(String agreementId, Instant signingTime) { - return ContractAgreement.Builder.newInstance() - .id(agreementId) - .providerId(UUID.randomUUID().toString()) - .consumerId(UUID.randomUUID().toString()) - .assetId(UUID.randomUUID().toString()) - .contractSigningDate(signingTime.getEpochSecond()) - .policy(Policy.Builder.newInstance().build()) - .build(); - } - - private static class ValidTimeProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext context) throws Exception { - return Stream.of( - of(GEQ, NOW.minus(ofDays(1)), LEQ, NOW.plus(ofDays(1))), - of(GEQ, NOW.minus(ofDays(1)), LEQ, NOW.plus(ofDays(1))), - of(GEQ, NOW, LEQ, NOW.plus(ofDays(1))), - of(GEQ, NOW.minus(ofDays(1)), LEQ, NOW), - of(GT, NOW.minus(ofSeconds(1)), LT, NOW.plusSeconds(1L)), - of(EQ, NOW, LT, NOW.plusSeconds(1)), - of(GEQ, NOW.minusSeconds(1), EQ, NOW), - of(NEQ, NOW.minusSeconds(4), LT, NOW.plusSeconds(10)) - ); - } - } - - private static class InvalidTimeProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext context) throws Exception { - return Stream.of( - of(NEQ, NOW, LEQ, NOW.plusSeconds(1)), //lower bound violation - of(GEQ, NOW, NEQ, NOW), // upper bound violation - of(GEQ, NOW.plusSeconds(1), LEQ, NOW.plusSeconds(10)), //NOW is before start - of(GEQ, NOW.minusSeconds(30), LEQ, NOW.minusSeconds(10)), //NOW is after end - of(GT, NOW, LEQ, NOW.plusSeconds(40)), // lower bound violation, NOW is exactly on start - of(GT, NOW.minusSeconds(10), LT, NOW), // upper bound violation, NOW is exactly on end - of(NEQ, NOW, LEQ, NOW.plusSeconds(30)) //start cannot be NOW, but it is - ); - } - } -} diff --git a/core/control-plane/lib/control-plane-policies-lib/src/test/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunctionTest.java b/core/control-plane/lib/control-plane-policies-lib/src/test/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunctionTest.java new file mode 100644 index 00000000000..bcb16a3fe80 --- /dev/null +++ b/core/control-plane/lib/control-plane-policies-lib/src/test/java/org/eclipse/edc/connector/controlplane/policy/contract/ContractExpiryCheckFunctionTest.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2024 Cofinity-X + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Cofinity-X - initial API and implementation + * + */ + +package org.eclipse.edc.connector.controlplane.policy.contract; + +import org.eclipse.edc.connector.controlplane.contract.spi.policy.AgreementPolicyContext; +import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; +import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.policy.model.Policy; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; +import java.util.stream.Stream; + +import static java.time.Instant.now; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.policy.model.Operator.EQ; +import static org.eclipse.edc.policy.model.Operator.GEQ; +import static org.eclipse.edc.policy.model.Operator.GT; +import static org.eclipse.edc.policy.model.Operator.LEQ; +import static org.eclipse.edc.policy.model.Operator.LT; +import static org.eclipse.edc.policy.model.Operator.NEQ; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class ContractExpiryCheckFunctionTest { + + private final ContractExpiryCheckFunction function = new ContractExpiryCheckFunction<>(); + + @Test + void shouldFail_whenRightValueIsNull() { + var context = new TestAgreementPolicyContext(now(), null); + var permission = Permission.Builder.newInstance().build(); + + var result = function.evaluate(EQ, null, permission, context); + + assertThat(result).isFalse(); + assertThat(context.hasProblems()).isTrue(); + } + + @Test + void shouldFail_whenRightValueIsNotString() { + var context = new TestAgreementPolicyContext(now(), null); + var permission = Permission.Builder.newInstance().build(); + + var result = function.evaluate(EQ, 3, permission, context); + + assertThat(result).isFalse(); + assertThat(context.hasProblems()).isTrue(); + } + + @Test + void shouldFail_whenRightValueIsNotParsable() { + var context = new TestAgreementPolicyContext(now(), null); + var permission = Permission.Builder.newInstance().build(); + + var result = function.evaluate(EQ, "unparsable", permission, context); + + assertThat(result).isFalse(); + assertThat(context.hasProblems()).isTrue(); + } + + @ParameterizedTest + @ArgumentsSource(ValidInstant.class) + void shouldSucceed_whenRightValueIsIso8061AndValid(Instant now, Operator operator, Instant bound) { + var context = new TestAgreementPolicyContext(now, null); + var permission = Permission.Builder.newInstance().build(); + + var result = function.evaluate(operator, bound.toString(), permission, context); + + assertThat(result).isTrue(); + assertThat(context.hasProblems()).isFalse(); + } + + @ParameterizedTest + @ArgumentsSource(InvalidInstant.class) + void shouldFail_whenRightValueIsIso8061AndInvalid(Instant now, Operator operator, Instant bound) { + var context = new TestAgreementPolicyContext(now, null); + var permission = Permission.Builder.newInstance().build(); + + var result = function.evaluate(operator, bound.toString(), permission, context); + + assertThat(result).isFalse(); + assertThat(context.hasProblems()).isFalse(); + } + + @ParameterizedTest + @ArgumentsSource(ValidDuration.class) + void shouldSucceed_whenRightValueIsDurationAndValid(Instant now, Operator operator, String duration, Instant signingTime) { + var context = new TestAgreementPolicyContext(now, createAgreement(signingTime)); + var permission = Permission.Builder.newInstance().build(); + + var result = function.evaluate(operator, "contractAgreement+" + duration, permission, context); + + assertThat(result).isTrue(); + assertThat(context.hasProblems()).isFalse(); + } + + @ParameterizedTest + @ArgumentsSource(InvalidDuration.class) + void shouldFail_whenRightValueIsDurationAndInvalid(Instant now, Operator operator, String duration, Instant signingTime) { + var context = new TestAgreementPolicyContext(now, createAgreement(signingTime)); + var permission = Permission.Builder.newInstance().build(); + + var result = function.evaluate(operator, "contractAgreement+" + duration, permission, context); + + assertThat(result).isFalse(); + assertThat(context.hasProblems()).isFalse(); + } + + private static class ValidInstant implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + var now = now(); + return Stream.of( + arguments(now, EQ, now), + arguments(now, GEQ, now), + arguments(now, GEQ, now.minusNanos(1)), + arguments(now, LEQ, now), + arguments(now, LEQ, now.plusNanos(1)), + arguments(now, GT, now.minusNanos(1)), + arguments(now, LT, now.plusNanos(1)), + arguments(now, NEQ, now.minusNanos(1)) + ); + } + } + + private static class InvalidInstant implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + var now = now(); + return Stream.of( + arguments(now, EQ, now.plusNanos(1)), + arguments(now, GEQ, now.plusNanos(1)), + arguments(now, LEQ, now.minusNanos(1)), + arguments(now, GT, now), + arguments(now, LT, now), + arguments(now, NEQ, now) + ); + } + } + + private static class ValidDuration implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + var now = now(); + return Stream.of( + arguments(now, GT, "34s", now.minus(Duration.ofSeconds(35))), + arguments(now, GT, "-23m", now.plus(Duration.ofMinutes(22))), + arguments(now, GT, "4h", now.minus(Duration.ofHours(5))), + arguments(now, GT, "-1d", now.plus(Duration.ofHours(23))), + arguments(now, LT, "-54s", now.plus(Duration.ofSeconds(55))), + arguments(now, LT, "17m", now.minus(Duration.ofMinutes(16))), + arguments(now, LT, "-2h", now.plus(Duration.ofHours(3))), + arguments(now, LT, "1d", now.minusSeconds(59)) + ); + } + } + + private static class InvalidDuration implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + var now = now(); + return Stream.of( + arguments(now, GT, "34s", now.minus(Duration.ofSeconds(33))), + arguments(now, GT, "-23m", now.plus(Duration.ofMinutes(24))), + arguments(now, GT, "4h", now.minus(Duration.ofHours(3))), + arguments(now, GT, "-1d", now.plus(Duration.ofHours(25))), + arguments(now, LT, "-54s", now.plus(Duration.ofSeconds(53))), + arguments(now, LT, "17m", now.minus(Duration.ofMinutes(18))), + arguments(now, LT, "-2h", now.plus(Duration.ofHours(1))), + arguments(now, LT, "1d", now.minus(Duration.ofDays(2))) + ); + } + } + + private ContractAgreement createAgreement(Instant signingTime) { + return ContractAgreement.Builder.newInstance() + .id(UUID.randomUUID().toString()) + .providerId(UUID.randomUUID().toString()) + .consumerId(UUID.randomUUID().toString()) + .assetId(UUID.randomUUID().toString()) + .contractSigningDate(signingTime.getEpochSecond()) + .policy(Policy.Builder.newInstance().build()) + .build(); + } + + private static class TestAgreementPolicyContext extends PolicyContextImpl implements AgreementPolicyContext { + + private final Instant now; + private final ContractAgreement contractAgreement; + + TestAgreementPolicyContext(Instant now, ContractAgreement contractAgreement) { + this.now = now; + this.contractAgreement = contractAgreement; + } + + @Override + public ContractAgreement contractAgreement() { + return contractAgreement; + } + + @Override + public Instant now() { + return now; + } + + @Override + public String scope() { + return "any"; + } + } +} diff --git a/core/policy-monitor/policy-monitor-core/src/main/java/org/eclipse/edc/connector/policy/monitor/PolicyMonitorExtension.java b/core/policy-monitor/policy-monitor-core/src/main/java/org/eclipse/edc/connector/policy/monitor/PolicyMonitorExtension.java index 41d1bcf1a0c..2048b940ac5 100644 --- a/core/policy-monitor/policy-monitor-core/src/main/java/org/eclipse/edc/connector/policy/monitor/PolicyMonitorExtension.java +++ b/core/policy-monitor/policy-monitor-core/src/main/java/org/eclipse/edc/connector/policy/monitor/PolicyMonitorExtension.java @@ -19,11 +19,11 @@ import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; import org.eclipse.edc.connector.controlplane.transfer.spi.event.TransferProcessStarted; import org.eclipse.edc.connector.policy.monitor.manager.PolicyMonitorManagerImpl; +import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext; import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorManager; import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorStore; import org.eclipse.edc.connector.policy.monitor.subscriber.StartMonitoring; import org.eclipse.edc.policy.engine.spi.PolicyEngine; -import org.eclipse.edc.policy.engine.spi.PolicyScope; import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.runtime.metamodel.annotation.Extension; @@ -41,6 +41,7 @@ import static org.eclipse.edc.connector.controlplane.policy.contract.ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY; import static org.eclipse.edc.connector.policy.monitor.PolicyMonitorExtension.NAME; +import static org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext.POLICY_MONITOR_SCOPE; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_USE_ACTION_ATTRIBUTE; import static org.eclipse.edc.statemachine.AbstractStateEntityManager.DEFAULT_BATCH_SIZE; import static org.eclipse.edc.statemachine.AbstractStateEntityManager.DEFAULT_ITERATION_WAIT; @@ -57,9 +58,6 @@ public class PolicyMonitorExtension implements ServiceExtension { @Setting(value = "the batch size in the policy monitor state machine. Default value " + DEFAULT_BATCH_SIZE, type = "int") private static final String POLICY_MONITOR_BATCH_SIZE = "edc.policy.monitor.state-machine.batch-size"; - @PolicyScope - public static final String POLICY_MONITOR_SCOPE = "policy.monitor"; - @Inject private ExecutorInstrumentation executorInstrumentation; @@ -94,9 +92,10 @@ public void initialize(ServiceExtensionContext context) { var iterationWaitMillis = context.getSetting(POLICY_MONITOR_ITERATION_WAIT_MILLIS, DEFAULT_ITERATION_WAIT); var waitStrategy = new ExponentialWaitStrategy(iterationWaitMillis); + policyEngine.registerScope(POLICY_MONITOR_SCOPE, PolicyMonitorContext.class); ruleBindingRegistry.bind(ODRL_USE_ACTION_ATTRIBUTE, POLICY_MONITOR_SCOPE); ruleBindingRegistry.bind(CONTRACT_EXPIRY_EVALUATION_KEY, POLICY_MONITOR_SCOPE); - policyEngine.registerFunction(POLICY_MONITOR_SCOPE, Permission.class, CONTRACT_EXPIRY_EVALUATION_KEY, new ContractExpiryCheckFunction()); + policyEngine.registerFunction(PolicyMonitorContext.class, Permission.class, CONTRACT_EXPIRY_EVALUATION_KEY, new ContractExpiryCheckFunction<>()); manager = PolicyMonitorManagerImpl.Builder.newInstance() .clock(clock) diff --git a/core/policy-monitor/policy-monitor-core/src/main/java/org/eclipse/edc/connector/policy/monitor/manager/PolicyMonitorManagerImpl.java b/core/policy-monitor/policy-monitor-core/src/main/java/org/eclipse/edc/connector/policy/monitor/manager/PolicyMonitorManagerImpl.java index 1fb5e8279d3..6960dd16465 100644 --- a/core/policy-monitor/policy-monitor-core/src/main/java/org/eclipse/edc/connector/policy/monitor/manager/PolicyMonitorManagerImpl.java +++ b/core/policy-monitor/policy-monitor-core/src/main/java/org/eclipse/edc/connector/policy/monitor/manager/PolicyMonitorManagerImpl.java @@ -14,16 +14,15 @@ package org.eclipse.edc.connector.policy.monitor.manager; -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.TerminateTransferCommand; +import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext; import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorEntry; import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorEntryStates; import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorManager; import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorStore; -import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.statemachine.AbstractStateEntityManager; @@ -34,7 +33,6 @@ import java.time.Instant; import java.util.function.Function; -import static org.eclipse.edc.connector.policy.monitor.PolicyMonitorExtension.POLICY_MONITOR_SCOPE; import static org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorEntryStates.STARTED; import static org.eclipse.edc.spi.persistence.StateEntityStore.hasState; @@ -93,12 +91,9 @@ private boolean processMonitoring(PolicyMonitorEntry entry) { } var policy = contractAgreement.getPolicy(); - var policyContext = PolicyContextImpl.Builder.newInstance() - .additional(Instant.class, Instant.now(clock)) - .additional(ContractAgreement.class, contractAgreement) - .build(); + var policyContext = new PolicyMonitorContext(Instant.now(clock), contractAgreement); - var result = policyEngine.evaluate(POLICY_MONITOR_SCOPE, policy, policyContext); + var result = policyEngine.evaluate(policy, policyContext); if (result.failed()) { monitor.debug(() -> "[policy-monitor] Policy evaluation for TP %s failed: %s".formatted(entry.getId(), result.getFailureDetail())); var command = new TerminateTransferCommand(entry.getId(), result.getFailureDetail()); diff --git a/core/policy-monitor/policy-monitor-core/src/test/java/org/eclipse/edc/connector/policy/monitor/PolicyMonitorExtensionTest.java b/core/policy-monitor/policy-monitor-core/src/test/java/org/eclipse/edc/connector/policy/monitor/PolicyMonitorExtensionTest.java index 3844d6d7369..7eebfe312d0 100644 --- a/core/policy-monitor/policy-monitor-core/src/test/java/org/eclipse/edc/connector/policy/monitor/PolicyMonitorExtensionTest.java +++ b/core/policy-monitor/policy-monitor-core/src/test/java/org/eclipse/edc/connector/policy/monitor/PolicyMonitorExtensionTest.java @@ -15,6 +15,7 @@ package org.eclipse.edc.connector.policy.monitor; import org.eclipse.edc.connector.controlplane.policy.contract.ContractExpiryCheckFunction; +import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.model.Permission; @@ -24,7 +25,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import static org.eclipse.edc.connector.controlplane.policy.contract.ContractExpiryCheckFunction.CONTRACT_EXPIRY_EVALUATION_KEY; -import static org.eclipse.edc.connector.policy.monitor.PolicyMonitorExtension.POLICY_MONITOR_SCOPE; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; @@ -44,6 +44,7 @@ void setUp(ServiceExtensionContext context) { void shouldRegisterExpiryFunctionToPolicyEngine(PolicyMonitorExtension extension, ServiceExtensionContext context) { extension.initialize(context); - verify(policyEngine).registerFunction(eq(POLICY_MONITOR_SCOPE), eq(Permission.class), eq(CONTRACT_EXPIRY_EVALUATION_KEY), isA(ContractExpiryCheckFunction.class)); + verify(policyEngine).registerFunction(eq(PolicyMonitorContext.class), eq(Permission.class), + eq(CONTRACT_EXPIRY_EVALUATION_KEY), isA(ContractExpiryCheckFunction.class)); } } diff --git a/core/policy-monitor/policy-monitor-core/src/test/java/org/eclipse/edc/connector/policy/monitor/manager/PolicyMonitorManagerImplTest.java b/core/policy-monitor/policy-monitor-core/src/test/java/org/eclipse/edc/connector/policy/monitor/manager/PolicyMonitorManagerImplTest.java index 8fbc0f83259..8ea0b76c1bd 100644 --- a/core/policy-monitor/policy-monitor-core/src/test/java/org/eclipse/edc/connector/policy/monitor/manager/PolicyMonitorManagerImplTest.java +++ b/core/policy-monitor/policy-monitor-core/src/test/java/org/eclipse/edc/connector/policy/monitor/manager/PolicyMonitorManagerImplTest.java @@ -19,11 +19,11 @@ import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; +import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext; import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorEntry; import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorManager; import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorStore; import org.eclipse.edc.policy.engine.spi.PolicyContext; -import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.query.Criterion; @@ -39,7 +39,6 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import static org.eclipse.edc.connector.policy.monitor.PolicyMonitorExtension.POLICY_MONITOR_SCOPE; import static org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorEntryStates.COMPLETED; import static org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorEntryStates.FAILED; import static org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorEntryStates.STARTED; @@ -48,7 +47,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; @@ -103,17 +101,17 @@ void started_shouldTerminateTransferAndTransitionToComplete_whenPolicyIsNotValid when(transferProcessService.findById(entry.getId())) .thenReturn(TransferProcess.Builder.newInstance().state(TransferProcessStates.STARTED.code()).build()); when(contractAgreementService.findById(any())).thenReturn(contractAgreement); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.failure("policy is not valid")); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.failure("policy is not valid")); when(transferProcessService.terminate(any())).thenReturn(ServiceResult.success()); manager.start(); await().untilAsserted(() -> { verify(contractAgreementService).findById("contractId"); - var captor = ArgumentCaptor.forClass(PolicyContextImpl.class); - verify(policyEngine).evaluate(eq(POLICY_MONITOR_SCOPE), same(policy), captor.capture()); + var captor = ArgumentCaptor.forClass(PolicyMonitorContext.class); + verify(policyEngine).evaluate(same(policy), captor.capture()); var policyContext = captor.getValue(); - assertThat(policyContext.getContextData(ContractAgreement.class)).isSameAs(contractAgreement); + assertThat(policyContext.contractAgreement()).isSameAs(contractAgreement); verify(transferProcessService).terminate(argThat(c -> c.getEntityId().equals("transferProcessId"))); verify(store).save(argThat(it -> it.getState() == COMPLETED.code())); }); @@ -131,7 +129,7 @@ void started_shouldNotTransitionToComplete_whenTransferProcessTerminationFails() when(transferProcessService.findById(entry.getId())) .thenReturn(TransferProcess.Builder.newInstance().state(TransferProcessStates.STARTED.code()).build()); when(contractAgreementService.findById(any())).thenReturn(createContractAgreement(policy)); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.failure("policy is not valid")); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.failure("policy is not valid")); when(transferProcessService.terminate(any())).thenReturn(ServiceResult.conflict("failure")); manager.start(); @@ -155,7 +153,7 @@ void started_shouldDoNothing_whenPolicyIsValid() { when(transferProcessService.findById(entry.getId())) .thenReturn(TransferProcess.Builder.newInstance().state(TransferProcessStates.STARTED.code()).build()); when(contractAgreementService.findById(any())).thenReturn(createContractAgreement(policy)); - when(policyEngine.evaluate(any(), any(), isA(PolicyContext.class))).thenReturn(Result.success()); + when(policyEngine.evaluate(any(), isA(PolicyContext.class))).thenReturn(Result.success()); manager.start(); diff --git a/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/DspHttpCoreExtension.java b/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/DspHttpCoreExtension.java index 8a935270cc2..96e922a2a29 100644 --- a/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/DspHttpCoreExtension.java +++ b/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/DspHttpCoreExtension.java @@ -31,8 +31,11 @@ import org.eclipse.edc.connector.controlplane.transfer.spi.types.protocol.TransferTerminationMessage; import org.eclipse.edc.http.spi.EdcHttpClient; import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestVersionPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; -import org.eclipse.edc.policy.engine.spi.PolicyScope; import org.eclipse.edc.protocol.dsp.http.dispatcher.DspHttpRemoteMessageDispatcherImpl; import org.eclipse.edc.protocol.dsp.http.message.DspRequestHandlerImpl; import org.eclipse.edc.protocol.dsp.http.protocol.DspProtocolParserImpl; @@ -57,6 +60,10 @@ import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import static org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext.CATALOGING_REQUEST_SCOPE; +import static org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext.CONTRACT_NEGOTIATION_REQUEST_SCOPE; +import static org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext.TRANSFER_PROCESS_REQUEST_SCOPE; +import static org.eclipse.edc.policy.context.request.spi.RequestVersionPolicyContext.VERSION_REQUEST_SCOPE; import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP_V_2024_1; import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_SCOPE; @@ -73,24 +80,6 @@ public class DspHttpCoreExtension implements ServiceExtension { public static final String NAME = "Dataspace Protocol Core Extension"; - /** - * Policy scope evaluated when a contract negotiation request is made. - */ - @PolicyScope - private static final String CONTRACT_NEGOTIATION_REQUEST_SCOPE = "request.contract.negotiation"; - - /** - * Policy scope evaluated when a transfer process request is made. - */ - @PolicyScope - private static final String TRANSFER_PROCESS_REQUEST_SCOPE = "request.transfer.process"; - - /** - * Policy scope evaluated when an outgoing catalog request is made - */ - @PolicyScope - private static final String CATALOGING_REQUEST_SCOPE = "request.catalog"; - @Inject private RemoteMessageDispatcherRegistry dispatcherRegistry; @Inject @@ -130,6 +119,11 @@ public String name() { @Provider public DspHttpRemoteMessageDispatcher dspHttpRemoteMessageDispatcher(ServiceExtensionContext context) { + policyEngine.registerScope(TRANSFER_PROCESS_REQUEST_SCOPE, RequestTransferProcessPolicyContext.class); + policyEngine.registerScope(CONTRACT_NEGOTIATION_REQUEST_SCOPE, RequestContractNegotiationPolicyContext.class); + policyEngine.registerScope(CATALOGING_REQUEST_SCOPE, RequestCatalogPolicyContext.class); + policyEngine.registerScope(VERSION_REQUEST_SCOPE, RequestVersionPolicyContext.class); + TokenDecorator td; // either a decorator, or noop if (decorator != null) { td = decorator; @@ -174,23 +168,23 @@ public DspProtocolParser dspProtocolParser() { } private void registerNegotiationPolicyScopes(DspHttpRemoteMessageDispatcher dispatcher) { - dispatcher.registerPolicyScope(ContractAgreementMessage.class, CONTRACT_NEGOTIATION_REQUEST_SCOPE, ContractRemoteMessage::getPolicy); - dispatcher.registerPolicyScope(ContractNegotiationEventMessage.class, CONTRACT_NEGOTIATION_REQUEST_SCOPE, ContractRemoteMessage::getPolicy); - dispatcher.registerPolicyScope(ContractRequestMessage.class, CONTRACT_NEGOTIATION_REQUEST_SCOPE, ContractRemoteMessage::getPolicy); - dispatcher.registerPolicyScope(ContractNegotiationTerminationMessage.class, CONTRACT_NEGOTIATION_REQUEST_SCOPE, ContractRemoteMessage::getPolicy); - dispatcher.registerPolicyScope(ContractAgreementVerificationMessage.class, CONTRACT_NEGOTIATION_REQUEST_SCOPE, ContractRemoteMessage::getPolicy); + dispatcher.registerPolicyScope(ContractAgreementMessage.class, ContractRemoteMessage::getPolicy, RequestContractNegotiationPolicyContext::new); + dispatcher.registerPolicyScope(ContractNegotiationEventMessage.class, ContractRemoteMessage::getPolicy, RequestContractNegotiationPolicyContext::new); + dispatcher.registerPolicyScope(ContractRequestMessage.class, ContractRemoteMessage::getPolicy, RequestContractNegotiationPolicyContext::new); + dispatcher.registerPolicyScope(ContractNegotiationTerminationMessage.class, ContractRemoteMessage::getPolicy, RequestContractNegotiationPolicyContext::new); + dispatcher.registerPolicyScope(ContractAgreementVerificationMessage.class, ContractRemoteMessage::getPolicy, RequestContractNegotiationPolicyContext::new); } private void registerTransferProcessPolicyScopes(DspHttpRemoteMessageDispatcher dispatcher) { - dispatcher.registerPolicyScope(TransferCompletionMessage.class, TRANSFER_PROCESS_REQUEST_SCOPE, TransferRemoteMessage::getPolicy); - dispatcher.registerPolicyScope(TransferSuspensionMessage.class, TRANSFER_PROCESS_REQUEST_SCOPE, TransferRemoteMessage::getPolicy); - dispatcher.registerPolicyScope(TransferTerminationMessage.class, TRANSFER_PROCESS_REQUEST_SCOPE, TransferRemoteMessage::getPolicy); - dispatcher.registerPolicyScope(TransferStartMessage.class, TRANSFER_PROCESS_REQUEST_SCOPE, TransferRemoteMessage::getPolicy); - dispatcher.registerPolicyScope(TransferRequestMessage.class, TRANSFER_PROCESS_REQUEST_SCOPE, TransferRemoteMessage::getPolicy); + dispatcher.registerPolicyScope(TransferCompletionMessage.class, TransferRemoteMessage::getPolicy, RequestTransferProcessPolicyContext::new); + dispatcher.registerPolicyScope(TransferSuspensionMessage.class, TransferRemoteMessage::getPolicy, RequestTransferProcessPolicyContext::new); + dispatcher.registerPolicyScope(TransferTerminationMessage.class, TransferRemoteMessage::getPolicy, RequestTransferProcessPolicyContext::new); + dispatcher.registerPolicyScope(TransferStartMessage.class, TransferRemoteMessage::getPolicy, RequestTransferProcessPolicyContext::new); + dispatcher.registerPolicyScope(TransferRequestMessage.class, TransferRemoteMessage::getPolicy, RequestTransferProcessPolicyContext::new); } private void registerCatalogPolicyScopes(DspHttpRemoteMessageDispatcher dispatcher) { - dispatcher.registerPolicyScope(CatalogRequestMessage.class, CATALOGING_REQUEST_SCOPE, CatalogRequestMessage::getPolicy); - dispatcher.registerPolicyScope(DatasetRequestMessage.class, CATALOGING_REQUEST_SCOPE, DatasetRequestMessage::getPolicy); + dispatcher.registerPolicyScope(CatalogRequestMessage.class, CatalogRequestMessage::getPolicy, RequestCatalogPolicyContext::new); + dispatcher.registerPolicyScope(DatasetRequestMessage.class, DatasetRequestMessage::getPolicy, RequestCatalogPolicyContext::new); } } diff --git a/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpRemoteMessageDispatcherImpl.java b/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpRemoteMessageDispatcherImpl.java index a84bf6fd6ed..d0cf9a9bf3e 100644 --- a/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpRemoteMessageDispatcherImpl.java +++ b/data-protocols/dsp/dsp-http-core/src/main/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpRemoteMessageDispatcherImpl.java @@ -18,7 +18,7 @@ import okhttp3.ResponseBody; import org.eclipse.edc.connector.controlplane.catalog.spi.CatalogRequestMessage; import org.eclipse.edc.http.spi.EdcHttpClient; -import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; +import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.protocol.dsp.http.spi.dispatcher.DspHttpRemoteMessageDispatcher; @@ -96,12 +96,9 @@ public CompletableFuture> dispatch( .direction(RequestContext.Direction.Egress) .build(); - var context = PolicyContextImpl.Builder.newInstance() - .additional(RequestScope.Builder.class, requestScopeBuilder) - .additional(RequestContext.class, requestContext) - .build(); + var context = policyScope.contextProvider.instantiate(requestContext, requestScopeBuilder); var policyProvider = (Function) policyScope.policyProvider; - policyEngine.evaluate(policyScope.scope, policyProvider.apply(message), context); + policyEngine.evaluate(policyProvider.apply(message), context); // catalog request messages can carry additional, user-supplied scopes if (message instanceof CatalogRequestMessage catalogRequestMessage) { @@ -114,7 +111,6 @@ public CompletableFuture> dispatch( if (!scopes.isEmpty()) { tokenParametersBuilder.claims(SCOPE_CLAIM, String.join(" ", scopes)); } - } return audienceResolver.resolve(message) @@ -138,8 +134,10 @@ public void registerMessage(Class clazz, DspHttp } @Override - public void registerPolicyScope(Class messageClass, String scope, Function policyProvider) { - policyScopes.put(messageClass, new PolicyScope<>(messageClass, scope, policyProvider)); + public void registerPolicyScope(Class messageClass, + Function policyProvider, + RequestPolicyContext.Provider contextProvider) { + policyScopes.put(messageClass, new PolicyScope<>(messageClass, policyProvider, contextProvider)); } @NotNull @@ -171,14 +169,11 @@ private String asString(ResponseBody it) { private record MessageHandler( DspHttpRequestFactory requestFactory, - DspHttpResponseBodyExtractor bodyExtractor - ) { - } + DspHttpResponseBodyExtractor bodyExtractor) { } private record PolicyScope( - Class messageClass, String scope, - Function policyProvider - ) { - } + Class messageClass, + Function policyProvider, + RequestPolicyContext.Provider contextProvider) { } } diff --git a/data-protocols/dsp/dsp-http-core/src/test/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpRemoteMessageDispatcherImplTest.java b/data-protocols/dsp/dsp-http-core/src/test/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpRemoteMessageDispatcherImplTest.java index 1aeb9f18a3b..06aff6b0fee 100644 --- a/data-protocols/dsp/dsp-http-core/src/test/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpRemoteMessageDispatcherImplTest.java +++ b/data-protocols/dsp/dsp-http-core/src/test/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpRemoteMessageDispatcherImplTest.java @@ -20,6 +20,7 @@ import okhttp3.ResponseBody; import org.eclipse.edc.connector.controlplane.catalog.spi.CatalogRequestMessage; import org.eclipse.edc.http.spi.EdcHttpClient; +import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.policy.model.Policy; @@ -40,6 +41,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.AdditionalMatchers; import org.mockito.ArgumentCaptor; import java.time.Duration; @@ -111,9 +113,9 @@ void dispatch_noScope() { when(identityService.obtainClientCredentials(any())) .thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token(authToken).build())); - dispatcher.registerPolicyScope(TestMessage.class, "scope.test", (m) -> policy); + dispatcher.registerPolicyScope(TestMessage.class, (m) -> policy, TestPolicyContext::new); - when(policyEngine.evaluate(eq("scope.test"), eq(policy), any())).thenReturn(Result.success()); + when(policyEngine.evaluate(eq(policy), any())).thenReturn(Result.success()); dispatcher.registerMessage(TestMessage.class, requestFactory, mock()); @@ -146,12 +148,11 @@ void dispatch_ensureTokenDecoratorScope() { when(identityService.obtainClientCredentials(any())) .thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token(authToken).build())); - dispatcher.registerPolicyScope(TestMessage.class, "scope.test", (m) -> policy); + dispatcher.registerPolicyScope(TestMessage.class, (m) -> policy, TestPolicyContext::new); - when(policyEngine.evaluate(eq("scope.test"), eq(policy), any())).thenAnswer(a -> { - PolicyContext context = a.getArgument(2); - var builder = context.getContextData(RequestScope.Builder.class); - builder.scope("policy-test-scope"); + when(policyEngine.evaluate(eq(policy), any())).thenAnswer(a -> { + RequestPolicyContext context = a.getArgument(1); + context.requestScopeBuilder().scope("policy-test-scope"); return Result.success(); }); @@ -186,12 +187,11 @@ void dispatch_PolicyEvaluatedScope() { when(identityService.obtainClientCredentials(any())) .thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token(authToken).build())); - dispatcher.registerPolicyScope(TestMessage.class, "scope.test", (m) -> policy); + dispatcher.registerPolicyScope(TestMessage.class, (m) -> policy, TestPolicyContext::new); - when(policyEngine.evaluate(eq("scope.test"), eq(policy), any())).thenAnswer(a -> { - PolicyContext context = a.getArgument(2); - var builder = context.getContextData(RequestScope.Builder.class); - builder.scope("policy-test-scope"); + when(policyEngine.evaluate(eq(policy), any())).thenAnswer(a -> { + RequestPolicyContext context = a.getArgument(1); + context.requestScopeBuilder().scope("policy-test-scope"); return Result.success(); }); @@ -206,10 +206,10 @@ void dispatch_PolicyEvaluatedScope() { verify(identityService).obtainClientCredentials(captor.capture()); verify(httpClient).executeAsync(argThat(r -> authToken.equals(r.headers().get("Authorization"))), isA(List.class)); verify(requestFactory).createRequest(message); - verify(policyEngine).evaluate(any(), any(), argThat(ctx -> { - var requestContext = ctx.getContextData(RequestContext.class); + verify(policyEngine).evaluate(any(), AdditionalMatchers.and(isA(RequestPolicyContext.class), argThat(ctx -> { + var requestContext = ctx.requestContext(); return requestContext.getMessage().getClass().equals(TestMessage.class) && requestContext.getDirection().equals(RequestContext.Direction.Egress); - })); + }))); assertThat(captor.getValue()).satisfies(tr -> { assertThat(tr.getStringClaim(SCOPE_CLAIM)).isEqualTo("policy-test-scope"); assertThat(tr.getStringClaim(AUDIENCE_CLAIM)).isEqualTo(AUDIENCE_VALUE); @@ -279,12 +279,11 @@ void dispatch_whenCatalogRequestMessage_shouldExtractScopes() { when(identityService.obtainClientCredentials(any())) .thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token(authToken).build())); - dispatcher.registerPolicyScope(CatalogRequestMessage.class, "scope.test", (m) -> policy); + dispatcher.registerPolicyScope(CatalogRequestMessage.class, (m) -> policy, TestPolicyContext::new); - when(policyEngine.evaluate(eq("scope.test"), eq(policy), any())).thenAnswer(a -> { - PolicyContext context = a.getArgument(2); - var builder = context.getContextData(RequestScope.Builder.class); - builder.scope("policy-test-scope"); + when(policyEngine.evaluate(eq(policy), any())).thenAnswer(a -> { + RequestPolicyContext context = a.getArgument(1); + context.requestScopeBuilder().scope("policy-test-scope"); return Result.success(); }); @@ -299,10 +298,10 @@ void dispatch_whenCatalogRequestMessage_shouldExtractScopes() { verify(identityService).obtainClientCredentials(captor.capture()); verify(httpClient).executeAsync(argThat(r -> authToken.equals(r.headers().get("Authorization"))), isA(List.class)); verify(rqFactory).createRequest(message); - verify(policyEngine).evaluate(any(), any(), argThat(ctx -> { - var requestContext = ctx.getContextData(RequestContext.class); + verify(policyEngine).evaluate(any(), and(isA(RequestPolicyContext.class), argThat(ctx -> { + var requestContext = ctx.requestContext(); return requestContext.getMessage().getClass().equals(CatalogRequestMessage.class) && requestContext.getDirection().equals(RequestContext.Direction.Egress); - })); + }))); assertThat(captor.getValue()).satisfies(tr -> { assertThat(tr.getStringClaim(SCOPE_CLAIM)).isEqualTo("policy-test-scope scope1 scope2"); assertThat(tr.getStringClaim(AUDIENCE_CLAIM)).isEqualTo(AUDIENCE_VALUE); @@ -318,20 +317,21 @@ void dispatch_shouldEvaluatePolicy() { when(httpClient.executeAsync(any(), isA(List.class))).thenReturn(completedFuture(dummyResponse(200))); when(identityService.obtainClientCredentials(any())) .thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("any").build())); - when(policyEngine.evaluate(eq("test.message"), eq(policy), isA(PolicyContext.class))).thenAnswer((a -> { - a.getArgument(2, PolicyContext.class).getContextData(RequestScope.Builder.class).scope("test-scope"); + when(policyEngine.evaluate(eq(policy), isA(RequestPolicyContext.class))).thenAnswer((a -> { + a.getArgument(1, RequestPolicyContext.class).requestScopeBuilder().scope("test-scope"); return Result.success(); })); dispatcher.registerMessage(TestMessage.class, requestFactory, mock()); - dispatcher.registerPolicyScope(TestMessage.class, "test.message", m -> policy); + dispatcher.registerPolicyScope(TestMessage.class, m -> policy, TestPolicyContext::new); var result = dispatcher.dispatch(String.class, new TestMessage()); var captor = ArgumentCaptor.forClass(TokenParameters.class); verify(identityService).obtainClientCredentials(captor.capture()); assertThat(result).succeedsWithin(timeout); - verify(policyEngine).evaluate(eq("test.message"), eq(policy), and(isA(PolicyContext.class), argThat(c -> c.getContextData(RequestScope.Builder.class) != null))); + verify(policyEngine).evaluate(eq(policy), and(isA(PolicyContext.class), + and(isA(RequestPolicyContext.class), argThat(c -> c.requestScopeBuilder() != null)))); assertThat(captor.getValue()).satisfies(tr -> { assertThat(tr.getStringClaim(SCOPE_CLAIM)).isEqualTo("test-scope"); }); @@ -354,6 +354,18 @@ public String getCounterPartyId() { } } + static class TestPolicyContext extends RequestPolicyContext { + + protected TestPolicyContext(RequestContext requestContext, RequestScope.Builder requestScopeBuilder) { + super(requestContext, requestScopeBuilder); + } + + @Override + public String scope() { + return "scope.test"; + } + } + @Nested class Response { diff --git a/data-protocols/dsp/dsp-http-spi/src/main/java/org/eclipse/edc/protocol/dsp/http/spi/dispatcher/DspHttpRemoteMessageDispatcher.java b/data-protocols/dsp/dsp-http-spi/src/main/java/org/eclipse/edc/protocol/dsp/http/spi/dispatcher/DspHttpRemoteMessageDispatcher.java index eae9d87183f..441ed4fe815 100644 --- a/data-protocols/dsp/dsp-http-spi/src/main/java/org/eclipse/edc/protocol/dsp/http/spi/dispatcher/DspHttpRemoteMessageDispatcher.java +++ b/data-protocols/dsp/dsp-http-spi/src/main/java/org/eclipse/edc/protocol/dsp/http/spi/dispatcher/DspHttpRemoteMessageDispatcher.java @@ -14,6 +14,7 @@ package org.eclipse.edc.protocol.dsp.http.spi.dispatcher; +import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.protocol.dsp.http.spi.dispatcher.response.DspHttpResponseBodyExtractor; import org.eclipse.edc.spi.message.RemoteMessageDispatcher; @@ -41,10 +42,10 @@ void registerMessage(Class clazz, DspHttpRequest /** * Registers a {@link Policy} scope to be evaluated for certain types of messages * - * @param messageClass the message type for which evaluate the policy. - * @param scope the scope to be used. + * @param the message type. + * @param messageClass the message type for which evaluate the policy. * @param policyProvider function that extracts the Policy from the message. - * @param the message type. */ - void registerPolicyScope(Class messageClass, String scope, Function policyProvider); + void registerPolicyScope(Class messageClass, Function policyProvider, + RequestPolicyContext.Provider contextProvider); } diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/DcpScopeExtractorExtension.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/DcpScopeExtractorExtension.java index 615490f4d3d..d36ad829466 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/DcpScopeExtractorExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/DcpScopeExtractorExtension.java @@ -16,6 +16,9 @@ import org.eclipse.edc.iam.identitytrust.core.scope.DcpScopeExtractorFunction; import org.eclipse.edc.iam.identitytrust.spi.scope.ScopeExtractorRegistry; +import org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; @@ -30,10 +33,6 @@ public class DcpScopeExtractorExtension implements ServiceExtension { public static final String NAME = "DCP scope extractor extension"; - public static final String CATALOG_REQUEST_SCOPE = "request.catalog"; - public static final String NEGOTIATION_REQUEST_SCOPE = "request.contract.negotiation"; - public static final String TRANSFER_PROCESS_REQUEST_SCOPE = "request.transfer.process"; - @Inject private PolicyEngine policyEngine; @@ -50,9 +49,8 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var contextMappingFunction = new DcpScopeExtractorFunction(scopeExtractorRegistry, monitor); - policyEngine.registerPreValidator(CATALOG_REQUEST_SCOPE, contextMappingFunction); - policyEngine.registerPreValidator(NEGOTIATION_REQUEST_SCOPE, contextMappingFunction); - policyEngine.registerPreValidator(TRANSFER_PROCESS_REQUEST_SCOPE, contextMappingFunction); + policyEngine.registerPreValidator(RequestCatalogPolicyContext.class, new DcpScopeExtractorFunction<>(scopeExtractorRegistry, monitor)); + policyEngine.registerPreValidator(RequestContractNegotiationPolicyContext.class, new DcpScopeExtractorFunction<>(scopeExtractorRegistry, monitor)); + policyEngine.registerPreValidator(RequestTransferProcessPolicyContext.class, new DcpScopeExtractorFunction<>(scopeExtractorRegistry, monitor)); } } diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/DcpScopeExtractorFunction.java b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/DcpScopeExtractorFunction.java index 26b83f31dbf..a0922043376 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/DcpScopeExtractorFunction.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/main/java/org/eclipse/edc/iam/identitytrust/core/scope/DcpScopeExtractorFunction.java @@ -16,21 +16,20 @@ import org.eclipse.edc.iam.identitytrust.spi.scope.ScopeExtractor; import org.eclipse.edc.iam.identitytrust.spi.scope.ScopeExtractorRegistry; -import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; +import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.iam.RequestScope; import org.eclipse.edc.spi.monitor.Monitor; -import java.util.function.BiFunction; - import static java.lang.String.format; /** * DCP pre-validator function for extracting scopes from a {@link Policy} using the registered {@link ScopeExtractor} * in the {@link ScopeExtractorRegistry}. */ -public class DcpScopeExtractorFunction implements BiFunction { +public class DcpScopeExtractorFunction implements PolicyValidatorRule { private final ScopeExtractorRegistry registry; private final Monitor monitor; @@ -41,8 +40,8 @@ public DcpScopeExtractorFunction(ScopeExtractorRegistry registry, Monitor monito } @Override - public Boolean apply(Policy policy, PolicyContext context) { - var params = context.getContextData(RequestScope.Builder.class); + public Boolean apply(Policy policy, C context) { + var params = context.requestScopeBuilder(); if (params == null) { throw new EdcException(format("%s not set in policy context", RequestScope.Builder.class.getName())); } @@ -54,5 +53,6 @@ public Boolean apply(Policy policy, PolicyContext context) { monitor.warning("Failed to extract scopes from a policy"); return false; } + } } diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/DcpScopeExtractorExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/DcpScopeExtractorExtensionTest.java index 8d10cad5d17..2289809c1e1 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/DcpScopeExtractorExtensionTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/DcpScopeExtractorExtensionTest.java @@ -16,15 +16,15 @@ import org.eclipse.edc.iam.identitytrust.core.scope.DcpScopeExtractorFunction; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import static org.eclipse.edc.iam.identitytrust.core.DcpScopeExtractorExtension.CATALOG_REQUEST_SCOPE; -import static org.eclipse.edc.iam.identitytrust.core.DcpScopeExtractorExtension.NEGOTIATION_REQUEST_SCOPE; -import static org.eclipse.edc.iam.identitytrust.core.DcpScopeExtractorExtension.TRANSFER_PROCESS_REQUEST_SCOPE; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.mock; @@ -33,7 +33,6 @@ @ExtendWith(DependencyInjectionExtension.class) class DcpScopeExtractorExtensionTest { - private final PolicyEngine policyEngine = mock(); @BeforeEach @@ -46,10 +45,10 @@ void initialize(ServiceExtensionContext context, DcpScopeExtractorExtension ext) ext.initialize(context); - verify(policyEngine).registerPreValidator(eq(CATALOG_REQUEST_SCOPE), isA(DcpScopeExtractorFunction.class)); - verify(policyEngine).registerPreValidator(eq(NEGOTIATION_REQUEST_SCOPE), isA(DcpScopeExtractorFunction.class)); - verify(policyEngine).registerPreValidator(eq(TRANSFER_PROCESS_REQUEST_SCOPE), isA(DcpScopeExtractorFunction.class)); + verify(policyEngine).registerPreValidator(eq(RequestCatalogPolicyContext.class), isA(DcpScopeExtractorFunction.class)); + verify(policyEngine).registerPreValidator(eq(RequestContractNegotiationPolicyContext.class), isA(DcpScopeExtractorFunction.class)); + verify(policyEngine).registerPreValidator(eq(RequestTransferProcessPolicyContext.class), isA(DcpScopeExtractorFunction.class)); } -} \ No newline at end of file +} diff --git a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/DcpScopeExtractorFunctionTest.java b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/DcpScopeExtractorFunctionTest.java index 80314059f5c..0cf22ada44b 100644 --- a/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/DcpScopeExtractorFunctionTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-core/src/test/java/org/eclipse/edc/iam/identitytrust/core/scope/DcpScopeExtractorFunctionTest.java @@ -15,9 +15,10 @@ package org.eclipse.edc.iam.identitytrust.core.scope; import org.eclipse.edc.iam.identitytrust.spi.scope.ScopeExtractorRegistry; -import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.iam.RequestContext; import org.eclipse.edc.spi.iam.RequestScope; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; @@ -36,12 +37,12 @@ public class DcpScopeExtractorFunctionTest { private final ScopeExtractorRegistry registry = mock(); - private final PolicyContext policyContext = mock(); - private DcpScopeExtractorFunction function; + private final TestRequestPolicyContext policyContext = mock(); + private DcpScopeExtractorFunction function; @BeforeEach void setup() { - function = new DcpScopeExtractorFunction(registry, mock(Monitor.class)); + function = new DcpScopeExtractorFunction<>(registry, mock(Monitor.class)); } @Test @@ -49,7 +50,7 @@ void apply() { var policy = Policy.Builder.newInstance().build(); var scopeBuilder = RequestScope.Builder.newInstance(); - when(policyContext.getContextData(RequestScope.Builder.class)).thenReturn(scopeBuilder); + when(policyContext.requestScopeBuilder()).thenReturn(scopeBuilder); when(registry.extractScopes(eq(policy), any())).thenReturn(Result.success(Set.of("scope1", "scope2"))); assertThat(function.apply(policy, policyContext)).isTrue(); @@ -71,11 +72,23 @@ void apply_shouldThrow_whenTokenBuilderMissing() { void apply_fail_whenScopeExtractorFails() { var policy = Policy.Builder.newInstance().build(); var scopeBuilder = RequestScope.Builder.newInstance(); - when(policyContext.getContextData(RequestScope.Builder.class)).thenReturn(scopeBuilder); + when(policyContext.requestScopeBuilder()).thenReturn(scopeBuilder); when(registry.extractScopes(eq(policy), any())).thenReturn(Result.failure("failure")); assertThat(function.apply(policy, policyContext)).isFalse(); + } + + private static class TestRequestPolicyContext extends RequestPolicyContext { + protected TestRequestPolicyContext(RequestContext requestContext, RequestScope.Builder requestScopeBuilder) { + super(requestContext, requestScopeBuilder); + } + + @Override + public String scope() { + return "request.test"; + } } + } diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformerTest.java b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformerTest.java index a78d590eb2b..44009b64c0b 100644 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformerTest.java +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformerTest.java @@ -16,7 +16,6 @@ import jakarta.json.Json; import jakarta.json.JsonObject; -import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction; import org.eclipse.edc.policy.engine.spi.PolicyValidatorFunction; import org.eclipse.edc.policy.engine.spi.RuleFunction; import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; @@ -33,7 +32,6 @@ import org.eclipse.edc.policy.model.AtomicConstraint; import org.eclipse.edc.policy.model.LiteralExpression; import org.eclipse.edc.policy.model.Permission; -import org.eclipse.edc.policy.model.Rule; import org.eclipse.edc.transform.spi.TransformerContext; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; @@ -78,22 +76,6 @@ public class JsonObjectFromPolicyEvaluationPlanTransformerTest { private final JsonObjectFromPolicyEvaluationPlanTransformer transformer = new JsonObjectFromPolicyEvaluationPlanTransformer(Json.createBuilderFactory(emptyMap())); private final TransformerContext context = mock(TransformerContext.class); - private static AtomicConstraint atomicConstraint(String key, String value) { - var left = new LiteralExpression(key); - var right = new LiteralExpression(value); - return AtomicConstraint.Builder.newInstance() - .leftExpression(left) - .operator(EQ) - .rightExpression(right) - .build(); - } - - private static AtomicConstraintStep atomicConstraintStep(AtomicConstraint atomicConstraint) { - AtomicConstraintFunction function = mock(); - when(function.name()).thenReturn("AtomicConstraintFunction"); - return new AtomicConstraintStep(atomicConstraint, List.of("filtered constraint"), mock(), function); - } - @Test void types() { assertThat(transformer.getInputType()).isEqualTo(PolicyEvaluationPlan.class); @@ -169,6 +151,20 @@ void transformWithMultiplicitySteps(PolicyEvaluationPlan plan, String ruleStepPr } + private static AtomicConstraint atomicConstraint(String key, String value) { + var left = new LiteralExpression(key); + var right = new LiteralExpression(value); + return AtomicConstraint.Builder.newInstance() + .leftExpression(left) + .operator(EQ) + .rightExpression(right) + .build(); + } + + private static AtomicConstraintStep atomicConstraintStep(AtomicConstraint atomicConstraint) { + return new AtomicConstraintStep(atomicConstraint, List.of("filtered constraint"), mock(), "AtomicConstraintFunction"); + } + private PolicyEvaluationPlan createPlan() { return PolicyEvaluationPlan.Builder.newInstance() .preValidator(new ValidatorStep(mock())) diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyContextImpl.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyContextImpl.java index 368a355e2ee..484f1e2d90f 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyContextImpl.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyContextImpl.java @@ -59,6 +59,7 @@ public void putContextData(Class type, T data) { additional.put(type, data); } + @Deprecated(since = "0.10.0") public static class Builder { private final PolicyContextImpl context = new PolicyContextImpl() { @@ -73,15 +74,18 @@ private Builder() { } + @Deprecated(since = "0.10.0") public static Builder newInstance() { return new Builder(); } + @Deprecated(since = "0.10.0") public Builder additional(Class clazz, Object object) { context.additional.put(clazz, object); return this; } + @Deprecated(since = "0.10.0") public PolicyContext build() { return context; } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyEngine.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyEngine.java index 94da840a5b3..adf152c109e 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyEngine.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyEngine.java @@ -54,10 +54,20 @@ public interface PolicyEngine { */ Policy filter(Policy policy, String scope); + /** + * Evaluates the given policy with a specific context. + */ + Result evaluate(Policy policy, C context); + /** * Evaluates the given policy with a context for the given scope. + * + * @deprecated use {{@link #evaluate(Policy, PolicyContext)}} because scope is determined by {@link PolicyContext} hierarchy. */ - Result evaluate(String scope, Policy policy, PolicyContext context); + @Deprecated(since = "0.10.0") + default Result evaluate(String scope, Policy policy, C context) { + return evaluate(policy, context); + } /** * Validates the given policy. @@ -69,6 +79,19 @@ public interface PolicyEngine { */ PolicyEvaluationPlan createEvaluationPlan(String scope, Policy policy); + void registerScope(String scope, Class contextType); + + /** + * Registers a function that is invoked when a policy contains an atomic constraint whose left operator expression + * evaluates to the given key for the specified context. + * + * @param contextType the type of the context. + * @param type the rule type. + * @param key the key. + * @param function the function. + */ + void registerFunction(Class contextType, Class type, String key, AtomicConstraintRuleFunction function); + /** * Registers a function that is invoked when a policy contains an atomic constraint whose left operator expression evaluates to the given key for the specified scope. * @@ -76,8 +99,20 @@ public interface PolicyEngine { * @param type the function type * @param key the key * @param function the function + * @deprecated use {{@link #registerFunction(Class, Class, String, AtomicConstraintRuleFunction)}} + */ + @Deprecated(since = "0.10.0") + void registerFunction(String scope, Class type, String key, AtomicConstraintRuleFunction function); + + /** + * Registers a function that is invoked when a policy contains an atomic constraint whose left operator expression evaluates to the given key that's not bound + * to an {@link AtomicConstraintRuleFunction}. + * + * @param contextType the context type + * @param type the function type + * @param function the function */ - void registerFunction(String scope, Class type, String key, AtomicConstraintFunction function); + void registerFunction(Class contextType, Class type, DynamicAtomicConstraintRuleFunction function); /** * Registers a function that is invoked when a policy contains an atomic constraint whose left operator expression evaluates to the given key that's not bound @@ -86,8 +121,19 @@ public interface PolicyEngine { * @param scope the scope the function applies to * @param type the function type * @param function the function + * @deprecated use {{@link #registerFunction(Class, Class, DynamicAtomicConstraintRuleFunction)}} */ - void registerFunction(String scope, Class type, DynamicAtomicConstraintFunction function); + @Deprecated(since = "0.10.0") + void registerFunction(String scope, Class type, DynamicAtomicConstraintRuleFunction function); + + /** + * Registers a function that is invoked when a policy contains a rule of the given type for the specified context type. + * + * @param contextType the context type + * @param type the {@link Rule} subtype + * @param function the function + */ + void registerFunction(Class contextType, Class type, PolicyRuleFunction function); /** * Registers a function that is invoked when a policy contains a rule of the given type for the specified scope. @@ -95,32 +141,50 @@ public interface PolicyEngine { * @param scope the scope the function applies to * @param type the {@link Rule} subtype * @param function the function + * @deprecated use {{@link #registerFunction(Class, Class, PolicyRuleFunction)}} + */ + @Deprecated(since = "0.10.0") + void registerFunction(String scope, Class type, PolicyRuleFunction function); + + /** + * Registers a function that performs pre-validation on the policy for the given context type. + */ + void registerPreValidator(Class contextType, PolicyValidatorRule validator); + + /** + * Registers a function that performs post-validation on the policy for the given context type. */ - void registerFunction(String scope, Class type, RuleFunction function); + void registerPostValidator(Class contextType, PolicyValidatorRule validator); /** * Registers a function that performs pre-validation on the policy for the given scope. * - * @deprecated Please use {@link PolicyEngine#registerPreValidator(String, PolicyValidatorFunction)} + * @deprecated use {@link #registerPreValidator(Class, PolicyValidatorRule)} */ @Deprecated(since = "0.10.0") void registerPreValidator(String scope, BiFunction validator); /** * Registers a function that performs pre-validation on the policy for the given scope. + * + * @deprecated use {@link #registerPreValidator(Class, PolicyValidatorRule)} */ + @Deprecated(since = "0.10.0") void registerPreValidator(String scope, PolicyValidatorFunction validator); /** * Registers a function that performs post-validation on the policy for the given scope. * - * @deprecated Please use {@link PolicyEngine#registerPostValidator(String, PolicyValidatorFunction)} + * @deprecated use {@link #registerPostValidator(Class, PolicyValidatorRule)} */ @Deprecated(since = "0.10.0") void registerPostValidator(String scope, BiFunction validator); /** * Registers a function that performs post-validation on the policy for the given scope. + * + * @deprecated use {@link #registerPostValidator(Class, PolicyValidatorRule)} */ + @Deprecated(since = "0.10.0") void registerPostValidator(String scope, PolicyValidatorFunction validator); } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/RulePolicyFunction.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyRuleFunction.java similarity index 92% rename from spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/RulePolicyFunction.java rename to spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyRuleFunction.java index a7d72279853..2079429c86b 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/RulePolicyFunction.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyRuleFunction.java @@ -19,7 +19,7 @@ /** * Invoked during policy evaluation to examine a rule node. */ -public interface RulePolicyFunction { +public interface PolicyRuleFunction { /** * Performs the rule evaluation. diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyScope.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyScope.java index 04e4baf99c4..2576682f3a8 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyScope.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/PolicyScope.java @@ -22,7 +22,7 @@ /** * Denotes a defined policy scope. */ -@Target({ElementType.FIELD}) +@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface PolicyScope { } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/RuleFunction.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/RuleFunction.java index 9a695d46e47..814a17d25d7 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/RuleFunction.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/RuleFunction.java @@ -19,9 +19,9 @@ /** * Invoked during policy evaluation to examine a rule node. * - * @deprecated use {@link RulePolicyFunction}. + * @deprecated use {@link PolicyRuleFunction}. */ @Deprecated(since = "0.10.0") -public interface RuleFunction extends RulePolicyFunction { +public interface RuleFunction extends PolicyRuleFunction { } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AtomicConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AtomicConstraintStep.java index 6516042afa8..98fbef6662b 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AtomicConstraintStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AtomicConstraintStep.java @@ -19,7 +19,6 @@ import org.eclipse.edc.policy.model.Rule; import java.util.List; -import java.util.Optional; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; @@ -32,7 +31,7 @@ public record AtomicConstraintStep(AtomicConstraint constraint, List filteringReasons, Rule rule, - AtomicConstraintFunction function) implements ConstraintStep { + String functionName) implements ConstraintStep { public static final String EDC_ATOMIC_CONSTRAINT_STEP_TYPE = EDC_NAMESPACE + "AtomicConstraintStep"; public static final String EDC_ATOMIC_CONSTRAINT_STEP_IS_FILTERED = EDC_NAMESPACE + "isFiltered"; @@ -44,12 +43,6 @@ public boolean isFiltered() { return !filteringReasons.isEmpty(); } - public String functionName() { - return Optional.ofNullable(function) - .map(AtomicConstraintFunction::name) - .orElse(null); - } - public List functionParams() { return List.of( constraint.getLeftExpression().toString(), diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleFunctionStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleFunctionStep.java index d85aa2ff03f..cf8605a2cd3 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleFunctionStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleFunctionStep.java @@ -14,13 +14,14 @@ package org.eclipse.edc.policy.engine.spi.plan.step; +import org.eclipse.edc.policy.engine.spi.PolicyRuleFunction; import org.eclipse.edc.policy.engine.spi.RuleFunction; import org.eclipse.edc.policy.model.Rule; /** * An evaluation step for {@link RuleFunction} associated to a {@link Rule} */ -public record RuleFunctionStep(RuleFunction function, R rule) { +public record RuleFunctionStep(PolicyRuleFunction function, R rule) { /** * Returns the {@link RuleFunction#name()} @@ -28,4 +29,4 @@ public record RuleFunctionStep(RuleFunction function, R rule) public String functionName() { return function.name(); } -} \ No newline at end of file +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ValidatorStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ValidatorStep.java index 229faadf018..c2ecaef9579 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ValidatorStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ValidatorStep.java @@ -14,12 +14,12 @@ package org.eclipse.edc.policy.engine.spi.plan.step; -import org.eclipse.edc.policy.engine.spi.PolicyValidatorFunction; +import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule; /** * An evaluation step for pre- and post-validators invoked during the evaluation process. */ -public record ValidatorStep(PolicyValidatorFunction validator) { +public record ValidatorStep(PolicyValidatorRule validator) { /** * Returns the name of the validator diff --git a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/policy/AgreementPolicyContext.java b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/policy/AgreementPolicyContext.java new file mode 100644 index 00000000000..7e37c30d092 --- /dev/null +++ b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/policy/AgreementPolicyContext.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Cofinity-X + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Cofinity-X - initial API and implementation + * + */ + +package org.eclipse.edc.connector.controlplane.contract.spi.policy; + +import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; +import org.eclipse.edc.policy.engine.spi.PolicyContext; + +import java.time.Instant; + +/** + * Marker interface + */ +public interface AgreementPolicyContext extends PolicyContext { + + /** + * The contract agreement. + * + * @return The contract agreement. + */ + ContractAgreement contractAgreement(); + + /** + * Current timestamp representation. + * + * @return Current timestamp representation. + */ + Instant now(); + +} diff --git a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/policy/TransferProcessPolicyContext.java b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/policy/TransferProcessPolicyContext.java index ccd11a1a2d9..308b445c38b 100644 --- a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/policy/TransferProcessPolicyContext.java +++ b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/policy/TransferProcessPolicyContext.java @@ -24,7 +24,7 @@ /** * Policy Context for "transfer.process" scope */ -public class TransferProcessPolicyContext extends PolicyContextImpl { +public class TransferProcessPolicyContext extends PolicyContextImpl implements AgreementPolicyContext { @PolicyScope public static final String TRANSFER_SCOPE = "transfer.process"; @@ -43,10 +43,12 @@ public ParticipantAgent agent() { return agent; } + @Override public Instant now() { return now; } + @Override public ContractAgreement contractAgreement() { return agreement; } diff --git a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/validation/ContractValidationService.java b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/validation/ContractValidationService.java index 0776d9cf6d7..a80ef60921d 100644 --- a/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/validation/ContractValidationService.java +++ b/spi/control-plane/contract-spi/src/main/java/org/eclipse/edc/connector/controlplane/contract/spi/validation/ContractValidationService.java @@ -18,7 +18,6 @@ import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractOffer; -import org.eclipse.edc.policy.engine.spi.PolicyScope; import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; import org.eclipse.edc.spi.agent.ParticipantAgent; import org.eclipse.edc.spi.result.Result; @@ -30,12 +29,6 @@ @ExtensionPoint public interface ContractValidationService { - @PolicyScope - String NEGOTIATION_SCOPE = "contract.negotiation"; - - @PolicyScope - String TRANSFER_SCOPE = "transfer.process"; - /** * Validates the contract offer for the consumer represented by the given claims. * diff --git a/spi/control-plane/control-plane-spi/build.gradle.kts b/spi/control-plane/control-plane-spi/build.gradle.kts index 3e19c85c7bf..1a2b66aeb44 100644 --- a/spi/control-plane/control-plane-spi/build.gradle.kts +++ b/spi/control-plane/control-plane-spi/build.gradle.kts @@ -18,6 +18,7 @@ plugins { dependencies { api(project(":spi:common:core-spi")) + api(project(":spi:common:policy:request-policy-context-spi")) api(project(":spi:control-plane:catalog-spi")) api(project(":spi:control-plane:contract-spi")) api(project(":spi:control-plane:transfer-spi")) diff --git a/spi/control-plane/control-plane-spi/src/main/java/org/eclipse/edc/connector/controlplane/services/spi/protocol/ProtocolTokenValidator.java b/spi/control-plane/control-plane-spi/src/main/java/org/eclipse/edc/connector/controlplane/services/spi/protocol/ProtocolTokenValidator.java index 10028cb3d0f..d8bb4b4a70e 100644 --- a/spi/control-plane/control-plane-spi/src/main/java/org/eclipse/edc/connector/controlplane/services/spi/protocol/ProtocolTokenValidator.java +++ b/spi/control-plane/control-plane-spi/src/main/java/org/eclipse/edc/connector/controlplane/services/spi/protocol/ProtocolTokenValidator.java @@ -14,6 +14,7 @@ package org.eclipse.edc.connector.controlplane.services.spi.protocol; +import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; import org.eclipse.edc.spi.agent.ParticipantAgent; @@ -31,34 +32,34 @@ public interface ProtocolTokenValidator { /** * Verify the {@link TokenRepresentation} * - * @param tokenRepresentation The token - * @param policyScope The policy scope + * @param tokenRepresentation The token + * @param policyContextProvider The policy scope * @return Returns the extracted {@link ParticipantAgent} if successful, failure otherwise */ - default ServiceResult verify(TokenRepresentation tokenRepresentation, String policyScope) { - return verify(tokenRepresentation, policyScope, Policy.Builder.newInstance().build(), null); + default ServiceResult verify(TokenRepresentation tokenRepresentation, RequestPolicyContext.Provider policyContextProvider) { + return verify(tokenRepresentation, policyContextProvider, Policy.Builder.newInstance().build(), null); } /** * Verify the {@link TokenRepresentation} * - * @param tokenRepresentation The token - * @param policyScope The policy scope - * @param message The {@link RemoteMessage} + * @param tokenRepresentation The token + * @param policyContextProvider The policy scope + * @param message The {@link RemoteMessage} * @return Returns the extracted {@link ParticipantAgent} if successful, failure otherwise */ - default ServiceResult verify(TokenRepresentation tokenRepresentation, String policyScope, RemoteMessage message) { - return verify(tokenRepresentation, policyScope, Policy.Builder.newInstance().build(), message); + default ServiceResult verify(TokenRepresentation tokenRepresentation, RequestPolicyContext.Provider policyContextProvider, RemoteMessage message) { + return verify(tokenRepresentation, policyContextProvider, Policy.Builder.newInstance().build(), message); } /** * Verify the {@link TokenRepresentation} in the context of a policy * - * @param tokenRepresentation The token - * @param policyScope The policy scope - * @param policy The policy - * @param message The {@link RemoteMessage} + * @param tokenRepresentation The token + * @param policyContextProvider The policy scope provider + * @param policy The policy + * @param message The {@link RemoteMessage} * @return Returns the extracted {@link ParticipantAgent} if successful, failure otherwise */ - ServiceResult verify(TokenRepresentation tokenRepresentation, String policyScope, Policy policy, RemoteMessage message); + ServiceResult verify(TokenRepresentation tokenRepresentation, RequestPolicyContext.Provider policyContextProvider, Policy policy, RemoteMessage message); } diff --git a/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/controlplane/transfer/spi/provision/ResourceManifestGenerator.java b/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/controlplane/transfer/spi/provision/ResourceManifestGenerator.java index f87b2203762..19f52ec1b7f 100644 --- a/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/controlplane/transfer/spi/provision/ResourceManifestGenerator.java +++ b/spi/control-plane/transfer-spi/src/main/java/org/eclipse/edc/connector/controlplane/transfer/spi/provision/ResourceManifestGenerator.java @@ -17,7 +17,6 @@ import org.eclipse.edc.connector.controlplane.transfer.spi.types.ResourceManifest; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; -import org.eclipse.edc.policy.engine.spi.PolicyScope; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; import org.eclipse.edc.spi.result.Result; @@ -29,9 +28,6 @@ @ExtensionPoint public interface ResourceManifestGenerator { - @PolicyScope - String MANIFEST_VERIFICATION_SCOPE = "provision.manifest.verify"; - /** * Registers a generator for consumer-side generation. * diff --git a/spi/policy-monitor/policy-monitor-spi/src/main/java/org/eclipse/edc/connector/policy/monitor/spi/PolicyMonitorContext.java b/spi/policy-monitor/policy-monitor-spi/src/main/java/org/eclipse/edc/connector/policy/monitor/spi/PolicyMonitorContext.java index daab00127cb..47c9cea8605 100644 --- a/spi/policy-monitor/policy-monitor-spi/src/main/java/org/eclipse/edc/connector/policy/monitor/spi/PolicyMonitorContext.java +++ b/spi/policy-monitor/policy-monitor-spi/src/main/java/org/eclipse/edc/connector/policy/monitor/spi/PolicyMonitorContext.java @@ -14,6 +14,7 @@ package org.eclipse.edc.connector.policy.monitor.spi; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.AgreementPolicyContext; import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; import org.eclipse.edc.policy.engine.spi.PolicyContextImpl; import org.eclipse.edc.policy.engine.spi.PolicyScope; @@ -23,7 +24,7 @@ /** * Policy Context for "policy-monitor" scope */ -public class PolicyMonitorContext extends PolicyContextImpl { +public class PolicyMonitorContext extends PolicyContextImpl implements AgreementPolicyContext { @PolicyScope public static final String POLICY_MONITOR_SCOPE = "policy.monitor"; @@ -36,10 +37,12 @@ public PolicyMonitorContext(Instant now, ContractAgreement contractAgreement) { this.contractAgreement = contractAgreement; } + @Override public Instant now() { return now; } + @Override public ContractAgreement contractAgreement() { return contractAgreement; } diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TestFunctions.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TestFunctions.java index 2c7ddbaa5a6..339501286b0 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TestFunctions.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/TestFunctions.java @@ -22,7 +22,6 @@ import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; import org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry; -import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction; import org.eclipse.edc.policy.engine.spi.PolicyValidatorFunction; import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; import org.eclipse.edc.policy.engine.spi.plan.step.AndConstraintStep; @@ -38,7 +37,6 @@ import org.eclipse.edc.policy.model.LiteralExpression; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.PolicyType; -import org.eclipse.edc.policy.model.Rule; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.spi.types.domain.callback.CallbackAddress; @@ -258,8 +256,10 @@ public static EndpointDataReferenceEntry createEdrEntry() { public static PolicyEvaluationPlan createPolicyEvaluationPlan() { - var firstConstraint = atomicConstraintStep(atomicConstraint("foo", "bar")); - var secondConstraint = atomicConstraintStep(atomicConstraint("baz", "bar")); + var firstConstraint = new AtomicConstraintStep(atomicConstraint("foo", "bar"), List.of("filtered constraint"), + mock(), "AtomicConstraintFunction"); + var secondConstraint = new AtomicConstraintStep(atomicConstraint("baz", "bar"), List.of("filtered constraint"), + mock(), "AtomicConstraintFunction"); List constraints = List.of(firstConstraint, secondConstraint); @@ -379,12 +379,6 @@ public static JsonObject policyEvaluationPlanRequest() { .build(); } - private static AtomicConstraintStep atomicConstraintStep(AtomicConstraint atomicConstraint) { - AtomicConstraintFunction function = mock(); - when(function.name()).thenReturn("AtomicConstraintFunction"); - return new AtomicConstraintStep(atomicConstraint, List.of("filtered constraint"), mock(), function); - } - private static AtomicConstraint atomicConstraint(String key, String value) { var left = new LiteralExpression(key); var right = new LiteralExpression(value);