diff --git a/edc-extensions/cx-policy/build.gradle.kts b/edc-extensions/cx-policy/build.gradle.kts new file mode 100644 index 000000000..40dbffbae --- /dev/null +++ b/edc-extensions/cx-policy/build.gradle.kts @@ -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) +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/AbstractVpConstraintFunction.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/AbstractVpConstraintFunction.java new file mode 100644 index 000000000..517581e08 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/AbstractVpConstraintFunction.java @@ -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 { + 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; + } + +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctions.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctions.java new file mode 100644 index 000000000..beafc5b3c --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctions.java @@ -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 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 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 extractObjectsOfType(String typeValue, Stream 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> partitionByType(Stream stream) { + var partitions = new HashMap>(); + 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 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() { + } + +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctions.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctions.java new file mode 100644 index 000000000..d036b99d9 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctions.java @@ -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; + } +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyNamespaces.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyNamespaces.java new file mode 100644 index 000000000..bfdc7b948 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyNamespaces.java @@ -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"; + +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyScopes.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyScopes.java new file mode 100644 index 000000000..4cb92f237 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyScopes.java @@ -0,0 +1,28 @@ +/* + * 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 standard EDC policy scopes. + */ +public interface PolicyScopes { + String CATALOG_REQUEST_SCOPE = "catalog.request"; + String NEGOTIATION_REQUEST_SCOPE = "contract.negotiation.request"; + String TRANSFER_PROCESS_REQUEST_SCOPE = "transfer.process.request"; + + String CATALOG_SCOPE = "contract.cataloging"; + String NEGOTIATION_SCOPE = "contract.negotiation"; + String TRANSFER_PROCESS_SCOPE = "transfer.process"; +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunction.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunction.java new file mode 100644 index 000000000..f95658f8c --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunction.java @@ -0,0 +1,175 @@ +/* + * 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.framework; + +import jakarta.json.JsonObject; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.tractusx.edc.policy.cx.common.AbstractVpConstraintFunction; + +import java.util.Objects; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static org.eclipse.edc.policy.model.Operator.EQ; +import static org.eclipse.edc.policy.model.Operator.GEQ; +import static org.eclipse.edc.policy.model.Operator.GT; +import static org.eclipse.tractusx.edc.policy.cx.common.JsonLdTypeFunctions.extractObjectsOfType; +import static org.eclipse.tractusx.edc.policy.cx.common.JsonLdValueFunctions.extractStringValue; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.TX_USE_CASE_NS; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.W3_VP_PROPERTY; + +/** + * Enforces a Framework Agreement constraint. + *

+ * A policy constraints requiring a usecase framework credential take a left operand in the form: + *

FrameworkAgreement.[type]
+ *

+ * The following example requires a client to present a sustainability credential: + *

+ * "constraint": {
+ *     "leftOperand": "FrameworkAgreement.sustainability",
+ *     "operator": "eq",
+ *     "rightOperand": "active"
+ * }
+ * 
+ *

+ * NB: This function will be enabled in the 3.2 release. + */ +public class FrameworkAgreementConstraintFunction extends AbstractVpConstraintFunction { + private static final String ACTIVE = "active"; + public static final String CONTRACT_VERSION_PROPERTY = TX_USE_CASE_NS + "/contractVersion"; + + private String agreementType; + private String agreementVersion; + + @Override + public boolean evaluate(Operator operator, Object rightValue, Permission permission, PolicyContext context) { + if (!validateOperator(operator, context, EQ, GT, GEQ)) { + return false; + } + + if (!validateRightOperand(ACTIVE, rightValue, context)) { + return false; + } + + var vp = (JsonObject) context.getParticipantAgent().getClaims().get(W3_VP_PROPERTY); + if (!validatePresentation(vp, context)) { + return false; + } + + return extractObjectsOfType(credentialType, vp) + .map(credential -> extractCredentialSubject(credential, context)) + .filter(Objects::nonNull) + .anyMatch(credentialSubject -> validateUseCase(credentialSubject, operator, context)); + } + + private boolean validateUseCase(JsonObject credentialSubject, Operator operator, PolicyContext context) { + var usecaseAgreement = extractObjectsOfType(agreementType, credentialSubject).findFirst().orElse(null); + if (usecaseAgreement == null) { + context.reportProblem(format("%s is missing the usecase type: %s", credentialType, agreementType)); + return false; + } + + return validateVersion(context, operator, usecaseAgreement); + } + + private boolean validateVersion(PolicyContext context, Operator operator, JsonObject usecaseAgreement) { + if (agreementVersion == null) { + return true; + } + var version = extractStringValue(usecaseAgreement.get(CONTRACT_VERSION_PROPERTY)); + if (version == null || version.trim().length() == 0) { + context.reportProblem(format("%s is missing a %s property", credentialType, CONTRACT_VERSION_PROPERTY)); + return false; + } + + switch (operator) { + case EQ -> { + if (!version.equals(agreementVersion)) { + context.reportProblem(format("%s version %s does not match required version: %s", credentialType, version, agreementVersion)); + return false; + } + return true; + } + case GT -> { + if (version.compareTo(agreementVersion) <= 0) { + context.reportProblem(format("%s version %s must be at greater than the required version: %s", credentialType, version, agreementVersion)); + return false; + } + return true; + } + case GEQ -> { + if (version.compareTo(agreementVersion) < 0) { + context.reportProblem(format("%s version %s must be at least the required version: %s", credentialType, version, agreementVersion)); + return false; + } + return true; + } + default -> { + return false; + } + } + } + + private FrameworkAgreementConstraintFunction(String credentialType) { + super(credentialType); + } + + /** + * Configures a new constraint instance. + */ + public static class Builder { + private final FrameworkAgreementConstraintFunction constraint; + + /** + * Ctor. + * + * @param credentialType the framework credential type required by the constraint instance. + * @return the builder + */ + public static Builder newInstance(String credentialType) { + return new Builder(credentialType); + } + + /** + * Sets the framework agreement type. + */ + public Builder agreementType(String agreementType) { + constraint.agreementType = agreementType; + return this; + } + + /** + * Sets the optional required agreement version. Equals, greater than, and greater than or equals operations are supported. + */ + public Builder agreementVersion(String version) { + constraint.agreementVersion = version; + return this; + } + + public FrameworkAgreementConstraintFunction build() { + requireNonNull(constraint.agreementType, "agreementType"); + return constraint; + } + + private Builder(String credentialType) { + constraint = new FrameworkAgreementConstraintFunction(credentialType); + } + } + + +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunction.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunction.java new file mode 100644 index 000000000..afd597d69 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunction.java @@ -0,0 +1,110 @@ +/* + * 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.summary; + +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.tractusx.edc.policy.cx.common.AbstractVpConstraintFunction; +import org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces; + +import java.util.Map; + +import static jakarta.json.JsonValue.ValueType.ARRAY; +import static jakarta.json.JsonValue.ValueType.OBJECT; +import static jakarta.json.JsonValue.ValueType.STRING; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static org.eclipse.edc.policy.model.Operator.EQ; +import static org.eclipse.tractusx.edc.policy.cx.common.JsonLdTypeFunctions.extractObjectsOfType; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.W3_VP_PROPERTY; + +/** + * Implements Catena-X policies by verifying policy constraints against the summary credential. + *

+ * Verifies the presence of an entry in the {@link #SUMMARY_CREDENTIAL_ITEMS} of a summary credential token. + */ +public class SummaryConstraintFunction extends AbstractVpConstraintFunction { + private static final String SUMMARY_CREDENTIAL_TYPE = PolicyNamespaces.TX_SUMMARY_NS + "/SummaryCredential"; + private static final String SUMMARY_CREDENTIAL_ITEMS = PolicyNamespaces.TX_SUMMARY_NS + "/items"; + private static final String CREDENTIAL_SUBJECT = "credentialSubject"; + + private static final String ACTIVE = "active"; + + private final String summaryType; + + public SummaryConstraintFunction(String summaryType) { + super("Summary"); + requireNonNull(summaryType); + this.summaryType = summaryType; + } + + @Override + public boolean evaluate(Operator operator, Object rightValue, Permission rule, PolicyContext context) { + if (!validateOperator(operator, context, EQ)) { + return false; + } + + if (!validateRightOperand(ACTIVE, rightValue, context)) { + return false; + } + + var vp = (JsonObject) context.getParticipantAgent().getClaims().get(W3_VP_PROPERTY); + if (!validatePresentation(vp, context)) { + return false; + } + + return extractObjectsOfType(SUMMARY_CREDENTIAL_TYPE, vp).anyMatch(credential -> hasSummaryType(credential, context)); + } + + /** + * Returns true if the summary credential has the item specified by {@link #summaryType}. + */ + private boolean hasSummaryType(JsonObject credential, PolicyContext context) { + var credentialSubject = extractCredentialSubject(credential, context); + if (credentialSubject == null) { + return false; + } + var items = credentialSubject.get(SUMMARY_CREDENTIAL_ITEMS); + + if (items == null || items.getValueType() != ARRAY) { + context.reportProblem(format("%s items not found in %s", errorPrefix, CREDENTIAL_SUBJECT)); + return false; + } + + if (items.asJsonArray().isEmpty()) { + context.reportProblem(format("%s empty %s items graph container", errorPrefix, CREDENTIAL_SUBJECT)); + return false; + } + + return items.asJsonArray().stream().filter(e -> e.getValueType() == OBJECT) + .flatMap(o -> o.asJsonObject().entrySet().stream()) + .anyMatch(this::matchSummaryType); + } + + /** + * Returns true if the entry is a string and matches the Json-Ld {@link #VALUE} type. + */ + private boolean matchSummaryType(Map.Entry e) { + return VALUE.equals(e.getKey()) && + e.getValue().getValueType() == STRING && + summaryType.equals(((JsonString) e.getValue()).getString()); + } + + +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunctionsProvider.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunctionsProvider.java new file mode 100644 index 000000000..c7f18be43 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunctionsProvider.java @@ -0,0 +1,77 @@ +/* + * 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.summary; + +import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.model.Permission; + +import java.util.Map; + +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.CATALOG_REQUEST_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.CATALOG_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.NEGOTIATION_REQUEST_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.NEGOTIATION_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.TRANSFER_PROCESS_REQUEST_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.TRANSFER_PROCESS_SCOPE; + +/** + * Registers {@link SummaryConstraintFunction} and {@link SummaryTokenPolicyFunction} instances with the runtime policy engine. + */ +public class SummaryConstraintFunctionsProvider { + + /** + * Mappings from policy constraint left operand values to the corresponding item value in the summary VP. + */ + static final Map CREDENTIAL_MAPPINGS = Map.of( + "Membership", "cx-active-member", + "Dismantler", "cx-dismantler", + "FrameworkAgreement.pcf", "cx-pcf", + "FrameworkAgreement.sustainability", "cx-sustainability", + "FrameworkAgreement.quality", "cx-quality", + "FrameworkAgreement.traceability", "cx-traceability", + "FrameworkAgreement.behavioraltwin", "cx-behavior-twin", + "BPN", "cx-bpn" + ); + + /** + * Configures and registers required summary functions with the policy engine. + */ + public static void registerFunctions(PolicyEngine engine) { + var tokenPolicyFunction = new SummaryTokenPolicyFunction(); + engine.registerPreValidator(CATALOG_REQUEST_SCOPE, tokenPolicyFunction); + engine.registerPreValidator(NEGOTIATION_REQUEST_SCOPE, tokenPolicyFunction); + engine.registerPreValidator(TRANSFER_PROCESS_REQUEST_SCOPE, tokenPolicyFunction); + + CREDENTIAL_MAPPINGS.forEach((credentialName, summaryType) -> { + + engine.registerFunction(CATALOG_SCOPE, + Permission.class, + credentialName, + new SummaryConstraintFunction(summaryType)); + + engine.registerFunction(NEGOTIATION_SCOPE, + Permission.class, + credentialName, + new SummaryConstraintFunction(summaryType)); + + engine.registerFunction(TRANSFER_PROCESS_SCOPE, + Permission.class, + credentialName, + new SummaryConstraintFunction(summaryType)); + }); + + } + +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryTokenPolicyFunction.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryTokenPolicyFunction.java new file mode 100644 index 000000000..45a633bf7 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryTokenPolicyFunction.java @@ -0,0 +1,40 @@ +/* + * 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.summary; + +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.iam.TokenParameters; + +import java.util.function.BiFunction; + +import static java.lang.String.format; + +/** + * Includes a summary credential in the token parameters. + */ +public class SummaryTokenPolicyFunction implements BiFunction { + + @Override + public Boolean apply(Policy policy, PolicyContext context) { + var params = context.getContextData(TokenParameters.Builder.class); + if (params == null) { + throw new EdcException(format("%s not set in policy context", TokenParameters.Builder.class.getName())); + } + // TODO set summary credential when we upgrade to the latest EDC snapshot + return true; + } +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/AbstractVpConstraintFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/AbstractVpConstraintFunctionTest.java new file mode 100644 index 000000000..efdce8ac7 --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/AbstractVpConstraintFunctionTest.java @@ -0,0 +1,111 @@ +/* + * 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 com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.Permission; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.policy.cx.fixtures.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.policy.cx.fixtures.JsonLdTextFixtures.expand; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +class AbstractVpConstraintFunctionTest { + private AbstractVpConstraintFunction function; + private PolicyContext context; + + @Test + void verify_operators() { + assertThat(function.validateOperator(Operator.EQ, context, Operator.EQ)).isEqualTo(true); + } + + @Test + void verify_invalid_operators() { + assertThat(function.validateOperator(Operator.NEQ, context, Operator.EQ)).isEqualTo(false); + verify(context).reportProblem(anyString()); + } + + @Test + void verify_presentation() throws JsonProcessingException { + var vp = expand(createObjectMapper().readValue(PRESENTATION, JsonObject.class), Map.of()); + + assertThat(function.validatePresentation(vp, context)).isTrue(); + + assertThat(function.validatePresentation(null, context)).isFalse(); + + assertThat(function.validatePresentation("invalid", context)).isFalse(); + + var array = Json.createArrayBuilder().build(); + assertThat(function.validatePresentation(array, context)).isFalse(); + } + + @Test + void verify_extract_credential_subject() throws JsonProcessingException { + var credential = expand(createObjectMapper().readValue(FOO_CREDENTIAL, JsonObject.class), Map.of()); + + var subject = function.extractCredentialSubject(credential, context); + + assertThat(subject).isNotNull(); + assertThat(((JsonString) subject.get("@id")).getString()).isEqualTo("did:web:test"); + } + + @BeforeEach + void setUp() { + context = mock(PolicyContext.class); + function = new AbstractVpConstraintFunction("FooCredential") { + @Override + public boolean evaluate(Operator operator, Object rightValue, Permission rule, PolicyContext context) { + throw new UnsupportedOperationException(); + } + }; + } + + private static final String FOO_CREDENTIAL = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "urn:uuid:12345678-1234-1234-1234-123456789abc", + "type": [ + "VerifiableCredential", + "FooCredential" + ], + "issuer": "did:web:test", + "credentialSubject": { + "id": "did:web:test" + } + } + """; + + private static final String PRESENTATION = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": "VerifiablePresentation" + } + """; +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctionsTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctionsTest.java new file mode 100644 index 000000000..f103c76b1 --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctionsTest.java @@ -0,0 +1,131 @@ +/* + * 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 com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.policy.cx.common.JsonLdTypeFunctions.extractObjectsOfType; +import static org.eclipse.tractusx.edc.policy.cx.common.JsonLdTypeFunctions.partitionByType; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.W3C_VC_PREFIX; +import static org.eclipse.tractusx.edc.policy.cx.fixtures.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.policy.cx.fixtures.JsonLdTextFixtures.expand; + +class JsonLdTypeFunctionsTest { + private static final String TYPE = "@type"; + private static final String VC_TYPE = W3C_VC_PREFIX + "#VerifiableCredential"; + + private static final String BAR_CREDENTIAL_TYPE = "BarCredential"; + private static final String FOO_CREDENTIAL_TYPE = "FooCredential"; + + @Test + void verify_credential_extraction() throws JsonProcessingException { + var vp = expand(createObjectMapper().readValue(SINGLE_VC_CLAIM, JsonObject.class), Map.of()); + + var credentials = extractObjectsOfType(VC_TYPE, vp).toList(); + + assertThat(credentials.size()).isEqualTo(1); + assertAllOfType(FOO_CREDENTIAL_TYPE, credentials); + } + + @Test + void verify_partitions_based_on_type() throws JsonProcessingException { + var vp = expand(createObjectMapper().readValue(MULTIPLE_VCS_CLAIM, JsonObject.class), Map.of()); + + var credentials = extractObjectsOfType(VC_TYPE, vp); + var partitions = partitionByType(credentials); + + assertThat(partitions.size()).isEqualTo(3); + + assertAllOfType(FOO_CREDENTIAL_TYPE, partitions.get(FOO_CREDENTIAL_TYPE)); + assertAllOfType(BAR_CREDENTIAL_TYPE, partitions.get(BAR_CREDENTIAL_TYPE)); + assertThat(partitions.get(VC_TYPE).size()).isEqualTo(2); + } + + /** + * Asserts that all objects in the collection are of a given type. + */ + private void assertAllOfType(String type, List objects) { + assertThat(objects.stream() + .flatMap(object -> object.get(TYPE).asJsonArray().stream()) + .filter(value -> value instanceof JsonString) + .filter(entryType -> type.equals(((JsonString) entryType).getString())) + .count()).isEqualTo(objects.size()); + } + + private static final String FOO_CREDENTIAL = """ + { + "type": "VerifiablePresentation", + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "urn:uuid:12345678-1234-1234-1234-123456789abc", + "type": [ + "VerifiableCredential", + "FooCredential" + ] + } + ] + }"""; + + private static final String BAR_CREDENTIAL = """ + { + "type": "VerifiablePresentation", + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": [ + "VerifiableCredential", + "BarCredential" + ] + } + ] + }"""; + + private static final String MULTIPLE_VCS_CLAIM = format(""" + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "vp":"test:vp" + } + ], + "vp": [%s,%s] + }""", FOO_CREDENTIAL, BAR_CREDENTIAL); + + private static final String SINGLE_VC_CLAIM = format(""" + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + { + "vp":"test:vp" + } + ], + "vp": %s + }""", FOO_CREDENTIAL); + + +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctionsTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctionsTest.java new file mode 100644 index 000000000..8b71178b5 --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctionsTest.java @@ -0,0 +1,65 @@ +/* + * 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 org.junit.jupiter.api.Test; + +import static jakarta.json.Json.createArrayBuilder; +import static jakarta.json.Json.createObjectBuilder; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.policy.cx.common.JsonLdValueFunctions.extractStringValue; + +class JsonLdValueFunctionsTest { + private static final String VALUE = "@value"; + + @Test + void validate_nested_array() { + var root = createArrayBuilder() + .add(createObjectBuilder().add(VALUE, "test").build()) + .build(); + + assertThat(extractStringValue(root)).isEqualTo("test"); + } + + @Test + void validate_empty_array() { + var root = createArrayBuilder().build(); + assertThat(extractStringValue(root)).isNull(); + } + + @Test + void validate_object() { + var root = createObjectBuilder().add(VALUE, "test").build(); + assertThat(extractStringValue(root)).isEqualTo("test"); + } + + @Test + void validate_object_int() { + var root = createObjectBuilder().add(VALUE, 1).build(); + assertThat(extractStringValue(root)).isEqualTo("1"); + } + + @Test + void validate_object_double() { + var root = createObjectBuilder().add(VALUE, 1.1d).build(); + assertThat(extractStringValue(root)).isEqualTo("1.1"); + } + + @Test + void validate_null() { + assertThat(extractStringValue(null)).isNull(); + } + +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/JsonLdTextFixtures.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/JsonLdTextFixtures.java new file mode 100644 index 000000000..639905da4 --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/JsonLdTextFixtures.java @@ -0,0 +1,92 @@ +/* + * 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.fixtures; + +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.JsonLdOptions; +import com.apicatalog.jsonld.document.JsonDocument; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.datatype.jsonp.JSONPModule; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import jakarta.json.JsonObject; +import org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces; + +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; + +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; +import static jakarta.json.Json.createBuilderFactory; +import static jakarta.json.Json.createObjectBuilder; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.W3C_VC_NS; +import static org.eclipse.tractusx.edc.policy.cx.fixtures.W3cVcContext.W3C_VC_CONTEXT; + +/** + * Test helpers for processing Json-Ld. + */ +public class JsonLdTextFixtures { + + /** + * Creates a mapper configured to support Json-Ld processing. + */ + public static ObjectMapper createObjectMapper() { + var mapper = new ObjectMapper(); + mapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.registerModule(new JavaTimeModule()); + mapper.registerModule(new JSONPModule()); + var module = new SimpleModule() { + @Override + public void setupModule(SetupContext context) { + super.setupModule(context); + } + }; + mapper.registerModule(module); + return mapper; + } + + /** + * Performs Json-Ld compaction on an object. + */ + public static JsonObject compact(JsonObject json) { + try { + var document = JsonDocument.of(json); + var jsonFactory = createBuilderFactory(Map.of()); + var contextDocument = JsonDocument.of(jsonFactory.createObjectBuilder().build()); + return com.apicatalog.jsonld.JsonLd.compact(document, contextDocument).get(); + } catch (JsonLdError e) { + throw new AssertionError(e); + } + } + + /** + * Expands the document using the provided cache for resolving referenced contexts. The {@link PolicyNamespaces#W3C_VC_NS} context is implicitly added to the cache. + */ + public static JsonObject expand(JsonObject json, Map contextCache) { + var map = new HashMap<>(contextCache); + map.put(W3C_VC_NS, W3C_VC_CONTEXT); + try { + var document = JsonDocument.of(json); + var options = new JsonLdOptions((url, options1) -> JsonDocument.of(new StringReader(map.get(url.toString())))); + var expanded = com.apicatalog.jsonld.JsonLd.expand(document).options(options).get(); + if (expanded.size() > 0) { + return expanded.getJsonObject(0); + } + return createObjectBuilder().build(); + } catch (JsonLdError e) { + throw new AssertionError(e); + } + } +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/W3cVcContext.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/W3cVcContext.java new file mode 100644 index 000000000..e61660052 --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/W3cVcContext.java @@ -0,0 +1,263 @@ +/* + * 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.fixtures; + +/** + * Local copy of the W3C VC data model context for testing, obtained from {@code https://www.w3.org/ns/credentials/v2}. + */ +public interface W3cVcContext { + + String W3C_VC_CONTEXT = """ + { + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "VerifiableCredential": { + "@id": "https://www.w3.org/2018/credentials#VerifiableCredential", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "credentialSchema": { + "@id": "cred:credentialSchema", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "cred": "https://www.w3.org/2018/credentials#", + + "JsonSchemaValidator2018": "cred:JsonSchemaValidator2018" + } + }, + "credentialStatus": {"@id": "cred:credentialStatus", "@type": "@id"}, + "credentialSubject": {"@id": "cred:credentialSubject", "@type": "@id"}, + "evidence": {"@id": "cred:evidence", "@type": "@id"}, + "expirationDate": {"@id": "cred:expirationDate", "@type": "xsd:dateTime"}, + "holder": {"@id": "cred:holder", "@type": "@id"}, + "issued": {"@id": "cred:issued", "@type": "xsd:dateTime"}, + "issuer": {"@id": "cred:issuer", "@type": "@id"}, + "issuanceDate": {"@id": "cred:issuanceDate", "@type": "xsd:dateTime"}, + "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"}, + "refreshService": { + "@id": "cred:refreshService", + "@type": "@id", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "cred": "https://www.w3.org/2018/credentials#", + + "ManualRefreshService2018": "cred:ManualRefreshService2018" + } + }, + "termsOfUse": {"@id": "cred:termsOfUse", "@type": "@id"}, + "validFrom": {"@id": "cred:validFrom", "@type": "xsd:dateTime"}, + "validUntil": {"@id": "cred:validUntil", "@type": "xsd:dateTime"} + } + }, + + "VerifiablePresentation": { + "@id": "https://www.w3.org/2018/credentials#VerifiablePresentation", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "cred": "https://www.w3.org/2018/credentials#", + "sec": "https://w3id.org/security#", + + "holder": {"@id": "cred:holder", "@type": "@id"}, + "proof": {"@id": "sec:proof", "@type": "@id", "@container": "@graph"}, + "verifiableCredential": {"@id": "cred:verifiableCredential", "@type": "@id", "@container": "@graph"} + } + }, + + "EcdsaSecp256k1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256k1Signature2019", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "challenge": "sec:challenge", + "created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"}, + "domain": "sec:domain", + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "sec": "https://w3id.org/security#", + + "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, + "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"} + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"} + } + }, + + "EcdsaSecp256r1Signature2019": { + "@id": "https://w3id.org/security#EcdsaSecp256r1Signature2019", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "challenge": "sec:challenge", + "created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"}, + "domain": "sec:domain", + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "sec": "https://w3id.org/security#", + + "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, + "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"} + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"} + } + }, + + "Ed25519Signature2018": { + "@id": "https://w3id.org/security#Ed25519Signature2018", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "challenge": "sec:challenge", + "created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"}, + "domain": "sec:domain", + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "sec": "https://w3id.org/security#", + + "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, + "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"} + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"} + } + }, + + "RsaSignature2018": { + "@id": "https://w3id.org/security#RsaSignature2018", + "@context": { + "@version": 1.1, + "@protected": true, + + "challenge": "sec:challenge", + "created": {"@id": "http://purl.org/dc/terms/created", "@type": "xsd:dateTime"}, + "domain": "sec:domain", + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "jws": "sec:jws", + "nonce": "sec:nonce", + "proofPurpose": { + "@id": "sec:proofPurpose", + "@type": "@vocab", + "@context": { + "@version": 1.1, + "@protected": true, + + "id": "@id", + "type": "@type", + + "sec": "https://w3id.org/security#", + + "assertionMethod": {"@id": "sec:assertionMethod", "@type": "@id", "@container": "@set"}, + "authentication": {"@id": "sec:authenticationMethod", "@type": "@id", "@container": "@set"} + } + }, + "proofValue": "sec:proofValue", + "verificationMethod": {"@id": "sec:verificationMethod", "@type": "@id"} + } + }, + + "proof": {"@id": "https://w3id.org/security#proof", "@type": "@id", "@container": "@graph"} + } + } + """; + + +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunctionTest.java new file mode 100644 index 000000000..bdb04919d --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunctionTest.java @@ -0,0 +1,142 @@ +/* + * 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.framework; + +import com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.json.JsonObject; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.spi.agent.ParticipantAgent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.policy.model.Operator.EQ; +import static org.eclipse.edc.policy.model.Operator.GEQ; +import static org.eclipse.edc.policy.model.Operator.GT; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.TX_USE_CASE_NS_V1; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.W3_VP_PROPERTY; +import static org.eclipse.tractusx.edc.policy.cx.fixtures.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.policy.cx.fixtures.JsonLdTextFixtures.expand; +import static org.eclipse.tractusx.edc.policy.cx.framework.PcfCredential.PCF_VP; +import static org.eclipse.tractusx.edc.policy.cx.framework.UseCaseContext.USE_CASE_CONTEXT; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class FrameworkAgreementConstraintFunctionTest { + private static final Map CONTEXT_CACHE = Map.of(TX_USE_CASE_NS_V1, USE_CASE_CONTEXT); + private Permission permission; + private PolicyContext context; + + @Test + void verify_constraint() throws JsonProcessingException { + var function = FrameworkAgreementConstraintFunction.Builder + .newInstance("PcfCredential") + .agreementType("PcfAgreement") + .build(); + + setVpInContextVp(); + + var result = function.evaluate(EQ, "active", permission, context); + + assertThat(result).isTrue(); + } + + @Test + void verify_contract_version() throws JsonProcessingException { + var function = FrameworkAgreementConstraintFunction.Builder + .newInstance("PcfCredential") + .agreementType("PcfAgreement") + .agreementVersion("1.0.0") + .build(); + + setVpInContextVp(); + + var result = function.evaluate(EQ, "active", permission, context); + assertThat(result).isTrue(); + + result = function.evaluate(GEQ, "active", permission, context); + assertThat(result).isTrue(); + + result = function.evaluate(GT, "active", permission, context); + assertThat(result).isFalse(); // should fail because version is equal + } + + @Test + void verify_contract_version_gt_fail() throws JsonProcessingException { + var function = FrameworkAgreementConstraintFunction.Builder + .newInstance("PcfCredential") + .agreementType("PcfAgreement") + .agreementVersion("2.0.0") + .build(); + + setVpInContextVp(); + + var result = function.evaluate(GT, "active", permission, context); + assertThat(result).isFalse(); // should fail because version is equal + + verify(context, times(1)).reportProblem(Mockito.contains("version")); + } + + @Test + void verify_invalid_agreement_fail() throws JsonProcessingException { + var function = FrameworkAgreementConstraintFunction.Builder + .newInstance("PcfCredential") + .agreementType("UnknownAgreement") + .build(); + + setVpInContextVp(); + + var result = function.evaluate(EQ, "active", permission, context); + + assertThat(result).isFalse(); + + verify(context, times(1)).reportProblem(Mockito.contains("missing the usecase type")); + } + + @Test + void verify_no_credential_fail() { + var function = FrameworkAgreementConstraintFunction.Builder + .newInstance("PcfCredential") + .agreementType("PcfAgreement") + .build(); + + when(context.getParticipantAgent()).thenReturn(new ParticipantAgent(Map.of(), Map.of())); + + var result = function.evaluate(EQ, "active", permission, context); + + assertThat(result).isFalse(); + + verify(context, times(1)).reportProblem(Mockito.contains("VP not found")); + } + + @BeforeEach + void setUp() { + permission = Permission.Builder.newInstance().build(); + context = mock(PolicyContext.class); + } + + private void setVpInContextVp() throws JsonProcessingException { + var vp = expand(createObjectMapper().readValue(PCF_VP, JsonObject.class), CONTEXT_CACHE); + when(context.getParticipantAgent()).thenReturn(new ParticipantAgent(Map.of(W3_VP_PROPERTY, vp), Map.of())); + } + + +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/PcfCredential.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/PcfCredential.java new file mode 100644 index 000000000..c9c0bbb1a --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/PcfCredential.java @@ -0,0 +1,59 @@ +/* + * 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.framework; + +public interface PcfCredential { + + String PCF_VP = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": "VerifiablePresentation", + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/2023/catenax/credentials/usecase/v1" + ], + "id": "urn:uuid:12345678-1234-1234-1234-123456789abc", + "type": [ + "VerifiableCredential", + "PcfCredential" + ], + "issuer": "did:web:issuer.example.com", + "issuanceDate": "2023-06-02T12:00:00Z", + "expirationDate": "2022-06-16T18:56:59Z", + "credentialSubject": { + "id": "did:web:example.com", + "holderIdentifier": "BPN of holder", + "usecaseAgreement": { + "value": "PCF", + "type": "PcfAgreement", + "contractTemplate": "https://public.catena-x.org/contracts/pcf.v1.pdf", + "contractVersion": "1.0.0" + } + }, + "proof": { + "type": "Ed25519Signature2018", + "created": "2023-06-02T12:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:issuer.example.com#key-1", + "jws": "xxx" + } + } + ] + }"""; +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/UseCaseContext.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/UseCaseContext.java new file mode 100644 index 000000000..249cd31ba --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/UseCaseContext.java @@ -0,0 +1,47 @@ +/* + * 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.framework; + +/** + * Defines the context for use case credentials. + */ +public interface UseCaseContext { + + String USE_CASE_CONTEXT = """ + { + "@context": { + "@version": 1.1, + "@protected": true, + "usecase": "https://w3id.org/2023/catenax/credentials/usecase/", + "id": "@id", + "type": "@type", + "usecaseAgreement": { + "@id": "usecase:usecaseAgreement", + "@context": { + "contractTemplate": { + "@id": "usecase:contractTemplate", + "@type": "https://schema.org/Text" + }, + "contractVersion": { + "@id": "usecase:contractVersion", + "@type": "https://schema.org/Text" + } + } + } + } + }"""; + + +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunctionTest.java new file mode 100644 index 000000000..6a07da58a --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunctionTest.java @@ -0,0 +1,65 @@ +/* + * 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.summary; + +import com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.json.JsonObject; +import org.eclipse.edc.policy.engine.spi.PolicyContext; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.spi.agent.ParticipantAgent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.policy.model.Operator.EQ; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.TX_SUMMARY_NS_V1; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.W3_VP_PROPERTY; +import static org.eclipse.tractusx.edc.policy.cx.fixtures.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.policy.cx.fixtures.JsonLdTextFixtures.expand; +import static org.eclipse.tractusx.edc.policy.cx.summary.SummaryCredential.SUMMARY_VP; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SummaryConstraintFunctionTest { + private static final Map CONTEXT_CACHE = Map.of(TX_SUMMARY_NS_V1, SummaryContext.SUMMARY_CONTEXT); + public static final String CX_QUALITY = "cx-quality"; + private Permission permission; + private PolicyContext context; + + @Test + void verify_constraint_success() throws JsonProcessingException { + var vp = expand(createObjectMapper().readValue(SUMMARY_VP, JsonObject.class), CONTEXT_CACHE); + + var function = new SummaryConstraintFunction(CX_QUALITY); + + when(context.getParticipantAgent()).thenReturn(new ParticipantAgent(Map.of(W3_VP_PROPERTY, vp), Map.of())); + + var result = function.evaluate(EQ, "active", permission, context); + + assertThat(result).isTrue(); + + verify(context, atLeastOnce()).getParticipantAgent(); + } + + @BeforeEach + void setUp() { + permission = Permission.Builder.newInstance().build(); + context = mock(PolicyContext.class); + } +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunctionsProviderTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunctionsProviderTest.java new file mode 100644 index 000000000..c5263f499 --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryConstraintFunctionsProviderTest.java @@ -0,0 +1,66 @@ +/* + * 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.summary; + +import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.model.Permission; +import org.junit.jupiter.api.Test; + +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.CATALOG_REQUEST_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.CATALOG_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.NEGOTIATION_REQUEST_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.NEGOTIATION_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.TRANSFER_PROCESS_REQUEST_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.common.PolicyScopes.TRANSFER_PROCESS_SCOPE; +import static org.eclipse.tractusx.edc.policy.cx.summary.SummaryConstraintFunctionsProvider.registerFunctions; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +class SummaryConstraintFunctionsProviderTest { + + @Test + void verify_registrations() { + var policyEngine = mock(PolicyEngine.class); + + registerFunctions(policyEngine); + + assertTokenFunctionsRegistered(CATALOG_REQUEST_SCOPE, policyEngine); + assertTokenFunctionsRegistered(NEGOTIATION_REQUEST_SCOPE, policyEngine); + assertTokenFunctionsRegistered(TRANSFER_PROCESS_REQUEST_SCOPE, policyEngine); + + SummaryConstraintFunctionsProvider.CREDENTIAL_MAPPINGS.forEach((credentialName, summaryType) -> { + assertSummaryFunctionsRegistered(CATALOG_SCOPE, policyEngine, credentialName); + assertSummaryFunctionsRegistered(NEGOTIATION_SCOPE, policyEngine, credentialName); + assertSummaryFunctionsRegistered(TRANSFER_PROCESS_SCOPE, policyEngine, credentialName); + }); + + } + + private void assertTokenFunctionsRegistered(String scope, PolicyEngine policyEngine) { + verify(policyEngine, times(1)).registerPreValidator(eq(scope), any()); + } + + private void assertSummaryFunctionsRegistered(String scope, PolicyEngine policyEngine, String credentialName) { + verify(policyEngine, times(1)).registerFunction( + eq(scope), + eq(Permission.class), + eq(credentialName), + any(SummaryConstraintFunction.class)); + } + +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryContext.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryContext.java new file mode 100644 index 000000000..1d57abc07 --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryContext.java @@ -0,0 +1,49 @@ +/* + * 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.summary; + +/** + * Defines the summary context. + */ +public interface SummaryContext { + String SUMMARY_CONTEXT = """ + { + "@context": { + "@version": 1.1, + "@protected": true, + "summary": "https://w3id.org/2023/catenax/credentials/summary/", + "id": "@id", + "type": "@type", + "SummaryCredential" : { + "@id":"summary:SummaryCredential" + }, + "holderIdentifier": { + "@id": "summary:holderIdentifier" + }, + "name": { + "@id": "summary:name", + "@type": "https://schema.org/Text" + }, + "items": { + "@id": "summary:items", + "@type": "https://schema.org/Text" + }, + "contract-template": { + "@id": "summary:contract-template", + "@type": "https://schema.org/Text" + } + } + }"""; +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryCredential.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryCredential.java new file mode 100644 index 000000000..942e35c38 --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryCredential.java @@ -0,0 +1,69 @@ +/* + * 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.summary; + +/** + * Sample summary credential. + */ +public interface SummaryCredential { + String SUMMARY_VP = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": "VerifiablePresentation", + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/2023/catenax/credentials/summary/v1" + ], + "id": "urn:uuid:12345678-1234-1234-1234-123456789abc", + "type": [ + "VerifiableCredential", + "SummaryCredential" + ], + "issuer": "did:web:a016-203-129-213-99.ngrok-free.app:BPNL000000000000", + "issuanceDate": "2023-06-02T12:00:00Z", + "expirationDate": "2022-06-16T18:56:59Z", + "credentialSubject": { + "id": "did:web:a016-203-129-213-99.ngrok-free.app:BPNL000000000000", + "holderIdentifier": "BPN of holder", + "type": "Summary-List", + "name": "CX-Credentials", + "items": [ + "cx-active-member", + "cx-dismantler", + "cx-pcf", + "cx-sustainability", + "cx-quality", + "cx-traceability", + "cx-behavior-twin", + "cx-bpn" + ], + "contract-templates": "https://public.catena-x.org/contracts/" + }, + "proof": { + "type": "Ed25519Signature2018", + "created": "2023-06-02T12:00:00Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:example.com#key-1", + "jws": "xxxx" + } + } + ] + } + """; +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51c9de912..f568ac62c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,9 @@ aws = "2.20.75" rsApi = "3.1.0" jupiter = "5.9.3" assertj = "3.24.2" +titanium = "1.3.2" +jackson = "2.15.2" +jakarta-json = "2.0.1" [libraries] edc-spi-catalog = { module = "org.eclipse.edc:catalog-spi", version.ref = "edc" } @@ -127,6 +130,11 @@ apache-sshd-sftp = { module = "org.apache.sshd:sshd-sftp", version.ref = "apache testcontainers-junit = { module = "org.testcontainers:junit-jupiter", version.ref = "testcontainers" } aws-s3 = { module = "software.amazon.awssdk:s3", version.ref = "aws" } jakarta-rsApi = { module = "jakarta.ws.rs:jakarta.ws.rs-api", version.ref = "rsApi" } +jakartaJson = { module = "org.glassfish:jakarta.json", version.ref = "jakarta-json" } +jacksonJsonP = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp", version.ref = "jackson" } +titaniumJsonLd = { module = "com.apicatalog:titanium-json-ld", version.ref = "titanium" } + + junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "jupiter" } assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 3cd6fd9e5..a79d36039 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,7 +27,6 @@ include(":spi:core-spi") // core modules include(":core:edr-cache-core") - include(":edc-extensions:business-partner-validation") include(":edc-extensions:cx-oauth2") include(":edc-extensions:data-encryption") @@ -42,8 +41,7 @@ include(":edc-extensions:transferprocess-sftp-provisioner") include(":edc-extensions:control-plane-adapter-api") include(":edc-extensions:control-plane-adapter-callback") include(":edc-extensions:edr-cache-sql") - - +include(":edc-extensions:cx-policy") include(":edc-tests:e2e-tests") include(":edc-tests:runtime:extensions")