From 3a55b24537d1356dde8b7aacfd4369228d8dbf4a Mon Sep 17 00:00:00 2001 From: Enrico Risa Date: Wed, 4 Sep 2024 16:19:31 +0200 Subject: [PATCH] feat: adds REST API for policy evaluation plan --- .../edc/policy/engine/PolicyEngineImpl.java | 3 +- .../engine/plan/PolicyEvaluationPlanner.java | 37 ++- .../engine/PolicyEngineImplPlannerTest.java | 7 +- .../PolicyDefinitionServiceImpl.java | 15 +- .../resources/management-api-version.json | 2 +- .../BasePolicyDefinitionApiController.java | 2 +- .../policy/PolicyDefinitionApiExtension.java | 7 + .../model/PolicyEvaluationPlanRequest.java | 22 ++ ...ctFromPolicyEvaluationPlanTransformer.java | 200 +++++++++++++++ ...olicyEvaluationPlanRequestTransformer.java | 38 +++ .../v31alpha/PolicyDefinitionApiV31Alpha.java | 65 ++++- ...PolicyDefinitionApiV31AlphaController.java | 27 +++ .../PolicyEvaluationPlanRequestValidator.java | 31 +++ .../policy/PolicyDefinitionApiTest.java | 38 ++- ...omPolicyEvaluationPlanTransformerTest.java | 228 ++++++++++++++++++ ...PolicyValidationResultTransformerTest.java | 60 +++++ ...yEvaluationPlanRequestTransformerTest.java | 59 +++++ ...cyDefinitionApiV31AlphaControllerTest.java | 74 +++++- ...icyEvaluationPlanRequestValidatorTest.java | 62 +++++ .../engine/spi/AtomicConstraintFunction.java | 10 +- .../spi/DynamicAtomicConstraintFunction.java | 8 +- .../edc/policy/engine/spi/RuleFunction.java | 7 + .../engine/spi/plan/PolicyEvaluationPlan.java | 20 +- .../spi/plan/step/AndConstraintStep.java | 4 + .../spi/plan/step/AtomicConstraintStep.java | 31 ++- .../policy/engine/spi/plan/step/DutyStep.java | 4 + .../plan/step/MultiplicityConstraintStep.java | 14 +- .../spi/plan/step/OrConstraintStep.java | 5 + .../engine/spi/plan/step/PermissionStep.java | 7 +- .../engine/spi/plan/step/ProhibitionStep.java | 4 + .../spi/plan/step/RuleFunctionStep.java | 6 + .../policy/engine/spi/plan/step/RuleStep.java | 17 ++ .../engine/spi/plan/step/ValidatorStep.java | 6 + .../spi/plan/step/XoneConstraintStep.java | 4 + .../PolicyDefinitionService.java | 13 +- .../PolicyDefinitionApiEndToEndTest.java | 51 ++++ 36 files changed, 1146 insertions(+), 42 deletions(-) create mode 100644 extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/model/PolicyEvaluationPlanRequest.java create mode 100644 extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformer.java create mode 100644 extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectToPolicyEvaluationPlanRequestTransformer.java create mode 100644 extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/validation/PolicyEvaluationPlanRequestValidator.java create mode 100644 extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformerTest.java create mode 100644 extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyValidationResultTransformerTest.java create mode 100644 extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectToPolicyEvaluationPlanRequestTransformerTest.java create mode 100644 extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/validation/PolicyEvaluationPlanRequestValidatorTest.java 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 31e38f08268..25b66e685a4 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 @@ -156,8 +156,7 @@ public Result validate(Policy policy) { @Override public PolicyEvaluationPlan createEvaluationPlan(String scope, Policy policy) { - var delimitedScope = scope + DELIMITER; - var planner = PolicyEvaluationPlanner.Builder.newInstance(delimitedScope).ruleValidator(ruleValidator); + var planner = PolicyEvaluationPlanner.Builder.newInstance(scope).ruleValidator(ruleValidator); preValidators.forEach(planner::preValidators); postValidators.forEach(planner::postValidators); 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 d280f77e316..dcab638363a 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 @@ -55,6 +55,7 @@ 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 { @@ -65,11 +66,13 @@ public class PolicyEvaluationPlanner implements Policy.Visitor> dynamicConstraintFunctions = new ArrayList<>(); private final List> ruleFunctions = new ArrayList<>(); private final String delimitedScope; + private final String scope; private RuleValidator ruleValidator; - private PolicyEvaluationPlanner(String delimitedScope) { - this.delimitedScope = delimitedScope; + private PolicyEvaluationPlanner(String scope) { + this.scope = scope; + this.delimitedScope = scope + DELIMITER; } @Override @@ -96,9 +99,18 @@ public AtomicConstraintStep visitAtomicConstraint(AtomicConstraint constraint) { var currentRule = currentRule(); var leftValue = constraint.getLeftExpression().accept(s -> s.getValue().toString()); var function = getFunctions(leftValue, currentRule.getClass()); - var isFiltered = !ruleValidator.isInScope(leftValue, delimitedScope) || function == null; - return new AtomicConstraintStep(constraint, isFiltered, currentRule, function); + var filteringReasons = new ArrayList(); + + if (!ruleValidator.isInScope(leftValue, delimitedScope)) { + filteringReasons.add("leftOperand '%s' is not bound to scope '%s'".formatted(leftValue, scope)); + } + + if (function == null) { + filteringReasons.add("leftOperand '%s' is not bound to any function within scope '%s'".formatted(leftValue, scope)); + } + + return new AtomicConstraintStep(constraint, filteringReasons, currentRule, function); } @Override @@ -115,7 +127,7 @@ public PolicyEvaluationPlan visitPolicy(Policy policy) { policy.getObligations().stream().map(obligation -> obligation.accept(this)) .map(DutyStep.class::cast) - .forEach(builder::obligation); + .forEach(builder::duty); policy.getProhibitions().stream().map(permission -> permission.accept(this)) .map(ProhibitionStep.class::cast) @@ -169,7 +181,11 @@ private void visitRule(R rule, RuleStep.Builder builder) { try { ruleContext.push(rule); - builder.filtered(shouldIgnoreRule(rule)); + + if (rule.getAction() != null && !ruleValidator.isBounded(rule.getAction().getType())) { + builder.filtered(true); + builder.filteringReason("action '%s' is not bound to scope '%s'".formatted(rule.getAction().getType(), scope)); + } builder.rule(rule); for (var functionEntry : ruleFunctions) { @@ -192,10 +208,6 @@ private Rule currentRule() { return ruleContext.peek(); } - private boolean shouldIgnoreRule(Rule rule) { - return rule.getAction() != null && !ruleValidator.isBounded(rule.getAction().getType()); - } - private List validateMultiplicityConstraint(MultiplicityConstraint multiplicityConstraint) { return multiplicityConstraint.getConstraints() .stream() @@ -235,6 +247,11 @@ public boolean evaluate(Operator operator, Object rightValue, R rule, PolicyCont public Result validate(Operator operator, Object rightValue, R rule) { return inner.validate(leftOperand, operator, rightValue, rule); } + + @Override + public String name() { + return inner.name(); + } } public static class Builder { 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 087faae57eb..61b650a05f1 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 @@ -240,7 +240,7 @@ public Stream provideArguments(ExtensionContext context) { var prohibition = Prohibition.Builder.newInstance().constraint(constraint).action(action).build(); Function>> permissionSteps = PolicyEvaluationPlan::getPermissionSteps; - Function>> dutySteps = PolicyEvaluationPlan::getDutySteps; + Function>> dutySteps = PolicyEvaluationPlan::getObligationSteps; Function>> prohibitionSteps = PolicyEvaluationPlan::getProhibitionSteps; var permission = Permission.Builder.newInstance().constraint(constraint).action(action).build(); @@ -275,6 +275,7 @@ void shouldIgnorePermissionStep_whenActionNotBound() { .first() .satisfies(permissionStep -> { assertThat(permissionStep.isFiltered()).isTrue(); + assertThat(permissionStep.getFilteringReasons()).hasSize(1); assertThat(permissionStep.getConstraintSteps()).hasSize(1) .first() .isInstanceOfSatisfying(AtomicConstraintStep.class, constraintStep -> { @@ -388,7 +389,7 @@ void shouldEvaluate_withMultiplicityConstraint(Policy policy, Class ruleCl assertThat(ruleStep.getConstraintSteps()).hasSize(1) .first() .isInstanceOfSatisfying(MultiplicityConstraintStep.class, constraintStep -> { - assertThat(constraintStep.getSteps()).hasSize(2); + assertThat(constraintStep.getConstraintSteps()).hasSize(2); assertThat(constraintStep.getConstraint()).isNotNull(); }); })); @@ -414,7 +415,7 @@ public Stream provideArguments(ExtensionContext context) { var duty = Duty.Builder.newInstance().constraint(xoneConstraint).build(); Function>> permissionSteps = PolicyEvaluationPlan::getPermissionSteps; - Function>> dutySteps = PolicyEvaluationPlan::getDutySteps; + Function>> dutySteps = PolicyEvaluationPlan::getObligationSteps; Function>> prohibitionSteps = PolicyEvaluationPlan::getProhibitionSteps; return Stream.of( diff --git a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/policydefinition/PolicyDefinitionServiceImpl.java b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/policydefinition/PolicyDefinitionServiceImpl.java index 53f1d630ffa..c1c4b82764f 100644 --- a/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/policydefinition/PolicyDefinitionServiceImpl.java +++ b/core/control-plane/control-plane-aggregate-services/src/main/java/org/eclipse/edc/connector/controlplane/services/policydefinition/PolicyDefinitionServiceImpl.java @@ -21,6 +21,7 @@ import org.eclipse.edc.connector.controlplane.services.query.QueryValidator; import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; import org.eclipse.edc.policy.model.AndConstraint; import org.eclipse.edc.policy.model.AtomicConstraint; import org.eclipse.edc.policy.model.Constraint; @@ -31,7 +32,6 @@ import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.XoneConstraint; import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.transaction.spi.TransactionContext; import org.jetbrains.annotations.NotNull; @@ -124,8 +124,17 @@ public ServiceResult update(PolicyDefinition policyDefinition) } @Override - public Result validate(Policy policy) { - return policyEngine.validate(policy); + public ServiceResult validate(Policy policy) { + var validationResult = policyEngine.validate(policy); + if (validationResult.failed()) { + return ServiceResult.badRequest(validationResult.getFailureMessages()); + } + return ServiceResult.success(); + } + + @Override + public ServiceResult createEvaluationPlan(String scope, Policy policy) { + return ServiceResult.success(policyEngine.createEvaluationPlan(scope, policy)); } private List queryPolicyDefinitions(QuerySpec query) { diff --git a/extensions/common/api/management-api-configuration/src/main/resources/management-api-version.json b/extensions/common/api/management-api-configuration/src/main/resources/management-api-version.json index d461ba5261b..22dc74a6a0e 100644 --- a/extensions/common/api/management-api-configuration/src/main/resources/management-api-version.json +++ b/extensions/common/api/management-api-configuration/src/main/resources/management-api-version.json @@ -8,7 +8,7 @@ { "version": "3.1.0-alpha", "urlPath": "/v3.1alpha", - "lastUpdated": "2024-08-30T10:17:00Z", + "lastUpdated": "2024-09-04T10:17:00Z", "maturity": "alpha" } ] diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/BasePolicyDefinitionApiController.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/BasePolicyDefinitionApiController.java index 7d7da29e1a0..a65e3595ed7 100644 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/BasePolicyDefinitionApiController.java +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/BasePolicyDefinitionApiController.java @@ -40,7 +40,7 @@ public abstract class BasePolicyDefinitionApiController { protected final Monitor monitor; protected final PolicyDefinitionService service; protected final TypeTransformerRegistry transformerRegistry; - private final JsonObjectValidatorRegistry validatorRegistry; + protected final JsonObjectValidatorRegistry validatorRegistry; public BasePolicyDefinitionApiController(Monitor monitor, TypeTransformerRegistry transformerRegistry, PolicyDefinitionService service, JsonObjectValidatorRegistry validatorRegistry) { diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/PolicyDefinitionApiExtension.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/PolicyDefinitionApiExtension.java index 13352226fbd..e7ea3bd1b0f 100644 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/PolicyDefinitionApiExtension.java +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/PolicyDefinitionApiExtension.java @@ -16,12 +16,15 @@ import jakarta.json.Json; import org.eclipse.edc.connector.controlplane.api.management.policy.transform.JsonObjectFromPolicyDefinitionTransformer; +import org.eclipse.edc.connector.controlplane.api.management.policy.transform.JsonObjectFromPolicyEvaluationPlanTransformer; import org.eclipse.edc.connector.controlplane.api.management.policy.transform.JsonObjectFromPolicyValidationResultTransformer; import org.eclipse.edc.connector.controlplane.api.management.policy.transform.JsonObjectToPolicyDefinitionTransformer; +import org.eclipse.edc.connector.controlplane.api.management.policy.transform.JsonObjectToPolicyEvaluationPlanRequestTransformer; import org.eclipse.edc.connector.controlplane.api.management.policy.v2.PolicyDefinitionApiV2Controller; import org.eclipse.edc.connector.controlplane.api.management.policy.v3.PolicyDefinitionApiV3Controller; import org.eclipse.edc.connector.controlplane.api.management.policy.v31alpha.PolicyDefinitionApiV31AlphaController; import org.eclipse.edc.connector.controlplane.api.management.policy.validation.PolicyDefinitionValidator; +import org.eclipse.edc.connector.controlplane.api.management.policy.validation.PolicyEvaluationPlanRequestValidator; import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; @@ -35,6 +38,7 @@ import java.util.Map; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest.EDC_POLICY_EVALUATION_PLAN_REQUEST_TYPE; import static org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition.EDC_POLICY_DEFINITION_TYPE; import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; @@ -69,11 +73,14 @@ public void initialize(ServiceExtensionContext context) { var jsonBuilderFactory = Json.createBuilderFactory(Map.of()); var managementApiTransformerRegistry = transformerRegistry.forContext("management-api"); var mapper = typeManager.getMapper(JSON_LD); + managementApiTransformerRegistry.register(new JsonObjectToPolicyEvaluationPlanRequestTransformer()); managementApiTransformerRegistry.register(new JsonObjectToPolicyDefinitionTransformer()); managementApiTransformerRegistry.register(new JsonObjectFromPolicyDefinitionTransformer(jsonBuilderFactory, mapper)); managementApiTransformerRegistry.register(new JsonObjectFromPolicyValidationResultTransformer(jsonBuilderFactory)); + managementApiTransformerRegistry.register(new JsonObjectFromPolicyEvaluationPlanTransformer(jsonBuilderFactory)); validatorRegistry.register(EDC_POLICY_DEFINITION_TYPE, PolicyDefinitionValidator.instance()); + validatorRegistry.register(EDC_POLICY_EVALUATION_PLAN_REQUEST_TYPE, PolicyEvaluationPlanRequestValidator.instance()); var monitor = context.getMonitor(); webService.registerResource(ApiContext.MANAGEMENT, new PolicyDefinitionApiV2Controller(monitor, managementApiTransformerRegistry, service, validatorRegistry)); diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/model/PolicyEvaluationPlanRequest.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/model/PolicyEvaluationPlanRequest.java new file mode 100644 index 00000000000..8a9ee3d4f0d --- /dev/null +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/model/PolicyEvaluationPlanRequest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 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.api.management.policy.model; + +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + +public record PolicyEvaluationPlanRequest(String policyScope) { + public static final String EDC_POLICY_EVALUATION_PLAN_REQUEST_TYPE = EDC_NAMESPACE + "PolicyEvaluationPlanRequest"; + public static final String EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE = EDC_NAMESPACE + "policyScope"; +} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformer.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformer.java new file mode 100644 index 00000000000..a1109c1f2f9 --- /dev/null +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformer.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2024 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.api.management.policy.transform; + +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; +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; +import org.eclipse.edc.policy.engine.spi.plan.step.ConstraintStep; +import org.eclipse.edc.policy.engine.spi.plan.step.DutyStep; +import org.eclipse.edc.policy.engine.spi.plan.step.MultiplicityConstraintStep; +import org.eclipse.edc.policy.engine.spi.plan.step.OrConstraintStep; +import org.eclipse.edc.policy.engine.spi.plan.step.PermissionStep; +import org.eclipse.edc.policy.engine.spi.plan.step.ProhibitionStep; +import org.eclipse.edc.policy.engine.spi.plan.step.RuleFunctionStep; +import org.eclipse.edc.policy.engine.spi.plan.step.RuleStep; +import org.eclipse.edc.policy.engine.spi.plan.step.ValidatorStep; +import org.eclipse.edc.policy.engine.spi.plan.step.XoneConstraintStep; +import org.eclipse.edc.policy.model.MultiplicityConstraint; +import org.eclipse.edc.policy.model.Rule; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_OBLIGATION_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_PERMISSION_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_POST_VALIDATORS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_PRE_VALIDATORS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_PROHIBITION_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.AndConstraintStep.EDC_AND_CONSTRAINT_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_FILTERING_REASONS; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_NAME; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_PARAMS; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_IS_FILTERED; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.DutyStep.EDC_DUTY_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.MultiplicityConstraintStep.EDC_MULTIPLICITY_CONSTRAINT_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.step.OrConstraintStep.EDC_OR_CONSTRAINT_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.PermissionStep.EDC_PERMISSION_STEP_DUTY_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.step.PermissionStep.EDC_PERMISSION_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.ProhibitionStep.EDC_PROHIBITION_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.RuleStep.EDC_RULE_CONSTRAINT_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.step.RuleStep.EDC_RULE_FUNCTIONS; +import static org.eclipse.edc.policy.engine.spi.plan.step.RuleStep.EDC_RULE_STEP_FILTERING_REASONS; +import static org.eclipse.edc.policy.engine.spi.plan.step.RuleStep.EDC_RULE_STEP_IS_FILTERED; +import static org.eclipse.edc.policy.engine.spi.plan.step.XoneConstraintStep.EDC_XONE_CONSTRAINT_STEP_TYPE; + +public class JsonObjectFromPolicyEvaluationPlanTransformer extends AbstractJsonLdTransformer { + + private final JsonBuilderFactory jsonFactory; + + public JsonObjectFromPolicyEvaluationPlanTransformer(JsonBuilderFactory jsonFactory) { + super(PolicyEvaluationPlan.class, JsonObject.class); + this.jsonFactory = jsonFactory; + } + + @Override + public @Nullable JsonObject transform(@NotNull PolicyEvaluationPlan plan, @NotNull TransformerContext context) { + var objectBuilder = jsonFactory.createObjectBuilder(); + objectBuilder.add(TYPE, EDC_POLICY_EVALUATION_PLAN_TYPE); + + var preValidators = jsonFactory.createArrayBuilder(); + plan.getPreValidators().stream().map(ValidatorStep::name).forEach(preValidators::add); + + var postValidators = jsonFactory.createArrayBuilder(); + plan.getPostValidators().stream().map(ValidatorStep::name).forEach(postValidators::add); + + var permissionSteps = jsonFactory.createArrayBuilder(); + plan.getPermissionSteps().stream().map(this::transformPermissionStep) + .forEach(permissionSteps::add); + + var prohibitionSteps = jsonFactory.createArrayBuilder(); + plan.getProhibitionSteps().stream().map(this::transformProhibitionStep) + .forEach(prohibitionSteps::add); + + var dutySteps = jsonFactory.createArrayBuilder(); + plan.getObligationSteps().stream().map(this::transformDutyStep) + .forEach(dutySteps::add); + + objectBuilder.add(EDC_POLICY_EVALUATION_PLAN_PRE_VALIDATORS, preValidators); + objectBuilder.add(EDC_POLICY_EVALUATION_PLAN_PERMISSION_STEPS, permissionSteps); + objectBuilder.add(EDC_POLICY_EVALUATION_PLAN_PROHIBITION_STEPS, prohibitionSteps); + objectBuilder.add(EDC_POLICY_EVALUATION_PLAN_OBLIGATION_STEPS, dutySteps); + objectBuilder.add(EDC_POLICY_EVALUATION_PLAN_POST_VALIDATORS, postValidators); + + return objectBuilder.build(); + } + + private JsonObjectBuilder transformDutyStep(DutyStep dutyStep) { + return transformRuleStep(dutyStep, EDC_DUTY_STEP_TYPE); + } + + private JsonObjectBuilder transformPermissionStep(PermissionStep permissionStep) { + var dutySteps = jsonFactory.createArrayBuilder(); + + permissionStep.getDutySteps().stream().map(this::transformDutyStep) + .forEach(dutySteps::add); + + return transformRuleStep(permissionStep, EDC_PERMISSION_STEP_TYPE) + .add(EDC_PERMISSION_STEP_DUTY_STEPS, dutySteps); + } + + private JsonObjectBuilder transformProhibitionStep(ProhibitionStep prohibitionStep) { + return transformRuleStep(prohibitionStep, EDC_PROHIBITION_STEP_TYPE); + } + + private JsonObjectBuilder transformRuleStep(RuleStep ruleStep, String type) { + + var builder = jsonFactory.createObjectBuilder(); + var constraintSteps = jsonFactory.createArrayBuilder(); + var ruleFunctionSteps = jsonFactory.createArrayBuilder(); + + ruleStep.getConstraintSteps().stream() + .map(this::transformConstraintStep) + .forEach(constraintSteps::add); + + ruleStep.getRuleFunctions().stream() + .map(RuleFunctionStep::functionName) + .forEach(ruleFunctionSteps::add); + + builder.add(TYPE, type); + builder.add(EDC_RULE_STEP_IS_FILTERED, ruleStep.isFiltered()); + builder.add(EDC_RULE_STEP_FILTERING_REASONS, jsonFactory.createArrayBuilder(ruleStep.getFilteringReasons())); + builder.add(EDC_RULE_FUNCTIONS, ruleFunctionSteps); + builder.add(EDC_RULE_CONSTRAINT_STEPS, constraintSteps); + + return builder; + } + + private JsonObject transformConstraintStep(ConstraintStep constraintStep) { + // TODO replace with pattern matching once we move to JDK 21 + if (constraintStep instanceof AtomicConstraintStep atomicConstraintStep) { + return transformAtomicConstraintStep(atomicConstraintStep); + } else if (constraintStep instanceof AndConstraintStep andConstraintStep) { + return transformAndConstraintStep(andConstraintStep); + } else if (constraintStep instanceof OrConstraintStep orConstraintStep) { + return transformOrConstraintStep(orConstraintStep); + } else if (constraintStep instanceof XoneConstraintStep xoneConstraintStep) { + return transformXoneConstraintStep(xoneConstraintStep); + } + return jsonFactory.createObjectBuilder().build(); + } + + private JsonObject transformAtomicConstraintStep(AtomicConstraintStep atomicConstraintStep) { + var builder = jsonFactory.createObjectBuilder(); + builder.add(TYPE, EDC_ATOMIC_CONSTRAINT_STEP_TYPE); + builder.add(EDC_ATOMIC_CONSTRAINT_STEP_IS_FILTERED, atomicConstraintStep.isFiltered()); + builder.add(EDC_ATOMIC_CONSTRAINT_STEP_FILTERING_REASONS, jsonFactory.createArrayBuilder(atomicConstraintStep.filteringReasons())); + + Optional.ofNullable(atomicConstraintStep.functionName()) + .ifPresent(name -> builder.add(EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_NAME, name)); + + builder.add(EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_PARAMS, jsonFactory.createArrayBuilder(atomicConstraintStep.functionParams())); + return builder.build(); + } + + private JsonObject transformOrConstraintStep(OrConstraintStep orConstraintStep) { + return transformMultiplicityConstraintStep(orConstraintStep, EDC_OR_CONSTRAINT_STEP_TYPE); + } + + private JsonObject transformAndConstraintStep(AndConstraintStep andConstraintStep) { + return transformMultiplicityConstraintStep(andConstraintStep, EDC_AND_CONSTRAINT_STEP_TYPE); + + } + + private JsonObject transformXoneConstraintStep(XoneConstraintStep xoneConstraintStep) { + return transformMultiplicityConstraintStep(xoneConstraintStep, EDC_XONE_CONSTRAINT_STEP_TYPE); + } + + private JsonObject transformMultiplicityConstraintStep(MultiplicityConstraintStep multiplicityConstraintStep, String type) { + var builder = jsonFactory.createObjectBuilder(); + var constraintSteps = jsonFactory.createArrayBuilder(); + + multiplicityConstraintStep.getConstraintSteps().stream().map(this::transformConstraintStep) + .forEach(constraintSteps::add); + + builder.add(TYPE, type); + builder.add(EDC_MULTIPLICITY_CONSTRAINT_STEPS, constraintSteps); + return builder.build(); + } +} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectToPolicyEvaluationPlanRequestTransformer.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectToPolicyEvaluationPlanRequestTransformer.java new file mode 100644 index 00000000000..67aadef1045 --- /dev/null +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectToPolicyEvaluationPlanRequestTransformer.java @@ -0,0 +1,38 @@ +/* + * 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.api.management.policy.transform; + +import jakarta.json.JsonObject; +import org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest; +import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest.EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE; + +public class JsonObjectToPolicyEvaluationPlanRequestTransformer extends AbstractJsonLdTransformer { + + public JsonObjectToPolicyEvaluationPlanRequestTransformer() { + super(JsonObject.class, PolicyEvaluationPlanRequest.class); + } + + @Override + public @Nullable PolicyEvaluationPlanRequest transform(@NotNull JsonObject input, @NotNull TransformerContext context) { + var policyScope = transformString(input.get(EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE), context); + return new PolicyEvaluationPlanRequest(policyScope); + } + +} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31Alpha.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31Alpha.java index 18632762413..df36479ea60 100644 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31Alpha.java +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31Alpha.java @@ -104,12 +104,23 @@ public interface PolicyDefinitionApiV31Alpha { @Operation(description = "Validates an existing Policy, If the Policy is not found, an error is reported", responses = { @ApiResponse(responseCode = "200", description = "Returns the validation result", content = @Content(schema = @Schema(implementation = PolicyValidationResultSchema.class))), - @ApiResponse(responseCode = "404", description = "policy definition could not be updated, because it does not exists", + @ApiResponse(responseCode = "404", description = "policy definition could not be validated, because it does not exists", content = @Content(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class))) } ) JsonObject validatePolicyDefinitionV3(String id); + + @Operation(description = "Creates an execution plane for an existing Policy, If the Policy is not found, an error is reported", + requestBody = @RequestBody(content = @Content(schema = @Schema(implementation = PolicyEvaluationPlanRequestSchema.class))), + responses = { + @ApiResponse(responseCode = "200", description = "Returns the evaluation plan", content = @Content(schema = @Schema(implementation = PolicyEvaluationPlanSchema.class))), + @ApiResponse(responseCode = "404", description = "An evaluation plan could not be created, because the policy definition does not exists", + content = @Content(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class))) + } + ) + JsonObject createExecutionPlaneV3(String id, JsonObject input); + @Schema(name = "PolicyDefinitionInput", example = PolicyDefinitionInputSchema.POLICY_DEFINITION_INPUT_EXAMPLE) record PolicyDefinitionInputSchema( @Schema(name = CONTEXT, requiredMode = REQUIRED) @@ -197,4 +208,56 @@ record PolicyValidationResultSchema( """; } + @Schema(name = "PolicyEvaluationPlanRequestSchema", example = PolicyEvaluationPlanRequestSchema.POLICY_EVALUATION_PLAN_REQUEST_INPUT_EXAMPLE) + record PolicyEvaluationPlanRequestSchema( + String policyScope) { + + public static final String POLICY_EVALUATION_PLAN_REQUEST_INPUT_EXAMPLE = """ + { + "@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" }, + "@type": "PolicyEvaluationPlanRequest", + "policyScope": "catalog" + } + """; + } + + @Schema(name = "PolicyEvaluationPlanSchema", example = PolicyEvaluationPlanSchema.POLICY_EVALUATION_PLANE_OUTPUT_EXAMPLE) + record PolicyEvaluationPlanSchema() { + + public static final String POLICY_EVALUATION_PLANE_OUTPUT_EXAMPLE = """ + { + "@type": "PolicyEvaluationPlan", + "preValidators": "DcpScopeExtractorFunction", + "permissionSteps": { + "@type": "PermissionStep", + "isFiltered": false, + "filteringReasons": [], + "ruleFunctions": [], + "constraintSteps": { + "@type": "AtomicConstraintStep", + "isFiltered": true, + "filteringReasons": [ + "leftOperand 'MembershipCredential' is not bound to scope 'request.catalog'", + "leftOperand 'MembershipCredential' is not bound to any function within scope 'request.catalog'" + ], + "functionParams": [ + "'MembershipCredential'", + "EQ", + "'active'" + ] + }, + "dutySteps": [] + }, + "prohibitionSteps": [], + "obligationSteps": [], + "postValidators": "DefaultScopeMappingFunction", + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/", + "edc": "https://w3id.org/edc/v0.0.1/ns/", + "odrl": "http://www.w3.org/ns/odrl/2/" + } + } + """; + } + } diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31AlphaController.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31AlphaController.java index 799f22d9be3..01739d082ff 100644 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31AlphaController.java +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31AlphaController.java @@ -25,6 +25,7 @@ import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import org.eclipse.edc.connector.controlplane.api.management.policy.BasePolicyDefinitionApiController; +import org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest; import org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult; import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; @@ -32,11 +33,15 @@ import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.web.spi.exception.InvalidRequestException; import org.eclipse.edc.web.spi.exception.ObjectNotFoundException; +import org.eclipse.edc.web.spi.exception.ValidationFailureException; import java.util.ArrayList; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest.EDC_POLICY_EVALUATION_PLAN_REQUEST_TYPE; +import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; @Consumes(APPLICATION_JSON) @Produces(APPLICATION_JSON) @@ -101,4 +106,26 @@ public JsonObject validatePolicyDefinitionV3(@PathParam("id") String id) { return transformerRegistry.transform(validationResult, JsonObject.class) .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); } + + @POST + @Path("{id}/evaluationplan") + @Override + public JsonObject createExecutionPlaneV3(@PathParam("id") String id, JsonObject request) { + + validatorRegistry.validate(EDC_POLICY_EVALUATION_PLAN_REQUEST_TYPE, request).orElseThrow(ValidationFailureException::new); + + var planeRequest = transformerRegistry.transform(request, PolicyEvaluationPlanRequest.class) + .orElseThrow(InvalidRequestException::new); + + var definition = service.findById(id); + if (definition == null) { + throw new ObjectNotFoundException(PolicyDefinition.class, id); + } + + var plan = service.createEvaluationPlan(planeRequest.policyScope(), definition.getPolicy()) + .orElseThrow(exceptionMapper(PolicyDefinition.class, definition.getId())); + + return transformerRegistry.transform(plan, JsonObject.class) + .orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail())); + } } diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/validation/PolicyEvaluationPlanRequestValidator.java b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/validation/PolicyEvaluationPlanRequestValidator.java new file mode 100644 index 00000000000..d0942f51801 --- /dev/null +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/main/java/org/eclipse/edc/connector/controlplane/api/management/policy/validation/PolicyEvaluationPlanRequestValidator.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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.api.management.policy.validation; + +import jakarta.json.JsonObject; +import org.eclipse.edc.validator.jsonobject.JsonObjectValidator; +import org.eclipse.edc.validator.jsonobject.validators.MandatoryValue; +import org.eclipse.edc.validator.spi.Validator; + +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest.EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE; + +public class PolicyEvaluationPlanRequestValidator { + + public static Validator instance() { + return JsonObjectValidator.newValidator() + .verify(EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE, MandatoryValue::new) + .build(); + } +} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/PolicyDefinitionApiTest.java b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/PolicyDefinitionApiTest.java index 14319e74653..e8d964be46d 100644 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/PolicyDefinitionApiTest.java +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/PolicyDefinitionApiTest.java @@ -29,12 +29,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest.EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest.EDC_POLICY_EVALUATION_PLAN_REQUEST_TYPE; import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult.EDC_POLICY_VALIDATION_RESULT_ERRORS; import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult.EDC_POLICY_VALIDATION_RESULT_IS_VALID; import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult.EDC_POLICY_VALIDATION_RESULT_TYPE; import static org.eclipse.edc.connector.controlplane.api.management.policy.v2.PolicyDefinitionApiV2.PolicyDefinitionInputSchema.POLICY_DEFINITION_INPUT_EXAMPLE; import static org.eclipse.edc.connector.controlplane.api.management.policy.v2.PolicyDefinitionApiV2.PolicyDefinitionOutputSchema.POLICY_DEFINITION_OUTPUT_EXAMPLE; +import static org.eclipse.edc.connector.controlplane.api.management.policy.v31alpha.PolicyDefinitionApiV31Alpha.PolicyEvaluationPlanRequestSchema.POLICY_EVALUATION_PLAN_REQUEST_INPUT_EXAMPLE; +import static org.eclipse.edc.connector.controlplane.api.management.policy.v31alpha.PolicyDefinitionApiV31Alpha.PolicyEvaluationPlanSchema.POLICY_EVALUATION_PLANE_OUTPUT_EXAMPLE; import static org.eclipse.edc.connector.controlplane.api.management.policy.v31alpha.PolicyDefinitionApiV31Alpha.PolicyValidationResultSchema.POLICY_VALIDATION_RESULT_OUTPUT_EXAMPLE; import static org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition.EDC_POLICY_DEFINITION_POLICY; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; @@ -43,6 +47,12 @@ import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.EDC_CREATED_AT; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.junit.extensions.TestServiceExtensionContext.testServiceExtensionContext; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_OBLIGATION_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_PERMISSION_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_POST_VALIDATORS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_PRE_VALIDATORS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_PROHIBITION_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_TYPE; import static org.mockito.Mockito.mock; class PolicyDefinitionApiTest { @@ -100,4 +110,30 @@ void policyValidationResultExample() throws JsonProcessingException { assertThat(content.getJsonArray(EDC_POLICY_VALIDATION_RESULT_ERRORS).size()).isEqualTo(2); }); } + + @Test + void policyEvaluationPlanRequestExample() throws JsonProcessingException { + var jsonObject = objectMapper.readValue(POLICY_EVALUATION_PLAN_REQUEST_INPUT_EXAMPLE, JsonObject.class); + var expanded = jsonLd.expand(jsonObject); + + assertThat(expanded).isSucceeded().satisfies(content -> { + assertThat(content.getJsonArray(TYPE).getString(0)).isEqualTo(EDC_POLICY_EVALUATION_PLAN_REQUEST_TYPE); + assertThat(content.getJsonArray(EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE).getJsonObject(0).getString(VALUE)).isEqualTo("catalog"); + }); + } + + @Test + void policyEvaluationPlanOutputExample() throws JsonProcessingException { + var jsonObject = objectMapper.readValue(POLICY_EVALUATION_PLANE_OUTPUT_EXAMPLE, JsonObject.class); + var expanded = jsonLd.expand(jsonObject); + + assertThat(expanded).isSucceeded().satisfies(content -> { + assertThat(content.getJsonArray(TYPE).getString(0)).isEqualTo(EDC_POLICY_EVALUATION_PLAN_TYPE); + assertThat(content.getJsonArray(EDC_POLICY_EVALUATION_PLAN_PRE_VALIDATORS)).hasSize(1); + assertThat(content.getJsonArray(EDC_POLICY_EVALUATION_PLAN_PERMISSION_STEPS)).hasSize(1); + assertThat(content.getJsonArray(EDC_POLICY_EVALUATION_PLAN_PROHIBITION_STEPS)).hasSize(0); + assertThat(content.getJsonArray(EDC_POLICY_EVALUATION_PLAN_OBLIGATION_STEPS)).hasSize(0); + assertThat(content.getJsonArray(EDC_POLICY_EVALUATION_PLAN_POST_VALIDATORS)).hasSize(1); + }); + } } 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 new file mode 100644 index 00000000000..d3c93af46ba --- /dev/null +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyEvaluationPlanTransformerTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2024 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.api.management.policy.transform; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction; +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.AndConstraintStep; +import org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep; +import org.eclipse.edc.policy.engine.spi.plan.step.ConstraintStep; +import org.eclipse.edc.policy.engine.spi.plan.step.DutyStep; +import org.eclipse.edc.policy.engine.spi.plan.step.OrConstraintStep; +import org.eclipse.edc.policy.engine.spi.plan.step.PermissionStep; +import org.eclipse.edc.policy.engine.spi.plan.step.ProhibitionStep; +import org.eclipse.edc.policy.engine.spi.plan.step.RuleFunctionStep; +import org.eclipse.edc.policy.engine.spi.plan.step.ValidatorStep; +import org.eclipse.edc.policy.engine.spi.plan.step.XoneConstraintStep; +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; +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.util.List; +import java.util.stream.Stream; + +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_OBLIGATION_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_PERMISSION_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_POST_VALIDATORS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_PRE_VALIDATORS; +import static org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan.EDC_POLICY_EVALUATION_PLAN_PROHIBITION_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.step.AndConstraintStep.EDC_AND_CONSTRAINT_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_FILTERING_REASONS; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_NAME; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_PARAMS; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_IS_FILTERED; +import static org.eclipse.edc.policy.engine.spi.plan.step.AtomicConstraintStep.EDC_ATOMIC_CONSTRAINT_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.MultiplicityConstraintStep.EDC_MULTIPLICITY_CONSTRAINT_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.step.OrConstraintStep.EDC_OR_CONSTRAINT_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.PermissionStep.EDC_PERMISSION_STEP_DUTY_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.step.PermissionStep.EDC_PERMISSION_STEP_TYPE; +import static org.eclipse.edc.policy.engine.spi.plan.step.RuleStep.EDC_RULE_CONSTRAINT_STEPS; +import static org.eclipse.edc.policy.engine.spi.plan.step.RuleStep.EDC_RULE_FUNCTIONS; +import static org.eclipse.edc.policy.engine.spi.plan.step.RuleStep.EDC_RULE_STEP_FILTERING_REASONS; +import static org.eclipse.edc.policy.engine.spi.plan.step.RuleStep.EDC_RULE_STEP_IS_FILTERED; +import static org.eclipse.edc.policy.engine.spi.plan.step.XoneConstraintStep.EDC_XONE_CONSTRAINT_STEP_TYPE; +import static org.eclipse.edc.policy.model.Operator.EQ; +import static org.junit.jupiter.params.provider.Arguments.of; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +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); + assertThat(transformer.getOutputType()).isEqualTo(JsonObject.class); + } + + @Test + void transform_withPermissionStep() { + var plan = PolicyEvaluationPlan.Builder.newInstance().permission(permissionStep()).build(); + + var result = transformer.transform(plan, context); + + assertThat(result).isNotNull(); + assertThat(result.getJsonArray(EDC_POLICY_EVALUATION_PLAN_PERMISSION_STEPS)).hasSize(1); + var permission = result.getJsonArray(EDC_POLICY_EVALUATION_PLAN_PERMISSION_STEPS).get(0).asJsonObject(); + + assertThat(permission.getString(TYPE)).isEqualTo(EDC_PERMISSION_STEP_TYPE); + assertThat(permission.getBoolean(EDC_RULE_STEP_IS_FILTERED)).isEqualTo(true); + assertThat(permission.getJsonArray(EDC_RULE_STEP_FILTERING_REASONS)).contains(Json.createValue("filter reason")); + assertThat(permission.getJsonArray(EDC_PERMISSION_STEP_DUTY_STEPS)).hasSize(1); + assertThat(permission.getJsonArray(EDC_RULE_FUNCTIONS)).hasSize(1).contains(Json.createValue("PermissionFunction")); + assertThat(permission.getJsonArray(EDC_RULE_CONSTRAINT_STEPS)).hasSize(1); + + var constraint = permission.getJsonArray(EDC_RULE_CONSTRAINT_STEPS).get(0).asJsonObject(); + + assertThat(constraint.getString(TYPE)).isEqualTo(EDC_ATOMIC_CONSTRAINT_STEP_TYPE); + assertThat(constraint.getBoolean(EDC_ATOMIC_CONSTRAINT_STEP_IS_FILTERED)).isTrue(); + assertThat(constraint.getJsonArray(EDC_ATOMIC_CONSTRAINT_STEP_FILTERING_REASONS)).hasSize(1) + .contains(Json.createValue("filtered constraint")); + assertThat(constraint.getString(EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_NAME)).isEqualTo("AtomicConstraintFunction"); + assertThat(constraint.getJsonArray(EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_PARAMS)) + .hasSize(3) + .containsExactly(Json.createValue("'foo'"), Json.createValue("EQ"), Json.createValue("'bar'")); + + } + + @Test + void transform_withValidators() { + + var validator = new ValidatorStep(mock()); + var plan = PolicyEvaluationPlan.Builder.newInstance() + .preValidator(validator) + .postValidator(validator) + .build(); + + var result = transformer.transform(plan, context); + + assertThat(result).isNotNull(); + assertThat(result.getJsonArray(EDC_POLICY_EVALUATION_PLAN_PRE_VALIDATORS)).hasSize(1); + assertThat(result.getJsonArray(EDC_POLICY_EVALUATION_PLAN_POST_VALIDATORS)).hasSize(1); + + } + + @ParameterizedTest + @ArgumentsSource(MultiplicityStepProvider.class) + void transformWithMultiplicitySteps(PolicyEvaluationPlan plan, String ruleStepProperty, String multiplicityType) { + + var result = transformer.transform(plan, context); + + assertThat(result).isNotNull(); + + assertThat(result.getJsonArray(ruleStepProperty)).hasSize(1); + var rule = result.getJsonArray(ruleStepProperty).get(0).asJsonObject(); + + assertThat(rule.getJsonArray(EDC_RULE_CONSTRAINT_STEPS)).hasSize(1); + var constraint = rule.getJsonArray(EDC_RULE_CONSTRAINT_STEPS).get(0).asJsonObject(); + + assertThat(constraint.getString(TYPE)).isEqualTo(multiplicityType); + assertThat(constraint.getJsonArray(EDC_MULTIPLICITY_CONSTRAINT_STEPS)).hasSize(2); + + } + + private PolicyEvaluationPlan createPlan() { + return PolicyEvaluationPlan.Builder.newInstance() + .preValidator(new ValidatorStep(mock())) + .duty(dutyStep()) + .permission(permissionStep()) + .prohibition(prohibitionStep()) + .postValidator(new ValidatorStep(mock())) + .build(); + } + + private DutyStep dutyStep() { + return DutyStep.Builder.newInstance().rule(mock()).filtered(false).build(); + } + + private PermissionStep permissionStep() { + return permissionStep(atomicConstraintStep(atomicConstraint("foo", "bar"))); + } + + private PermissionStep permissionStep(ConstraintStep constraintStep) { + RuleFunction function = mock(); + when(function.name()).thenReturn("PermissionFunction"); + return PermissionStep.Builder.newInstance() + .rule(mock()) + .filtered(true) + .filteringReason("filter reason") + .ruleFunction(new RuleFunctionStep<>(function, mock())) + .constraint(constraintStep) + .dutyStep(DutyStep.Builder.newInstance().rule(mock()).filtered(false).build()).build(); + } + + private ProhibitionStep prohibitionStep() { + return ProhibitionStep.Builder.newInstance() + .rule(mock()) + .build(); + } + + private static class MultiplicityStepProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + + var firstConstraint = atomicConstraintStep(atomicConstraint("foo", "bar")); + var secondConstraint = atomicConstraintStep(atomicConstraint("baz", "bar")); + + List constraints = List.of(firstConstraint, secondConstraint); + + var orConstraintStep = new OrConstraintStep(constraints, mock()); + var andConstraintStep = new AndConstraintStep(constraints, mock()); + var xoneConstraintStep = new XoneConstraintStep(constraints, mock()); + + var permission = PermissionStep.Builder.newInstance().constraint(orConstraintStep).rule(mock()).build(); + var duty = DutyStep.Builder.newInstance().constraint(xoneConstraintStep).rule(mock()).build(); + var prohibition = ProhibitionStep.Builder.newInstance().constraint(andConstraintStep).rule(mock()).build(); + + return Stream.of( + of(PolicyEvaluationPlan.Builder.newInstance().permission(permission).build(), EDC_POLICY_EVALUATION_PLAN_PERMISSION_STEPS, EDC_OR_CONSTRAINT_STEP_TYPE), + of(PolicyEvaluationPlan.Builder.newInstance().duty(duty).build(), EDC_POLICY_EVALUATION_PLAN_OBLIGATION_STEPS, EDC_XONE_CONSTRAINT_STEP_TYPE), + of(PolicyEvaluationPlan.Builder.newInstance().prohibition(prohibition).build(), EDC_POLICY_EVALUATION_PLAN_PROHIBITION_STEPS, EDC_AND_CONSTRAINT_STEP_TYPE) + ); + } + } +} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyValidationResultTransformerTest.java b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyValidationResultTransformerTest.java new file mode 100644 index 00000000000..a069b1cc79a --- /dev/null +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectFromPolicyValidationResultTransformerTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 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.api.management.policy.transform; + +import jakarta.json.Json; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; +import org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult.EDC_POLICY_VALIDATION_RESULT_ERRORS; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult.EDC_POLICY_VALIDATION_RESULT_IS_VALID; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult.EDC_POLICY_VALIDATION_RESULT_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.mockito.Mockito.mock; + +class JsonObjectFromPolicyValidationResultTransformerTest { + + private final JsonBuilderFactory jsonFactory = Json.createBuilderFactory(Map.of()); + + private final JsonObjectFromPolicyValidationResultTransformer transformer = new JsonObjectFromPolicyValidationResultTransformer(jsonFactory); + private final TransformerContext context = mock(TransformerContext.class); + + @Test + void types() { + assertThat(transformer.getOutputType()).isEqualTo(JsonObject.class); + assertThat(transformer.getInputType()).isEqualTo(PolicyValidationResult.class); + } + + @Test + void transform() { + var validationResult = new PolicyValidationResult(false, List.of("error1", "error2")); + + var result = transformer.transform(validationResult, context); + + assertThat(result).isNotNull(); + assertThat(result.getString(TYPE)).isEqualTo(EDC_POLICY_VALIDATION_RESULT_TYPE); + assertThat(result.getBoolean(EDC_POLICY_VALIDATION_RESULT_IS_VALID)).isFalse(); + assertThat(result.getJsonArray(EDC_POLICY_VALIDATION_RESULT_ERRORS)).hasSize(2) + .contains(Json.createValue("error1"), Json.createValue("error2")); + } + +} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectToPolicyEvaluationPlanRequestTransformerTest.java b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectToPolicyEvaluationPlanRequestTransformerTest.java new file mode 100644 index 00000000000..f95aa8cad3c --- /dev/null +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/transform/JsonObjectToPolicyEvaluationPlanRequestTransformerTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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.api.management.policy.transform; + +import jakarta.json.JsonObject; +import org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest; +import org.eclipse.edc.jsonld.TitaniumJsonLd; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.transform.spi.TransformerContext; +import org.junit.jupiter.api.Test; + +import static jakarta.json.Json.createObjectBuilder; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest.EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest.EDC_POLICY_EVALUATION_PLAN_REQUEST_TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.mockito.Mockito.mock; + +public class JsonObjectToPolicyEvaluationPlanRequestTransformerTest { + + private final JsonObjectToPolicyEvaluationPlanRequestTransformer transformer = new JsonObjectToPolicyEvaluationPlanRequestTransformer(); + private final TransformerContext context = mock(TransformerContext.class); + private final TitaniumJsonLd jsonLd = new TitaniumJsonLd(mock(Monitor.class)); + + @Test + void types() { + assertThat(transformer.getInputType()).isEqualTo(JsonObject.class); + assertThat(transformer.getOutputType()).isEqualTo(PolicyEvaluationPlanRequest.class); + } + + @Test + void transform() { + var json = createObjectBuilder() + .add(TYPE, EDC_POLICY_EVALUATION_PLAN_REQUEST_TYPE) + .add(EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE, "scope") + .build(); + + var result = transformer.transform(expand(json), context); + + assertThat(result).isNotNull(); + assertThat(result.policyScope()).isEqualTo("scope"); + } + + private JsonObject expand(JsonObject jsonObject) { + return jsonLd.expand(jsonObject).orElseThrow(f -> new AssertionError(f.getFailureDetail())); + } +} diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31AlphaControllerTest.java b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31AlphaControllerTest.java index 67a8d341b35..8299dffca5a 100644 --- a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31AlphaControllerTest.java +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/v31alpha/PolicyDefinitionApiV31AlphaControllerTest.java @@ -18,16 +18,21 @@ import jakarta.json.Json; import jakarta.json.JsonObject; import org.eclipse.edc.connector.controlplane.api.management.policy.BasePolicyDefinitionApiControllerTest; +import org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest; import org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult; import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; +import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.ServiceResult; +import org.eclipse.edc.validator.spi.ValidationResult; import org.junit.jupiter.api.Test; import java.util.List; import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; +import static org.eclipse.edc.validator.spi.Violation.violation; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -54,7 +59,7 @@ void validate_shouldReturnValid_whenValidationSucceed() { var policyDefinition = PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).build(); when(service.findById(any())).thenReturn(policyDefinition); - when(service.validate(policyDefinition.getPolicy())).thenReturn(Result.success()); + when(service.validate(policyDefinition.getPolicy())).thenReturn(ServiceResult.success()); when(transformerRegistry.transform(any(PolicyValidationResult.class), eq(JsonObject.class))).then(answer -> { PolicyValidationResult result = answer.getArgument(0); var response = Json.createObjectBuilder() @@ -81,7 +86,7 @@ void validate_shouldReturnInvalidValid_whenValidationFails() { when(service.findById(any())).thenReturn(policyDefinition); - when(service.validate(policyDefinition.getPolicy())).thenReturn(Result.failure(List.of("error1", "error2"))); + when(service.validate(policyDefinition.getPolicy())).thenReturn(ServiceResult.badRequest(List.of("error1", "error2"))); when(transformerRegistry.transform(any(PolicyValidationResult.class), eq(JsonObject.class))).then(answer -> { PolicyValidationResult result = answer.getArgument(0); var response = Json.createObjectBuilder() @@ -101,6 +106,71 @@ void validate_shouldReturnInvalidValid_whenValidationFails() { .body("errors.size()", is(2)); } + @Test + void createEvaluationPlan() { + + var policyScope = "scope"; + var policyDefinition = PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).build(); + var plan = PolicyEvaluationPlan.Builder.newInstance().build(); + var response = Json.createObjectBuilder().build(); + var body = Json.createObjectBuilder().add("policyScope", policyScope).build(); + + when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success()); + when(service.findById(any())).thenReturn(policyDefinition); + when(service.createEvaluationPlan(policyScope, policyDefinition.getPolicy())).thenReturn(ServiceResult.success(plan)); + + when(transformerRegistry.transform(any(JsonObject.class), eq(PolicyEvaluationPlanRequest.class))) + .thenReturn(Result.success(new PolicyEvaluationPlanRequest(policyScope))); + + when(transformerRegistry.transform(any(PolicyEvaluationPlan.class), eq(JsonObject.class))).thenReturn(Result.success(response)); + + baseRequest() + .contentType(JSON) + .body(body) + .post("/id/evaluationplan") + .then() + .statusCode(200) + .contentType(JSON); + } + + @Test + void createEvaluationPlan_fails_whenPolicyNotFound() { + + var policyScope = "scope"; + var body = Json.createObjectBuilder().add("policyScope", policyScope).build(); + + when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.success()); + when(service.findById(any())).thenReturn(null); + when(transformerRegistry.transform(any(JsonObject.class), eq(PolicyEvaluationPlanRequest.class))) + .thenReturn(Result.success(new PolicyEvaluationPlanRequest(policyScope))); + + baseRequest() + .contentType(JSON) + .body(body) + .post("/id/evaluationplan") + .then() + .statusCode(404); + } + + @Test + void createEvaluationPlan_fails_whenRequestValidation() { + + var policyScope = "scope"; + var body = Json.createObjectBuilder().add("policyScope", policyScope).build(); + + when(validatorRegistry.validate(any(), any())).thenReturn(ValidationResult.failure(violation("failure", "failure path"))); + when(service.findById(any())).thenReturn(null); + when(transformerRegistry.transform(any(JsonObject.class), eq(PolicyEvaluationPlanRequest.class))) + .thenReturn(Result.success(new PolicyEvaluationPlanRequest(policyScope))); + + baseRequest() + .contentType(JSON) + .body(body) + .post("/id/evaluationplan") + .then() + .statusCode(400); + } + @Override protected Object controller() { return new PolicyDefinitionApiV31AlphaController(monitor, transformerRegistry, service, validatorRegistry); diff --git a/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/validation/PolicyEvaluationPlanRequestValidatorTest.java b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/validation/PolicyEvaluationPlanRequestValidatorTest.java new file mode 100644 index 00000000000..3e017d6dc30 --- /dev/null +++ b/extensions/control-plane/api/management-api/policy-definition-api/src/test/java/org/eclipse/edc/connector/controlplane/api/management/policy/validation/PolicyEvaluationPlanRequestValidatorTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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.api.management.policy.validation; + +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import org.assertj.core.api.Assertions; +import org.eclipse.edc.validator.spi.ValidationFailure; +import org.eclipse.edc.validator.spi.Validator; +import org.eclipse.edc.validator.spi.Violation; +import org.junit.jupiter.api.Test; + +import static jakarta.json.Json.createArrayBuilder; +import static jakarta.json.Json.createObjectBuilder; +import static org.assertj.core.api.InstanceOfAssertFactories.list; +import static org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyEvaluationPlanRequest.EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; + +public class PolicyEvaluationPlanRequestValidatorTest { + + private final Validator validator = PolicyEvaluationPlanRequestValidator.instance(); + + @Test + void shouldSucceed_whenObjectIsValid() { + var request = createObjectBuilder() + .add(EDC_POLICY_EVALUATION_PLAN_REQUEST_POLICY_SCOPE, value("scope")) + .build(); + + var result = validator.validate(request); + + assertThat(result).isSucceeded(); + } + + @Test + void shouldFail_whenIdIsBlank() { + var request = createObjectBuilder() + .build(); + + var result = validator.validate(request); + + assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class)) + .isNotEmpty() + .anySatisfy(violation -> Assertions.assertThat(violation.message()).contains("blank")); + } + + private JsonArrayBuilder value(String value) { + return createArrayBuilder().add(createObjectBuilder().add(VALUE, value)); + } +} diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/AtomicConstraintFunction.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/AtomicConstraintFunction.java index b3ec17b7cb3..1b364a28038 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/AtomicConstraintFunction.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/AtomicConstraintFunction.java @@ -34,8 +34,7 @@ public interface AtomicConstraintFunction { * @param context the policy context */ boolean evaluate(Operator operator, Object rightValue, R rule, PolicyContext context); - - + /** * Performs a validation of an atomic constraint * @@ -47,4 +46,11 @@ public interface AtomicConstraintFunction { default Result validate(Operator operator, Object rightValue, R rule) { return Result.success(); } + + /** + * Returns the name of the function + */ + default String name() { + return getClass().getSimpleName(); + } } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/DynamicAtomicConstraintFunction.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/DynamicAtomicConstraintFunction.java index 4cfc29dbfcb..bc4f67284b1 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/DynamicAtomicConstraintFunction.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/DynamicAtomicConstraintFunction.java @@ -43,7 +43,6 @@ public interface DynamicAtomicConstraintFunction { */ boolean canHandle(Object leftValue); - /** * Performs a validation of an atomic constraint * @@ -57,4 +56,11 @@ default Result validate(Object leftValue, Operator operator, Object rightV return Result.success(); } + /** + * Returns the name of the function + */ + default String name() { + return getClass().getSimpleName(); + } + } 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 ce273de4f38..c075e82549d 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 @@ -25,4 +25,11 @@ public interface RuleFunction { * Performs the rule evaluation. */ boolean evaluate(R rule, PolicyContext context); + + /** + * Returns the name of the function + */ + default String name() { + return getClass().getSimpleName(); + } } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/PolicyEvaluationPlan.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/PolicyEvaluationPlan.java index 1045ea8a3cf..fb52598dcf7 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/PolicyEvaluationPlan.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/PolicyEvaluationPlan.java @@ -23,17 +23,27 @@ import java.util.ArrayList; import java.util.List; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + /** * The {@link PolicyEvaluationPlan} contains information about the evaluation process of a {@link Policy} * withing a scope without executing it. */ public class PolicyEvaluationPlan { + public static final String EDC_POLICY_EVALUATION_PLAN_TYPE = EDC_NAMESPACE + "PolicyEvaluationPlan"; + public static final String EDC_POLICY_EVALUATION_PLAN_PRE_VALIDATORS = EDC_NAMESPACE + "preValidators"; + public static final String EDC_POLICY_EVALUATION_PLAN_POST_VALIDATORS = EDC_NAMESPACE + "postValidators"; + public static final String EDC_POLICY_EVALUATION_PLAN_PERMISSION_STEPS = EDC_NAMESPACE + "permissionSteps"; + public static final String EDC_POLICY_EVALUATION_PLAN_PROHIBITION_STEPS = EDC_NAMESPACE + "prohibitionSteps"; + public static final String EDC_POLICY_EVALUATION_PLAN_OBLIGATION_STEPS = EDC_NAMESPACE + "obligationSteps"; + + private final List preValidators = new ArrayList<>(); private final List postValidators = new ArrayList<>(); private final List permissionSteps = new ArrayList<>(); private final List prohibitionSteps = new ArrayList<>(); - private final List dutySteps = new ArrayList<>(); + private final List obligationSteps = new ArrayList<>(); public List getPostValidators() { return postValidators; @@ -47,8 +57,8 @@ public List getPermissionSteps() { return permissionSteps; } - public List getDutySteps() { - return dutySteps; + public List getObligationSteps() { + return obligationSteps; } public List getProhibitionSteps() { @@ -83,8 +93,8 @@ public Builder prohibition(ProhibitionStep prohibitionStep) { return this; } - public Builder obligation(DutyStep dutyStep) { - plan.dutySteps.add(dutyStep); + public Builder duty(DutyStep dutyStep) { + plan.obligationSteps.add(dutyStep); return this; } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AndConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AndConstraintStep.java index 9403273051a..72020bfa5a8 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AndConstraintStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/AndConstraintStep.java @@ -18,11 +18,15 @@ import java.util.List; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + /** * An evaluation step for {@link AndConstraint} */ public final class AndConstraintStep extends MultiplicityConstraintStep implements ConstraintStep { + public static final String EDC_AND_CONSTRAINT_STEP_TYPE = EDC_NAMESPACE + "AndConstraintStep"; + public AndConstraintStep(List steps, AndConstraint constraint) { super(steps, constraint); } 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 b1ec41c7254..6516042afa8 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 @@ -18,13 +18,42 @@ import org.eclipse.edc.policy.model.AtomicConstraint; 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; + /** * An evaluation step for {@link AtomicConstraint}. *

* The {@link AtomicConstraintStep} should be considered filtered when the left expression is not bound to a * scope or an evaluation function {@link AtomicConstraintFunction} */ -public record AtomicConstraintStep(AtomicConstraint constraint, boolean isFiltered, Rule rule, +public record AtomicConstraintStep(AtomicConstraint constraint, + List filteringReasons, + Rule rule, AtomicConstraintFunction function) 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"; + public static final String EDC_ATOMIC_CONSTRAINT_STEP_FILTERING_REASONS = EDC_NAMESPACE + "filteringReasons"; + public static final String EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_NAME = EDC_NAMESPACE + "functionName"; + public static final String EDC_ATOMIC_CONSTRAINT_STEP_FUNCTION_PARAMS = EDC_NAMESPACE + "functionParams"; + + 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(), + constraint.getOperator().toString(), + constraint.getRightExpression().toString()); + } } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/DutyStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/DutyStep.java index 5394485a79d..6e2b33af044 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/DutyStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/DutyStep.java @@ -16,11 +16,15 @@ import org.eclipse.edc.policy.model.Duty; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + /** * An evaluation step for {@link Duty} rule; */ public class DutyStep extends RuleStep { + public static final String EDC_DUTY_STEP_TYPE = EDC_NAMESPACE + "DutyStep"; + public static class Builder extends RuleStep.Builder { private Builder() { diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/MultiplicityConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/MultiplicityConstraintStep.java index 96b0c80357a..690e089506a 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/MultiplicityConstraintStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/MultiplicityConstraintStep.java @@ -18,22 +18,26 @@ import java.util.List; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + /** * Base evaluation step for {@link MultiplicityConstraint}. It carries the {@link MultiplicityConstraint} * and the collection of child {@link ConstraintStep}. */ public abstract class MultiplicityConstraintStep { + + public static final String EDC_MULTIPLICITY_CONSTRAINT_STEPS = EDC_NAMESPACE + "constraintSteps"; - private final List steps; + private final List constraintSteps; private final T constraint; - public MultiplicityConstraintStep(List steps, T constraint) { + public MultiplicityConstraintStep(List constraintSteps, T constraint) { this.constraint = constraint; - this.steps = steps; + this.constraintSteps = constraintSteps; } - public List getSteps() { - return steps; + public List getConstraintSteps() { + return constraintSteps; } public T getConstraint() { diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/OrConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/OrConstraintStep.java index 55264bf8420..5746a2ce333 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/OrConstraintStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/OrConstraintStep.java @@ -18,12 +18,17 @@ import java.util.List; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + /** * An evaluation step for {@link OrConstraint} */ public final class OrConstraintStep extends MultiplicityConstraintStep implements ConstraintStep { + public static final String EDC_OR_CONSTRAINT_STEP_TYPE = EDC_NAMESPACE + "OrConstraintStep"; + public OrConstraintStep(List steps, OrConstraint constraint) { super(steps, constraint); } + } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/PermissionStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/PermissionStep.java index bc8cdb3dd0e..8f42d96a741 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/PermissionStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/PermissionStep.java @@ -19,11 +19,16 @@ import java.util.ArrayList; import java.util.List; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + /** * An evaluation step for {@link Permission} rule; */ public class PermissionStep extends RuleStep { - + + public static final String EDC_PERMISSION_STEP_TYPE = EDC_NAMESPACE + "PermissionStep"; + public static final String EDC_PERMISSION_STEP_DUTY_STEPS = EDC_NAMESPACE + "dutySteps"; + private final List dutySteps = new ArrayList<>(); public List getDutySteps() { diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ProhibitionStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ProhibitionStep.java index bdb5c53ad80..90a251a362e 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ProhibitionStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/ProhibitionStep.java @@ -17,11 +17,15 @@ import com.fasterxml.jackson.annotation.JsonCreator; import org.eclipse.edc.policy.model.Prohibition; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + /** * An evaluation step for {@link Prohibition} rule; */ public class ProhibitionStep extends RuleStep { + public static final String EDC_PROHIBITION_STEP_TYPE = EDC_NAMESPACE + "ProhibitionStep"; + public static class Builder extends RuleStep.Builder { private Builder() { 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 f0a967e3f1b..d85aa2ff03f 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 @@ -22,4 +22,10 @@ */ public record RuleFunctionStep(RuleFunction function, R rule) { + /** + * Returns the {@link RuleFunction#name()} + */ + 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/RuleStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleStep.java index e1e1df7b844..057b5da6621 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/RuleStep.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Objects; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + /** * Base step class for {@link Rule}s evaluation. A rule can have multiple {@link ConstraintStep} * and {@link RuleFunctionStep} associated during the evaluation process. @@ -27,10 +29,16 @@ */ public abstract class RuleStep { + public static final String EDC_RULE_STEP_IS_FILTERED = EDC_NAMESPACE + "isFiltered"; + public static final String EDC_RULE_STEP_FILTERING_REASONS = EDC_NAMESPACE + "filteringReasons"; + public static final String EDC_RULE_CONSTRAINT_STEPS = EDC_NAMESPACE + "constraintSteps"; + public static final String EDC_RULE_FUNCTIONS = EDC_NAMESPACE + "ruleFunctions"; + protected R rule; protected boolean isFiltered = false; protected List constraintSteps = new ArrayList<>(); + protected List filteringReasons = new ArrayList<>(); protected List> ruleFunctions = new ArrayList<>(); @@ -42,6 +50,10 @@ public List> getRuleFunctions() { return ruleFunctions; } + public List getFilteringReasons() { + return filteringReasons; + } + public boolean isFiltered() { return isFiltered; } @@ -71,6 +83,11 @@ public B filtered(boolean isFiltered) { return (B) this; } + public B filteringReason(String reason) { + ruleStep.filteringReasons.add(reason); + return (B) this; + } + public T build() { Objects.requireNonNull(ruleStep.rule, "Rule should not be null"); return ruleStep; 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 c7a4bdae7aa..1f1210bfd04 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 @@ -24,4 +24,10 @@ */ public record ValidatorStep(BiFunction validator) { + /** + * Returns the name of the validator + */ + public String name() { + return validator.getClass().getSimpleName(); + } } diff --git a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/XoneConstraintStep.java b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/XoneConstraintStep.java index 828b7cf9e18..eddd0bdc5ed 100644 --- a/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/XoneConstraintStep.java +++ b/spi/common/policy-engine-spi/src/main/java/org/eclipse/edc/policy/engine/spi/plan/step/XoneConstraintStep.java @@ -18,11 +18,15 @@ import java.util.List; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + /** * An evaluation step for {@link XoneConstraint} */ public final class XoneConstraintStep extends MultiplicityConstraintStep implements ConstraintStep { + public static final String EDC_XONE_CONSTRAINT_STEP_TYPE = EDC_NAMESPACE + "XoneConstraintStep"; + public XoneConstraintStep(List steps, XoneConstraint constraint) { super(steps, constraint); } diff --git a/spi/control-plane/control-plane-spi/src/main/java/org/eclipse/edc/connector/controlplane/services/spi/policydefinition/PolicyDefinitionService.java b/spi/control-plane/control-plane-spi/src/main/java/org/eclipse/edc/connector/controlplane/services/spi/policydefinition/PolicyDefinitionService.java index 04e271c5584..cd606d739fc 100644 --- a/spi/control-plane/control-plane-spi/src/main/java/org/eclipse/edc/connector/controlplane/services/spi/policydefinition/PolicyDefinitionService.java +++ b/spi/control-plane/control-plane-spi/src/main/java/org/eclipse/edc/connector/controlplane/services/spi/policydefinition/PolicyDefinitionService.java @@ -15,10 +15,10 @@ package org.eclipse.edc.connector.controlplane.services.spi.policydefinition; import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; +import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceResult; import org.jetbrains.annotations.NotNull; @@ -80,6 +80,13 @@ public interface PolicyDefinitionService { * @param policy the policy * @return successful if valid, a failure otherwise */ - Result validate(Policy policy); - + ServiceResult validate(Policy policy); + + /** + * Validates a {@link Policy} + * + * @param policy the policy + * @return successful if valid, a failure otherwise + */ + ServiceResult createEvaluationPlan(String scope, Policy policy); } diff --git a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PolicyDefinitionApiEndToEndTest.java b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PolicyDefinitionApiEndToEndTest.java index 7a7861656b0..eb746db8332 100644 --- a/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PolicyDefinitionApiEndToEndTest.java +++ b/system-tests/management-api/management-api-test-runner/src/test/java/org/eclipse/edc/test/e2e/managementapi/PolicyDefinitionApiEndToEndTest.java @@ -43,6 +43,8 @@ import static org.eclipse.edc.spi.query.Criterion.criterion; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; public class PolicyDefinitionApiEndToEndTest { @@ -303,6 +305,55 @@ void shouldValidatePolicy(ManagementEndToEndTestContext context) { } + @Test + void shouldCreateEvaluationPlan(ManagementEndToEndTestContext context) { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .build()) + .add(TYPE, "PolicyDefinition") + .add("policy", sampleOdrlPolicy()) + .build(); + + var id = context.baseRequest() + .body(requestBody) + .contentType(JSON) + .post("/v3/policydefinitions") + .then() + .statusCode(200) + .extract().jsonPath().getString(ID); + + var planBody = createObjectBuilder().add(CONTEXT, createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .build()) + .add(TYPE, "PolicyEvaluationPlanRequest") + .add("policyScope", "catalog") + .build(); + + context.baseRequest() + .contentType(JSON) + .body(planBody) + .post("/v3.1alpha/policydefinitions/" + id + "/evaluationplan") + .then() + .statusCode(200) + .body("preValidators.size()", is(0)) + .body("permissionSteps.isFiltered", is(false)) + .body("permissionSteps.filteringReasons.size()", is(0)) + .body("permissionSteps.constraintSteps.'@type'", is("AtomicConstraintStep")) + .body("permissionSteps.constraintSteps.isFiltered", is(true)) + .body("permissionSteps.constraintSteps.filteringReasons.size()", is(2)) + .body("permissionSteps.constraintSteps.functionName", nullValue()) + .body("permissionSteps.constraintSteps.functionParams.size()", is(3)) + .body("prohibitionSteps.isFiltered", is(true)) + .body("prohibitionSteps.filteringReasons", notNullValue()) + .body("prohibitionSteps.constraintSteps.size()", is(0)) + .body("obligationSteps.isFiltered", is(false)) + .body("obligationSteps.filteringReasons.size()", is(0)) + .body("obligationSteps.constraintSteps.size()", is(0)) + .body("postValidators.size()", is(0)); + + } + private JsonObject sampleOdrlPolicy() { return createObjectBuilder() .add(CONTEXT, "http://www.w3.org/ns/odrl.jsonld")