Skip to content

Commit

Permalink
feat: adds REST API for policy validation
Browse files Browse the repository at this point in the history
  • Loading branch information
wolf4ood committed Aug 30, 2024
1 parent b8ea9a2 commit 3243c50
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.eclipse.edc.connector.controlplane.services.contractnegotiation.ContractNegotiationServiceImpl;
import org.eclipse.edc.connector.controlplane.services.policydefinition.PolicyDefinitionEventListener;
import org.eclipse.edc.connector.controlplane.services.policydefinition.PolicyDefinitionServiceImpl;
import org.eclipse.edc.connector.controlplane.services.policydefinition.PolicyServiceImpl;
import org.eclipse.edc.connector.controlplane.services.protocol.ProtocolTokenValidatorImpl;
import org.eclipse.edc.connector.controlplane.services.protocol.VersionProtocolServiceImpl;
import org.eclipse.edc.connector.controlplane.services.secret.SecretEventListener;
Expand All @@ -50,6 +51,7 @@
import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationProtocolService;
import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService;
import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService;
import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyService;
import org.eclipse.edc.connector.controlplane.services.spi.protocol.ProtocolTokenValidator;
import org.eclipse.edc.connector.controlplane.services.spi.protocol.ProtocolVersionRegistry;
import org.eclipse.edc.connector.controlplane.services.spi.protocol.VersionProtocolService;
Expand Down Expand Up @@ -260,4 +262,9 @@ public VersionProtocolService versionProtocolService() {
return new VersionProtocolServiceImpl(protocolVersionRegistry, protocolTokenValidator());
}

