Skip to content

Commit

Permalink
feat(api): adds validation for PolicyDefinition (#3863)
Browse files Browse the repository at this point in the history
missing validation for logical constraints
missing validation for different action value types
makes action to json transformation null safe
organises constants in `PropertyAndTypeNames.java`

feat(api): add remedy validation for PolicyDefinitionValidator
fixes validation and testing for expanded JSON-LD

feat(api): ordered PropertyAndTypeNames

feat(api): added compatibility with LogicalConstraints

feat(api): review comments
removed todos
removed unused instance() methods
changed test dummy values
simplified constraint validation
added unit tests for PolicyTransformer with null action

Signed-off-by: Sascha Isele <[email protected]>
  • Loading branch information
saschaisele-zf authored Feb 29, 2024
1 parent c971584 commit a477eba
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,11 @@ private JsonArray visitConstraints(Rule rule) {
return constraintsBuilder.build();
}

private JsonObject visitAction(Action action) {
private JsonObject visitAction(@Nullable Action action) {
var actionBuilder = jsonFactory.createObjectBuilder();
if (action == null) {
return actionBuilder.build();
}
actionBuilder.add(ODRL_ACTION_TYPE_ATTRIBUTE, action.getType());
if (action.getIncludedIn() != null) {
actionBuilder.add(ODRL_INCLUDED_IN_ATTRIBUTE, action.getIncludedIn());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,17 @@ void transform_actionWithAllAttributes_returnJsonObject() {

verify(context, never()).reportProblem(anyString());
}

@Test
void transform_actionNull_returnJsonObject() {
var permission = Permission.Builder.newInstance().action(null).build();
var policy = Policy.Builder.newInstance().permission(permission).build();

var result = transformer.transform(policy, context);

var permissionJson = result.get(ODRL_PERMISSION_ATTRIBUTE).asJsonArray().get(0).asJsonObject();
assertThat(permissionJson.get(ODRL_ACTION_ATTRIBUTE)).isNotNull();
}

@Test
void transform_permissionWithConstraintAndDuty_returnJsonObject() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,153 @@
package org.eclipse.edc.connector.api.management.policy.validation;

import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import org.eclipse.edc.validator.jsonobject.JsonLdPath;
import org.eclipse.edc.validator.jsonobject.JsonObjectValidator;
import org.eclipse.edc.validator.jsonobject.validators.MandatoryObject;
import org.eclipse.edc.validator.jsonobject.validators.OptionalIdNotBlank;
import org.eclipse.edc.validator.jsonobject.validators.TypeIs;
import org.eclipse.edc.validator.spi.ValidationResult;
import org.eclipse.edc.validator.spi.Validator;
import org.eclipse.edc.validator.spi.Violation;

import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import static java.lang.String.format;
import static org.eclipse.edc.connector.policy.spi.PolicyDefinition.EDC_POLICY_DEFINITION_POLICY;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_ACTION_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSEQUENCE_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_DUTY_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LEFT_OPERAND_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LOGICAL_CONSTRAINT_TYPE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OBLIGATION_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OPERATOR_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PERMISSION_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_POLICY_TYPE_SET;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PROHIBITION_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_REMEDY_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_RIGHT_OPERAND_ATTRIBUTE;

public class PolicyDefinitionValidator {
public static Validator<JsonObject> instance() {
return JsonObjectValidator.newValidator()
.verifyId(OptionalIdNotBlank::new)
.verify(EDC_POLICY_DEFINITION_POLICY, MandatoryObject::new)
.verifyObject(EDC_POLICY_DEFINITION_POLICY, builder -> builder.verify(path -> new TypeIs(path, ODRL_POLICY_TYPE_SET)))
.verifyObject(EDC_POLICY_DEFINITION_POLICY, PolicyValidator::instance)
.build();
}

private static class PolicyValidator {
public static JsonObjectValidator.Builder instance(JsonObjectValidator.Builder builder) {
return builder
.verify(path -> new TypeIs(path, ODRL_POLICY_TYPE_SET))
.verifyArrayItem(ODRL_PERMISSION_ATTRIBUTE, PermissionValidator::instance)
.verifyArrayItem(ODRL_OBLIGATION_ATTRIBUTE, DutyValidator::instance)
.verifyArrayItem(ODRL_PROHIBITION_ATTRIBUTE, ProhibitionValidator::instance);
}

}

private static class PermissionValidator {
public static JsonObjectValidator.Builder instance(JsonObjectValidator.Builder builder) {

return builder
.verify(ActionValidator::new)
.verifyArrayItem(ODRL_DUTY_ATTRIBUTE, DutyValidator::instance)
.verifyArrayItem(ODRL_CONSTRAINT_ATTRIBUTE, ConstraintValidatorWrapper::instance);
}

}

private static class DutyValidator {
public static JsonObjectValidator.Builder instance(JsonObjectValidator.Builder builder) {

return builder
.verify(ActionValidator::new)
.verifyArrayItem(ODRL_CONSEQUENCE_ATTRIBUTE, ConsequenceValidator::instance)
.verifyArrayItem(ODRL_CONSTRAINT_ATTRIBUTE, ConstraintValidatorWrapper::instance);
}

}

private static class ProhibitionValidator {
public static JsonObjectValidator.Builder instance(JsonObjectValidator.Builder builder) {

return builder
.verify(ActionValidator::new)
.verifyArrayItem(ODRL_REMEDY_ATTRIBUTE, DutyValidator::instance)
.verifyArrayItem(ODRL_CONSTRAINT_ATTRIBUTE, ConstraintValidatorWrapper::instance);
}

}

private static class ConsequenceValidator {
public static JsonObjectValidator.Builder instance(JsonObjectValidator.Builder builder) {

return builder
.verify(ActionValidator::new)
.verifyArrayItem(ODRL_CONSTRAINT_ATTRIBUTE, ConstraintValidatorWrapper::instance);
}

}

private record ActionValidator(JsonLdPath path) implements Validator<JsonObject> {
@Override
public ValidationResult validate(JsonObject input) {
return Optional.of(input.containsKey(ODRL_ACTION_ATTRIBUTE))
.filter(it -> input.get(ODRL_ACTION_ATTRIBUTE) != null)
.map(it -> ValidationResult.success())
.orElse(ValidationResult.failure(Violation.violation(format("%s is mandatory but missing or null", path.append(ODRL_ACTION_ATTRIBUTE)), ODRL_ACTION_ATTRIBUTE)));
}

}

private static class ConstraintValidatorWrapper {
public static JsonObjectValidator.Builder instance(JsonObjectValidator.Builder builder) {
return builder
.verify(ConstraintValidator::new);
}

}

private record ConstraintValidator(JsonLdPath path) implements Validator<JsonObject> {
@Override
public ValidationResult validate(JsonObject input) {
var types = Optional.of(input)
.map(it -> it.getJsonArray(TYPE))
.stream().flatMap(Collection::stream)
.filter(it -> it.getValueType() == JsonValue.ValueType.STRING)
.map(JsonString.class::cast)
.map(JsonString::getString)
.toList();

if (types.contains(ODRL_LOGICAL_CONSTRAINT_TYPE)) {
return ValidationResult.success();
}

var violations = Stream.of(ODRL_LEFT_OPERAND_ATTRIBUTE, ODRL_OPERATOR_ATTRIBUTE, ODRL_RIGHT_OPERAND_ATTRIBUTE)
.map(it -> {
var jsonValue = input.get(it);
if (jsonValue == null) {
return Violation.violation(format("%s is mandatory but missing or null", path.append(it)), it);
}
return null;
})
.filter(Objects::nonNull)
.toList();

if (violations.isEmpty()) {
return ValidationResult.success();
}
return ValidationResult.failure(violations);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

package org.eclipse.edc.connector.api.management.policy.validation;

import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObject;
import org.eclipse.edc.validator.spi.ValidationFailure;
import org.eclipse.edc.validator.spi.Validator;
Expand All @@ -27,7 +28,16 @@
import static org.eclipse.edc.connector.policy.spi.PolicyDefinition.EDC_POLICY_DEFINITION_POLICY;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE;
import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_ACTION_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_AND_CONSTRAINT_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LEFT_OPERAND_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LOGICAL_CONSTRAINT_TYPE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OPERATOR_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PERMISSION_ATTRIBUTE;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_POLICY_TYPE_SET;
import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_RIGHT_OPERAND_ATTRIBUTE;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;

class PolicyDefinitionValidatorTest {
Expand Down Expand Up @@ -98,4 +108,133 @@ void shouldFail_whenTypeIsInvalid() {
.filteredOn(it -> it.path().contains(EDC_POLICY_DEFINITION_POLICY))
.anySatisfy(violation -> assertThat(violation.message()).contains("was expected to be"));
}

@Test
void shouldSucceed_whenPolicyWithPermissionIsValid() {
var policyDefinition = createValidDefinition();

var result = validator.validate(policyDefinition);

assertThat(result).isSucceeded();
}

@Test
void shouldFail_whenPermissionActionIsMissing() {
var permission = createArrayBuilder().add(createObjectBuilder()
.add(ODRL_CONSTRAINT_ATTRIBUTE, createValidConstraint("GroupNumber", "isPartOf", "allowedGroups")));
var policy = createArrayBuilder()
.add(createObjectBuilder().add(ODRL_PERMISSION_ATTRIBUTE, permission))
.add(createObjectBuilder().add(TYPE, createValidType()));
var policyDefinition = createObjectBuilder()
.add(EDC_POLICY_DEFINITION_POLICY, policy)
.build();

var result = validator.validate(policyDefinition);

assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class))
.isNotEmpty()
.filteredOn(it -> it.path().contains(ODRL_ACTION_ATTRIBUTE))
.anySatisfy(violation -> assertThat(violation.message()).contains("mandatory"));
}

@Test
void shouldSucceed_whenPermissionConstraintIsMissing() {
var constraint = createArrayBuilder();
var permission = createArrayBuilder().add(createObjectBuilder()
.add(ODRL_ACTION_ATTRIBUTE, createValidAction())
.add(ODRL_CONSTRAINT_ATTRIBUTE, constraint));
var policy = createArrayBuilder()
.add(createObjectBuilder().add(TYPE, createValidType()))
.add(createObjectBuilder().add(ODRL_PERMISSION_ATTRIBUTE, permission));
var policyDefinition = createObjectBuilder()
.add(EDC_POLICY_DEFINITION_POLICY, policy)
.build();

var result = validator.validate(policyDefinition);

assertThat(result).isSucceeded();
}

@Test
void shouldFail_whenConstraintOperatorIsMissing() {
var constraint = createArrayBuilder().add(createObjectBuilder());
var permission = createArrayBuilder().add(createObjectBuilder()
.add(ODRL_ACTION_ATTRIBUTE, createValidAction())
.add(ODRL_CONSTRAINT_ATTRIBUTE, constraint));
var policy = createArrayBuilder()
.add(createObjectBuilder().add(ODRL_PERMISSION_ATTRIBUTE, permission))
.add(createObjectBuilder().add(TYPE, createValidType()));
var policyDefinition = createObjectBuilder()
.add(EDC_POLICY_DEFINITION_POLICY, policy)
.build();

var result = validator.validate(policyDefinition);

assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class))
.isNotEmpty()
.filteredOn(it -> ODRL_LEFT_OPERAND_ATTRIBUTE.equals(it.path()))
.anySatisfy(violation -> assertThat(violation.message()).contains("mandatory"));
}

@Test
void shouldSucceed_whenLogicalConstraintIsPresent() {
var permission = createArrayBuilder().add(createObjectBuilder()
.add(ODRL_ACTION_ATTRIBUTE, createValidAction())
.add(ODRL_CONSTRAINT_ATTRIBUTE, createValidLogicalConstraint()));
var policy = createArrayBuilder()
.add(createObjectBuilder().add(TYPE, createValidType()))
.add(createObjectBuilder().add(ODRL_PERMISSION_ATTRIBUTE, permission));
var policyDefinition = createObjectBuilder()
.add(EDC_POLICY_DEFINITION_POLICY, policy)
.build();

var result = validator.validate(policyDefinition);

assertThat(result).isSucceeded();
}

private JsonObject createValidDefinition() {
return createObjectBuilder()
.add(EDC_POLICY_DEFINITION_POLICY, createValidPolicy()).build();
}

private JsonArrayBuilder createValidPolicy() {
return createArrayBuilder().add(createObjectBuilder()
.add(ODRL_ACTION_ATTRIBUTE, createValidAction())
.add(TYPE, createValidType())
.add(ODRL_PERMISSION_ATTRIBUTE, createValidPermission()));
}

private JsonArrayBuilder createValidType() {
return createArrayBuilder().add(ODRL_POLICY_TYPE_SET);
}

private JsonArrayBuilder createValidPermission() {
return createArrayBuilder().add(createObjectBuilder()
.add(ODRL_ACTION_ATTRIBUTE, createValidAction())
.add(ODRL_CONSTRAINT_ATTRIBUTE, createValidConstraint("GroupNumber", "isPartOf", "allowedGroups")));
}

private JsonArrayBuilder createValidAction() {
return createArrayBuilder().add(createObjectBuilder().add(ID, "use"));
}

private JsonArrayBuilder createValidConstraint(String left, String operator, String right) {
var leftOperand = createArrayBuilder().add(createObjectBuilder().add(ID, left));
var operatorObject = createArrayBuilder().add(createObjectBuilder().add(ID, operator));
var rightOperand = createArrayBuilder().add(createObjectBuilder().add(VALUE, right));
return createArrayBuilder().add(createObjectBuilder()
.add(ODRL_LEFT_OPERAND_ATTRIBUTE, leftOperand)
.add(ODRL_OPERATOR_ATTRIBUTE, operatorObject)
.add(ODRL_RIGHT_OPERAND_ATTRIBUTE, rightOperand));
}

private JsonArrayBuilder createValidLogicalConstraint() {
return createArrayBuilder().add(createObjectBuilder()
.add(TYPE, createArrayBuilder().add(ODRL_LOGICAL_CONSTRAINT_TYPE))
.add(ODRL_AND_CONSTRAINT_ATTRIBUTE, createArrayBuilder()
.add(createValidConstraint("inForceDate", "gteq", "2024-01-01T00:00:00Z"))
.add(createValidConstraint("inForceDate", "lteq", "2024-04-01T00:00:00Z"))));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,32 @@
* Collection of DCAT, DCT and ODRL type and attribute names.
*/
public interface PropertyAndTypeNames {

//DCAT
String DCAT_CATALOG_TYPE = DCAT_SCHEMA + "Catalog";
String DCAT_DATASET_TYPE = DCAT_SCHEMA + "Dataset";
String DCAT_DISTRIBUTION_TYPE = DCAT_SCHEMA + "Distribution";
String DCAT_DATA_SERVICE_TYPE = DCAT_SCHEMA + "DataService";

String EDC_CREATED_AT = EDC_NAMESPACE + "createdAt";

String DCAT_DATA_SERVICE_ATTRIBUTE = DCAT_SCHEMA + "service";
String DCAT_DATASET_ATTRIBUTE = DCAT_SCHEMA + "dataset";
String DCAT_DISTRIBUTION_ATTRIBUTE = DCAT_SCHEMA + "distribution";
String DCAT_ACCESS_SERVICE_ATTRIBUTE = DCAT_SCHEMA + "accessService";
String ODRL_POLICY_ATTRIBUTE = ODRL_SCHEMA + "hasPolicy";

//EDC
String EDC_CREATED_AT = EDC_NAMESPACE + "createdAt";

//DCT
@Deprecated(since = "0.5.1")
String DEPRECATED_DCT_FORMAT_ATTRIBUTE = "https://purl.org/dc/terms/format";
String DCT_FORMAT_ATTRIBUTE = DCT_SCHEMA + "format";
String DCT_TERMS_ATTRIBUTE = DCT_SCHEMA + "terms";
String DCT_ENDPOINT_URL_ATTRIBUTE = DCT_SCHEMA + "endpointUrl";

String ODRL_POLICY_ATTRIBUTE = ODRL_SCHEMA + "hasPolicy";
String ODRL_POLICY_TYPE_SET = ODRL_SCHEMA + "Set";
String ODRL_POLICY_TYPE_OFFER = ODRL_SCHEMA + "Offer";
String ODRL_POLICY_TYPE_AGREEMENT = ODRL_SCHEMA + "Agreement";

String ODRL_CONSTRAINT_TYPE = ODRL_SCHEMA + "Constraint";
String ODRL_LOGICAL_CONSTRAINT_TYPE = ODRL_SCHEMA + "LogicalConstraint";

String ODRL_TARGET_ATTRIBUTE = ODRL_SCHEMA + "target";
String ODRL_ASSIGNEE_ATTRIBUTE = ODRL_SCHEMA + "assignee";
String ODRL_ASSIGNER_ATTRIBUTE = ODRL_SCHEMA + "assigner";
Expand All @@ -59,6 +58,7 @@ public interface PropertyAndTypeNames {
String ODRL_ACTION_ATTRIBUTE = ODRL_SCHEMA + "action";
String ODRL_ACTION_TYPE_ATTRIBUTE = ODRL_SCHEMA + "type";
String ODRL_CONSEQUENCE_ATTRIBUTE = ODRL_SCHEMA + "consequence";
String ODRL_REMEDY_ATTRIBUTE = ODRL_SCHEMA + "remedy";
String ODRL_INCLUDED_IN_ATTRIBUTE = ODRL_SCHEMA + "includedIn";
String ODRL_REFINEMENT_ATTRIBUTE = ODRL_SCHEMA + "refinement";
String ODRL_CONSTRAINT_ATTRIBUTE = ODRL_SCHEMA + "constraint";
Expand Down

0 comments on commit a477eba

Please sign in to comment.