-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Initial implementation of Catena-X policies (#477)
* 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
Showing
24 changed files
with
1,968 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
135 changes: 135 additions & 0 deletions
135
...src/main/java/org/eclipse/tractusx/edc/policy/cx/common/AbstractVpConstraintFunction.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
} |
107 changes: 107 additions & 0 deletions
107
...x-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctions.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() { | ||
} | ||
|
||
} |
70 changes: 70 additions & 0 deletions
70
...-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctions.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
...s/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyNamespaces.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; | ||
|
||
} |
Oops, something went wrong.