@Provider
public PolicyService policyValidationService() {
return new PolicyServiceImpl(policyEngine);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.services.policydefinition;

import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyService;
import org.eclipse.edc.policy.engine.spi.PolicyEngine;
import org.eclipse.edc.policy.model.Policy;
import org.eclipse.edc.spi.result.Result;

public class PolicyServiceImpl implements PolicyService {

private final PolicyEngine policyEngine;

public PolicyServiceImpl(PolicyEngine policyEngine) {
this.policyEngine = policyEngine;
}

@Override
public Result<Void> validate(Policy policy) {
return policyEngine.validate(policy);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
public abstract class BasePolicyDefinitionApiController {

protected final Monitor monitor;
private final TypeTransformerRegistry transformerRegistry;
private final PolicyDefinitionService service;
protected final PolicyDefinitionService service;
protected final TypeTransformerRegistry transformerRegistry;
private final JsonObjectValidatorRegistry validatorRegistry;

public BasePolicyDefinitionApiController(Monitor monitor, TypeTransformerRegistry transformerRegistry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@

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.JsonObjectFromPolicyValidationResultTransformer;
import org.eclipse.edc.connector.controlplane.api.management.policy.transform.JsonObjectToPolicyDefinitionTransformer;
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.services.spi.policydefinition.PolicyDefinitionService;
import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyService;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.spi.system.ServiceExtension;
Expand Down Expand Up @@ -57,6 +60,9 @@ public class PolicyDefinitionApiExtension implements ServiceExtension {
@Inject
private TypeManager typeManager;

@Inject
private PolicyService policyService;

@Override
public String name() {
return NAME;
Expand All @@ -69,11 +75,13 @@ public void initialize(ServiceExtensionContext context) {
var mapper = typeManager.getMapper(JSON_LD);
managementApiTransformerRegistry.register(new JsonObjectToPolicyDefinitionTransformer());
managementApiTransformerRegistry.register(new JsonObjectFromPolicyDefinitionTransformer(jsonBuilderFactory, mapper));
managementApiTransformerRegistry.register(new JsonObjectFromPolicyValidationResultTransformer(jsonBuilderFactory));

validatorRegistry.register(EDC_POLICY_DEFINITION_TYPE, PolicyDefinitionValidator.instance());

var monitor = context.getMonitor();
webService.registerResource(ApiContext.MANAGEMENT, new PolicyDefinitionApiV2Controller(monitor, managementApiTransformerRegistry, service, validatorRegistry));
webService.registerResource(ApiContext.MANAGEMENT, new PolicyDefinitionApiV3Controller(monitor, managementApiTransformerRegistry, service, validatorRegistry));
webService.registerResource(ApiContext.MANAGEMENT, new PolicyDefinitionApiV31AlphaController(monitor, managementApiTransformerRegistry, service, validatorRegistry, policyService));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 java.util.List;

import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE;

public record PolicyValidationResult(boolean isValid, List<String> errors) {
public static final String EDC_POLICY_VALIDATION_RESULT_TYPE = EDC_NAMESPACE + "PolicyValidationResult";
public static final String EDC_POLICY_VALIDATION_RESULT_IS_VALID = EDC_NAMESPACE + "isValid";
public static final String EDC_POLICY_VALIDATION_RESULT_ERRORS = EDC_NAMESPACE + "errors";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.JsonBuilderFactory;
import jakarta.json.JsonObject;
import org.eclipse.edc.connector.controlplane.api.management.policy.model.PolicyValidationResult;
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.PolicyValidationResult.EDC_POLICY_VALIDATION_RESULT_TYPE;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;

public class JsonObjectFromPolicyValidationResultTransformer extends AbstractJsonLdTransformer<PolicyValidationResult, JsonObject> {

private final JsonBuilderFactory jsonFactory;

public JsonObjectFromPolicyValidationResultTransformer(JsonBuilderFactory jsonFactory) {
super(PolicyValidationResult.class, JsonObject.class);
this.jsonFactory = jsonFactory;
}

@Override
public @Nullable JsonObject transform(@NotNull PolicyValidationResult input, @NotNull TransformerContext context) {
var objectBuilder = jsonFactory.createObjectBuilder();
objectBuilder.add(TYPE, EDC_POLICY_VALIDATION_RESULT_TYPE);
objectBuilder.add(PolicyValidationResult.EDC_POLICY_VALIDATION_RESULT_IS_VALID, input.isValid());
objectBuilder.add(PolicyValidationResult.EDC_POLICY_VALIDATION_RESULT_ERRORS, jsonFactory.createArrayBuilder(input.errors()));
return objectBuilder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public interface PolicyDefinitionApiV31Alpha {
)
void updatePolicyDefinitionV3(String id, JsonObject policyDefinition);

JsonObject validatePolicyDefinitionV3(String id);

@Schema(name = "PolicyDefinitionInput", example = PolicyDefinitionInputSchema.POLICY_DEFINITION_INPUT_EXAMPLE)
record PolicyDefinitionInputSchema(
@Schema(name = CONTEXT, requiredMode = REQUIRED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,31 @@
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.PolicyValidationResult;
import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition;
import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService;
import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyService;
import org.eclipse.edc.spi.EdcException;
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.ObjectNotFoundException;

import java.util.ArrayList;

import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;

@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@Path("/v3.1alpha/policydefinitions")
public class PolicyDefinitionApiV31AlphaController extends BasePolicyDefinitionApiController implements PolicyDefinitionApiV31Alpha {
public PolicyDefinitionApiV31AlphaController(Monitor monitor, TypeTransformerRegistry transformerRegistry, PolicyDefinitionService service, JsonObjectValidatorRegistry validatorRegistry) {
private final PolicyService policyService;

public PolicyDefinitionApiV31AlphaController(Monitor monitor, TypeTransformerRegistry transformerRegistry, PolicyDefinitionService service,
JsonObjectValidatorRegistry validatorRegistry,
PolicyService policyService) {
super(monitor, transformerRegistry, service, validatorRegistry);
this.policyService = policyService;
}

@POST
Expand Down Expand Up @@ -73,4 +85,24 @@ public void deletePolicyDefinitionV3(@PathParam("id") String id) {
public void updatePolicyDefinitionV3(@PathParam("id") String id, JsonObject input) {
updatePolicyDefinition(id, input);
}

@POST
@Path("{id}/validate")
@Override
public JsonObject validatePolicyDefinitionV3(@PathParam("id") String id) {
var definition = service.findById(id);
if (definition == null) {
throw new ObjectNotFoundException(PolicyDefinition.class, id);
}

var messages = new ArrayList<String>();

var result = policyService.validate(definition.getPolicy())
.onFailure(failure -> messages.addAll(failure.getMessages()));

var validationResult = new PolicyValidationResult(result.succeeded(), messages);

return transformerRegistry.transform(validationResult, JsonObject.class)
.orElseThrow(f -> new EdcException("Error creating response body: " + f.getFailureDetail()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.eclipse.edc.boot.system.injection.ObjectFactory;
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.junit.extensions.DependencyInjectionExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.transform.spi.TypeTransformerRegistry;
Expand Down Expand Up @@ -64,5 +65,6 @@ void initialize_shouldRegisterControllers(ServiceExtensionContext context) {

verify(webService).registerResource(any(), isA(PolicyDefinitionApiV2Controller.class));
verify(webService).registerResource(any(), isA(PolicyDefinitionApiV3Controller.class));
verify(webService).registerResource(any(), isA(PolicyDefinitionApiV31AlphaController.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,98 @@
package org.eclipse.edc.connector.controlplane.api.management.policy.v31alpha;

import io.restassured.specification.RequestSpecification;
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.PolicyValidationResult;
import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition;
import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyService;
import org.eclipse.edc.policy.model.Policy;
import org.eclipse.edc.spi.result.Result;
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.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class PolicyDefinitionApiV31AlphaControllerTest extends BasePolicyDefinitionApiControllerTest {

protected final PolicyService policyService = mock();

@Test
void validate_shouldReturnNotFound_whenNotFound() {
when(service.findById(any())).thenReturn(null);

baseRequest()
.contentType(JSON)
.post("/id/validate")
.then()
.statusCode(404);
}


@Test
void validate_shouldReturnValid_whenValidationSucceed() {

var policyDefinition = PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).build();

when(service.findById(any())).thenReturn(policyDefinition);
when(policyService.validate(policyDefinition.getPolicy())).thenReturn(Result.success());
when(transformerRegistry.transform(any(PolicyValidationResult.class), eq(JsonObject.class))).then(answer -> {
PolicyValidationResult result = answer.getArgument(0);
var response = Json.createObjectBuilder()
.add("isValid", result.isValid())
.add("errors", Json.createArrayBuilder(result.errors()))
.build();
return Result.success(response);
});

baseRequest()
.contentType(JSON)
.post("/id/validate")
.then()
.statusCode(200)
.contentType(JSON)
.body("isValid", is(true))
.body("errors.size()", is(0));
}

@Test
void validate_shouldReturnInvalidValid_whenValidationFails() {

var policyDefinition = PolicyDefinition.Builder.newInstance().policy(Policy.Builder.newInstance().build()).build();


when(service.findById(any())).thenReturn(policyDefinition);
when(policyService.validate(policyDefinition.getPolicy())).thenReturn(Result.failure(List.of("error1", "error2")));
when(transformerRegistry.transform(any(PolicyValidationResult.class), eq(JsonObject.class))).then(answer -> {
PolicyValidationResult result = answer.getArgument(0);
var response = Json.createObjectBuilder()
.add("isValid", result.isValid())
.add("errors", Json.createArrayBuilder(result.errors()))
.build();
return Result.success(response);
});

baseRequest()
.contentType(JSON)
.post("/id/validate")
.then()
.statusCode(200)
.contentType(JSON)
.body("isValid", is(false))
.body("errors.size()", is(2));
}

@Override
protected Object controller() {
return new PolicyDefinitionApiV31AlphaController(monitor, transformerRegistry, service, validatorRegistry);
return new PolicyDefinitionApiV31AlphaController(monitor, transformerRegistry, service, validatorRegistry, policyService);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.services.spi.policydefinition;

import org.eclipse.edc.policy.model.Policy;
import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint;
import org.eclipse.edc.spi.result.Result;

/**
* Service that permits actions on Policy entity, e.g. validation
*/
@ExtensionPoint
public interface PolicyService {

Result<Void> validate(Policy policy);
}
Loading

0 comments on commit 3243c50

Please sign in to comment.