-
Notifications
You must be signed in to change notification settings - Fork 60
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: Initial implementation of Catena-X policies #477
Merged
paullatzelsperger
merged 8 commits into
eclipse-tractusx:previews/preview_0_1_1_snapshot
from
jimmarino:previews/preview_0_1_1_snapshot
Jun 14, 2023
Merged
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
41b2b44
Initial implementation of Catena-X policies
jimmarino 9b5c3f8
Add header
jimmarino 004628b
Fix checkstyle
jimmarino f6b0ec1
Fix typo
jimmarino 18c9429
Fix typo
jimmarino 159a1b2
Add javadoc
jimmarino e2a5fbf
Add javadoc
jimmarino 826039a
Switch token eval to a policy validator function
jimmarino File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eventually we'll likely want to upstream these utilities There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I thinking we need separate set of Json-Ld utils. |
||
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; | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
...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,32 @@ | ||
/* | ||
* 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"; | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the expected value should be a vararg, because there could be several allowed ones, same as we have for the
validateOperator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that method is going to actually have to be modified when we switch versions from the left to right operand as described here. I'll do that in another PR.