Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds REST API for policy evaluation plan #4452

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,11 @@ private <R extends Rule> 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) {
Expand All @@ -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<ConstraintStep> validateMultiplicityConstraint(MultiplicityConstraint multiplicityConstraint) {
return multiplicityConstraint.getConstraints()
.stream()
Expand Down Expand Up @@ -235,6 +247,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 @@ -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 @@ -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 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,17 @@ public ServiceResult<PolicyDefinition> update(PolicyDefinition policyDefinition)
}

@Override
public Result<Void> validate(Policy policy) {
return policyEngine.validate(policy);
public ServiceResult<Void> validate(Policy policy) {
var validationResult = policyEngine.validate(policy);
if (validationResult.failed()) {
return ServiceResult.badRequest(validationResult.getFailureMessages());
}
return ServiceResult.success();
}

@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
Loading