Skip to content

Commit

Permalink
feat: Initial implementation of Catena-X policies (#477)
Browse files Browse the repository at this point in the history
* Initial implementation of Catena-X policies

* Add header

* Fix checkstyle

* Fix typo

* Fix typo

* Add javadoc

* Add javadoc

* Switch token eval to a policy validator function
  • Loading branch information
jimmarino authored Jun 14, 2023
1 parent ac795cd commit 249bd8a
Show file tree
Hide file tree
Showing 24 changed files with 1,968 additions and 3 deletions.
24 changes: 24 additions & 0 deletions edc-extensions/cx-policy/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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
*
*/

plugins {
`java-library`
}

dependencies {
implementation(libs.edc.spi.policyengine)
implementation(libs.jakartaJson)
testImplementation(libs.jacksonJsonP)
testImplementation(libs.titaniumJsonLd)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* 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.tractusx.edc.policy.cx.common;

import jakarta.json.JsonObject;
import jakarta.json.JsonValue;
import org.eclipse.edc.policy.engine.spi.AtomicConstraintFunction;
import org.eclipse.edc.policy.engine.spi.PolicyContext;
import org.eclipse.edc.policy.model.Operator;
import org.eclipse.edc.policy.model.Permission;
import org.jetbrains.annotations.Nullable;

import java.util.stream.Collectors;

import static jakarta.json.JsonValue.ValueType.ARRAY;
import static jakarta.json.JsonValue.ValueType.OBJECT;
import static java.lang.String.format;
import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;

/**
* Base processing for constraint functions that verify a permission against a Catena-X verifiable presentation.
*/
public abstract class AbstractVpConstraintFunction implements AtomicConstraintFunction<Permission> {
protected static final String CREDENTIAL_SUBJECT = PolicyNamespaces.W3C_VC_PREFIX + "#credentialSubject";

protected static final String VALUE = "@value";

protected final String errorPrefix;

protected final String credentialType;

private static final String ERROR_PREFIX_TEMPLATE = "Invalid %s VC format: ";

/**
* Ctor.
*
* @param credentialType the credential type that will be verified against.
*/
public AbstractVpConstraintFunction(String credentialType) {
requireNonNull(credentialType);
this.credentialType = credentialType;
this.errorPrefix = format(ERROR_PREFIX_TEMPLATE, credentialType);
}

/**
* Validates the operator is in the set of expected operators.
*/
protected boolean validateOperator(Operator operator, PolicyContext context, Operator... expectedOperators) {
var set = stream(expectedOperators).collect(Collectors.toSet());
if (!set.contains(operator)) {
var valid = set.stream().map(Enum::toString).collect(joining(","));
context.reportProblem(format("Unsupported operator for %s credential constraint, only %s allowed: %s", credentialType, valid, operator));
return false;
}
return true;
}

/**
* Validates the VP by checking that it is a {@link JsonObject}.
*/
protected boolean validatePresentation(@Nullable Object vp, PolicyContext context) {
if (vp == null) {
context.reportProblem(format("%s VP not found", credentialType));
return false;
}

if (!(vp instanceof JsonValue jsonValue)) {
context.reportProblem(format("%s VP is not a JSON type: %s", credentialType, vp.getClass().getName()));
return false;
}

if (!(OBJECT == jsonValue.getValueType())) {
context.reportProblem(format("%s VP must be type %s but was: %s", credentialType, OBJECT, jsonValue.getValueType()));
return false;
}

return true;
}

/**
* Returns the credential subject portion of a VC or null if there was an error. Error information will be reported to the context.
*/
@Nullable
protected JsonObject extractCredentialSubject(JsonObject credential, PolicyContext context) {
var subjectArray = credential.get(CREDENTIAL_SUBJECT);
if (subjectArray == null || subjectArray.getValueType() != ARRAY) {
context.reportProblem(errorPrefix + " no credentialSubject found");
return null;
}
if (subjectArray.asJsonArray().size() != 1) {
context.reportProblem(errorPrefix + " empty credentialSubject");
return null;
}

var subjectValue = subjectArray.asJsonArray().get(0);
if (subjectValue == null || subjectValue.getValueType() != OBJECT) {
context.reportProblem(errorPrefix + " invalid credentialSubject format");
return null;
}

return subjectValue.asJsonObject();
}

/**
* Returns true if the actual operand value is a string literal case-insensitive equal to the expected value.
*/
protected boolean validateRightOperand(String expectedValue, Object actualValue, PolicyContext context) {
if (!(actualValue instanceof String)) {
context.reportProblem(format("Invalid right operand format specified for %s credential", credentialType));
return false;
}

if (!expectedValue.equalsIgnoreCase(actualValue.toString().trim())) {
context.reportProblem(format("Invalid right operand specified for %s credential: %s", credentialType, actualValue));
return false;
}

return true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* 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.tractusx.edc.policy.cx.common;

import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonStructure;
import jakarta.json.JsonValue;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toSet;

/**
* Provides functions for working with Json-Ld types.
*/
public class JsonLdTypeFunctions {
private static final String TYPE = "@type";
private static final Stream<JsonObject> EMPTY_STREAM = Stream.of();

/**
* Returns a stream of objects that are of the given Json-Ld type starting at the root.
*
* @param typeValue the type to include
* @param root the root object to traverse
* @return the stream of types
*/
public static Stream<JsonObject> extractObjectsOfType(String typeValue, JsonStructure root) {
if (root instanceof JsonObject rootObject) {
return matchTypeValue(typeValue, rootObject.get(TYPE)) ? Stream.of(rootObject) :
extractObjectsOfType(typeValue, rootObject.values().stream());
} else if (root instanceof JsonArray rootArray) {
return extractObjectsOfType(typeValue, rootArray.stream());
}
return EMPTY_STREAM;
}

/**
* Returns a stream of objects that are of the given Json-Ld type in the stream.
*
* @param typeValue the type to include
* @param stream the stream of roots to traverse
* @return the stream of types
*/
public static Stream<JsonObject> extractObjectsOfType(String typeValue, Stream<JsonValue> stream) {
return stream.filter(v -> v instanceof JsonStructure)
.flatMap(v -> extractObjectsOfType(typeValue, (JsonStructure) v)).filter(Objects::nonNull);
}

/**
* Partitions a stream of objects by their type, returning a type-to-collection mapping.
*/
public static Map<String, List<JsonObject>> partitionByType(Stream<JsonObject> stream) {
var partitions = new HashMap<String, List<JsonObject>>();
stream.forEach(object -> getTypes(object).forEach(type -> partitions.computeIfAbsent(type, k -> new ArrayList<>()).add(object)));
return partitions;
}

/**
* Returns the types associated with the object
*/
private static Set<String> getTypes(JsonObject object) {
var result = object.get(TYPE);
if (result instanceof JsonArray resultArray) {
return resultArray.stream().filter(e -> e instanceof JsonString).map(s -> ((JsonString) s).getString()).collect(toSet());
} else if (result instanceof JsonString resultString) {
return Set.of(resultString.getString());
}
return emptySet();
}

/**
* Returns true if the type value matches the Json value.
*/
private static boolean matchTypeValue(String typeValue, JsonValue jsonValue) {
if (jsonValue instanceof JsonString stringValue) {
return typeValue.equals(stringValue.getString());
} else if (jsonValue instanceof JsonArray arrayValue) {
return arrayValue.stream().anyMatch(v -> v instanceof JsonString && typeValue.equals(((JsonString) v).getString()));
}
return false;
}

private JsonLdTypeFunctions() {
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.tractusx.edc.policy.cx.common;

import jakarta.json.JsonArray;
import jakarta.json.JsonNumber;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import org.jetbrains.annotations.Nullable;

import static jakarta.json.JsonValue.ValueType.FALSE;
import static jakarta.json.JsonValue.ValueType.TRUE;
import static java.lang.String.valueOf;

/**
* Functions for working with Json-ld values.
*/
public class JsonLdValueFunctions {
private static final String VALUE = "@value";

/**
* Extracts the value of a root node and converts it to a string representation. Note this method accepts null nodes as a convenience.
*/
@Nullable
public static String extractStringValue(@Nullable JsonValue root) {
if (root == null) {
return null;
} else if (root instanceof JsonArray rootArray) {
if (rootArray.isEmpty()) {
return null;
}
var jsonValue = rootArray.get(0);
return (jsonValue instanceof JsonObject elementObject) ? convertType(elementObject.get(VALUE)) : null;
} else if (root instanceof JsonObject rootObject) {
return convertType(rootObject.get(VALUE));
} else {
return convertType(root);
}
}

/**
* Converts the value to a string representation.
*/
@Nullable
private static String convertType(JsonValue value) {
if (value instanceof JsonString valueString) {
return valueString.getString();
} else if (value instanceof JsonNumber valueNumber) {
return valueNumber.isIntegral() ? valueOf(valueNumber.longValue()) : valueOf(valueNumber.doubleValue());
} else if (TRUE == value.getValueType()) {
return "TRUE";
} else if (FALSE == value.getValueType()) {
return "FALSE";
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.tractusx.edc.policy.cx.common;

/**
* Defines policy namespaces.
*/
public interface PolicyNamespaces {

String W3C_VC_PREFIX = "https://www.w3.org/2018/credentials";
String W3C_VC_NS = W3C_VC_PREFIX + "/v1";
String W3_VP_PROPERTY = W3C_VC_PREFIX + "/vp";

String TX_NS = "https://w3id.org/2023/catenax/credentials/";
String TX_SUMMARY_NS = TX_NS + "summary";
String TX_SUMMARY_NS_V1 = TX_SUMMARY_NS + "/v1";
String TX_USE_CASE_NS = TX_NS + "usecase";
String TX_USE_CASE_NS_V1 = TX_USE_CASE_NS + "/v1";

String TX_SUMMARY_CREDENTIAL = "SummaryCredential";

}
Loading

0 comments on commit 249bd8a

Please sign in to comment.