Skip to content

Commit

Permalink
feat: adds REST API for policy evaluation plan
Browse files Browse the repository at this point in the history
  • Loading branch information
wolf4ood committed Sep 4, 2024
1 parent f5fcd2e commit bcc639a
Show file tree
Hide file tree
Showing 35 changed files with 1,088 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ public Result<Void> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PolicyEvaluationPlan>, Rule.Visitor<RuleStep<? extends Rule>>, Constraint.Visitor<ConstraintStep> {

Expand All @@ -65,11 +66,13 @@ public class PolicyEvaluationPlanner implements Policy.Visitor<PolicyEvaluationP
private final List<DynamicAtomicConstraintFunctionEntry<Rule>> dynamicConstraintFunctions = new ArrayList<>();
private final List<RuleFunctionFunctionEntry<Rule>> 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
Expand All @@ -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<String>();

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
Expand All @@ -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)
Expand Down Expand Up @@ -169,7 +181,13 @@ private <R extends Rule> void visitRule(R rule, RuleStep.Builder builder) {

try {
ruleContext.push(rule);
builder.filtered(shouldIgnoreRule(rule));

var filtered = shouldIgnoreRule(rule);
builder.filtered(filtered);

if (filtered) {
builder.filteringReason("action '%s' is not bound to scope '%s'".formatted(rule.getAction().getType(), scope));
}
builder.rule(rule);

for (var functionEntry : ruleFunctions) {
Expand Down Expand Up @@ -235,6 +253,11 @@ public boolean evaluate(Operator operator, Object rightValue, R rule, PolicyCont
public Result<Void> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ void withRule(Policy policy, Class<Rule> ruleClass, String action, String key, F
.first()
.satisfies(ruleStep -> {
assertThat(ruleStep.isFiltered()).isFalse();
assertThat(ruleStep.getRuleFunctions()).hasSize(0);
assertThat(ruleStep.getRuleFunctionSteps()).hasSize(0);
assertThat(ruleStep.getConstraintSteps()).hasSize(1)
.first()
.isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> {
Expand Down Expand Up @@ -127,7 +127,7 @@ void withRuleAndDynFunction(Policy policy, Class<Rule> ruleClass, String action,
.first()
.satisfies(ruleStep -> {
assertThat(ruleStep.isFiltered()).isFalse();
assertThat(ruleStep.getRuleFunctions()).hasSize(0);
assertThat(ruleStep.getRuleFunctionSteps()).hasSize(0);
assertThat(ruleStep.getConstraintSteps()).hasSize(1)
.first()
.isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> {
Expand Down Expand Up @@ -158,7 +158,7 @@ void withRuleAndRuleFunction(Policy policy, Class<Rule> ruleClass, String action
.first()
.satisfies(ruleStep -> {
assertThat(ruleStep.isFiltered()).isFalse();
assertThat(ruleStep.getRuleFunctions()).hasSize(2);
assertThat(ruleStep.getRuleFunctionSteps()).hasSize(2);
assertThat(ruleStep.getConstraintSteps()).hasSize(1)
.first()
.isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> {
Expand Down Expand Up @@ -187,7 +187,7 @@ void withRuleAndRuleFunctionNotBound(Policy policy, Class<Rule> ruleClass, Strin
.first()
.satisfies(ruleStep -> {
assertThat(ruleStep.isFiltered()).isFalse();
assertThat(ruleStep.getRuleFunctions()).hasSize(0);
assertThat(ruleStep.getRuleFunctionSteps()).hasSize(0);
});
}

Expand All @@ -214,7 +214,7 @@ void withPermissionContainingDuty() {
.satisfies(ruleStep -> {
assertThat(ruleStep.isFiltered()).isFalse();
assertThat(ruleStep.getDutySteps()).hasSize(1);
assertThat(ruleStep.getRuleFunctions()).hasSize(0);
assertThat(ruleStep.getRuleFunctionSteps()).hasSize(0);
assertThat(ruleStep.getConstraintSteps()).hasSize(1)
.first()
.isInstanceOfSatisfying(AtomicConstraintStep.class, (constraintStep) -> {
Expand All @@ -240,7 +240,7 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
var prohibition = Prohibition.Builder.newInstance().constraint(constraint).action(action).build();

Function<PolicyEvaluationPlan, List<? extends RuleStep<? extends Rule>>> permissionSteps = PolicyEvaluationPlan::getPermissionSteps;
Function<PolicyEvaluationPlan, List<? extends RuleStep<? extends Rule>>> dutySteps = PolicyEvaluationPlan::getDutySteps;
Function<PolicyEvaluationPlan, List<? extends RuleStep<? extends Rule>>> dutySteps = PolicyEvaluationPlan::getObligationSteps;
Function<PolicyEvaluationPlan, List<? extends RuleStep<? extends Rule>>> prohibitionSteps = PolicyEvaluationPlan::getProhibitionSteps;

var permission = Permission.Builder.newInstance().constraint(constraint).action(action).build();
Expand Down Expand Up @@ -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 -> {
Expand Down Expand Up @@ -377,8 +378,8 @@ void shouldEvaluate_withMultiplicityConstraint(Policy policy, Class<Rule> ruleCl

var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, policy);

assertThat(plan.getPreValidators()).isEmpty();
assertThat(plan.getPostValidators()).isEmpty();
assertThat(plan.getPreValidatorSteps()).isEmpty();
assertThat(plan.getPostValidatorSteps()).isEmpty();


assertThat(stepsProvider.apply(plan)).hasSize(1)
Expand All @@ -388,7 +389,7 @@ void shouldEvaluate_withMultiplicityConstraint(Policy policy, Class<Rule> 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();
});
}));
Expand All @@ -414,7 +415,7 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
var duty = Duty.Builder.newInstance().constraint(xoneConstraint).build();

Function<PolicyEvaluationPlan, List<? extends RuleStep<? extends Rule>>> permissionSteps = PolicyEvaluationPlan::getPermissionSteps;
Function<PolicyEvaluationPlan, List<? extends RuleStep<? extends Rule>>> dutySteps = PolicyEvaluationPlan::getDutySteps;
Function<PolicyEvaluationPlan, List<? extends RuleStep<? extends Rule>>> dutySteps = PolicyEvaluationPlan::getObligationSteps;
Function<PolicyEvaluationPlan, List<? extends RuleStep<? extends Rule>>> prohibitionSteps = PolicyEvaluationPlan::getProhibitionSteps;

return Stream.of(
Expand All @@ -436,8 +437,8 @@ void shouldEvaluate_withNoValidators() {

var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, emptyPolicy);

assertThat(plan.getPostValidators()).isEmpty();
assertThat(plan.getPreValidators()).isEmpty();
assertThat(plan.getPostValidatorSteps()).isEmpty();
assertThat(plan.getPreValidatorSteps()).isEmpty();
}

@Test
Expand All @@ -448,8 +449,8 @@ void shouldEvaluate_withValidators() {

var plan = policyEngine.createEvaluationPlan(TEST_SCOPE, emptyPolicy);

assertThat(plan.getPreValidators()).hasSize(1);
assertThat(plan.getPostValidators()).hasSize(1);
assertThat(plan.getPreValidatorSteps()).hasSize(1);
assertThat(plan.getPostValidatorSteps()).hasSize(1);

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -124,8 +124,13 @@ public ServiceResult<PolicyDefinition> update(PolicyDefinition policyDefinition)
}

@Override
public Result<Void> validate(Policy policy) {
return policyEngine.validate(policy);
public ServiceResult<Void> validate(Policy policy) {
return policyEngine.validate(policy).flatMap(ServiceResult::from);
}

@Override
public ServiceResult<PolicyEvaluationPlan> createEvaluationPlan(String scope, Policy policy) {
return ServiceResult.success(policyEngine.createEvaluationPlan(scope, policy));
}

private List<PolicyDefinition> queryPolicyDefinitions(QuerySpec query) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
Loading

0 comments on commit bcc639a

Please sign in to comment.