diff --git a/core/json-ld-core/build.gradle.kts b/core/json-ld-core/build.gradle.kts new file mode 100644 index 000000000..22bf99e1a --- /dev/null +++ b/core/json-ld-core/build.gradle.kts @@ -0,0 +1,23 @@ +/* + * 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.core) + implementation(libs.edc.spi.jsonld) + testImplementation(testFixtures(libs.edc.junit)) +} \ No newline at end of file diff --git a/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtension.java b/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtension.java new file mode 100644 index 000000000..cf80367fe --- /dev/null +++ b/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtension.java @@ -0,0 +1,76 @@ +/* + * 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.jsonld; + +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +import static java.lang.String.format; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +public class JsonLdExtension implements ServiceExtension { + + public static final String CREDENTIALS_V_1 = "https://www.w3.org/2018/credentials/v1"; + public static final String CREDENTIALS_SUMMARY_V_1 = "https://w3id.org/2023/catenax/credentials/summary/v1"; + private static final String PREFIX = "document" + File.separator; + private static final Map FILES = Map.of( + CREDENTIALS_V_1, PREFIX + "credential-v1.jsonld", + CREDENTIALS_SUMMARY_V_1, PREFIX + "summary-vc-context-v1.jsonld"); + @Inject + private JsonLd jsonLdService; + + @Inject + private Monitor monitor; + + @Override + public void initialize(ServiceExtensionContext context) { + FILES.entrySet().stream().map(this::mapToFile) + .forEach(result -> result.onSuccess(entry -> jsonLdService.registerCachedDocument(entry.getKey(), entry.getValue())) + .onFailure(failure -> monitor.warning("Failed to register cached json-ld document: " + failure.getFailureDetail()))); + } + + private Result> mapToFile(Map.Entry fileEntry) { + return getResourceFile(fileEntry.getValue()) + .map(file1 -> Map.entry(fileEntry.getKey(), file1)); + } + + @NotNull + private Result getResourceFile(String name) { + try (var stream = getClass().getClassLoader().getResourceAsStream(name)) { + if (stream == null) { + return Result.failure(format("Cannot find resource %s", name)); + } + + var filename = Path.of(name).getFileName().toString(); + var parts = filename.split("\\."); + var tempFile = Files.createTempFile(parts[0], "." + parts[1]); + Files.copy(stream, tempFile, REPLACE_EXISTING); + return Result.success(tempFile.toFile()); + } catch (Exception e) { + return Result.failure(format("Cannot read resource %s: ", name)); + } + } + +} diff --git a/core/json-ld-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/core/json-ld-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..b427bdb0b --- /dev/null +++ b/core/json-ld-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# 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 +# +# + +org.eclipse.tractusx.edc.jsonld.JsonLdExtension \ No newline at end of file diff --git a/core/json-ld-core/src/main/resources/document/credential-v1.jsonld b/core/json-ld-core/src/main/resources/document/credential-v1.jsonld new file mode 100644 index 000000000..0124a3c41 --- /dev/null +++ b/core/json-ld-core/src/main/resources/document/credential-v1.jsonld @@ -0,0 +1,237 @@ +{ + "@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/core/json-ld-core/src/main/resources/document/summary-vc-context-v1.jsonld b/core/json-ld-core/src/main/resources/document/summary-vc-context-v1.jsonld new file mode 100644 index 000000000..de053634a --- /dev/null +++ b/core/json-ld-core/src/main/resources/document/summary-vc-context-v1.jsonld @@ -0,0 +1,26 @@ +{ + "@context": { + "@version": 1.1, + "@protected": true, + "summary": "https://w3id.org/2023/catenax/credentials/summary/", + "id": "@id", + "type": "@type", + "SummaryCredential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "@id": "summary:SummaryCredential" + }, + "holderIdentifier": { + "@id": "summary:holderIdentifier" + }, + "items": { + "@id": "summary:items", + "@type": "https://schema.org/Text" + }, + "contract-template": { + "@id": "summary:contract-template", + "@type": "https://schema.org/Text" + } + } +} \ No newline at end of file diff --git a/core/json-ld-core/src/test/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtensionTest.java b/core/json-ld-core/src/test/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtensionTest.java new file mode 100644 index 000000000..c0626b7d0 --- /dev/null +++ b/core/json-ld-core/src/test/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtensionTest.java @@ -0,0 +1,50 @@ +/* + * 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.jsonld; + +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.injection.ObjectFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.eclipse.tractusx.edc.jsonld.JsonLdExtension.CREDENTIALS_SUMMARY_V_1; +import static org.eclipse.tractusx.edc.jsonld.JsonLdExtension.CREDENTIALS_V_1; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; + +@ExtendWith(DependencyInjectionExtension.class) +public class JsonLdExtensionTest { + + JsonLdExtension extension; + + JsonLd jsonLdService = mock(JsonLd.class); + + @BeforeEach + void setup(ObjectFactory factory, ServiceExtensionContext context) { + context.registerService(JsonLd.class, jsonLdService); + extension = factory.constructInstance(JsonLdExtension.class); + } + + @Test + void initialize(ServiceExtensionContext context) { + extension.initialize(context); + jsonLdService.registerCachedDocument(eq(CREDENTIALS_V_1), any()); + jsonLdService.registerCachedDocument(eq(CREDENTIALS_SUMMARY_V_1), any()); + } +} diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java index 58105f6f9..afd6f92ec 100644 --- a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/AdapterEdrControllerTest.java @@ -44,8 +44,8 @@ import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.tractusx.edc.api.cp.adapter.TestFunctions.negotiationRequest; import static org.eclipse.tractusx.edc.api.cp.adapter.TestFunctions.openRequest; -import static org.eclipse.tractusx.edc.api.cp.adapter.TestFunctions.requestDto; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.TX_NAMESPACE; import static org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry.EDR_ENTRY_AGREEMENT_ID; import static org.eclipse.tractusx.edc.edr.spi.EndpointDataReferenceEntry.EDR_ENTRY_ASSET_ID; @@ -82,7 +82,8 @@ void initEdrNegotiation_shouldWork_whenValidRequest() { when(transformerRegistry.transform(any(), eq(NegotiateEdrRequest.class))).thenReturn(Result.success(openRequest)); when(adapterTransferProcessService.initiateEdrNegotiation(openRequest)).thenReturn(ServiceResult.success(contractNegotiation)); when(transformerRegistry.transform(any(IdResponseDto.class), eq(JsonObject.class))).thenReturn(Result.success(responseBody)); - var request = requestDto(); + + var request = negotiationRequest(); baseRequest() .contentType(MediaType.APPLICATION_JSON) diff --git a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java index ec1a89824..c3ad7947a 100644 --- a/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java +++ b/edc-extensions/control-plane-adapter-api/src/test/java/org/eclipse/tractusx/edc/api/cp/adapter/TestFunctions.java @@ -14,6 +14,8 @@ package org.eclipse.tractusx.edc.api.cp.adapter; +import jakarta.json.Json; +import jakarta.json.JsonObject; import org.eclipse.edc.connector.api.management.contractnegotiation.model.ContractOfferDescription; import org.eclipse.edc.connector.contract.spi.types.offer.ContractOffer; import org.eclipse.edc.policy.model.Policy; @@ -22,6 +24,9 @@ import java.util.UUID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; + public class TestFunctions { public static ContractOfferDescription createOffer(String offerId, String assetId) { @@ -48,15 +53,18 @@ public static ContractOfferDescription createOffer() { return createOffer(UUID.randomUUID().toString(), UUID.randomUUID().toString()); } - public static NegotiateEdrRequestDto requestDto() { - return NegotiateEdrRequestDto.Builder.newInstance() - .connectorAddress("test") - .connectorId("id") - .protocol("test-protocol") - .offer(ContractOfferDescription.Builder.newInstance() - .offerId("offerId") - .assetId("assetId") - .policy(Policy.Builder.newInstance().build()).build()) + public static JsonObject negotiationRequest() { + return Json.createObjectBuilder() + .add(TYPE, NegotiateEdrRequestDto.EDR_REQUEST_DTO_TYPE) + .add(EDC_NAMESPACE + "connectorId", "test") + .add(EDC_NAMESPACE + "providerId", "test") + .add(EDC_NAMESPACE + "connectorAddress", "test") + .add(EDC_NAMESPACE + "protocol", "dataspace-protocol-http") + .add(EDC_NAMESPACE + "offer", Json.createObjectBuilder() + .add(EDC_NAMESPACE + "offerId", "offerId") + .add(EDC_NAMESPACE + "assetId", "assetId") + .add(EDC_NAMESPACE + "policy", Json.createObjectBuilder().build()) + ) .build(); } diff --git a/edc-extensions/cx-policy/build.gradle.kts b/edc-extensions/cx-policy/build.gradle.kts index 40dbffbae..b146181fc 100644 --- a/edc-extensions/cx-policy/build.gradle.kts +++ b/edc-extensions/cx-policy/build.gradle.kts @@ -17,8 +17,10 @@ plugins { } dependencies { + implementation(project(":spi:ssi-spi")) implementation(libs.edc.spi.policyengine) implementation(libs.jakartaJson) testImplementation(libs.jacksonJsonP) testImplementation(libs.titaniumJsonLd) + testImplementation(testFixtures(project(":spi:ssi-spi"))) } 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 index 517581e08..0a055d78e 100644 --- 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 @@ -20,30 +20,31 @@ 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.iam.ssi.spi.jsonld.JsonLdFieldExtractor; 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; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.CREDENTIAL_SUBJECT; /** * 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"; - + private static final String ERROR_PREFIX_TEMPLATE = "Invalid %s VC format: "; protected final String errorPrefix; - protected final String credentialType; - - private static final String ERROR_PREFIX_TEMPLATE = "Invalid %s VC format: "; + private JsonLdFieldExtractor credentialSubjectExtractor = JsonLdFieldExtractor.Builder.newInstance() + .field(CREDENTIAL_SUBJECT) + .fieldAlias("credentialSubject") + .build(); /** * Ctor. @@ -54,6 +55,11 @@ public AbstractVpConstraintFunction(String credentialType) { requireNonNull(credentialType); this.credentialType = credentialType; this.errorPrefix = format(ERROR_PREFIX_TEMPLATE, credentialType); + this.credentialSubjectExtractor = JsonLdFieldExtractor.Builder.newInstance() + .field(CREDENTIAL_SUBJECT) + .fieldAlias("credentialSubject") + .errorPrefix(errorPrefix) + .build(); } /** @@ -96,23 +102,7 @@ protected boolean validatePresentation(@Nullable Object vp, PolicyContext contex */ @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(); + return credentialSubjectExtractor.extract(credential).onFailure(failure -> context.reportProblem(failure.getFailureDetail())).getContent(); } /** 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 index 4cb92f237..99304a9d9 100644 --- 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 @@ -18,11 +18,11 @@ * 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_REQUEST_SCOPE = "request.catalog"; + String NEGOTIATION_REQUEST_SCOPE = "request.contract.negotiation"; + String TRANSFER_PROCESS_REQUEST_SCOPE = "request.transfer.process"; - String CATALOG_SCOPE = "contract.cataloging"; + String CATALOG_SCOPE = "catalog"; 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 index cb895a74d..bc9fca518 100644 --- 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 @@ -27,10 +27,11 @@ 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.CX_USE_CASE_NS; -import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.W3_VP_PROPERTY; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.CX_USE_CASE_NS; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.VP_PROPERTY; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTypeFunctions.extractObjectsOfType; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdValueFunctions.extractStringValue; + /** * Enforces a Framework Agreement constraint. @@ -50,12 +51,15 @@ * 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 = CX_USE_CASE_NS + "/contractVersion"; - + private static final String ACTIVE = "active"; private String agreementType; private String agreementVersion; + private FrameworkAgreementConstraintFunction(String credentialType) { + super(credentialType); + } + @Override public boolean evaluate(Operator operator, Object rightValue, Permission permission, PolicyContext context) { if (!validateOperator(operator, context, EQ, GT, GEQ)) { @@ -66,7 +70,7 @@ public boolean evaluate(Operator operator, Object rightValue, Permission permiss return false; } - var vp = (JsonObject) context.getParticipantAgent().getClaims().get(W3_VP_PROPERTY); + var vp = (JsonObject) context.getParticipantAgent().getClaims().get(VP_PROPERTY); if (!validatePresentation(vp, context)) { return false; } @@ -125,16 +129,16 @@ private boolean validateVersion(PolicyContext context, Operator operator, JsonOb } } - private FrameworkAgreementConstraintFunction(String credentialType) { - super(credentialType); - } - /** * Configures a new constraint instance. */ public static class Builder { private final FrameworkAgreementConstraintFunction constraint; + private Builder(String credentialType) { + constraint = new FrameworkAgreementConstraintFunction(credentialType); + } + /** * Ctor. * @@ -165,10 +169,6 @@ 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 index fa8b76ddd..448cc6795 100644 --- 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 @@ -20,8 +20,8 @@ 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.iam.ssi.spi.jsonld.CredentialsNamespaces; import org.eclipse.tractusx.edc.policy.cx.common.AbstractVpConstraintFunction; -import org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces; import java.util.Map; @@ -31,8 +31,10 @@ 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; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.SUMMARY_CREDENTIAL_TYPE; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.VP_PROPERTY; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTypeFunctions.extractObjectsOfType; + /** * Implements Catena-X policies by verifying policy constraints against the summary credential. @@ -40,8 +42,7 @@ * 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.CX_SUMMARY_NS + "/SummaryCredential"; - private static final String SUMMARY_CREDENTIAL_ITEMS = PolicyNamespaces.CX_SUMMARY_NS + "/items"; + private static final String SUMMARY_CREDENTIAL_ITEMS = CredentialsNamespaces.CX_SUMMARY_NS + "/items"; private static final String CREDENTIAL_SUBJECT = "credentialSubject"; private static final String ACTIVE = "active"; @@ -64,7 +65,7 @@ public boolean evaluate(Operator operator, Object rightValue, Permission rule, P return false; } - var vp = (JsonObject) context.getParticipantAgent().getClaims().get(W3_VP_PROPERTY); + var vp = (JsonObject) context.getParticipantAgent().getClaims().get(VP_PROPERTY); if (!validatePresentation(vp, context)) { return false; } 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 index 2d0d1d4b0..693325e5d 100644 --- 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 @@ -22,12 +22,12 @@ import java.util.function.BiFunction; import static java.lang.String.format; -import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.CX_SUMMARY_CREDENTIAL; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.CX_SUMMARY_CREDENTIAL; /** * Includes a summary credential in the token parameters. */ -public class SummaryTokenPolicyFunction implements BiFunction { +public class SummaryTokenPolicyFunction implements BiFunction { @Override public Boolean apply(Policy policy, PolicyContext context) { 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 index efdce8ac7..03df86e46 100644 --- 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 @@ -27,13 +27,37 @@ 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.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.expand; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; class AbstractVpConstraintFunctionTest { + 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" + } + """; private AbstractVpConstraintFunction function; private PolicyContext context; @@ -82,30 +106,4 @@ public boolean evaluate(Operator operator, Object rightValue, Permission rule, P } }; } - - 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/framework/FrameworkAgreementConstraintFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunctionTest.java index 9a980b026..3cc5085af 100644 --- 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 @@ -29,10 +29,10 @@ 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.CX_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.iam.ssi.spi.jsonld.CredentialsNamespaces.CX_USE_CASE_NS_V1; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.VP_PROPERTY; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.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; @@ -135,7 +135,7 @@ void setUp() { 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())); + when(context.getParticipantAgent()).thenReturn(new ParticipantAgent(Map.of(VP_PROPERTY, vp), Map.of())); } 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 index 47a7d51e8..44c1854a1 100644 --- 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 @@ -19,6 +19,7 @@ import org.eclipse.edc.policy.engine.spi.PolicyContext; import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.spi.agent.ParticipantAgent; +import org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.SummaryContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -26,19 +27,19 @@ 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.CX_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.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.CX_SUMMARY_NS_V1; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.VP_PROPERTY; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.expand; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.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(CX_SUMMARY_NS_V1, SummaryContext.SUMMARY_CONTEXT); public static final String CX_QUALITY = "QualityCredential"; + private static final Map CONTEXT_CACHE = Map.of(CX_SUMMARY_NS_V1, SummaryContext.SUMMARY_CONTEXT); private Permission permission; private PolicyContext context; @@ -48,7 +49,7 @@ void verify_constraint_success() throws JsonProcessingException { var function = new SummaryConstraintFunction(CX_QUALITY); - when(context.getParticipantAgent()).thenReturn(new ParticipantAgent(Map.of(W3_VP_PROPERTY, vp), Map.of())); + when(context.getParticipantAgent()).thenReturn(new ParticipantAgent(Map.of(VP_PROPERTY, vp), Map.of())); var result = function.evaluate(EQ, "active", permission, context); diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryTokenPolicyFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryTokenPolicyFunctionTest.java index f5e259b5e..d6d9a26ca 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryTokenPolicyFunctionTest.java +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryTokenPolicyFunctionTest.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.tractusx.edc.policy.cx.common.PolicyNamespaces.CX_SUMMARY_CREDENTIAL; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.CX_SUMMARY_CREDENTIAL; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/edc-extensions/ssi/ssi-identity-core/README.md b/edc-extensions/ssi/ssi-identity-core/README.md index c483f2605..59cc63bfe 100644 --- a/edc-extensions/ssi/ssi-identity-core/README.md +++ b/edc-extensions/ssi/ssi-identity-core/README.md @@ -18,4 +18,8 @@ Custom rule could be like: - Expiration - ..etc -This module it's still in development, but it will likely to contain also the Identity extractor from the `ClaimToken` +## Configuration + +| Key | Required | Example | Description | +|-----------------------------------------|----------|----------------|---------------------------------------| +| tx.ssi.endpoint.audience | X | | Endpoint URL for audience check (DSP) | diff --git a/edc-extensions/ssi/ssi-identity-core/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/SsiIdentityServiceExtension.java b/edc-extensions/ssi/ssi-identity-core/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/SsiIdentityServiceExtension.java index 3ab9937bb..ca69eb878 100644 --- a/edc-extensions/ssi/ssi-identity-core/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/SsiIdentityServiceExtension.java +++ b/edc-extensions/ssi/ssi-identity-core/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/SsiIdentityServiceExtension.java @@ -17,9 +17,11 @@ import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provides; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.iam.ssi.identity.rule.SsiAudienceValidationRule; import org.eclipse.tractusx.edc.iam.ssi.spi.SsiCredentialClient; import org.eclipse.tractusx.edc.iam.ssi.spi.SsiValidationRuleRegistry; @@ -29,6 +31,9 @@ public class SsiIdentityServiceExtension implements ServiceExtension { public static final String EXTENSION_NAME = "SSI Identity Service"; + @Setting(value = "SSI Endpoint audience of this connector") + public static final String ENDPOINT_AUDIENCE = "tx.ssi.endpoint.audience"; + @Inject private SsiCredentialClient credentialClient; @@ -40,10 +45,17 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { var validationRulesRegistry = new SsiValidationRulesRegistryImpl(); + configureRules(context, validationRulesRegistry); context.registerService(SsiValidationRuleRegistry.class, validationRulesRegistry); var identityService = new SsiIdentityService(new SsiTokenValidationService(validationRulesRegistry, credentialClient), credentialClient); context.registerService(IdentityService.class, identityService); } + + + private void configureRules(ServiceExtensionContext context, SsiValidationRuleRegistry registry) { + var endpointAudience = context.getConfig().getString(ENDPOINT_AUDIENCE); + registry.addRule(new SsiAudienceValidationRule(endpointAudience)); + } } diff --git a/edc-extensions/ssi/ssi-identity-core/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/rule/SsiAudienceValidationRule.java b/edc-extensions/ssi/ssi-identity-core/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/rule/SsiAudienceValidationRule.java new file mode 100644 index 000000000..dc3c7eae9 --- /dev/null +++ b/edc-extensions/ssi/ssi-identity-core/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/rule/SsiAudienceValidationRule.java @@ -0,0 +1,45 @@ +/* + * 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.iam.ssi.identity.rule; + +import org.eclipse.edc.jwt.spi.TokenValidationRule; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.result.Result; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; + +public class SsiAudienceValidationRule implements TokenValidationRule { + + private final String endpointAudience; + + public SsiAudienceValidationRule(String endpointAudience) { + this.endpointAudience = endpointAudience; + } + + @Override + public Result checkRule(@NotNull ClaimToken toVerify, @Nullable Map additional) { + var audiences = toVerify.getListClaim(AUDIENCE); + if (audiences.isEmpty()) { + return Result.failure("Required audience (aud) claim is missing in token"); + } else if (!audiences.contains(endpointAudience)) { + return Result.failure("Token audience (aud) claim did not contain audience: " + endpointAudience); + } + return Result.success(); + } +} diff --git a/edc-extensions/ssi/ssi-identity-core/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/SsiIdentityServiceExtensionTest.java b/edc-extensions/ssi/ssi-identity-core/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/SsiIdentityServiceExtensionTest.java index 94e9a08c5..42a88786c 100644 --- a/edc-extensions/ssi/ssi-identity-core/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/SsiIdentityServiceExtensionTest.java +++ b/edc-extensions/ssi/ssi-identity-core/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/SsiIdentityServiceExtensionTest.java @@ -17,6 +17,7 @@ import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.spi.iam.IdentityService; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.injection.ObjectFactory; import org.eclipse.tractusx.edc.iam.ssi.spi.SsiCredentialClient; import org.eclipse.tractusx.edc.iam.ssi.spi.SsiValidationRuleRegistry; @@ -25,8 +26,11 @@ import org.junit.jupiter.api.extension.ExtendWith; import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.iam.ssi.identity.SsiIdentityServiceExtension.ENDPOINT_AUDIENCE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @ExtendWith(DependencyInjectionExtension.class) public class SsiIdentityServiceExtensionTest { @@ -41,12 +45,19 @@ void setup(ObjectFactory factory, ServiceExtensionContext context) { context.registerService(SsiCredentialClient.class, mock(SsiCredentialClient.class)); extension = factory.constructInstance(SsiIdentityServiceExtension.class); } - + @Test void initialize() { + var cfg = mock(Config.class); + when(context.getConfig()).thenReturn(cfg); + when(cfg.getString(ENDPOINT_AUDIENCE)).thenReturn("test"); + extension.initialize(context); assertThat(context.getService(IdentityService.class)).isNotNull().isInstanceOf(SsiIdentityService.class); assertThat(context.getService(SsiValidationRuleRegistry.class)).isNotNull().isInstanceOf(SsiValidationRulesRegistryImpl.class); + + verify(cfg).getString(ENDPOINT_AUDIENCE); + } } diff --git a/edc-extensions/ssi/ssi-identity-core/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/rule/SsiAudienceValidationRuleTest.java b/edc-extensions/ssi/ssi-identity-core/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/rule/SsiAudienceValidationRuleTest.java new file mode 100644 index 000000000..d6a1567e7 --- /dev/null +++ b/edc-extensions/ssi/ssi-identity-core/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/rule/SsiAudienceValidationRuleTest.java @@ -0,0 +1,67 @@ +/* + * 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.iam.ssi.identity.rule; + +import org.eclipse.edc.jwt.spi.TokenValidationRule; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; + +public class SsiAudienceValidationRuleTest { + + private final String endpointAudience = "test-audience"; + private final TokenValidationRule rule = new SsiAudienceValidationRule(endpointAudience); + + @Test + void validAudience() { + var token = ClaimToken.Builder.newInstance() + .claim(AUDIENCE, List.of(endpointAudience)) + .build(); + + var result = rule.checkRule(token, emptyMap()); + + assertThat(result.succeeded()).isTrue(); + } + + @Test + void validationKoBecauseAudienceNotRespected() { + var token = ClaimToken.Builder.newInstance() + .claim(AUDIENCE, List.of("fake-audience")) + .build(); + + var result = rule.checkRule(token, emptyMap()); + + assertThat(result.succeeded()).isFalse(); + assertThat(result.getFailureMessages()).hasSize(1) + .contains("Token audience (aud) claim did not contain audience: test-audience"); + } + + @Test + void validationKoBecauseAudienceNotProvided() { + var token = ClaimToken.Builder.newInstance() + .build(); + + var result = rule.checkRule(token, emptyMap()); + + assertThat(result.succeeded()).isFalse(); + assertThat(result.getFailureMessages()).hasSize(1) + .contains("Required audience (aud) claim is missing in token"); + } +} diff --git a/edc-extensions/ssi/ssi-identity-extractor/build.gradle.kts b/edc-extensions/ssi/ssi-identity-extractor/build.gradle.kts new file mode 100644 index 000000000..9cbc7a854 --- /dev/null +++ b/edc-extensions/ssi/ssi-identity-extractor/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * 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` + `maven-publish` +} + +dependencies { + implementation(project(":spi:ssi-spi")) + implementation(libs.edc.spi.core) + implementation(libs.jakartaJson) + testImplementation(testFixtures(libs.edc.junit)) + testImplementation(testFixtures(project(":spi:ssi-spi"))) +} diff --git a/edc-extensions/ssi/ssi-identity-extractor/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/CredentialIdentityExtractor.java b/edc-extensions/ssi/ssi-identity-extractor/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/CredentialIdentityExtractor.java new file mode 100644 index 000000000..7390223cb --- /dev/null +++ b/edc-extensions/ssi/ssi-identity-extractor/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/CredentialIdentityExtractor.java @@ -0,0 +1,82 @@ +/* + * 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.iam.ssi.identity.extractor; + +import jakarta.json.JsonObject; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.agent.ParticipantAgentServiceExtension; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdFieldExtractor; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import static org.eclipse.edc.spi.agent.ParticipantAgent.PARTICIPANT_IDENTITY; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.CREDENTIAL_SUBJECT; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.HOLDER_IDENTIFIER; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.SUMMARY_CREDENTIAL_TYPE; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.VP_PROPERTY; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTypeFunctions.extractObjectsOfType; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdValueFunctions.extractStringValue; + +public class CredentialIdentityExtractor implements ParticipantAgentServiceExtension { + + private static final String IDENTITY_EXTRACTOR_PREFIX = "Identity extractor:"; + + private final JsonLdFieldExtractor holderIdentifierExtractor = JsonLdFieldExtractor.Builder.newInstance() + .field(HOLDER_IDENTIFIER) + .fieldAlias("holderIdentifier") + .errorPrefix(IDENTITY_EXTRACTOR_PREFIX) + .build(); + private final JsonLdFieldExtractor credentialSubjectExtractor = JsonLdFieldExtractor.Builder.newInstance() + .field(CREDENTIAL_SUBJECT) + .fieldAlias("credentialSubject") + .errorPrefix(IDENTITY_EXTRACTOR_PREFIX) + .build(); + + @Override + public @NotNull Map attributesFor(ClaimToken token) { + var vp = (JsonObject) token.getClaim(VP_PROPERTY); + + var extractionResult = Optional.ofNullable(vp) + .map(v -> extractObjectsOfType(SUMMARY_CREDENTIAL_TYPE, v)) + .orElse(Stream.empty()) + .map(this::extractHolderIdentifier) + .findFirst() + .orElseThrow(() -> new EdcException("Failed to extract identity from the membership credential")); + + var bpn = extractionResult.orElseThrow((failure) -> new EdcException(failure.getFailureDetail())); + return Map.of(PARTICIPANT_IDENTITY, bpn); + + } + + private Result extractHolderIdentifier(JsonObject credential) { + return this.credentialSubjectExtractor.extract(credential) + .compose(holderIdentifierExtractor::extract) + .compose(this::extractHolderIdentifierValue); + } + + private Result extractHolderIdentifierValue(JsonObject identifier) { + var bpn = extractStringValue(identifier); + if (bpn == null) { + return Result.failure("Failed to find the holder identifier"); + } else { + return Result.success(bpn); + } + } +} diff --git a/edc-extensions/ssi/ssi-identity-extractor/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/SsiIdentityExtractorExtension.java b/edc-extensions/ssi/ssi-identity-extractor/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/SsiIdentityExtractorExtension.java new file mode 100644 index 000000000..b570a3895 --- /dev/null +++ b/edc-extensions/ssi/ssi-identity-extractor/src/main/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/SsiIdentityExtractorExtension.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.iam.ssi.identity.extractor; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.agent.ParticipantAgentService; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +@Extension(SsiIdentityExtractorExtension.EXTENSION_NAME) +public class SsiIdentityExtractorExtension implements ServiceExtension { + + public static final String EXTENSION_NAME = "SSI Identity extractor"; + + @Inject + private ParticipantAgentService participantAgentService; + + @Override + public String name() { + return EXTENSION_NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + participantAgentService.register(new CredentialIdentityExtractor()); + } +} diff --git a/edc-extensions/ssi/ssi-identity-extractor/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/ssi/ssi-identity-extractor/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 000000000..845bc7fc6 --- /dev/null +++ b/edc-extensions/ssi/ssi-identity-extractor/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,15 @@ +# +# 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 +# +# + +org.eclipse.tractusx.edc.iam.ssi.identity.extractor.SsiIdentityExtractorExtension \ No newline at end of file diff --git a/edc-extensions/ssi/ssi-identity-extractor/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/CredentialIdentityExtractorTest.java b/edc-extensions/ssi/ssi-identity-extractor/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/CredentialIdentityExtractorTest.java new file mode 100644 index 000000000..fcbb900ae --- /dev/null +++ b/edc-extensions/ssi/ssi-identity-extractor/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/CredentialIdentityExtractorTest.java @@ -0,0 +1,82 @@ +/* + * 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.iam.ssi.identity.extractor; + +import com.fasterxml.jackson.core.JsonProcessingException; +import jakarta.json.JsonObject; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.SummaryContext; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.edc.spi.agent.ParticipantAgent.PARTICIPANT_IDENTITY; +import static org.eclipse.tractusx.edc.iam.ssi.identity.extractor.fixtures.Credentials.SIMPLE_VP; +import static org.eclipse.tractusx.edc.iam.ssi.identity.extractor.fixtures.Credentials.SUMMARY_VP_NO_HOLDER; +import static org.eclipse.tractusx.edc.iam.ssi.identity.extractor.fixtures.Credentials.SUMMARY_VP_NO_SUBJECT; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.CX_SUMMARY_NS_V1; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.VP_PROPERTY; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.expand; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.SummaryCredential.SUMMARY_VP; + +public class CredentialIdentityExtractorTest { + + static final Map CONTEXT_CACHE = Map.of(CX_SUMMARY_NS_V1, SummaryContext.SUMMARY_CONTEXT); + + CredentialIdentityExtractor extractor = new CredentialIdentityExtractor(); + + @Test + void attributeFor() throws JsonProcessingException { + var vp = expand(createObjectMapper().readValue(SUMMARY_VP, JsonObject.class), CONTEXT_CACHE); + var attributes = extractor.attributesFor(ClaimToken.Builder.newInstance().claim(VP_PROPERTY, vp).build()); + + assertThat(attributes).contains(Map.entry(PARTICIPANT_IDENTITY, "BPN of holder")); + } + + @Test + void attributeFor_exception_whenVpNotPresent() { + assertThatThrownBy(() -> extractor.attributesFor(ClaimToken.Builder.newInstance().build())) + .isInstanceOf(EdcException.class) + .hasMessage("Failed to extract identity from the membership credential"); + } + + @Test + void attributeFor_exception_whenCredentialTypeNotMatch() throws JsonProcessingException { + var vp = expand(createObjectMapper().readValue(SIMPLE_VP, JsonObject.class), CONTEXT_CACHE); + assertThatThrownBy(() -> extractor.attributesFor(ClaimToken.Builder.newInstance().claim(VP_PROPERTY, vp).build())) + .isInstanceOf(EdcException.class) + .hasMessage("Failed to extract identity from the membership credential"); + } + + @Test + void attributeFor_exception_whenHolderIdentifierNotFound() throws JsonProcessingException { + var vp = expand(createObjectMapper().readValue(SUMMARY_VP_NO_HOLDER, JsonObject.class), CONTEXT_CACHE); + assertThatThrownBy(() -> extractor.attributesFor(ClaimToken.Builder.newInstance().claim(VP_PROPERTY, vp).build())) + .isInstanceOf(EdcException.class) + .hasMessage("Identity extractor: no holderIdentifier found"); + } + + @Test + void attributeFor_exception_whenCredentialSubjectNotFound() throws JsonProcessingException { + var vp = expand(createObjectMapper().readValue(SUMMARY_VP_NO_SUBJECT, JsonObject.class), CONTEXT_CACHE); + assertThatThrownBy(() -> extractor.attributesFor(ClaimToken.Builder.newInstance().claim(VP_PROPERTY, vp).build())) + .isInstanceOf(EdcException.class) + .hasMessage("Identity extractor: no credentialSubject found"); + } +} diff --git a/edc-extensions/ssi/ssi-identity-extractor/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/SsiIdentityExtractorExtensionTest.java b/edc-extensions/ssi/ssi-identity-extractor/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/SsiIdentityExtractorExtensionTest.java new file mode 100644 index 000000000..83abe72b6 --- /dev/null +++ b/edc-extensions/ssi/ssi-identity-extractor/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/SsiIdentityExtractorExtensionTest.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.iam.ssi.identity.extractor; + +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.agent.ParticipantAgentService; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.injection.ObjectFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ExtendWith(DependencyInjectionExtension.class) +public class SsiIdentityExtractorExtensionTest { + + SsiIdentityExtractorExtension extension; + + ParticipantAgentService participantAgentService = mock(ParticipantAgentService.class); + + @BeforeEach + void setup(ObjectFactory factory, ServiceExtensionContext context) { + context.registerService(ParticipantAgentService.class, participantAgentService); + extension = factory.constructInstance(SsiIdentityExtractorExtension.class); + } + + @Test + void initialize(ServiceExtensionContext context) { + extension.initialize(context); + verify(participantAgentService).register(isA(CredentialIdentityExtractor.class)); + } +} diff --git a/edc-extensions/ssi/ssi-identity-extractor/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/fixtures/Credentials.java b/edc-extensions/ssi/ssi-identity-extractor/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/fixtures/Credentials.java new file mode 100644 index 000000000..fd14612eb --- /dev/null +++ b/edc-extensions/ssi/ssi-identity-extractor/src/test/java/org/eclipse/tractusx/edc/iam/ssi/identity/extractor/fixtures/Credentials.java @@ -0,0 +1,97 @@ +/* + * 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.iam.ssi.identity.extractor.fixtures; + +public interface Credentials { + + String SIMPLE_VP = """ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "type": "VerifiablePresentation", + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "urn:uuid:12345678-1234-1234-1234-123456789abc", + "type": [ + "VerifiableCredential" + ], + "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" + } + } + ] + } + """; + + String SUMMARY_VP_NO_HOLDER = """ + { + "@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" + } + } + ] + } + """; + + String SUMMARY_VP_NO_SUBJECT = """ + { + "@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" + } + ] + } + """; +} diff --git a/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/SsiMiwApiClientExtension.java b/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/SsiMiwApiClientExtension.java index 557727ba5..9108d9c05 100644 --- a/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/SsiMiwApiClientExtension.java +++ b/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/SsiMiwApiClientExtension.java @@ -38,7 +38,7 @@ public class SsiMiwApiClientExtension implements ServiceExtension { @Setting(value = "MIW Authority ID") public static final String MIW_AUTHORITY_ID = "tx.ssi.miw.authority.id"; - + @Inject private MiwOauth2Client oauth2Client; diff --git a/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/SsiMiwCredentialClientExtension.java b/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/SsiMiwCredentialClientExtension.java index 563874a62..f4503e1b6 100644 --- a/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/SsiMiwCredentialClientExtension.java +++ b/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/SsiMiwCredentialClientExtension.java @@ -14,9 +14,11 @@ package org.eclipse.tractusx.edc.iam.ssi.miw; +import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.tractusx.edc.iam.ssi.miw.api.MiwApiClient; import org.eclipse.tractusx.edc.iam.ssi.miw.credentials.SsiMiwCredentialClient; @@ -30,14 +32,22 @@ public class SsiMiwCredentialClientExtension implements ServiceExtension { @Inject private MiwApiClient apiClient; + @Inject + private JsonLd jsonLdService; + + @Inject + private Monitor monitor; + @Override public String name() { return EXTENSION_NAME; } + @Provider public SsiCredentialClient credentialVerifier() { - return new SsiMiwCredentialClient(apiClient); + return new SsiMiwCredentialClient(apiClient, jsonLdService, monitor); } + } diff --git a/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/credentials/SsiMiwCredentialClient.java b/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/credentials/SsiMiwCredentialClient.java index 81928ed4c..13c4f88cd 100644 --- a/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/credentials/SsiMiwCredentialClient.java +++ b/edc-extensions/ssi/ssi-miw-credential-client/src/main/java/org/eclipse/tractusx/edc/iam/ssi/miw/credentials/SsiMiwCredentialClient.java @@ -15,9 +15,12 @@ package org.eclipse.tractusx.edc.iam.ssi.miw.credentials; import com.nimbusds.jwt.SignedJWT; +import jakarta.json.Json; +import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; import org.eclipse.tractusx.edc.iam.ssi.miw.api.MiwApiClient; import org.eclipse.tractusx.edc.iam.ssi.spi.SsiCredentialClient; @@ -33,8 +36,13 @@ public class SsiMiwCredentialClient implements SsiCredentialClient { private final MiwApiClient apiClient; - public SsiMiwCredentialClient(MiwApiClient apiClient) { + private final JsonLd jsonLdService; + private final Monitor monitor; + + public SsiMiwCredentialClient(MiwApiClient apiClient, JsonLd jsonLdService, Monitor monitor) { this.apiClient = apiClient; + this.jsonLdService = jsonLdService; + this.monitor = monitor; } @Override @@ -81,6 +89,10 @@ private Result extractClaims(TokenRepresentation tokenRepresentation var tokenBuilder = ClaimToken.Builder.newInstance(); jwt.getJWTClaimsSet().getClaims().entrySet().stream() .filter(entry -> entry.getValue() != null) + .map(this::mapClaim) + .peek(this::logIfError) + .filter(Result::succeeded) + .map(Result::getContent) .forEach(entry -> tokenBuilder.claim(entry.getKey(), entry.getValue())); return Result.success(tokenBuilder.build()); @@ -89,4 +101,17 @@ private Result extractClaims(TokenRepresentation tokenRepresentation } } + private Result> mapClaim(Map.Entry entry) { + if (entry.getKey().equals(VP)) { + var json = Json.createObjectBuilder((Map) entry.getValue()).build(); + return jsonLdService.expand(json) + .map((expanded) -> Map.entry(entry.getKey(), expanded)); + } else { + return Result.success(entry); + } + } + + private void logIfError(Result result) { + result.onFailure(f -> monitor.warning(f.getFailureDetail())); + } } diff --git a/edc-extensions/ssi/ssi-miw-credential-client/src/test/java/org/eclipse/tractusx/edc/iam/ssi/miw/credentials/SsiMiwCredentialClientTest.java b/edc-extensions/ssi/ssi-miw-credential-client/src/test/java/org/eclipse/tractusx/edc/iam/ssi/miw/credentials/SsiMiwCredentialClientTest.java index 5b99cca2e..0164302d4 100644 --- a/edc-extensions/ssi/ssi-miw-credential-client/src/test/java/org/eclipse/tractusx/edc/iam/ssi/miw/credentials/SsiMiwCredentialClientTest.java +++ b/edc-extensions/ssi/ssi-miw-credential-client/src/test/java/org/eclipse/tractusx/edc/iam/ssi/miw/credentials/SsiMiwCredentialClientTest.java @@ -23,8 +23,11 @@ import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import jakarta.json.Json; +import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; import org.eclipse.tractusx.edc.iam.ssi.miw.api.MiwApiClient; import org.junit.jupiter.api.BeforeEach; @@ -40,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.edc.iam.ssi.miw.api.MiwApiClient.VP; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -50,11 +54,14 @@ public class SsiMiwCredentialClientTest { private final String audience = "audience"; SsiMiwCredentialClient credentialClient; MiwApiClient apiClient = mock(MiwApiClient.class); + JsonLd jsonLdService = mock(JsonLd.class); + + Monitor monitor = mock(Monitor.class); private RSAKey key; @BeforeEach void setup() throws JOSEException { - credentialClient = new SsiMiwCredentialClient(apiClient); + credentialClient = new SsiMiwCredentialClient(apiClient, jsonLdService, monitor); key = testKey(); } @@ -63,6 +70,7 @@ void validate_success() throws JOSEException { var claims = createClaims(Instant.now()); var jwt = createJwt(UUID.randomUUID().toString(), claims, key.toPrivateKey()); when(apiClient.verifyPresentation(jwt, audience)).thenReturn(Result.success()); + when(jsonLdService.expand(any())).thenReturn(Result.success(Json.createObjectBuilder().build())); var result = credentialClient.validate(TokenRepresentation.Builder.newInstance().token(jwt).build()); @@ -75,6 +83,7 @@ void validate_success_whenClientFails() throws JOSEException { var claims = createClaims(Instant.now()); var jwt = createJwt(UUID.randomUUID().toString(), claims, key.toPrivateKey()); when(apiClient.verifyPresentation(jwt, audience)).thenReturn(Result.failure("fail")); + when(jsonLdService.expand(any())).thenReturn(Result.success(Json.createObjectBuilder().build())); var result = credentialClient.validate(TokenRepresentation.Builder.newInstance().token(jwt).build()); @@ -115,6 +124,7 @@ void obtainCredentials_success() { private JWTClaimsSet createClaims(Instant exp) { return new JWTClaimsSet.Builder() .claim("foo", "bar") + .claim(VP, Map.of()) .audience(audience) .expirationTime(Date.from(exp)) .build(); diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java index 7b476a86d..ee7bb0689 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/helpers/PolicyHelperFunctions.java @@ -21,22 +21,15 @@ import jakarta.json.JsonObjectBuilder; import org.eclipse.edc.connector.policy.spi.PolicyDefinition; import org.eclipse.edc.policy.model.AtomicConstraint; -import org.eclipse.edc.policy.model.Operator; import java.util.Map; import java.util.stream.Stream; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; -import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_ACTION_ATTRIBUTE; -import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_ATTRIBUTE; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_TYPE; -import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LEFT_OPERAND_ATTRIBUTE; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LOGICAL_CONSTRAINT_TYPE; -import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OPERATOR_ATTRIBUTE; -import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_OR_CONSTRAINT_ATTRIBUTE; -import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_PERMISSION_ATTRIBUTE; -import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_RIGHT_OPERAND_ATTRIBUTE; import static org.eclipse.edc.spi.CoreConstants.EDC_NAMESPACE; public class PolicyHelperFunctions { @@ -87,7 +80,8 @@ private static JsonObject noConstraintPolicy() { private static JsonObject bnpPolicy(String... bnps) { return Json.createObjectBuilder() - .add(ODRL_PERMISSION_ATTRIBUTE, Json.createArrayBuilder() + .add(CONTEXT, "http://www.w3.org/ns/odrl.jsonld") + .add("permission", Json.createArrayBuilder() .add(permission(bnps))) .build(); } @@ -95,21 +89,22 @@ private static JsonObject bnpPolicy(String... bnps) { private static JsonObject permission(String... bpns) { var bpnConstraints = Stream.of(bpns) - .map(bpn -> atomicConstraint(BUSINESS_PARTNER_EVALUATION_KEY, Operator.EQ, bpn)) + .map(bpn -> atomicConstraint(BUSINESS_PARTNER_EVALUATION_KEY, "eq", bpn)) .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add); return Json.createObjectBuilder() - .add(ODRL_ACTION_ATTRIBUTE, "USE") - .add(ODRL_CONSTRAINT_ATTRIBUTE, Json.createObjectBuilder() + .add("action", "USE") + .add("constraint", Json.createObjectBuilder() .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) - .add(ODRL_OR_CONSTRAINT_ATTRIBUTE, bpnConstraints) + .add("or", bpnConstraints) .build()) .build(); } private static JsonObject frameworkPolicy(Map permissions) { return Json.createObjectBuilder() - .add(ODRL_PERMISSION_ATTRIBUTE, Json.createArrayBuilder() + .add(CONTEXT, "http://www.w3.org/ns/odrl.jsonld") + .add("permission", Json.createArrayBuilder() .add(frameworkPermission(permissions))) .build(); } @@ -117,24 +112,24 @@ private static JsonObject frameworkPolicy(Map permissions) { private static JsonObject frameworkPermission(Map permissions) { var constraints = permissions.entrySet().stream() - .map(permission -> atomicConstraint(permission.getKey(), Operator.EQ, permission.getValue())) + .map(permission -> atomicConstraint(permission.getKey(), "eq", permission.getValue())) .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add); return Json.createObjectBuilder() - .add(ODRL_ACTION_ATTRIBUTE, "USE") - .add(ODRL_CONSTRAINT_ATTRIBUTE, Json.createObjectBuilder() + .add("action", "USE") + .add("constraint", Json.createObjectBuilder() .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) - .add(ODRL_OR_CONSTRAINT_ATTRIBUTE, constraints) + .add("or", constraints) .build()) .build(); } - private static JsonObject atomicConstraint(String leftOperand, Operator operator, Object rightOperand) { + private static JsonObject atomicConstraint(String leftOperand, String operator, Object rightOperand) { return Json.createObjectBuilder() .add(TYPE, ODRL_CONSTRAINT_TYPE) - .add(ODRL_LEFT_OPERAND_ATTRIBUTE, leftOperand) - .add(ODRL_OPERATOR_ATTRIBUTE, operator.toString()) - .add(ODRL_RIGHT_OPERAND_ATTRIBUTE, rightOperand.toString()) + .add("leftOperand", leftOperand) + .add("operator", operator) + .add("rightOperand", rightOperand.toString()) .build(); } } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java index 7f77f1726..5767bc2dc 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/Participant.java @@ -193,14 +193,8 @@ public String getContractAgreementId(String negotiationId) { return getContractNegotiationField(negotiationId, "contractAgreementId"); } - private String getContractNegotiationField(String negotiationId, String fieldName) { - return baseRequest() - .when() - .get("/v2/contractnegotiations/{id}", negotiationId) - .then() - .statusCode(200) - .extract().body().jsonPath() - .getString(format("'edc:%s'", fieldName)); + public String getContractNegotiationError(String negotiationId) { + return getContractNegotiationField(negotiationId, "errorDetail"); } public JsonObject getEdr(String transferProcessId) { @@ -236,7 +230,6 @@ public JsonArray getEdrEntriesByAgreementId(String agreementId) { .as(JsonArray.class); } - /** * Returns this participant's BusinessPartnerNumber (=BPN). This is constructed of the runtime name plus "-BPN" */ @@ -345,6 +338,25 @@ public String pullProxyDataByTransferProcessId(Participant provider, String tran } + public JsonObject getDatasetForAsset(Participant provider, String assetId) { + var datasets = getCatalogDatasets(provider); + return datasets.stream() + .map(JsonValue::asJsonObject) + .filter(it -> assetId.equals(getDatasetAssetId(it))) + .findFirst() + .orElseThrow(() -> new EdcException(format("No dataset for asset %s in the catalog", assetId))); + } + + private String getContractNegotiationField(String negotiationId, String fieldName) { + return baseRequest() + .when() + .get("/v2/contractnegotiations/{id}", negotiationId) + .then() + .statusCode(200) + .extract().body().jsonPath() + .getString(format("'edc:%s'", fieldName)); + } + private String getProxyData(Map body) { return proxyRequest(body) .then() @@ -360,15 +372,6 @@ private Response proxyRequest(Map body) { .post(PROXY_SUBPATH); } - public JsonObject getDatasetForAsset(Participant provider, String assetId) { - var datasets = getCatalogDatasets(provider); - return datasets.stream() - .map(JsonValue::asJsonObject) - .filter(it -> assetId.equals(getDatasetAssetId(it))) - .findFirst() - .orElseThrow(() -> new EdcException(format("No dataset for asset %s in the catalog", assetId))); - } - private RequestSpecification baseRequest() { return given() .baseUri(managementUrl) diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java index 05982c8b9..4fc4f4f36 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/lifecycle/TestRuntimeConfiguration.java @@ -43,13 +43,13 @@ public class TestRuntimeConfiguration { static final String PLATO_CONNECTOR_PATH = "/api"; static final String PLATO_MANAGEMENT_PATH = "/api/v1/management"; static final int PLATO_DSP_API_PORT = getFreePort(); - static final String PLATO_DSP_CALLBACK = "http://localhost:" + PLATO_DSP_API_PORT + DSP_PATH; + public static final String PLATO_DSP_CALLBACK = "http://localhost:" + PLATO_DSP_API_PORT + DSP_PATH; static final int SOKRATES_CONNECTOR_PORT = getFreePort(); static final int SOKRATES_MANAGEMENT_PORT = getFreePort(); static final String SOKRATES_CONNECTOR_PATH = "/api"; static final String SOKRATES_MANAGEMENT_PATH = "/api/v1/management"; static final int SOKRATES_DSP_API_PORT = getFreePort(); - static final String SOKRATES_DSP_CALLBACK = "http://localhost:" + SOKRATES_DSP_API_PORT + DSP_PATH; + public static final String SOKRATES_DSP_CALLBACK = "http://localhost:" + SOKRATES_DSP_API_PORT + DSP_PATH; static final String SOKRATES_PUBLIC_API_PORT = String.valueOf(getFreePort()); static final String PLATO_PUBLIC_API_PORT = String.valueOf(getFreePort()); static final String PLATO_DATAPLANE_CONTROL_PORT = String.valueOf(getFreePort()); @@ -120,6 +120,7 @@ public static Map sokratesSsiConfiguration() { put("tx.ssi.oauth.client.secret.alias", "client_secret_alias"); put("tx.ssi.miw.authority.id", "authorityId"); put("tx.vault.seed.secrets", "client_secret_alias:client_secret"); + put("tx.ssi.endpoint.audience", SOKRATES_DSP_CALLBACK); } }; var baseConfiguration = sokratesConfiguration(); @@ -200,6 +201,7 @@ public static Map platoSsiConfiguration() { put("tx.ssi.oauth.client.secret.alias", "client_secret_alias"); put("tx.ssi.miw.authority.id", "authorityId"); put("tx.vault.seed.secrets", "client_secret_alias:client_secret"); + put("tx.ssi.endpoint.audience", PLATO_DSP_CALLBACK); } }; var baseConfiguration = platoConfiguration(); diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/SsiHttpConsumerPullWithProxyInMemoryTest.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/SsiHttpConsumerPullWithProxyInMemoryTest.java index 68f17ce6e..fd72a75e4 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/SsiHttpConsumerPullWithProxyInMemoryTest.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/SsiHttpConsumerPullWithProxyInMemoryTest.java @@ -25,13 +25,17 @@ import org.junit.jupiter.api.extension.RegisterExtension; import java.io.IOException; +import java.util.Map; +import static org.eclipse.tractusx.edc.helpers.PolicyHelperFunctions.frameworkPolicy; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.MIW_PLATO_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.MIW_SOKRATES_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.OAUTH_PORT; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_BPN; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_DSP_CALLBACK; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.PLATO_NAME; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_BPN; +import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_DSP_CALLBACK; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.SOKRATES_NAME; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.platoSsiConfiguration; import static org.eclipse.tractusx.edc.lifecycle.TestRuntimeConfiguration.sokratesSsiConfiguration; @@ -39,6 +43,7 @@ @EndToEndTest public class SsiHttpConsumerPullWithProxyInMemoryTest extends AbstractHttpConsumerPullWithProxyTest { + public static final String SUMMARY_VC_TEMPLATE = "summary-vc.json"; @RegisterExtension protected static final ParticipantRuntime SOKRATES_RUNTIME = new ParticipantRuntime( ":edc-tests:runtime:runtime-memory-ssi", @@ -58,14 +63,14 @@ public class SsiHttpConsumerPullWithProxyInMemoryTest extends AbstractHttpConsum MockWebServer miwPlatoServer = new MockWebServer(); MockWebServer oauthServer = new MockWebServer(); - + @BeforeEach void setup() throws IOException { miwSokratesServer.start(MIW_SOKRATES_PORT); - miwSokratesServer.setDispatcher(new MiwDispatcher(SOKRATES_BPN, "audience")); + miwSokratesServer.setDispatcher(new MiwDispatcher(SOKRATES_BPN, SUMMARY_VC_TEMPLATE, PLATO_DSP_CALLBACK)); miwPlatoServer.start(MIW_PLATO_PORT); - miwPlatoServer.setDispatcher(new MiwDispatcher(PLATO_BPN, "audience")); + miwPlatoServer.setDispatcher(new MiwDispatcher(PLATO_BPN, SUMMARY_VC_TEMPLATE, SOKRATES_DSP_CALLBACK)); oauthServer.start(OAUTH_PORT); oauthServer.setDispatcher(new KeycloakDispatcher()); @@ -80,6 +85,6 @@ void teardown() throws IOException { @Override protected JsonObject createTestPolicy(String policyId, String bpn) { - return super.createTestPolicy(policyId, bpn); + return frameworkPolicy(policyId, Map.of("Dismantler", "active")); } } diff --git a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/token/MiwDispatcher.java b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/token/MiwDispatcher.java index 0bf644e3c..c0c70f9b7 100644 --- a/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/token/MiwDispatcher.java +++ b/edc-tests/e2e-tests/src/test/java/org/eclipse/tractusx/edc/token/MiwDispatcher.java @@ -45,28 +45,14 @@ public class MiwDispatcher extends Dispatcher { private static final TypeManager MAPPER = new TypeManager(); - - private static final String SUMMARY_JSON; - - static { - - var classloader = Thread.currentThread().getContextClassLoader(); - - try (var jsonStream = classloader.getResourceAsStream("summary-vc.json")) { - Objects.requireNonNull(jsonStream); - SUMMARY_JSON = new String(jsonStream.readAllBytes(), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - + private final String audience; private final Map summaryVc; - public MiwDispatcher(String bpn, String audience) { + public MiwDispatcher(String bpn, String vcFile, String audience) { this.audience = audience; - var json = format(SUMMARY_JSON, bpn); + var json = format(readVcContent(vcFile), bpn); summaryVc = MAPPER.readValue(json, new TypeReference<>() { }); } @@ -82,19 +68,39 @@ public MockResponse dispatch(@NotNull RecordedRequest recordedRequest) throws In }; } + private String readVcContent(String vcFile) { + var classloader = Thread.currentThread().getContextClassLoader(); + + try (var jsonStream = classloader.getResourceAsStream(vcFile)) { + Objects.requireNonNull(jsonStream); + return new String(jsonStream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private MockResponse credentialResponse() { return new MockResponse().setBody(MAPPER.writeValueAsString(Map.of("content", List.of(summaryVc)))); } private MockResponse presentationResponse() { try { - var jwt = createJwt(UUID.randomUUID().toString(), createClaims(Instant.now(), Map.of("verifiableCredential", List.of(summaryVc))), testKey().toPrivateKey()); + var jwt = createJwt(UUID.randomUUID().toString(), createClaims(Instant.now(), createVerifiablePresentationClaim()), testKey().toPrivateKey()); return new MockResponse().setBody(MAPPER.writeValueAsString(Map.of("vp", jwt))); } catch (JOSEException e) { throw new RuntimeException(e); } } + private Map createVerifiablePresentationClaim() { + var ctx = List.of("https://www.w3.org/2018/credentials/v1"); + var type = List.of("VerifiablePresentation"); + return Map.of( + "@context", ctx, + "type", type, + "verifiableCredential", List.of(summaryVc)); + } + private MockResponse presentationValidationResponse() { return new MockResponse().setBody(MAPPER.writeValueAsString(Map.of("valid", true))); } diff --git a/edc-tests/e2e-tests/src/test/resources/summary-vc-no-dismantler.json b/edc-tests/e2e-tests/src/test/resources/summary-vc-no-dismantler.json new file mode 100644 index 000000000..89e89a518 --- /dev/null +++ b/edc-tests/e2e-tests/src/test/resources/summary-vc-no-dismantler.json @@ -0,0 +1,37 @@ +{ + "@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": "%s", + "type": "Summary-List", + "name": "CX-Credentials", + "items": [ + "MembershipCredential", + "PcfCredential", + "SustainabilityCredential", + "QualityCredential", + "TraceabilityCredential", + "BehaviorTwinCredential", + "BpnCredential" + ], + "contractTemplates": "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" + } +} \ No newline at end of file diff --git a/edc-tests/runtime/extensions/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerServicesExtension.java b/edc-tests/runtime/extensions/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerServicesExtension.java index 5b339d2ec..401d54b7a 100644 --- a/edc-tests/runtime/extensions/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerServicesExtension.java +++ b/edc-tests/runtime/extensions/src/main/java/org/eclipse/tractusx/edc/lifecycle/ConsumerServicesExtension.java @@ -15,7 +15,6 @@ package org.eclipse.tractusx.edc.lifecycle; import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.agent.ParticipantAgentService; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.web.spi.WebService; @@ -26,13 +25,9 @@ public class ConsumerServicesExtension implements ServiceExtension { @Inject private WebService webService; - @Inject - private ParticipantAgentService participantAgentService; - @Override public void initialize(ServiceExtensionContext context) { webService.registerResource("default", new ConsumerEdrHandlerController(context.getMonitor())); - participantAgentService.register(new SsiParticipantExtractor()); } } diff --git a/edc-tests/runtime/extensions/src/main/java/org/eclipse/tractusx/edc/lifecycle/SsiParticipantExtractor.java b/edc-tests/runtime/extensions/src/main/java/org/eclipse/tractusx/edc/lifecycle/SsiParticipantExtractor.java deleted file mode 100644 index e10e2055f..000000000 --- a/edc-tests/runtime/extensions/src/main/java/org/eclipse/tractusx/edc/lifecycle/SsiParticipantExtractor.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.lifecycle; - -import org.eclipse.edc.spi.agent.ParticipantAgentServiceExtension; -import org.eclipse.edc.spi.iam.ClaimToken; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; -import java.util.Optional; - -import static org.eclipse.edc.spi.agent.ParticipantAgent.PARTICIPANT_IDENTITY; -import static org.eclipse.edc.util.reflection.ReflectionUtil.getFieldValue; - -public class SsiParticipantExtractor implements ParticipantAgentServiceExtension { - - private static final String EXTRACTING_KEY = "verifiableCredential[0].credentialSubject.holderIdentifier"; - - @Override - public @NotNull Map attributesFor(ClaimToken token) { - var vp = (Map) token.getClaim("vp"); - return Optional.ofNullable(vp) - .flatMap(this::extractIdentity) - .map(this::identityMap) - .orElse(Map.of()); - } - - private Optional extractIdentity(Map vp) { - return Optional.ofNullable(getFieldValue(EXTRACTING_KEY, vp)); - } - - private Map identityMap(String identity) { - return Map.of(PARTICIPANT_IDENTITY, identity); - } - -} diff --git a/edc-tests/runtime/runtime-memory-ssi/build.gradle.kts b/edc-tests/runtime/runtime-memory-ssi/build.gradle.kts index 0a69659c3..ffd3d0aaa 100644 --- a/edc-tests/runtime/runtime-memory-ssi/build.gradle.kts +++ b/edc-tests/runtime/runtime-memory-ssi/build.gradle.kts @@ -26,9 +26,13 @@ dependencies { exclude("org.eclipse.edc", "oauth2-daps") exclude(module = "data-encryption") } + implementation(project(":core:json-ld-core")) + implementation(project(":edc-extensions:ssi:ssi-identity-core")) implementation(project(":edc-extensions:ssi:ssi-miw-credential-client")); + implementation(project(":edc-extensions:ssi:ssi-identity-extractor")) + implementation(project(":edc-extensions:cx-policy")) implementation(project(":edc-tests:runtime:extensions")) diff --git a/gradle.properties b/gradle.properties index fe50648e7..bd546586e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,9 @@ group=org.eclipse.tractusx.edc version=0.4.2-SNAPSHOT # configure the build: -annotationProcessorVersion=0.1.1-SNAPSHOT -edcGradlePluginsVersion=0.1.1-SNAPSHOT -metaModelVersion=0.1.1-SNAPSHOT +annotationProcessorVersion=0.1.1 +edcGradlePluginsVersion=0.1.1 +metaModelVersion=0.1.1 txScmConnection=scm:git:git@github.com:eclipse-tractusx/tractusx-edc.git txWebsiteUrl=https://github.com/eclipse-tractusx/tractusx-edc.git txScmUrl=https://github.com/eclipse-tractusx/tractusx-edc.git diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4e37ce6b7..ea80601d9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ format.version = "1.1" [versions] -edc = "0.1.1-SNAPSHOT" +edc = "0.1.1" postgres = "42.6.0" awaitility = "4.2.0" nimbus = "9.31" diff --git a/settings.gradle.kts b/settings.gradle.kts index 2d173083d..2fd72dfc6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,8 @@ include(":spi:ssi-spi") // core modules include(":core:edr-cache-core") +include(":core:json-ld-core") + include(":edc-extensions:business-partner-validation") include(":edc-extensions:cx-oauth2") @@ -47,7 +49,7 @@ include(":edc-extensions:cx-policy") include("edc-extensions:ssi:ssi-identity-core") include("edc-extensions:ssi:ssi-miw-credential-client") include("edc-extensions:ssi:jws2020-crypto-suite") - +include(":edc-extensions:ssi:ssi-identity-extractor") include(":edc-tests:e2e-tests") diff --git a/spi/ssi-spi/build.gradle.kts b/spi/ssi-spi/build.gradle.kts index d37a21733..24289ef22 100644 --- a/spi/ssi-spi/build.gradle.kts +++ b/spi/ssi-spi/build.gradle.kts @@ -20,4 +20,8 @@ plugins { dependencies { implementation(libs.edc.spi.core) implementation(libs.edc.spi.jwt) + implementation(libs.jakartaJson) + + testFixturesImplementation(libs.jacksonJsonP) + testFixturesImplementation(libs.titaniumJsonLd) } diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyNamespaces.java b/spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/CredentialsNamespaces.java similarity index 73% rename from edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyNamespaces.java rename to spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/CredentialsNamespaces.java index f27f93d98..164511ef9 100644 --- a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/PolicyNamespaces.java +++ b/spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/CredentialsNamespaces.java @@ -12,23 +12,23 @@ * */ -package org.eclipse.tractusx.edc.policy.cx.common; +package org.eclipse.tractusx.edc.iam.ssi.spi.jsonld; /** * Defines policy namespaces. */ -public interface PolicyNamespaces { +public interface CredentialsNamespaces { 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 VP_PROPERTY = "vp"; String CX_NS = "https://w3id.org/2023/catenax/credentials/"; String CX_SUMMARY_NS = CX_NS + "summary"; String CX_SUMMARY_NS_V1 = CX_SUMMARY_NS + "/v1"; + String SUMMARY_CREDENTIAL_TYPE = CX_SUMMARY_NS + "/SummaryCredential"; + String HOLDER_IDENTIFIER = CX_SUMMARY_NS + "/holderIdentifier"; String CX_USE_CASE_NS = CX_NS + "usecase"; String CX_USE_CASE_NS_V1 = CX_USE_CASE_NS + "/v1"; - String CX_SUMMARY_CREDENTIAL = "SummaryCredential"; - + String CREDENTIAL_SUBJECT = W3C_VC_PREFIX + "#credentialSubject"; } diff --git a/spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdFieldExtractor.java b/spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdFieldExtractor.java new file mode 100644 index 000000000..12cae7254 --- /dev/null +++ b/spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdFieldExtractor.java @@ -0,0 +1,93 @@ +/* + * 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.iam.ssi.spi.jsonld; + +import jakarta.json.JsonObject; +import org.eclipse.edc.spi.result.Result; + +import java.util.Objects; + +import static jakarta.json.JsonValue.ValueType.ARRAY; +import static jakarta.json.JsonValue.ValueType.OBJECT; +import static java.lang.String.format; + +/** + * Extractor for field from a {@link JsonObject} with a customizable error reporting + */ +public class JsonLdFieldExtractor { + + private String fieldAlias; + private String errorPrefix = ""; + private String field; + + private JsonLdFieldExtractor() { + } + + /** + * Extract a field by name. If not found return an error. + */ + public Result extract(JsonObject root) { + var subjectArray = root.get(field); + if (subjectArray == null || subjectArray.getValueType() != ARRAY) { + return Result.failure(errorPrefix + format(" no %s found", fieldAlias)); + } + if (subjectArray.asJsonArray().size() != 1) { + return Result.failure(errorPrefix + format(" empty %s", fieldAlias)); + } + + var subjectValue = subjectArray.asJsonArray().get(0); + if (subjectValue == null || subjectValue.getValueType() != OBJECT) { + return Result.failure(errorPrefix + format(" invalid %s format", fieldAlias)); + } + return Result.success(subjectValue.asJsonObject()); + } + + public static class Builder { + + private final JsonLdFieldExtractor extractor; + + private Builder(JsonLdFieldExtractor extractor) { + this.extractor = extractor; + } + + public static Builder newInstance() { + return new Builder(new JsonLdFieldExtractor()); + } + + public Builder field(String field) { + this.extractor.field = field; + return this; + } + + public Builder fieldAlias(String fieldAlias) { + this.extractor.fieldAlias = fieldAlias; + return this; + } + + public Builder errorPrefix(String errorPrefix) { + this.extractor.errorPrefix = errorPrefix; + return this; + } + + public JsonLdFieldExtractor build() { + Objects.requireNonNull(extractor.field); + Objects.requireNonNull(extractor.fieldAlias); + Objects.requireNonNull(extractor.errorPrefix); + return extractor; + } + + } + +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctions.java b/spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdTypeFunctions.java similarity index 98% rename from edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctions.java rename to spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdTypeFunctions.java index beafc5b3c..a77f8eff1 100644 --- a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctions.java +++ b/spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdTypeFunctions.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.tractusx.edc.policy.cx.common; +package org.eclipse.tractusx.edc.iam.ssi.spi.jsonld; import jakarta.json.JsonArray; import jakarta.json.JsonObject; @@ -38,6 +38,9 @@ public class JsonLdTypeFunctions { private static final String TYPE = "@type"; private static final Stream EMPTY_STREAM = Stream.of(); + private JsonLdTypeFunctions() { + } + /** * Returns a stream of objects that are of the given Json-Ld type starting at the root. * @@ -100,8 +103,4 @@ private static boolean matchTypeValue(String typeValue, JsonValue jsonValue) { } return false; } - - private JsonLdTypeFunctions() { - } - } diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctions.java b/spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdValueFunctions.java similarity index 82% rename from edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctions.java rename to spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdValueFunctions.java index d036b99d9..3d976ae07 100644 --- a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctions.java +++ b/spi/ssi-spi/src/main/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdValueFunctions.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.tractusx.edc.policy.cx.common; +package org.eclipse.tractusx.edc.iam.ssi.spi.jsonld; import jakarta.json.JsonArray; import jakarta.json.JsonNumber; @@ -21,9 +21,7 @@ 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; +import static jakarta.json.JsonValue.ValueType; /** * Functions for working with Json-ld values. @@ -31,6 +29,9 @@ public class JsonLdValueFunctions { private static final String VALUE = "@value"; + private JsonLdValueFunctions() { + } + /** * Extracts the value of a root node and converts it to a string representation. Note this method accepts null nodes as a convenience. */ @@ -59,10 +60,10 @@ 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 valueNumber.isIntegral() ? String.valueOf(valueNumber.longValue()) : String.valueOf(valueNumber.doubleValue()); + } else if (ValueType.TRUE == value.getValueType()) { return "TRUE"; - } else if (FALSE == value.getValueType()) { + } else if (ValueType.FALSE == value.getValueType()) { return "FALSE"; } return null; diff --git a/spi/ssi-spi/src/test/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdFieldExtractorTest.java b/spi/ssi-spi/src/test/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdFieldExtractorTest.java new file mode 100644 index 000000000..b994057c2 --- /dev/null +++ b/spi/ssi-spi/src/test/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdFieldExtractorTest.java @@ -0,0 +1,75 @@ +/* + * 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.iam.ssi.spi.jsonld; + +import jakarta.json.JsonObject; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.CREDENTIAL_SUBJECT; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.CX_SUMMARY_NS_V1; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.HOLDER_IDENTIFIER; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.SUMMARY_CREDENTIAL_TYPE; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.expand; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTypeFunctions.extractObjectsOfType; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.SummaryCredential.SUMMARY_VP; + +public class JsonLdFieldExtractorTest { + + private static final Map CONTEXT_CACHE = Map.of(CX_SUMMARY_NS_V1, SummaryContext.SUMMARY_CONTEXT); + + @Test + void extract() throws Exception { + var vp = expand(createObjectMapper().readValue(SUMMARY_VP, JsonObject.class), CONTEXT_CACHE); + + var extractor = JsonLdFieldExtractor.Builder.newInstance() + .field(CREDENTIAL_SUBJECT) + .fieldAlias("credentialSubject") + .errorPrefix("prefix") + .build(); + + + var summaryCredential = extractObjectsOfType(SUMMARY_CREDENTIAL_TYPE, vp).findFirst().orElseThrow(); + + var subject = extractor.extract(summaryCredential); + assertThat(subject).matches(Result::succeeded).extracting(Result::getContent) + .satisfies(jsonObject -> assertThat(jsonObject.containsKey(HOLDER_IDENTIFIER)).isTrue()); + + } + + @Test + void extract_fail() throws Exception { + var vp = expand(createObjectMapper().readValue(SUMMARY_VP, JsonObject.class), CONTEXT_CACHE); + + var extractor = JsonLdFieldExtractor.Builder.newInstance() + .field(HOLDER_IDENTIFIER) + .fieldAlias("holderIdentifier") + .errorPrefix("prefix") + .build(); + + var summaryCredential = extractObjectsOfType(SUMMARY_CREDENTIAL_TYPE, vp).findFirst().orElseThrow(); + + var subject = extractor.extract(summaryCredential); + assertThat(subject).matches(Result::failed).extracting(Result::getFailureDetail) + .satisfies(errorMessage -> { + assertThat(errorMessage).isEqualTo("prefix no holderIdentifier found"); + }); + + } +} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctionsTest.java b/spi/ssi-spi/src/test/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdTypeFunctionsTest.java similarity index 89% rename from edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctionsTest.java rename to spi/ssi-spi/src/test/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdTypeFunctionsTest.java index f103c76b1..7aed90107 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdTypeFunctionsTest.java +++ b/spi/ssi-spi/src/test/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdTypeFunctionsTest.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.tractusx.edc.policy.cx.common; +package org.eclipse.tractusx.edc.iam.ssi.spi.jsonld; import com.fasterxml.jackson.core.JsonProcessingException; import jakarta.json.JsonObject; @@ -24,11 +24,11 @@ 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; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.W3C_VC_PREFIX; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.createObjectMapper; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTextFixtures.expand; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTypeFunctions.extractObjectsOfType; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdTypeFunctions.partitionByType; class JsonLdTypeFunctionsTest { private static final String TYPE = "@type"; @@ -36,42 +36,6 @@ class JsonLdTypeFunctionsTest { 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", @@ -88,7 +52,6 @@ private void assertAllOfType(String type, List objects) { } ] }"""; - private static final String BAR_CREDENTIAL = """ { "type": "VerifiablePresentation", @@ -104,7 +67,6 @@ private void assertAllOfType(String type, List objects) { } ] }"""; - private static final String MULTIPLE_VCS_CLAIM = format(""" { "@context": [ @@ -115,7 +77,6 @@ private void assertAllOfType(String type, List objects) { ], "vp": [%s,%s] }""", FOO_CREDENTIAL, BAR_CREDENTIAL); - private static final String SINGLE_VC_CLAIM = format(""" { "@context": [ @@ -127,5 +88,40 @@ private void assertAllOfType(String type, List objects) { "vp": %s }""", FOO_CREDENTIAL); + @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()); + } + } diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctionsTest.java b/spi/ssi-spi/src/test/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdValueFunctionsTest.java similarity index 92% rename from edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctionsTest.java rename to spi/ssi-spi/src/test/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdValueFunctionsTest.java index 8b71178b5..a79d59b06 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/common/JsonLdValueFunctionsTest.java +++ b/spi/ssi-spi/src/test/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdValueFunctionsTest.java @@ -12,14 +12,14 @@ * */ -package org.eclipse.tractusx.edc.policy.cx.common; +package org.eclipse.tractusx.edc.iam.ssi.spi.jsonld; 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; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.JsonLdValueFunctions.extractStringValue; class JsonLdValueFunctionsTest { private static final String VALUE = "@value"; diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/JsonLdTextFixtures.java b/spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdTextFixtures.java similarity index 83% rename from edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/JsonLdTextFixtures.java rename to spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdTextFixtures.java index 639905da4..10b3ab552 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/JsonLdTextFixtures.java +++ b/spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/JsonLdTextFixtures.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.tractusx.edc.policy.cx.fixtures; +package org.eclipse.tractusx.edc.iam.ssi.spi.jsonld; import com.apicatalog.jsonld.JsonLdError; import com.apicatalog.jsonld.JsonLdOptions; @@ -21,18 +21,16 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsonp.JSONPModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import jakarta.json.Json; 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; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.CredentialsNamespaces.W3C_VC_NS; +import static org.eclipse.tractusx.edc.iam.ssi.spi.jsonld.W3cVcContext.W3C_VC_CONTEXT; /** * Test helpers for processing Json-Ld. @@ -63,7 +61,7 @@ public void setupModule(SetupContext context) { public static JsonObject compact(JsonObject json) { try { var document = JsonDocument.of(json); - var jsonFactory = createBuilderFactory(Map.of()); + var jsonFactory = Json.createBuilderFactory(Map.of()); var contextDocument = JsonDocument.of(jsonFactory.createObjectBuilder().build()); return com.apicatalog.jsonld.JsonLd.compact(document, contextDocument).get(); } catch (JsonLdError e) { @@ -72,7 +70,7 @@ public static JsonObject compact(JsonObject json) { } /** - * Expands the document using the provided cache for resolving referenced contexts. The {@link PolicyNamespaces#W3C_VC_NS} context is implicitly added to the cache. + * Expands the document using the provided cache for resolving referenced contexts. The {@link CredentialsNamespaces#W3C_VC_NS} context is implicitly added to the cache. */ public static JsonObject expand(JsonObject json, Map contextCache) { var map = new HashMap<>(contextCache); @@ -84,7 +82,7 @@ public static JsonObject expand(JsonObject json, Map contextCach if (expanded.size() > 0) { return expanded.getJsonObject(0); } - return createObjectBuilder().build(); + return Json.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/summary/SummaryContext.java b/spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/SummaryContext.java similarity index 96% rename from edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryContext.java rename to spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/SummaryContext.java index 1d57abc07..245d96722 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryContext.java +++ b/spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/SummaryContext.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.tractusx.edc.policy.cx.summary; +package org.eclipse.tractusx.edc.iam.ssi.spi.jsonld; /** * Defines the summary context. diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryCredential.java b/spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/SummaryCredential.java similarity index 97% rename from edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryCredential.java rename to spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/SummaryCredential.java index d58ba3b04..7f190449b 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/summary/SummaryCredential.java +++ b/spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/SummaryCredential.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.tractusx.edc.policy.cx.summary; +package org.eclipse.tractusx.edc.iam.ssi.spi.jsonld; /** * Sample summary credential. diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/W3cVcContext.java b/spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/W3cVcContext.java similarity index 99% rename from edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/W3cVcContext.java rename to spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/W3cVcContext.java index e61660052..f84193b7e 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/fixtures/W3cVcContext.java +++ b/spi/ssi-spi/src/testFixtures/java/org/eclipse/tractusx/edc/iam/ssi/spi/jsonld/W3cVcContext.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.tractusx.edc.policy.cx.fixtures; +package org.eclipse.tractusx.edc.iam.ssi.spi.jsonld; /** * Local copy of the W3C VC data model context for testing, obtained from {@code https://www.w3.org/ns/credentials/v2}.