From 51626fd8ab6bc15333c3db3a4ff1dce12e7b8232 Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Wed, 19 Nov 2025 17:16:03 +0100 Subject: [PATCH 001/112] infra: Update to jre lts version 25 (#2400) * Update to jre lts version 25 Signed-off-by: Lars Geyer-Blaumeiser * Update references to java versions Signed-off-by: Lars Geyer-Blaumeiser --------- Signed-off-by: Lars Geyer-Blaumeiser --- .github/actions/setup-java/action.yml | 8 ++++---- .../edc-controlplane-postgresql-hashicorp-vault/notice.md | 4 ++-- edc-controlplane/edc-runtime-memory/notice.md | 2 +- edc-dataplane/edc-dataplane-hashicorp-vault/notice.md | 4 ++-- edc-tests/runtime/mock-connector/notice.md | 2 +- resources/Dockerfile | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/actions/setup-java/action.yml b/.github/actions/setup-java/action.yml index 4af08fc008..6220c98509 100644 --- a/.github/actions/setup-java/action.yml +++ b/.github/actions/setup-java/action.yml @@ -20,15 +20,15 @@ --- -name: "Setup JDK 17" -description: "Setup JDK 17" +name: "Setup JDK" +description: "Setup JDK" runs: using: "composite" steps: - - name: Setup JDK 17 + - name: Setup JDK 21 uses: actions/setup-java@v5.0.0 with: - java-version: '17' + java-version: '21' distribution: 'temurin' - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/notice.md b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/notice.md index 8082e841da..7c31b58051 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/notice.md +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/notice.md @@ -15,7 +15,7 @@ Eclipse Tractus-X product(s) installed within the image: ## Used base image -- [eclipse-temurin:21.0.2_13-jre-alpine](https://github.com/adoptium/containers) +- [eclipse-temurin:25.0.1_8-jre-alpine](https://github.com/adoptium/containers) - Official Eclipse Temurin DockerHub page: - Eclipse Temurin Project: - Additional information about the Eclipse Temurin @@ -23,7 +23,7 @@ Eclipse Tractus-X product(s) installed within the image: ## Third-Party Software -- OpenTelemetry Agent v1.32.0: +- OpenTelemetry Agent v.2.21.0: As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc from the base distribution, along with any direct or indirect dependencies of the primary software being contained). diff --git a/edc-controlplane/edc-runtime-memory/notice.md b/edc-controlplane/edc-runtime-memory/notice.md index 969ecd3e42..ee549a4052 100644 --- a/edc-controlplane/edc-runtime-memory/notice.md +++ b/edc-controlplane/edc-runtime-memory/notice.md @@ -15,7 +15,7 @@ Eclipse Tractus-X product(s) installed within the image: ## Used base image -- [eclipse-temurin:21.0.2_13-jre-alpine](https://github.com/adoptium/containers) +- [eclipse-temurin:25.0.1_8-jre-alpine](https://github.com/adoptium/containers) - Official Eclipse Temurin DockerHub page: - Eclipse Temurin Project: - Additional information about the Eclipse Temurin diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/notice.md b/edc-dataplane/edc-dataplane-hashicorp-vault/notice.md index f7ca02b02a..c76c0368a3 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/notice.md +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/notice.md @@ -15,7 +15,7 @@ Eclipse Tractus-X product(s) installed within the image: ## Used base image -- [eclipse-temurin:21.0.2_13-jre-alpine](https://github.com/adoptium/containers) +- [eclipse-temurin:25.0.1_8-jre-alpine](https://github.com/adoptium/containers) - Official Eclipse Temurin DockerHub page: - Eclipse Temurin Project: - Additional information about the Eclipse Temurin @@ -23,7 +23,7 @@ Eclipse Tractus-X product(s) installed within the image: ## Third-Party Software -- OpenTelemetry Agent v1.32.0: +- OpenTelemetry Agent v2.21.0: As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc from the base distribution, along with any direct or indirect dependencies of the primary software being contained). diff --git a/edc-tests/runtime/mock-connector/notice.md b/edc-tests/runtime/mock-connector/notice.md index a57605c570..1bfe0a6234 100644 --- a/edc-tests/runtime/mock-connector/notice.md +++ b/edc-tests/runtime/mock-connector/notice.md @@ -15,7 +15,7 @@ Eclipse Tractus-X product(s) installed within the image: ## Used base image -- [eclipse-temurin:22.0.1_8-jre-alpine](https://github.com/adoptium/containers) +- [eclipse-temurin:25.0.1_8-jre-alpine](https://github.com/adoptium/containers) - Official Eclipse Temurin DockerHub page: - Eclipse Temurin Project: - Additional information about the Eclipse Temurin diff --git a/resources/Dockerfile b/resources/Dockerfile index bc3268e709..8de95a3c34 100644 --- a/resources/Dockerfile +++ b/resources/Dockerfile @@ -19,7 +19,7 @@ # SPDX-License-Identifier: Apache-2.0 ################################################################################# -FROM eclipse-temurin:24.0.2_12-jre-alpine +FROM eclipse-temurin:25.0.1_8-jre-alpine ARG JAR ARG OTEL_JAR ARG ADDITIONAL_FILES From 88b019e018217b023db28c53408a0ceee4c5603e Mon Sep 17 00:00:00 2001 From: Ronja Quensel <72978761+ronjaquensel@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:16:55 +0100 Subject: [PATCH 002/112] fix: consider inForceDate constraint in legacy check (#2403) --- .../edc/policy/cx/validator/LegacyPolicyCheck.java | 9 ++++++--- .../edc/policy/cx/validator/LegacyPolicyCheckTest.java | 10 ++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/LegacyPolicyCheck.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/LegacyPolicyCheck.java index 2dd80e09cd..7af82ec2ca 100644 --- a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/LegacyPolicyCheck.java +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/LegacyPolicyCheck.java @@ -30,12 +30,13 @@ public class LegacyPolicyCheck { private static final String BPN_LEGACY_LEFT_OPERAND = "https://w3id.org/tractusx/v0.0.1/ns/BusinessPartnerNumber"; private static final String BPN_GROUP_LEGACY_LEFT_OPERAND = "https://w3id.org/tractusx/v0.0.1/ns/BusinessPartnerGroup"; - + private static final String IN_FORCE_DATE_LEGACY_LEFT_OPERAND = "https://w3id.org/edc/v0.0.1/ns/inForceDate"; + private LegacyPolicyCheck() {} /** * Checks whether a policy is a legacy policy. Returns true, if the legacy namespace is found - * or a legacy BPN constraint, which utilizes a different namespace. + * or a legacy BPN or inForceDate constraint, which utilizes a different namespace. * * @param policy the policy * @return true, if any legacy reference is encountered; false otherwise @@ -46,7 +47,9 @@ public static boolean isLegacy(JsonObject policy) { if (json.contains(CX_POLICY_NS)) { return true; } else { - return json.contains(BPN_LEGACY_LEFT_OPERAND) || json.contains(BPN_GROUP_LEGACY_LEFT_OPERAND); + return json.contains(BPN_LEGACY_LEFT_OPERAND) || + json.contains(BPN_GROUP_LEGACY_LEFT_OPERAND) || + json.contains(IN_FORCE_DATE_LEGACY_LEFT_OPERAND); } } diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/validator/LegacyPolicyCheckTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/validator/LegacyPolicyCheckTest.java index 502be27a36..f9f49c46b9 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/validator/LegacyPolicyCheckTest.java +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/validator/LegacyPolicyCheckTest.java @@ -71,4 +71,14 @@ void legacyBpnGroupConstraint_shouldReturnTrue() { assertThat(result).isTrue(); } + + @Test + void legacyInForceDateConstraint_shouldReturnTrue() { + var permission = rule(ACTION_USAGE, atomicConstraint("https://w3id.org/edc/v0.0.1/ns/inForceDate")); + var policy = policy(ODRL_PERMISSION_ATTRIBUTE, permission); + + var result = LegacyPolicyCheck.isLegacy(policy); + + assertThat(result).isTrue(); + } } From 63a881a994fe4e9d74c5232c261b99c6be56fffb Mon Sep 17 00:00:00 2001 From: Andrii Yurkevych Date: Fri, 21 Nov 2025 13:39:22 +0100 Subject: [PATCH 003/112] feat: remove dependency json-schema-validator (#2386) * feat: remove dependency json-schema-validator * feat: move to wiremock for iatp-tests --- build.gradle.kts | 6 -- edc-extensions/cx-policy/build.gradle.kts | 3 - edc-tests/e2e-fixtures/build.gradle.kts | 1 + .../tests/transfer/ConsumerPullBaseTest.java | 45 ++++----- edc-tests/e2e/dcp-tck-tests/build.gradle.kts | 8 -- edc-tests/e2e/iatp-tests/build.gradle.kts | 2 +- .../AbstractIatpConsumerPullTest.java | 33 ++++--- .../tests/transfer/CredentialSpoofTest.java | 38 ++++---- .../tests/transfer/DimConsumerPullTest.java | 25 +++-- .../extension/DidServerExtension.java | 17 ++-- .../iatp/dispatchers/DimDispatcher.java | 92 ++++++++++++------- .../transfer/TransferPullEndToEndTest.java | 70 ++++++-------- gradle/libs.versions.toml | 2 + 13 files changed, 175 insertions(+), 167 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c32c2b0790..6a5e77ab8c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -82,12 +82,6 @@ allprojects { implementation("io.netty:netty-codec-http2:4.2.7.Final") { because("Version 4.1.123.Final vulnerability: https://www.cve.org/CVERecord?id=CVE-2025-8916") } - testImplementation("com.networknt:json-schema-validator:1.5.9") { - because("There's a conflict between mockserver-netty and identity-hub dependencies for testing, forcing json-schema-validator to 1.5.6 is solving that.") - } - testFixturesApi("com.networknt:json-schema-validator:1.5.9") { - because("There's a conflict between mockserver-netty and identity-hub dependencies for testing, forcing json-schema-validator to 1.5.6 is solving that.") - } } } diff --git a/edc-extensions/cx-policy/build.gradle.kts b/edc-extensions/cx-policy/build.gradle.kts index e57f82b063..1cd2385ebf 100644 --- a/edc-extensions/cx-policy/build.gradle.kts +++ b/edc-extensions/cx-policy/build.gradle.kts @@ -43,9 +43,6 @@ dependencies { implementation(libs.edc.lib.validator) testImplementation(libs.edc.junit) - implementation("com.networknt:json-schema-validator:1.5.9") { - because("There's a conflict between mockserver-netty and identity-hub dependencies for testing, forcing json-schema-validator to 1.5.9 is solving that.") - } testFixturesImplementation(libs.edc.junit) testFixturesImplementation(libs.edc.spi.jsonld) } diff --git a/edc-tests/e2e-fixtures/build.gradle.kts b/edc-tests/e2e-fixtures/build.gradle.kts index 099f6bf509..c7243d0c3b 100644 --- a/edc-tests/e2e-fixtures/build.gradle.kts +++ b/edc-tests/e2e-fixtures/build.gradle.kts @@ -59,6 +59,7 @@ dependencies { testFixturesApi(libs.testcontainers.minio) testFixturesApi(libs.testcontainers.localstack) testFixturesApi(libs.testcontainers.postgres) + testFixturesApi(libs.wiremock) } edcBuild { diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/transfer/ConsumerPullBaseTest.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/transfer/ConsumerPullBaseTest.java index 028716068f..4360c54e3e 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/transfer/ConsumerPullBaseTest.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/transfer/ConsumerPullBaseTest.java @@ -19,29 +19,32 @@ package org.eclipse.tractusx.edc.tests.transfer; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import jakarta.json.JsonObject; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; import org.eclipse.tractusx.edc.tests.ParticipantAwareTest; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.verify.VerificationTimes; +import org.junit.jupiter.api.extension.RegisterExtension; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static jakarta.json.Json.createObjectBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.awaitility.pollinterval.FibonacciPollInterval.fibonacci; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; -import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.bpnPolicy; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; /** * Base tests for Http PULL scenario @@ -50,13 +53,15 @@ public abstract class ConsumerPullBaseTest implements ParticipantAwareTest { public static final String MOCK_BACKEND_REMOTE_HOST = "localhost"; public static final String MOCK_BACKEND_PATH = "/mock/api"; - protected ClientAndServer server; + @RegisterExtension + protected static WireMockExtension server = WireMockExtension.newInstance() + .options(wireMockConfig().bindAddress(MOCK_BACKEND_REMOTE_HOST).dynamicPort()) + .build(); protected String privateBackendUrl; @BeforeEach void setup() { - server = ClientAndServer.startClientAndServer(MOCK_BACKEND_REMOTE_HOST, getFreePort()); privateBackendUrl = "http://%s:%d%s".formatted(MOCK_BACKEND_REMOTE_HOST, server.getPort(), MOCK_BACKEND_PATH); } @@ -89,7 +94,7 @@ void transferData_privateBackend() { }); // wait until EDC is available on the consumer side - server.when(request().withMethod("GET").withPath(MOCK_BACKEND_PATH)).respond(response().withStatusCode(200).withBody("test response")); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(ok("test response"))); await().pollInterval(fibonacci()) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { @@ -101,12 +106,9 @@ void transferData_privateBackend() { // Prov-DP -> Prov-backend assertThat(consumer().data().pullData(edr.get(), Map.of())).isEqualTo("test response"); - server.verify(request() - .withPath(MOCK_BACKEND_PATH) - .withHeader("Edc-Contract-Agreement-Id") - .withHeader("Edc-Bpn", consumer().getBpn()) - .withMethod("GET"), VerificationTimes.exactly(1)); - + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH)) + .withHeader("Edc-Bpn", equalTo(consumer().getBpn())) + .withHeader("Edc-Contract-Agreement-Id", matching(".+"))); } @Test @@ -138,7 +140,7 @@ void transferData_privateBackend_withConsumerDataPlane() { }); // wait until EDC is available on the consumer side - server.when(request().withMethod("GET").withPath(MOCK_BACKEND_PATH)).respond(response().withStatusCode(200).withBody("test response")); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(ok("test response"))); await().pollInterval(fibonacci()) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { @@ -151,11 +153,7 @@ void transferData_privateBackend_withConsumerDataPlane() { //Consumer-DP -> Prov-DP -> Prov-backend assertThat(consumer().dataPlane().pullData(Map.of("transferProcessId", transferProcessId))).isEqualTo("test response"); - server.verify(request() - .withPath(MOCK_BACKEND_PATH) - .withHeader("Edc-Contract-Agreement-Id") - .withHeader("Edc-Bpn", consumer().getBpn()) - .withMethod("GET"), VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH)).withHeader("Edc-Bpn", equalTo(consumer().getBpn())).withHeader("Edc-Contract-Agreement-Id", matching(".+"))); } protected JsonObject httpDataDestination() { @@ -168,11 +166,6 @@ protected JsonObject httpDataDestination() { .build(); } - @AfterEach - void teardown() { - server.stop(); - } - protected JsonObject createAccessPolicy(String bpn) { return bpnPolicy(bpn); } diff --git a/edc-tests/e2e/dcp-tck-tests/build.gradle.kts b/edc-tests/e2e/dcp-tck-tests/build.gradle.kts index 23210036ed..74d586c490 100644 --- a/edc-tests/e2e/dcp-tck-tests/build.gradle.kts +++ b/edc-tests/e2e/dcp-tck-tests/build.gradle.kts @@ -22,14 +22,6 @@ plugins { } dependencies { - - constraints { - // netty's mockserver depends on an older version of the json schema validator, but TCK needs this: - implementation("com.networknt:json-schema-validator:1.5.9") { - because("This version is required by the TCK") - } - } - testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) testRuntimeOnly(libs.dcp.testcases) testImplementation(libs.edc.junit) diff --git a/edc-tests/e2e/iatp-tests/build.gradle.kts b/edc-tests/e2e/iatp-tests/build.gradle.kts index 3ff0cc3530..89087872ef 100644 --- a/edc-tests/e2e/iatp-tests/build.gradle.kts +++ b/edc-tests/e2e/iatp-tests/build.gradle.kts @@ -35,7 +35,7 @@ dependencies { testImplementation(libs.edc.sts.core) testRuntimeOnly(libs.edc.transaction.local) - testImplementation(libs.netty.mockserver) + testImplementation(libs.wiremock) testImplementation(libs.restAssured) testImplementation(libs.awaitility) testImplementation(libs.bouncyCastle.bcpkixJdk18on) diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java index b475f46d03..b7d69958e9 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.edc.tests.transfer; +import com.github.tomakehurst.wiremock.WireMockServer; import jakarta.json.Json; import jakarta.json.JsonObject; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; @@ -42,7 +43,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; -import org.mockserver.verify.VerificationTimes; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -50,6 +50,15 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.matching; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.awaitility.pollinterval.FibonacciPollInterval.fibonacci; @@ -57,9 +66,6 @@ import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_2025_09_NS; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkPolicy; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; -import static org.mockserver.integration.ClientAndServer.startClientAndServer; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; public abstract class AbstractIatpConsumerPullTest extends ConsumerPullBaseTest { @@ -106,7 +112,7 @@ void transferData_whenContractPolicyFulfilled(JsonObject contractPolicy, String }); // wait until EDC is available on the consumer side - server.when(request().withMethod("GET").withPath(MOCK_BACKEND_PATH)).respond(response().withStatusCode(200).withBody("test response")); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(ok("test response"))); await().pollInterval(fibonacci()) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { @@ -118,11 +124,8 @@ void transferData_whenContractPolicyFulfilled(JsonObject contractPolicy, String // Prov-DP -> Prov-backend assertThat(consumer().data().pullData(edr.get(), Map.of())).isEqualTo("test response"); - server.verify(request() - .withPath(MOCK_BACKEND_PATH) - .withHeader("Edc-Contract-Agreement-Id") - .withHeader("Edc-Bpn", consumer().getBpn()) - .withMethod("GET"), VerificationTimes.exactly(1)); + + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH)).withHeader("Edc-Bpn", equalTo(consumer().getBpn())).withHeader("Edc-Contract-Agreement-Id", matching(".+"))); } // TODO: Add test for transfer process with a contract policy that is not fulfilled @@ -226,10 +229,15 @@ void catalogRequest_whenCredentialRevoked() { .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); // return a StatusListCredential, where the credential's status is "revocation" - try (var revocationServer = startClientAndServer(port)) { + WireMockServer revocationServer = new WireMockServer(options().port(port)); + try { + revocationServer.start(); + var slCred = StatusList2021.create(dataspaceIssuer().didUrl(), "revocation") .withStatus(12345, true); - revocationServer.when(request().withPath("/status/list/7")).respond(response().withBody(slCred.toJsonObject().toString())); + + revocationServer.stubFor(post(urlPathEqualTo("/status/list/7")).willReturn(aResponse().withStatus(200) + .withBody(slCred.toJsonObject().toString()))); // verify the failed catalog request consumer().getCatalog(provider()) @@ -238,6 +246,7 @@ void catalogRequest_whenCredentialRevoked() { } finally { // restore the original credential store.update(existingCred); + revocationServer.stop(); } } diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java index bd983ef4f8..f666348f7c 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.tests.transfer; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import jakarta.json.JsonObject; import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.iam.did.spi.document.DidDocument; @@ -39,19 +40,21 @@ import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.DataspaceIssuer; import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.IatpParticipant; import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.StsParticipant; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.integration.ClientAndServer; import java.net.URI; import java.util.List; import java.util.Map; import java.util.function.Function; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_BPN; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; @@ -60,8 +63,6 @@ import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.bpnPolicy; import static org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.Runtimes.iatpRuntime; import static org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.Runtimes.stsRuntime; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; @EndToEndTest public class CredentialSpoofTest { @@ -97,7 +98,10 @@ public class CredentialSpoofTest { () -> STS.stsConfig(CONSUMER, PROVIDER, MALICIOUS_ACTOR).merge(BDRS_SERVER_EXTENSION.getConfig())); private static final Integer MOCKED_CS_SERVICE_PORT = getFreePort(); - protected ClientAndServer server; + @RegisterExtension + protected static WireMockExtension server = WireMockExtension.newInstance() + .options(wireMockConfig().bindAddress("localhost").port(MOCKED_CS_SERVICE_PORT)) + .build(); private static IatpParticipant participant(String name, String bpn) { return IatpParticipant.Builder.newInstance().name(name).id(bpn) @@ -123,18 +127,11 @@ static void beforeAll() { @BeforeEach void setup() { - server = ClientAndServer.startClientAndServer("localhost", getFreePort(), MOCKED_CS_SERVICE_PORT); - CONSUMER.configureParticipant(DATASPACE_ISSUER_PARTICIPANT, CONSUMER_RUNTIME, STS_RUNTIME); PROVIDER.configureParticipant(DATASPACE_ISSUER_PARTICIPANT, PROVIDER_RUNTIME, STS_RUNTIME); MALICIOUS_ACTOR.configureParticipant(DATASPACE_ISSUER_PARTICIPANT, MALICIOUS_ACTOR_RUNTIME, STS_RUNTIME); } - @AfterEach - void shutdown() { - server.stop(); - } - @Test @DisplayName("Malicious actor should not impersonate a consumer by creating a VP with the consumer membership credential") void shouldNotImpersonateConsumer_withWrappedConsumerCredential() { @@ -212,15 +209,18 @@ void withMock(Function transformerRegistry.transform(p, JsonObject.class)) + .compose(jsonLd::compact) + .orElseThrow(f -> new EdcException(f.getFailureDetail())); - server.when(request().withMethod("POST").withPath("/presentations/query")).respond((request -> { - var json = response.apply(sokratesMembershipCredential) - .compose(presentation -> transformerRegistry.transform(presentation, JsonObject.class)) - .compose(jsonLd::compact) - .orElseThrow(failure -> new EdcException(failure.getFailureDetail())); - return response().withStatusCode(200).withBody(json.toString()); - })); + server.stubFor(post(urlPathEqualTo("/presentations/query")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(json.toString()))); } protected JsonObject createAccessPolicy(String bpn) { diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DimConsumerPullTest.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DimConsumerPullTest.java index 14b7f07fa2..990067c205 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DimConsumerPullTest.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DimConsumerPullTest.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.tests.transfer; +import com.github.tomakehurst.wiremock.WireMockServer; import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.iam.identitytrust.sts.service.EmbeddedSecureTokenService; import org.eclipse.edc.iam.identitytrust.sts.spi.model.StsAccount; @@ -44,14 +45,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.model.HttpResponse; import java.net.URI; import java.time.Clock; import java.util.Map; import java.util.UUID; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_BPN; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; @@ -62,7 +66,6 @@ import static org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.Runtimes.dimRuntime; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.mockserver.model.HttpRequest.request; @EndToEndTest public class DimConsumerPullTest extends AbstractIatpConsumerPullTest { @@ -107,8 +110,8 @@ public class DimConsumerPullTest extends AbstractIatpConsumerPullTest { () -> PROVIDER.iatpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); private static final TypeManager MAPPER = new JacksonTypeManager(); - private static ClientAndServer oauthServer; - private static ClientAndServer dimServer; + private static WireMockServer oauthServer; + private static WireMockServer dimServer; @BeforeAll static void prepare() { @@ -127,13 +130,15 @@ static void prepare() { PROVIDER.getDid(), tokenServiceFor(providerTokenGeneration, PROVIDER)); var stsUri = STS.stsUri().get(); - oauthServer = ClientAndServer.startClientAndServer(stsUri.getPort()); - oauthServer.when(request().withMethod("POST").withPath(stsUri.getPath() + "/token")) - .respond(HttpResponse.response(MAPPER.writeValueAsString(Map.of("access_token", "token")))); + oauthServer = new WireMockServer(options().port(stsUri.getPort())); + oauthServer.start(); + oauthServer.stubFor(post(urlPathEqualTo(stsUri.getPath() + "/token")).willReturn(aResponse().withStatus(200) + .withBody(MAPPER.writeValueAsString(Map.of("access_token", "token"))))); - dimServer = ClientAndServer.startClientAndServer(DIM_URI.get().getPort()); - dimServer.when(request().withMethod("POST")).respond(new DimDispatcher(generatorServices)); + dimServer = new WireMockServer(options().port(DIM_URI.get().getPort()).extensions(new DimDispatcher(generatorServices))); + dimServer.start(); + dimServer.stubFor(post(anyUrl()).willReturn(aResponse().withTransformers("dim-dispatcher"))); CONSUMER.setJsonLd(CONSUMER_RUNTIME.getService(JsonLd.class)); } diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java index 902825cd09..e80aa0d284 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java @@ -19,18 +19,20 @@ package org.eclipse.tractusx.edc.tests.transfer.extension; +import com.github.tomakehurst.wiremock.WireMockServer; import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.iam.did.spi.document.DidDocument; import org.eclipse.edc.util.io.Ports; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.model.HttpResponse; import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonProcessingException; import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; -import static org.mockserver.model.HttpRequest.request; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; /** * Centralized server for tests that exposes DIDs for participant. @@ -40,11 +42,12 @@ public class DidServerExtension implements BeforeAllCallback, AfterAllCallback { private final ObjectMapper objectMapper = new ObjectMapper(); private final LazySupplier port = new LazySupplier<>(Ports::getFreePort); - private ClientAndServer server; + private WireMockServer server; @Override public void beforeAll(ExtensionContext context) { - server = ClientAndServer.startClientAndServer(port.get()); + server = new WireMockServer(options().port(port.get())); + server.start(); } @Override @@ -56,8 +59,8 @@ public void afterAll(ExtensionContext context) { public DidServerExtension register(String name, DidDocument didDocument) { try { - server.when(request("/%s/.well-known/did.json".formatted(name.toLowerCase()))) - .respond(HttpResponse.response(objectMapper.writeValueAsString(didDocument))); + server.stubFor(get(urlPathEqualTo("/%s/.well-known/did.json".formatted(name.toLowerCase()))) + .willReturn(aResponse().withStatus(200).withBody(objectMapper.writeValueAsString(didDocument)))); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DimDispatcher.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DimDispatcher.java index 21ea1471a5..f6a7e8c05c 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DimDispatcher.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DimDispatcher.java @@ -19,13 +19,15 @@ package org.eclipse.tractusx.edc.tests.transfer.iatp.dispatchers; +import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2; +import com.github.tomakehurst.wiremock.http.HttpHeader; +import com.github.tomakehurst.wiremock.http.HttpHeaders; +import com.github.tomakehurst.wiremock.http.Response; +import com.github.tomakehurst.wiremock.stubbing.ServeEvent; import org.eclipse.edc.iam.identitytrust.sts.service.EmbeddedSecureTokenService; import org.eclipse.edc.json.JacksonTypeManager; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.types.TypeManager; -import org.mockserver.mock.action.ExpectationResponseCallback; -import org.mockserver.model.HttpRequest; -import org.mockserver.model.HttpResponse; import java.util.Collection; import java.util.Map; @@ -40,7 +42,7 @@ /** * Mock service for DIM interaction. Underlying it uses the {@link EmbeddedSecureTokenService} for generating SI tokens */ -public class DimDispatcher implements ExpectationResponseCallback { +public class DimDispatcher implements ResponseTransformerV2 { private static final TypeManager MAPPER = new JacksonTypeManager(); private final String path; @@ -56,26 +58,40 @@ public DimDispatcher(String path, Map secure } @Override - public HttpResponse handle(HttpRequest httpRequest) { - if (httpRequest.getPath().getValue().split("\\?")[0].equals(path)) { + public String getName() { + return "dim-dispatcher"; + } - var body = MAPPER.readValue(httpRequest.getBody().getRawBytes(), Map.class); + @Override + public Response transform(Response response, ServeEvent serveEvent) { + var request = serveEvent.getRequest(); + var reqPathOnly = request.getUrl().split("\\?")[0]; + if (!reqPathOnly.equals(path)) { + return notFound(response); + } - var grant = Optional.ofNullable(body.get("grantAccess")) - .map((payload) -> grantAccessHandler((Map) payload)); + Map body = MAPPER.readValue(request.getBody(), Map.class); - var sign = Optional.ofNullable(body.get("signToken")) - .map((payload) -> signTokenHandler((Map) payload)); + Optional grant = Optional.ofNullable(body.get("grantAccess")) + .map(payload -> grantAccessHandler((Map) payload, response)); - return grant.or(() -> sign).orElse(HttpResponse.response().withStatusCode(404)); - } - return HttpResponse.response().withStatusCode(404); + Optional sign = Optional.ofNullable(body.get("signToken")) + .map(payload -> signTokenHandler((Map) payload, response)); + + return grant.or(() -> sign).orElse(notFound(response)); + } + + @Override + public boolean applyGlobally() { + return false; } @SuppressWarnings("unchecked") - private HttpResponse grantAccessHandler(Map params) { - var issuer = params.get("consumerDid").toString(); - var audience = params.get("providerDid").toString(); + private Response grantAccessHandler(Map params, Response base) { + var issuer = String.valueOf(params.get("consumerDid")); + var audience = String.valueOf(params.get("providerDid")); + + @SuppressWarnings("unchecked") Collection scopes = (Collection) params.get("credentialTypes"); var scope = scopes.stream().map("org.eclipse.tractusx.vc.type:%s:read"::formatted).collect(Collectors.joining(" ")); var claims = Map.of(ISSUER, issuer, SUBJECT, issuer, AUDIENCE, audience); @@ -83,28 +99,42 @@ private HttpResponse grantAccessHandler(Map params) { var sts = secureTokenServices.get(issuer); var token = sts.createToken(issuer, claims, scope) .map(TokenRepresentation::getToken) - .orElseThrow(failure -> new RuntimeException(failure.getFailureDetail())); + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); - return HttpResponse.response(MAPPER.writeValueAsString(Map.of("jwt", token))); + return jsonOk(base, Map.of("jwt", token)); } - private HttpResponse signTokenHandler(Map params) { - var subject = params.get("subject").toString(); - var accessToken = params.get("token").toString(); - var audience = params.get("audience").toString(); - var issuer = params.get("issuer").toString(); + private Response signTokenHandler(Map params, Response base) { + var subject = String.valueOf(params.get("subject")); + var accessToken = String.valueOf(params.get("token")); + var audience = String.valueOf(params.get("audience")); + var issuer = String.valueOf(params.get("issuer")); - var claims = Map.of( - ISSUER, issuer, - SUBJECT, subject, - AUDIENCE, audience, - PRESENTATION_TOKEN_CLAIM, accessToken); + var claims = Map.of(ISSUER, issuer, SUBJECT, subject, AUDIENCE, audience, PRESENTATION_TOKEN_CLAIM, accessToken); var sts = secureTokenServices.get(issuer); var token = sts.createToken(issuer, claims, null) .map(TokenRepresentation::getToken) - .orElseThrow(failure -> new RuntimeException(failure.getFailureDetail())); + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); + + return jsonOk(base, Map.of("jwt", token)); + } + + private Response jsonOk(Response base, Object payload) { + var json = MAPPER.writeValueAsString(payload); + return Response.Builder.like(base) + .but() + .status(200) + .headers(new HttpHeaders(HttpHeader.httpHeader("Content-Type", "application/json"))) + .body(json) + .build(); + } - return HttpResponse.response(MAPPER.writeValueAsString(Map.of("jwt", token))); + private Response notFound(Response base) { + return Response.Builder.like(base) + .but() + .status(404) + .body("") + .build(); } } diff --git a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPullEndToEndTest.java b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPullEndToEndTest.java index fa2400117f..c38e773991 100644 --- a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPullEndToEndTest.java +++ b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPullEndToEndTest.java @@ -34,10 +34,15 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.mockserver.model.HttpStatusCode; -import org.mockserver.verify.VerificationTimes; import java.util.Map; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.moreThanOrExactly; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_BPN; @@ -51,9 +56,6 @@ import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_NAME; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.inForceDateUsagePolicy; import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; -import static org.mockserver.verify.VerificationTimes.atLeast; @EndToEndTest public class TransferPullEndToEndTest { @@ -85,8 +87,6 @@ public TractusxParticipantBase consumer() { void transferData_withSuspendResume() { var assetId = "api-asset-1"; - var requestDefinition = request().withMethod("GET").withPath(MOCK_BACKEND_PATH); - Map dataAddress = Map.of( "baseUrl", privateBackendUrl, "type", "HttpData", @@ -106,7 +106,7 @@ void transferData_withSuspendResume() { CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); // wait until EDC is available on the consumer side - server.when(requestDefinition).respond(response().withStatusCode(200).withBody("test response")); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(ok("test response"))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); @@ -114,7 +114,7 @@ void transferData_withSuspendResume() { var data = CONSUMER.data().pullData(edr, Map.of()); assertThat(data).isNotNull().isEqualTo("test response"); - server.verify(requestDefinition, VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); CONSUMER.suspendTransfer(transferProcessId, "reason"); CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.SUSPENDED); @@ -122,7 +122,7 @@ void transferData_withSuspendResume() { // consumer cannot fetch data with the prev token (suspended) await().untilAsserted(() -> { CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(403); - server.verify(requestDefinition, atLeast(1)); + server.verify(moreThanOrExactly(1), getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); }); CONSUMER.resumeTransfer(transferProcessId); @@ -134,19 +134,17 @@ void transferData_withSuspendResume() { data = CONSUMER.data().pullData(newEdr, Map.of()); assertThat(data).isNotNull().isEqualTo("test response"); - server.verify(requestDefinition, VerificationTimes.atLeast(2)); + server.verify(moreThanOrExactly(2), getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); // consumer cannot fetch data with the prev token (suspended) after the transfer process has been resumed CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(403); - server.verify(requestDefinition, VerificationTimes.atLeast(2)); + server.verify(moreThanOrExactly(2), getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } @Test void transferData_withTerminate() { var assetId = "api-asset-1"; - var requestDefinition = request().withMethod("GET").withPath(MOCK_BACKEND_PATH); - Map dataAddress = Map.of( "baseUrl", privateBackendUrl, "type", "HttpData", @@ -166,7 +164,7 @@ void transferData_withTerminate() { CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); // wait until EDC is available on the consumer side - server.when(requestDefinition).respond(response().withStatusCode(200).withBody("test response")); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(ok("test response"))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); @@ -174,13 +172,13 @@ void transferData_withTerminate() { var data = CONSUMER.data().pullData(edr, Map.of()); assertThat(data).isNotNull().isEqualTo("test response"); - server.verify(requestDefinition, VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.TERMINATED); // consumer cannot fetch data with the prev token (suspended) var body = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(403).extract().body().asString(); - server.verify(requestDefinition, VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } protected JsonObject inForcePolicy() { @@ -192,8 +190,6 @@ protected JsonObject inForcePolicy() { void transferData_successful_notReturnOriginalSourceResponseCode_withTerminate() { var assetId = "api-asset-1"; - var requestDefinition = request().withMethod("GET").withPath(MOCK_BACKEND_PATH); - Map dataAddress = Map.of( "baseUrl", privateBackendUrl, "type", "HttpData", @@ -213,8 +209,7 @@ void transferData_successful_notReturnOriginalSourceResponseCode_withTerminate() CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); // wait until EDC is available on the consumer side - server.when(requestDefinition).respond(response().withStatusCode(HttpStatusCode.CREATED_201.code()).withBody("test response") - .withHeader("to-be-returned", "false")); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(aResponse().withStatus(201).withBody("test response").withHeader("to-be-returned", "false"))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); @@ -225,15 +220,13 @@ void transferData_successful_notReturnOriginalSourceResponseCode_withTerminate() var data = response.extract().body().asString(); assertThat(data).isNotNull().isEqualTo("test response"); - server.verify(requestDefinition, VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } @Test void transferData_unsuccessful_notReturnOriginalSourceResponseCode_withTerminate() { var assetId = "api-asset-1"; - var requestDefinition = request().withMethod("GET").withPath(MOCK_BACKEND_PATH); - Map dataAddress = Map.of( "baseUrl", privateBackendUrl, "type", "HttpData", @@ -253,21 +246,19 @@ void transferData_unsuccessful_notReturnOriginalSourceResponseCode_withTerminate CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); // wait until EDC is available on the consumer side - server.when(requestDefinition).respond(response().withStatusCode(HttpStatusCode.EXPECTATION_FAILED_417.code())); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(aResponse().withStatus(417))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(HttpStatusCode.INTERNAL_SERVER_ERROR_500.code()); - server.verify(requestDefinition, VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } @Test void transferData_success_withProxyOriginalResponse() { var assetId = "api-asset-proxy-1"; - var requestDefinition = request().withMethod("GET").withPath(MOCK_BACKEND_PATH); - Map dataAddress = Map.of( "name", "transfer-test", "baseUrl", privateBackendUrl, @@ -288,8 +279,7 @@ void transferData_success_withProxyOriginalResponse() { CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); // wait until EDC is available on the consumer side - server.when(requestDefinition).respond(response().withStatusCode(HttpStatusCode.CREATED_201.code()).withBody("test created") - .withHeader("to-be-returned", "true")); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(aResponse().withStatus(201).withBody("test created").withHeader("to-be-returned", "true"))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); @@ -301,15 +291,13 @@ void transferData_success_withProxyOriginalResponse() { var data = response.extract().body().asString(); assertThat(data).isNotNull().isEqualTo("test created"); - server.verify(requestDefinition, VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } @Test void transferData_success_withProxyOriginalResponse_withoutSourceResponseBody() { var assetId = "api-asset-proxy-2"; - var requestDefinition = request().withMethod("GET").withPath(MOCK_BACKEND_PATH); - Map dataAddress = Map.of( "name", "transfer-test", "baseUrl", privateBackendUrl, @@ -330,8 +318,7 @@ void transferData_success_withProxyOriginalResponse_withoutSourceResponseBody() CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); // wait until EDC is available on the consumer side - server.when(requestDefinition).respond(response().withStatusCode(HttpStatusCode.NO_CONTENT_204.code()) - .withHeader("to-be-returned", "true")); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(aResponse().withStatus(204).withHeader("to-be-returned", "true"))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); @@ -343,15 +330,13 @@ void transferData_success_withProxyOriginalResponse_withoutSourceResponseBody() var data = response.extract().body().asString(); assertThat(data).isEmpty(); - server.verify(requestDefinition, VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } @Test void transferData_failing_withProxyOriginalResponse() { var assetId = "api-asset-proxy-3"; - var requestDefinition = request().withMethod("GET").withPath(MOCK_BACKEND_PATH); - Map dataAddress = Map.of( "name", "transfer-test", "baseUrl", privateBackendUrl, @@ -371,8 +356,7 @@ void transferData_failing_withProxyOriginalResponse() { CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); - server.when(requestDefinition).respond(response().withStatusCode(HttpStatusCode.EXPECTATION_FAILED_417.code()) - .withBody("test failed response")); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(aResponse().withStatus(417).withBody("test failed response"))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); @@ -381,15 +365,13 @@ void transferData_failing_withProxyOriginalResponse() { var data = response.extract().body().asString(); assertThat(data).isNotNull().isEqualTo("test failed response"); - server.verify(requestDefinition, VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } @Test void transferData_failing_withProxyOriginalResponse_withoutSourceResponseBody() { var assetId = "api-asset-proxy-4"; - var requestDefinition = request().withMethod("GET").withPath(MOCK_BACKEND_PATH); - Map dataAddress = Map.of( "name", "transfer-test", "baseUrl", privateBackendUrl, @@ -409,7 +391,7 @@ void transferData_failing_withProxyOriginalResponse_withoutSourceResponseBody() CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); - server.when(requestDefinition).respond(response().withStatusCode(HttpStatusCode.GATEWAY_TIMEOUT_504.code())); + server.stubFor(get(MOCK_BACKEND_PATH).willReturn(aResponse().withStatus(504))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); @@ -418,7 +400,7 @@ void transferData_failing_withProxyOriginalResponse_withoutSourceResponseBody() var data = response.extract().body().asString(); assertThat(data).isNotNull().isEmpty(); - server.verify(requestDefinition, VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7357a73dd9..94ccd8f158 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,7 @@ testcontainers = "1.21.3" testcontainers-keycloak = "3.9.0" titanium = "1.7.0" log4j2 = "2.25.2" +wiremock = "3.13.1" [libraries] @@ -220,6 +221,7 @@ testcontainers-minio = { module = "org.testcontainers:minio", version.ref = "tes testcontainers-localstack = { module = "org.testcontainers:localstack", version.ref = "testcontainers" } testcontainers-postgres = { module = "org.testcontainers:postgresql", version.ref = "testcontainers" } titaniumJsonLd = { module = "com.apicatalog:titanium-json-ld", version.ref = "titanium" } +wiremock = { module = "org.wiremock:wiremock-jetty12", version.ref = "wiremock" } log4j2-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j2" } log4j2-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j2" } From d471f75ed90e68496cd1784e67b9d393cc86ba7a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 08:45:10 +0100 Subject: [PATCH 004/112] chore(deps): bump actions/checkout (#2420) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/run-deployment-test/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/run-deployment-test/action.yml b/.github/actions/run-deployment-test/action.yml index 94661f8043..19b7e5a02f 100644 --- a/.github/actions/run-deployment-test/action.yml +++ b/.github/actions/run-deployment-test/action.yml @@ -48,7 +48,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java - uses: ./.github/actions/setup-helm From 6fe38f9c3631c9c2c9b3eb6ebfa33f3a4d3f9c54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 08:45:48 +0100 Subject: [PATCH 005/112] chore(deps): bump actions/checkout (#2419) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/publish-docker-image/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/publish-docker-image/action.yml b/.github/actions/publish-docker-image/action.yml index f707332ba7..6c549a730b 100644 --- a/.github/actions/publish-docker-image/action.yml +++ b/.github/actions/publish-docker-image/action.yml @@ -46,7 +46,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 ############################################### # Enable emulation for cross-arch builds From 577a95f07b3303a3c23fa1930a97b763a7f982bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 08:54:07 +0100 Subject: [PATCH 006/112] chore(deps): bump actions/checkout from 5 to 6 (#2418) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yaml | 2 +- .github/workflows/deployment-test.yaml | 4 ++-- .github/workflows/draft-release.yaml | 4 ++-- .../generate-and-publish-dependencies.yaml | 2 +- .github/workflows/helm-lint.yaml | 2 +- .github/workflows/kics.yml | 2 +- .github/workflows/publish-context.yaml | 2 +- .github/workflows/publish-new-snapshot.yaml | 4 ++-- .github/workflows/publish-openapi-ui.yml | 4 ++-- .github/workflows/release.yml | 10 +++++----- .github/workflows/secrets-scan.yml | 2 +- .github/workflows/trigger-docker-publish.yaml | 2 +- .github/workflows/trigger-maven-publish.yaml | 2 +- .github/workflows/trivy.yml | 4 ++-- .github/workflows/upgradeability-test.yaml | 2 +- .github/workflows/verify.yaml | 20 +++++++++---------- 16 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 432e9a8845..a15c7b81ad 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -55,7 +55,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/deployment-test.yaml b/.github/workflows/deployment-test.yaml index 149faeda57..b7c3534616 100644 --- a/.github/workflows/deployment-test.yaml +++ b/.github/workflows/deployment-test.yaml @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest needs: test-prepare steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/run-deployment-test name: "Run deployment test using KinD and Helm" with: @@ -70,7 +70,7 @@ jobs: "v1.31.9" ] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: ./.github/actions/run-deployment-test name: "Run deployment test using KinD and Helm" with: diff --git a/.github/workflows/draft-release.yaml b/.github/workflows/draft-release.yaml index babb01a497..2a9ef9b360 100644 --- a/.github/workflows/draft-release.yaml +++ b/.github/workflows/draft-release.yaml @@ -40,7 +40,7 @@ jobs: branch_name: ${{ steps.resolve_branch.outputs.branch_name }} is_official_release: ${{ steps.validation.outputs.is_official_release }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - id: validation @@ -106,7 +106,7 @@ jobs: packages: write pages: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Create Release branch run: git checkout -b ${{ needs.validate-and-prepare.outputs.branch_name }} - uses: ./.github/actions/setup-java diff --git a/.github/workflows/generate-and-publish-dependencies.yaml b/.github/workflows/generate-and-publish-dependencies.yaml index 3f6969a07e..2bbff80f70 100644 --- a/.github/workflows/generate-and-publish-dependencies.yaml +++ b/.github/workflows/generate-and-publish-dependencies.yaml @@ -33,7 +33,7 @@ jobs: check-dependencies: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java - uses: ./.github/actions/generate-and-check-dependencies - name: Prepare to publish diff --git a/.github/workflows/helm-lint.yaml b/.github/workflows/helm-lint.yaml index 30b27e2d2b..7cef5f3386 100644 --- a/.github/workflows/helm-lint.yaml +++ b/.github/workflows/helm-lint.yaml @@ -45,7 +45,7 @@ jobs: ############## ### Set-Up ### ############## - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: ./.github/actions/setup-helm diff --git a/.github/workflows/kics.yml b/.github/workflows/kics.yml index 8ce11efaa6..c01ab4b35d 100644 --- a/.github/workflows/kics.yml +++ b/.github/workflows/kics.yml @@ -41,7 +41,7 @@ jobs: security-events: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: KICS scan uses: checkmarx/kics-github-action@v2.1.15 diff --git a/.github/workflows/publish-context.yaml b/.github/workflows/publish-context.yaml index 2d04533684..e8f84038f4 100644 --- a/.github/workflows/publish-context.yaml +++ b/.github/workflows/publish-context.yaml @@ -34,7 +34,7 @@ jobs: contents: write pages: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: copy contexts into public folder run: | mkdir -p public/context diff --git a/.github/workflows/publish-new-snapshot.yaml b/.github/workflows/publish-new-snapshot.yaml index 77c6536ea6..b4f426da03 100644 --- a/.github/workflows/publish-new-snapshot.yaml +++ b/.github/workflows/publish-new-snapshot.yaml @@ -75,7 +75,7 @@ jobs: VERSION: ${{ steps.get-version.outputs.VERSION }} DATED: ${{ steps.get-version.outputs.DATED }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: "Get version" id: get-version run: | @@ -132,7 +132,7 @@ jobs: needs: [ determine-version ] if: ${{ needs.determine-version.outputs.DATED == 'true' }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/publish-latest-versioned-snapshot env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-openapi-ui.yml b/.github/workflows/publish-openapi-ui.yml index 275c64a0d4..f674ec4584 100644 --- a/.github/workflows/publish-openapi-ui.yml +++ b/.github/workflows/publish-openapi-ui.yml @@ -40,7 +40,7 @@ jobs: generate-openapi-spec: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java - name: Generate openapi spec @@ -62,7 +62,7 @@ jobs: { name: "data-plane", folder: "edc-dataplane/edc-dataplane-base" } ] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: eclipse-edc/.github/.github/actions/setup-build@main - uses: actions/download-artifact@v6 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ada43ba912..6f42a7da69 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} update_main_branch_version: ${{ steps.update-main.outputs.update_main_branch_version }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Output release version id: release-version run: | @@ -108,7 +108,7 @@ jobs: if: needs.validation.outputs.RELEASE_VERSION steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: ./.github/actions/setup-helm @@ -141,7 +141,7 @@ jobs: contents: write if: needs.validation.outputs.RELEASE_VERSION steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Prepare Git Config shell: bash run: | @@ -188,7 +188,7 @@ jobs: contents: write if: needs.validation.outputs.RELEASE_VERSION steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/github-script@v8 with: @@ -223,7 +223,7 @@ jobs: pages: write steps: - name: Checkout main - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: main diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml index 6508fbe245..45b075ffb4 100644 --- a/.github/workflows/secrets-scan.yml +++ b/.github/workflows/secrets-scan.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 # Ensure full clone for pull request workflows diff --git a/.github/workflows/trigger-docker-publish.yaml b/.github/workflows/trigger-docker-publish.yaml index b8cd76b19d..495528df0c 100644 --- a/.github/workflows/trigger-docker-publish.yaml +++ b/.github/workflows/trigger-docker-publish.yaml @@ -60,7 +60,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Log inputs run: | echo "Input Version: ${{ inputs.docker_tag }}, Input namespace: ${{ inputs.namespace}}" diff --git a/.github/workflows/trigger-maven-publish.yaml b/.github/workflows/trigger-maven-publish.yaml index e584f6ee02..78d5a2fb71 100644 --- a/.github/workflows/trigger-maven-publish.yaml +++ b/.github/workflows/trigger-maven-publish.yaml @@ -42,7 +42,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java - run: | diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 22568ab00b..a4650792a1 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -56,7 +56,7 @@ jobs: contents: read security-events: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Run Trivy vulnerability scanner in repo mode uses: aquasecurity/trivy-action@0.33.1 with: @@ -88,7 +88,7 @@ jobs: - edc-controlplane-postgresql-hashicorp-vault - edc-dataplane-hashicorp-vault steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 ## This step will fail if the docker images is not found - name: "Check if image exists" diff --git a/.github/workflows/upgradeability-test.yaml b/.github/workflows/upgradeability-test.yaml index 87d153c22f..29da2b8ebf 100644 --- a/.github/workflows/upgradeability-test.yaml +++ b/.github/workflows/upgradeability-test.yaml @@ -43,7 +43,7 @@ jobs: needs: [ test-prepare ] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: ./.github/actions/setup-helm - uses: ./.github/actions/setup-kubectl diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index 104260cb1c..65d443e0dd 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -32,7 +32,7 @@ jobs: verify-helm-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - run: | docker run -v ${{ github.workspace }}/charts:/helm-docs jnorwood/helm-docs helm-docs @@ -47,7 +47,7 @@ jobs: verify-formatting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java - name: Run Checkstyle @@ -59,7 +59,7 @@ jobs: verify-javadoc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java - name: Run Javadoc @@ -70,7 +70,7 @@ jobs: unit-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java @@ -117,7 +117,7 @@ jobs: integration-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java @@ -131,7 +131,7 @@ jobs: api-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java @@ -145,7 +145,7 @@ jobs: outputs: matrix: ${{ steps.outputStep.outputs.matrix }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: get api groups and create matrix for next job id: outputStep run: | @@ -159,7 +159,7 @@ jobs: fail-fast: false matrix: ${{ fromJson(needs.prepare-end-to-end-tests.outputs.matrix) }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java - name: Run E2E tests (${{ matrix.dir }}) @@ -183,7 +183,7 @@ jobs: postgres-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java - name: Run Postgresql E2E tests @@ -197,7 +197,7 @@ jobs: if: ${{ github.ref_name == 'main' || startsWith(github.ref_name, 'release/') }} steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Output report version id: release-version From f69db44be81e99528e0bbd79c473842bab140458 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 08:57:13 +0100 Subject: [PATCH 007/112] chore(deps): bump flyway from 11.17.0 to 11.17.1 (#2414) Bumps `flyway` from 11.17.0 to 11.17.1. Updates `org.flywaydb:flyway-core` from 11.17.0 to 11.17.1 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-11.17.0...flyway-11.17.1) Updates `org.flywaydb:flyway-database-postgresql` from 11.17.0 to 11.17.1 --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-version: 11.17.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.flywaydb:flyway-database-postgresql dependency-version: 11.17.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 94ccd8f158..ed9fa85a0f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.82" dcp-tck = "1.0.0-RC4" dsp-tck = "1.0.0-RC4" -flyway = "11.17.0" +flyway = "11.17.1" jackson = "2.20.1" jakarta-json = "2.0.1" junit = "6.0.1" From 7ead9f35d9fd69d759f190786e1f1e598660da58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:02:55 +0100 Subject: [PATCH 008/112] chore(deps): bump checkmarx/kics-github-action from 2.1.15 to 2.1.16 (#2416) Bumps [checkmarx/kics-github-action](https://github.com/checkmarx/kics-github-action) from 2.1.15 to 2.1.16. - [Release notes](https://github.com/checkmarx/kics-github-action/releases) - [Commits](https://github.com/checkmarx/kics-github-action/compare/v2.1.15...v2.1.16) --- updated-dependencies: - dependency-name: checkmarx/kics-github-action dependency-version: 2.1.16 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/kics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/kics.yml b/.github/workflows/kics.yml index c01ab4b35d..acaf45bc5b 100644 --- a/.github/workflows/kics.yml +++ b/.github/workflows/kics.yml @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v6 - name: KICS scan - uses: checkmarx/kics-github-action@v2.1.15 + uses: checkmarx/kics-github-action@v2.1.16 with: path: "." fail_on: high From 68138a77a8091cd77b39b41feef63ec6e9627b5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:05:44 +0100 Subject: [PATCH 009/112] chore(deps): bump aws from 2.38.7 to 2.39.2 (#2413) Bumps `aws` from 2.38.7 to 2.39.2. Updates `software.amazon.awssdk:s3` from 2.38.7 to 2.39.2 Updates `software.amazon.awssdk:s3-transfer-manager` from 2.38.7 to 2.39.2 --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.39.2 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: software.amazon.awssdk:s3-transfer-manager dependency-version: 2.39.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ed9fa85a0f..2b77cc2b85 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ edc = "0.14.1" edc-build = "1.1.2" allure = "2.30.0" awaitility = "4.3.0" -aws = "2.38.7" +aws = "2.39.2" azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.82" dcp-tck = "1.0.0-RC4" From d9c1d25a340f7aa6122d611588cd1fc538a4ddb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:06:15 +0100 Subject: [PATCH 010/112] chore(deps): bump com.squareup.okhttp3:okhttp from 5.3.1 to 5.3.2 (#2415) Bumps [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp) from 5.3.1 to 5.3.2. - [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md) - [Commits](https://github.com/square/okhttp/compare/parent-5.3.1...parent-5.3.2) --- updated-dependencies: - dependency-name: com.squareup.okhttp3:okhttp dependency-version: 5.3.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2b77cc2b85..bb797fc3f1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ jakarta-json = "2.0.1" junit = "6.0.1" nimbus = "10.6" netty-mockserver = "5.15.0" -okhttp = "5.3.1" +okhttp = "5.3.2" opentelemetry = "2.21.0" opentelemetry-instrumentation = "2.21.0" opentelemetry-log4j-appender = "2.21.0-alpha" From 7bc3ae8bee25c7e3f765a6a614922cf2ecb7cd1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:07:11 +0100 Subject: [PATCH 011/112] chore(deps): bump org.wiremock:wiremock-jetty12 from 3.13.1 to 3.13.2 (#2412) Bumps [org.wiremock:wiremock-jetty12](https://github.com/wiremock/wiremock) from 3.13.1 to 3.13.2. - [Release notes](https://github.com/wiremock/wiremock/releases) - [Commits](https://github.com/wiremock/wiremock/compare/3.13.1...3.13.2) --- updated-dependencies: - dependency-name: org.wiremock:wiremock-jetty12 dependency-version: 3.13.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bb797fc3f1..a78296d384 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ testcontainers = "1.21.3" testcontainers-keycloak = "3.9.0" titanium = "1.7.0" log4j2 = "2.25.2" -wiremock = "3.13.1" +wiremock = "3.13.2" [libraries] From bcb22d58e15a219b851ccf2c230e8701471f5806 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:15:08 +0100 Subject: [PATCH 012/112] chore(deps): bump trufflesecurity/trufflehog from 3.91.0 to 3.91.1 (#2417) Bumps [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) from 3.91.0 to 3.91.1. - [Release notes](https://github.com/trufflesecurity/trufflehog/releases) - [Changelog](https://github.com/trufflesecurity/trufflehog/blob/main/.goreleaser.yml) - [Commits](https://github.com/trufflesecurity/trufflehog/compare/cb6aeefd6e2498240d0418e63f69684d28337e7b...aade3bff5594fe8808578dd4db3dfeae9bf2abdc) --- updated-dependencies: - dependency-name: trufflesecurity/trufflehog dependency-version: 3.91.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/secrets-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml index 45b075ffb4..5d2db3a398 100644 --- a/.github/workflows/secrets-scan.yml +++ b/.github/workflows/secrets-scan.yml @@ -46,7 +46,7 @@ jobs: - name: TruffleHog OSS id: trufflehog - uses: trufflesecurity/trufflehog@cb6aeefd6e2498240d0418e63f69684d28337e7b + uses: trufflesecurity/trufflehog@aade3bff5594fe8808578dd4db3dfeae9bf2abdc continue-on-error: true with: path: ./ # Scan the entire repository From aab7bcf8d6117425690ff3fe179802cc4df3e472 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:15:21 +0100 Subject: [PATCH 013/112] chore(deps): bump com.gradleup.shadow from 8.3.8 to 9.2.2 (#2307) * chore(deps): bump com.gradleup.shadow from 8.3.8 to 9.2.2 Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 8.3.8 to 9.2.2. - [Release notes](https://github.com/GradleUp/shadow/releases) - [Commits](https://github.com/GradleUp/shadow/compare/8.3.8...9.2.2) --- updated-dependencies: - dependency-name: com.gradleup.shadow dependency-version: 9.2.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * build: bump gradlew to latest version --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: ndr_brt --- build.gradle.kts | 33 ++++++------------ .../build.gradle.kts | 3 +- .../edc-runtime-memory/build.gradle.kts | 3 +- .../build.gradle.kts | 3 +- .../runtime/mock-connector/build.gradle.kts | 3 +- gradle/libs.versions.toml | 3 +- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 9 +++-- gradlew.bat | 4 +-- 10 files changed, 27 insertions(+), 36 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 6a5e77ab8c..f692f14375 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,8 +18,6 @@ ********************************************************************************/ import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage -import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin -import java.time.Duration plugins { checkstyle @@ -28,9 +26,8 @@ plugins { jacoco `jacoco-report-aggregation` `java-test-fixtures` - alias(libs.plugins.shadow) + alias(libs.plugins.shadow) apply false alias(libs.plugins.docker) - alias(libs.plugins.nexus) alias(libs.plugins.edc.build) } @@ -92,8 +89,6 @@ allprojects { configure { pom { - // this is actually important, so we can publish under the correct GID - groupId = project.group.toString() projectName.set(project.name) description.set("edc :: ${project.name}") projectUrl.set(txWebsiteUrl) @@ -134,7 +129,7 @@ subprojects { afterEvaluate { // the "dockerize" task is added to all projects that use the `shadowJar` plugin if (project.plugins.hasPlugin(libs.plugins.shadow.get().pluginId)) { - val downloadOpentelemetryAgent = tasks.create("downloadOpentelemetryAgent", Copy::class) { + val downloadOpentelemetryAgent = tasks.register("downloadOpentelemetryAgent", Copy::class) { val openTelemetry = configurations.create("open-telemetry") dependencies { @@ -146,19 +141,19 @@ subprojects { rename { "opentelemetry-javaagent.jar" } } - val copyLegalDocs = tasks.create("copyLegalDocs", Copy::class) { + val copyLegalDocs = tasks.register("copyLegalDocs", Copy::class) { from(project.rootProject.projectDir) into("build/legal") include("SECURITY.md", "NOTICE.md", "DEPENDENCIES", "LICENSE") } - val copyDockerfile = tasks.create("copyDockerfile", Copy::class) { + val copyDockerfile = tasks.register("copyDockerfile", Copy::class) { from(rootProject.projectDir.toPath().resolve("resources")) into(project.layout.buildDirectory.dir("resources").get().dir("docker")) include("Dockerfile") } - val shadowJarTask = tasks.named(ShadowJavaPlugin.SHADOW_JAR_TASK_NAME).get() + val shadowJarTask = tasks.named("shadowJar").get() shadowJarTask .dependsOn(copyDockerfile) @@ -168,7 +163,7 @@ subprojects { //actually apply the plugin to the (sub-)project apply(plugin = libs.plugins.docker.get().pluginId) - val dockerTask: DockerBuildImage = tasks.create("dockerize", DockerBuildImage::class) { + tasks.register("dockerize", DockerBuildImage::class) { dockerFile.set(File("build/resources/docker/Dockerfile")) val dockerContextDir = project.projectDir @@ -183,9 +178,10 @@ subprojects { buildArgs.put("OTEL_JAR", "build/resources/otel/opentelemetry-javaagent.jar") buildArgs.put("ADDITIONAL_FILES", "build/legal/*") inputDir.set(file(dockerContextDir)) + + dependsOn(shadowJarTask) } - dockerTask.dependsOn(shadowJarTask) } if (path.startsWith(":edc-tests")) { @@ -217,22 +213,15 @@ subprojects { .map { childrenDependencies(it) }.flatten() .distinct() .forEach { dep -> - downloadYamlArtifact(dep, "management-api", destinationDirectory); - downloadYamlArtifact(dep, "observability-api", destinationDirectory); - downloadYamlArtifact(dep, "public-api", destinationDirectory); + downloadYamlArtifact(dep, "management-api", destinationDirectory) + downloadYamlArtifact(dep, "observability-api", destinationDirectory) + downloadYamlArtifact(dep, "public-api", destinationDirectory) } } } } -nexusPublishing { - transitionCheckOptions { - maxRetries.set(120) - delayBetween.set(Duration.ofSeconds(10)) - } -} - tasks.check { dependsOn(tasks.named("testCodeCoverageReport")) } diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts index 2ee1dd6919..86ffa36730 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts @@ -46,8 +46,9 @@ dependencies { runtimeOnly(libs.edc.vault.hashicorp) } -tasks.withType { +tasks.shadowJar { mergeServiceFiles() + duplicatesStrategy = DuplicatesStrategy.INCLUDE archiveFileName.set("${project.name}.jar") transform(com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer()) } diff --git a/edc-controlplane/edc-runtime-memory/build.gradle.kts b/edc-controlplane/edc-runtime-memory/build.gradle.kts index 06ba0752ed..c68d0f3b64 100644 --- a/edc-controlplane/edc-runtime-memory/build.gradle.kts +++ b/edc-controlplane/edc-runtime-memory/build.gradle.kts @@ -36,8 +36,9 @@ dependencies { testImplementation(libs.edc.lib.boot) } -tasks.withType { +tasks.shadowJar { mergeServiceFiles() + duplicatesStrategy = DuplicatesStrategy.INCLUDE archiveFileName.set("${project.name}.jar") transform(com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer()) } diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts index d3e8e7200c..e60452ea76 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts @@ -33,8 +33,9 @@ dependencies { runtimeOnly(libs.edc.vault.hashicorp) } -tasks.withType { +tasks.shadowJar { mergeServiceFiles() + duplicatesStrategy = DuplicatesStrategy.INCLUDE archiveFileName.set("${project.name}.jar") transform(com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer()) } diff --git a/edc-tests/runtime/mock-connector/build.gradle.kts b/edc-tests/runtime/mock-connector/build.gradle.kts index 797e0310da..3a4b1c59bb 100644 --- a/edc-tests/runtime/mock-connector/build.gradle.kts +++ b/edc-tests/runtime/mock-connector/build.gradle.kts @@ -59,8 +59,9 @@ edcBuild { publish.set(false) } -tasks.withType { +tasks.shadowJar { mergeServiceFiles() + duplicatesStrategy = DuplicatesStrategy.INCLUDE archiveFileName.set("${project.name}.jar") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a78296d384..13e627e405 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -239,7 +239,6 @@ edc-sts = [ [plugins] docker = { id = "com.bmuschko.docker-remote-api", version = "10.0.0" } -nexus = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" } -shadow = { id = "com.gradleup.shadow", version = "8.3.8" } +shadow = { id = "com.gradleup.shadow", version = "9.2.2" } swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.40" } edc-build = { id = "org.eclipse.edc.edc-build", version.ref = "edc-build" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 34943 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

Gn4F+B~IivciU9^t0j-Mgao3 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 DDw4oP diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c83a7..23449a2b54 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d6b..23d15a9367 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -206,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9b42019c79..5eed7ee845 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 3a0bf2709589f46ad4a974d5041ca8adf89297f2 Mon Sep 17 00:00:00 2001 From: Ronja Quensel <72978761+ronjaquensel@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:47:19 +0100 Subject: [PATCH 014/112] fix: scope extraction for legacy policy constraints (#2423) * fix: scope extraction for legacy policy constraints * chore: clean-up legacy credential types --- .../cx/legacy/CxLegacyPolicyExtension.java | 8 +- ...FrameworkAgreementConstraintFunction.java} | 4 +- .../policy/cx/legacy/CredentialFunctions.java | 9 +- ...ntlerCredentialConstraintFunctionTest.java | 8 +- ...eworkAgreementConstraintFunctionTest.java} | 81 +++++++++--------- .../cx/legacy/framework/PcfCredential.java | 64 -------------- ...rshipCredentialConstraintFunctionTest.java | 4 +- .../edc/policy/cx/CxPolicyExtension.java | 10 +-- ...FrameworkAgreementConstraintFunction.java} | 4 +- .../edc/policy/cx/CredentialFunctions.java | 9 +- ...ntlerCredentialConstraintFunctionTest.java | 10 +-- ...eworkAgreementConstraintFunctionTest.java} | 85 ++++++++++--------- .../policy/cx/framework/PcfCredential.java | 64 -------------- ...rshipCredentialConstraintFunctionTest.java | 6 +- edc-extensions/dcp/tx-dcp/build.gradle.kts | 1 + .../iatp/scope/CredentialScopeExtractor.java | 53 ++++-------- .../scope/CredentialScopeExtractorTest.java | 47 ++++++++-- .../iatp/harness/IatpParticipant.java | 1 - .../resources/cx-credentials-context.json | 18 ---- 19 files changed, 180 insertions(+), 306 deletions(-) rename edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/{FrameworkAgreementCredentialConstraintFunction.java => FrameworkAgreementConstraintFunction.java} (97%) rename edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/{FrameworkAgreementCredentialConstraintFunctionTest.java => FrameworkAgreementConstraintFunctionTest.java} (76%) delete mode 100644 edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/PcfCredential.java rename edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/{FrameworkAgreementCredentialConstraintFunction.java => FrameworkAgreementConstraintFunction.java} (97%) rename edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/{FrameworkAgreementCredentialConstraintFunctionTest.java => FrameworkAgreementConstraintFunctionTest.java} (77%) delete mode 100644 edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/PcfCredential.java diff --git a/edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/CxLegacyPolicyExtension.java b/edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/CxLegacyPolicyExtension.java index 512a01ecce..d492146006 100644 --- a/edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/CxLegacyPolicyExtension.java +++ b/edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/CxLegacyPolicyExtension.java @@ -30,7 +30,7 @@ import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.tractusx.edc.policy.cx.legacy.contractreference.ContractReferenceConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.legacy.dismantler.DismantlerCredentialConstraintFunction; -import org.eclipse.tractusx.edc.policy.cx.legacy.framework.FrameworkAgreementCredentialConstraintFunction; +import org.eclipse.tractusx.edc.policy.cx.legacy.framework.FrameworkAgreementConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.legacy.membership.MembershipCredentialConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.legacy.usage.UsagePurposeConstraintFunction; @@ -69,8 +69,8 @@ public static void registerFunctions(PolicyEngine engine) { engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, new DismantlerCredentialConstraintFunction<>()); engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, new DismantlerCredentialConstraintFunction<>()); - engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, new FrameworkAgreementCredentialConstraintFunction<>()); - engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, new FrameworkAgreementCredentialConstraintFunction<>()); + engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, new FrameworkAgreementConstraintFunction<>()); + engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, new FrameworkAgreementConstraintFunction<>()); engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, new MembershipCredentialConstraintFunction<>()); engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, new MembershipCredentialConstraintFunction<>()); @@ -84,7 +84,7 @@ public static void registerFunctions(PolicyEngine engine) { public static void registerBindings(RuleBindingRegistry registry) { registry.dynamicBind(s -> { - if (Stream.of(FrameworkAgreementCredentialConstraintFunction.FRAMEWORK_AGREEMENT_LITERAL, DismantlerCredentialConstraintFunction.DISMANTLER_LITERAL, MembershipCredentialConstraintFunction.MEMBERSHIP_LITERAL) + if (Stream.of(FrameworkAgreementConstraintFunction.FRAMEWORK_AGREEMENT_LITERAL, DismantlerCredentialConstraintFunction.DISMANTLER_LITERAL, MembershipCredentialConstraintFunction.MEMBERSHIP_LITERAL) .anyMatch(postfix -> s.startsWith(CX_POLICY_NS + postfix))) { return RULE_SCOPES; } diff --git a/edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementCredentialConstraintFunction.java b/edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementConstraintFunction.java similarity index 97% rename from edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementCredentialConstraintFunction.java rename to edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementConstraintFunction.java index 2cae699675..1c141fd1df 100644 --- a/edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementCredentialConstraintFunction.java +++ b/edc-extensions/cx-policy-legacy/src/main/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementConstraintFunction.java @@ -52,7 +52,7 @@ * policy is considered not fulfilled. Note that if the {@code version} is specified, it must be satisfied by the same * credential that satisfies the {@code subtype} requirement. */ -public class FrameworkAgreementCredentialConstraintFunction extends AbstractDynamicCredentialConstraintFunction { +public class FrameworkAgreementConstraintFunction extends AbstractDynamicCredentialConstraintFunction { public static final String CONTRACT_VERSION_LITERAL = "contractVersion"; public static final String FRAMEWORK_AGREEMENT_LITERAL = "FrameworkAgreement"; @@ -117,7 +117,7 @@ public boolean evaluate(Object leftValue, Operator operator, Object rightValue, } /** - * Returns {@code true} if the left-operand starts with {@link FrameworkAgreementCredentialConstraintFunction#FRAMEWORK_AGREEMENT_LITERAL}, {@code false} otherwise. + * Returns {@code true} if the left-operand starts with {@link FrameworkAgreementConstraintFunction#FRAMEWORK_AGREEMENT_LITERAL}, {@code false} otherwise. */ @Override public boolean canHandle(Object leftValue) { diff --git a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/CredentialFunctions.java b/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/CredentialFunctions.java index d49f58cb89..fc8b3e17ea 100644 --- a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/CredentialFunctions.java +++ b/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/CredentialFunctions.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2025 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -66,12 +67,12 @@ public static VerifiableCredential.Builder createPlainFrameworkCredential(String .build()); } - public static VerifiableCredential.Builder createPcfCredential() { - return createCredential("PcfCredential", "1.0.0"); + public static VerifiableCredential.Builder createDataExchangeGovernanceCredential() { + return createCredential("DataExchangeGovernanceCredential", "1.0.0"); } - public static VerifiableCredential.Builder createPlainPcfCredential() { - return createPlainFrameworkCredential("PcfCredential", "1.0.0"); + public static VerifiableCredential.Builder createPlainDataExchangeGovernanceCredential() { + return createPlainFrameworkCredential("DataExchangeGovernanceCredential", "1.0.0"); } public static VerifiableCredential.Builder createDismantlerCredential(String... brands) { diff --git a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/dismantler/DismantlerCredentialConstraintFunctionTest.java b/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/dismantler/DismantlerCredentialConstraintFunctionTest.java index 1335e56fcb..462b9f9948 100644 --- a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/dismantler/DismantlerCredentialConstraintFunctionTest.java +++ b/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/dismantler/DismantlerCredentialConstraintFunctionTest.java @@ -89,13 +89,13 @@ void evaluate_eq_withoutNamespace() { @Test void evaluate_eq_notSatisfied() { - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(CredentialFunctions.createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of())); assertThat(function.evaluate(CX_POLICY_NS + "Dismantler", Operator.EQ, "active", null, context)).isFalse(); } @Test void evaluate_neq_satisfied() { - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(CredentialFunctions.createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(CredentialFunctions.createMembershipCredential().build()))); assertThat(function.evaluate(CX_POLICY_NS + "Dismantler", Operator.NEQ, "active", null, context)).isTrue(); } @@ -110,7 +110,7 @@ void evaluate_invalidOperators() { var invalidOperators = new ArrayList<>(Arrays.asList(Operator.values())); invalidOperators.remove(Operator.EQ); invalidOperators.remove(Operator.NEQ); - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(CredentialFunctions.createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of())); assertThat(invalidOperators).allSatisfy(invalidOperator -> assertThat(function.evaluate(CX_POLICY_NS + "Dismantler", invalidOperator, "active", null, context)).isFalse()); @@ -118,7 +118,7 @@ void evaluate_invalidOperators() { @Test void evaluate_rightOperandInvalid() { - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(CredentialFunctions.createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(CredentialFunctions.createMembershipCredential().build()))); assertThat(function.evaluate(CX_POLICY_NS + "Dismantler", Operator.EQ, "invalid", null, context)).isFalse(); assertThat(context.getProblems()).containsOnly("Right-operand must be equal to 'active', but was 'invalid'"); } diff --git a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementCredentialConstraintFunctionTest.java b/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementConstraintFunctionTest.java similarity index 76% rename from edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementCredentialConstraintFunctionTest.java rename to edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementConstraintFunctionTest.java index 8765ec5969..416f65b01b 100644 --- a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementCredentialConstraintFunctionTest.java +++ b/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/FrameworkAgreementConstraintFunctionTest.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2025 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -37,9 +38,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class FrameworkAgreementCredentialConstraintFunctionTest { +class FrameworkAgreementConstraintFunctionTest { private final ParticipantAgent participantAgent = mock(); - private final FrameworkAgreementCredentialConstraintFunction function = new FrameworkAgreementCredentialConstraintFunction<>(); + private final FrameworkAgreementConstraintFunction function = new FrameworkAgreementConstraintFunction<>(); private final ParticipantAgentPolicyContext context = new TestParticipantAgentPolicyContext(participantAgent); @Test @@ -96,10 +97,10 @@ void evaluate_vcClaimCredentialsEmpty() { @Test void evaluate_rightOperandInvalidFormat() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement.pcf", Operator.EQ, "/violate$", null, context); + var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "/violate$", null, context); assertThat(result).isFalse(); assertThat(context.getProblems()).containsOnly("Right-operand must contain the keyword 'active' followed by an optional version string: 'active'[:version], but was '/violate$'."); @@ -109,8 +110,8 @@ void evaluate_rightOperandInvalidFormat() { void evaluate_requiredCredentialNotFound() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - CredentialFunctions.createCredential(CX_POLICY_NS + "PcfCredential", "1.3.0").build(), - CredentialFunctions.createCredential(CX_POLICY_NS + "PcfCredential", "1.0.0").build()) + CredentialFunctions.createCredential(CX_POLICY_NS + "DataExchangeGovernanceCredential", "1.3.0").build(), + CredentialFunctions.createCredential(CX_POLICY_NS + "DataExchangeGovernanceCredential", "1.0.0").build()) )); var result = function.evaluate("FrameworkAgreement", Operator.EQ, "someOther:1.3.0", null, context); @@ -123,11 +124,11 @@ void evaluate_requiredCredential_wrongVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( CredentialFunctions.createCredential("SomeOtherCredential", "2.0.0").build(), - CredentialFunctions.createCredential("PcfCredential", "1.8.0").build(), - CredentialFunctions.createCredential("PcfCredential", "1.0.0").build()) + CredentialFunctions.createCredential("DataExchangeGovernanceCredential", "1.8.0").build(), + CredentialFunctions.createCredential("DataExchangeGovernanceCredential", "1.0.0").build()) )); - var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "pcf:1.3.0", null, context); + var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "dataExchangeGovernance:1.3.0", null, context); assertThat(result).isFalse(); } @@ -136,12 +137,12 @@ void evaluate_requiredCredential_wrongVersion() { void evaluate_requiredCredentialFound() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - CredentialFunctions.createCredential("PcfCredential", "6.0.0").build(), + CredentialFunctions.createCredential("DataExchangeGovernanceCredential", "6.0.0").build(), CredentialFunctions.createCredential("SomeOtherType", "3.4.1").build() ) )); - var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "pcf", null, context); + var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "dataExchangeGovernance", null, context); assertThat(result).isTrue(); } @@ -150,12 +151,12 @@ void evaluate_requiredCredentialFound() { void evaluate_requiredCredentialFound_withCorrectVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - CredentialFunctions.createCredential("PcfCredential", "2.0.0").build(), - CredentialFunctions.createCredential("PcfCredential", "1.3.0").build(), - CredentialFunctions.createCredential("PcfCredential", "1.0.0").build()) + CredentialFunctions.createCredential("DataExchangeGovernanceCredential", "2.0.0").build(), + CredentialFunctions.createCredential("DataExchangeGovernanceCredential", "1.3.0").build(), + CredentialFunctions.createCredential("DataExchangeGovernanceCredential", "1.0.0").build()) )); - var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "pcf:1.3.0", null, context); + var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "dataExchangeGovernance:1.3.0", null, context); assertThat(result).isTrue(); } @@ -164,7 +165,7 @@ void evaluate_requiredCredentialFound_withCorrectVersion() { void evaluate_neq_requiredCredentialFound() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - CredentialFunctions.createCredential("PcfCredential", "6.0.0").build(), + CredentialFunctions.createCredential("DataExchangeGovernanceCredential", "6.0.0").build(), CredentialFunctions.createCredential("SomeOtherType", "3.4.1").build() ) )); @@ -178,7 +179,7 @@ void evaluate_neq_requiredCredentialFound() { void evaluate_neq_oneOfManyViolates() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - CredentialFunctions.createCredential("PcfCredential", "6.0.0").build(), + CredentialFunctions.createCredential("DataExchangeGovernanceCredential", "6.0.0").build(), CredentialFunctions.createCredential("SustainabilityCredential", "6.0.0").build(), CredentialFunctions.createCredential("SomeOtherType", "3.4.1").build() ) @@ -206,7 +207,7 @@ void evaluate_neq_oneViolates() { void evaluate_neq_requiredCredentialFound_withCorrectVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - CredentialFunctions.createCredential("PcfCredential", "2.0.0").build(), + CredentialFunctions.createCredential("DataExchangeGovernanceCredential", "2.0.0").build(), CredentialFunctions.createCredential("FooBarCredential", "1.3.0").build(), CredentialFunctions.createCredential("BarBazCredential", "1.0.0").build()) )); @@ -222,7 +223,7 @@ class LegacyLeftOperand { @Test void evaluate_leftOperand_notContainSubtype() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement.", Operator.EQ, "active:0.4.2", null, context); @@ -234,10 +235,10 @@ void evaluate_leftOperand_notContainSubtype() { @Test void evaluate_leftOperand_notContainFrameworkLiteral() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_NS + "foobar.pcf", Operator.EQ, "active:0.4.2", null, context); + var result = function.evaluate(CX_POLICY_NS + "foobar.dataExchangeGovernance", Operator.EQ, "active:0.4.2", null, context); assertThat(result).isFalse(); assertThat(context.getProblems()).hasSize(1).allMatch(it -> it.startsWith("Constraint left-operand must start with 'FrameworkAgreement' but was")); @@ -246,10 +247,10 @@ void evaluate_leftOperand_notContainFrameworkLiteral() { @Test void evaluate_leftOperand_notStartsWithFrameworkLiteral() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_NS + "foobarFrameworkAgreement.pcf", Operator.EQ, "active:0.4.2", null, context); + var result = function.evaluate(CX_POLICY_NS + "foobarFrameworkAgreement.dataExchangeGovernance", Operator.EQ, "active:0.4.2", null, context); assertThat(result).isFalse(); assertThat(context.getProblems()).hasSize(1).allMatch(it -> it.startsWith("Constraint left-operand must start with 'FrameworkAgreement' but was")); @@ -258,10 +259,10 @@ void evaluate_leftOperand_notStartsWithFrameworkLiteral() { @Test void evaluate_rightOperand_notStartWithActiveLiteral() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement.pcf", Operator.EQ, "violates", null, context); + var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "violates", null, context); assertThat(result).isFalse(); assertThat(context.getProblems()).containsOnly("Right-operand must contain the keyword 'active' followed by an optional version string: 'active'[:version], but was 'violates'."); @@ -270,19 +271,19 @@ void evaluate_rightOperand_notStartWithActiveLiteral() { @Test void evaluate_rightOperandWithVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); - assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement.pcf", Operator.EQ, "active:1.0.0", null, context)).isTrue(); - assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement.pcf", Operator.EQ, "active:5.3.1", null, context)).isFalse(); + assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "active:1.0.0", null, context)).isTrue(); + assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "active:5.3.1", null, context)).isFalse(); } @Test void evaluate_rightOperand() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement.pcf", Operator.EQ, "active", null, context); + var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "active", null, context); assertThat(result).isTrue(); } @@ -293,7 +294,7 @@ class NewLeftOperand { @Test void evaluate_leftOperandNotFrameworkLiteral() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); var result = function.evaluate("Foobar", Operator.EQ, "active:0.4.2", null, context); @@ -305,10 +306,10 @@ void evaluate_leftOperandNotFrameworkLiteral() { @Test void evaluate_rightOperand_onlySubtype() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "pcf", null, context); + var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "DataExchangeGovernance", null, context); assertThat(result).isTrue(); } @@ -316,28 +317,28 @@ void evaluate_rightOperand_onlySubtype() { @Test void evaluate_rightOperand_withVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "pcf:1.0.0", null, context); + var result = function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:1.0.0", null, context); assertThat(result).isTrue(); - assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "pcf:4.2.0", null, context)).isFalse(); + assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:4.2.0", null, context)).isFalse(); } @Test void evaluate_withoutNamespace() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPlainPcfCredential().build()) + "vc", List.of(CredentialFunctions.createPlainDataExchangeGovernanceCredential().build()) )); - assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "pcf:1.0.0", null, context)).isTrue(); - assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "pcf:4.2.0", null, context)).isFalse(); + assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:1.0.0", null, context)).isTrue(); + assertThat(function.evaluate(CX_POLICY_NS + "FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:4.2.0", null, context)).isFalse(); } @Test void evaluate_rightOperandMissesSubtype() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(CredentialFunctions.createPcfCredential().build()) + "vc", List.of(CredentialFunctions.createMembershipCredential().build()) )); var result = function.evaluate("FrameworkAgreement", Operator.EQ, ":1.0.0", null, context); diff --git a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/PcfCredential.java b/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/PcfCredential.java deleted file mode 100644 index f658735627..0000000000 --- a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/framework/PcfCredential.java +++ /dev/null @@ -1,64 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * 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. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -package org.eclipse.tractusx.edc.policy.cx.legacy.framework; - -public interface PcfCredential { - - String PCF_VP = """ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1" - ], - "type": "VerifiablePresentation", - "verifiableCredential": [ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://w3id.org/2023/catenax/credentials/usecase/v1" - ], - "id": "urn:uuid:12345678-1234-1234-1234-123456789abc", - "type": [ - "VerifiableCredential", - "PcfCredential" - ], - "issuer": "did:web:issuer.example.com", - "issuanceDate": "2023-06-02T12:00:00Z", - "expirationDate": "2022-06-16T18:56:59Z", - "credentialSubject": { - "id": "did:web:example.com", - "holderIdentifier": "BPN of holder", - "usecaseAgreement": { - "value": "PCF", - "type": "PcfAgreement", - "contractTemplate": "https://public.catena-x.org/contracts/pcf.v1.pdf", - "contractVersion": "1.0.0" - } - }, - "proof": { - "type": "Ed25519Signature2018", - "created": "2023-06-02T12:00:00Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:web:issuer.example.com#key-1", - "jws": "xxx" - } - } - ] - }"""; -} diff --git a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/membership/MembershipCredentialConstraintFunctionTest.java b/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/membership/MembershipCredentialConstraintFunctionTest.java index eb2a3e0b89..5851ea2021 100644 --- a/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/membership/MembershipCredentialConstraintFunctionTest.java +++ b/edc-extensions/cx-policy-legacy/src/test/java/org/eclipse/tractusx/edc/policy/cx/legacy/membership/MembershipCredentialConstraintFunctionTest.java @@ -101,7 +101,7 @@ void evaluate_whenSingleCredentialFound() { void evaluate_whenMultipleCredentialsFound() { when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(CredentialFunctions.createMembershipCredential().build(), CredentialFunctions.createMembershipCredential().build(), - CredentialFunctions.createPcfCredential().build()))); + CredentialFunctions.createDismantlerCredential("Tatra", "Moskvich").build()))); var result = function.evaluate(CX_POLICY_NS + "Membership", Operator.EQ, "active", null, context); @@ -110,7 +110,7 @@ void evaluate_whenMultipleCredentialsFound() { @Test void evaluate_whenCredentialNotFound() { - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(CredentialFunctions.createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(CredentialFunctions.createDismantlerCredential("Tatra", "Moskvich").build()))); var result = function.evaluate(CX_POLICY_NS + "Membership", Operator.EQ, "active", null, context); diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/CxPolicyExtension.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/CxPolicyExtension.java index 47cc0475b8..52ec18c7fa 100644 --- a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/CxPolicyExtension.java +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/CxPolicyExtension.java @@ -50,7 +50,7 @@ import org.eclipse.tractusx.edc.policy.cx.datausage.DataUsageEndDefinitionConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.datausage.DataUsageEndDurationDaysConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.dismantler.DismantlerCredentialConstraintFunction; -import org.eclipse.tractusx.edc.policy.cx.framework.FrameworkAgreementCredentialConstraintFunction; +import org.eclipse.tractusx.edc.policy.cx.framework.FrameworkAgreementConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.jurisdictionlocation.JurisdictionLocationConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.jurisdictionlocation.JurisdictionLocationReferenceConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.liability.LiabilityConstraintFunction; @@ -94,7 +94,7 @@ import static org.eclipse.tractusx.edc.policy.cx.datausage.DataUsageEndDefinitionConstraintFunction.DATA_USAGE_END_DEFINITION; import static org.eclipse.tractusx.edc.policy.cx.datausage.DataUsageEndDurationDaysConstraintFunction.DATA_USAGE_END_DURATION_DAYS; import static org.eclipse.tractusx.edc.policy.cx.dismantler.DismantlerCredentialConstraintFunction.DISMANTLER_LITERAL; -import static org.eclipse.tractusx.edc.policy.cx.framework.FrameworkAgreementCredentialConstraintFunction.FRAMEWORK_AGREEMENT_LITERAL; +import static org.eclipse.tractusx.edc.policy.cx.framework.FrameworkAgreementConstraintFunction.FRAMEWORK_AGREEMENT_LITERAL; import static org.eclipse.tractusx.edc.policy.cx.jurisdictionlocation.JurisdictionLocationConstraintFunction.JURISDICTION_LOCATION; import static org.eclipse.tractusx.edc.policy.cx.jurisdictionlocation.JurisdictionLocationReferenceConstraintFunction.JURISDICTION_LOCATION_REFERENCE; import static org.eclipse.tractusx.edc.policy.cx.liability.LiabilityConstraintFunction.LIABILITY; @@ -168,9 +168,9 @@ public void registerFunctions(PolicyEngine engine) { engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, new DismantlerCredentialConstraintFunction<>()); engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, new DismantlerCredentialConstraintFunction<>()); - engine.registerFunction(CatalogPolicyContext.class, Permission.class, new FrameworkAgreementCredentialConstraintFunction<>()); - engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, new FrameworkAgreementCredentialConstraintFunction<>()); - engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, new FrameworkAgreementCredentialConstraintFunction<>()); + engine.registerFunction(CatalogPolicyContext.class, Permission.class, new FrameworkAgreementConstraintFunction<>()); + engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, new FrameworkAgreementConstraintFunction<>()); + engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, new FrameworkAgreementConstraintFunction<>()); engine.registerFunction(CatalogPolicyContext.class, Permission.class, new MembershipCredentialConstraintFunction<>()); engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, new MembershipCredentialConstraintFunction<>()); diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementCredentialConstraintFunction.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunction.java similarity index 97% rename from edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementCredentialConstraintFunction.java rename to edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunction.java index 608b4c0afb..3999fc490f 100644 --- a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementCredentialConstraintFunction.java +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunction.java @@ -52,7 +52,7 @@ * policy is considered not fulfilled. Note that if the {@code version} is specified, it must be satisfied by the same * credential that satisfies the {@code subtype} requirement. */ -public class FrameworkAgreementCredentialConstraintFunction extends AbstractDynamicCredentialConstraintFunction { +public class FrameworkAgreementConstraintFunction extends AbstractDynamicCredentialConstraintFunction { public static final String CONTRACT_VERSION_LITERAL = "contractVersion"; public static final String FRAMEWORK_AGREEMENT_LITERAL = "FrameworkAgreement"; @@ -116,7 +116,7 @@ public boolean evaluate(Object leftValue, Operator operator, Object rightValue, } /** - * Returns {@code true} if the left-operand starts with {@link FrameworkAgreementCredentialConstraintFunction#FRAMEWORK_AGREEMENT_LITERAL}, {@code false} otherwise. + * Returns {@code true} if the left-operand starts with {@link FrameworkAgreementConstraintFunction#FRAMEWORK_AGREEMENT_LITERAL}, {@code false} otherwise. */ @Override public boolean canHandle(Object leftValue) { diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/CredentialFunctions.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/CredentialFunctions.java index 2a3256ce5f..58bf781bf7 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/CredentialFunctions.java +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/CredentialFunctions.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2025 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -66,12 +67,12 @@ public static VerifiableCredential.Builder createPlainFrameworkCredential(String .build()); } - public static VerifiableCredential.Builder createPcfCredential() { - return createCredential("PcfCredential", "1.0.0"); + public static VerifiableCredential.Builder createDataExchangeGovernanceCredential() { + return createCredential("DataExchangeGovernanceCredential", "1.0.0"); } - public static VerifiableCredential.Builder createPlainPcfCredential() { - return createPlainFrameworkCredential("PcfCredential", "1.0.0"); + public static VerifiableCredential.Builder createPlainDataExchangeGovernanceCredential() { + return createPlainFrameworkCredential("DataExchangeGovernanceCredential", "1.0.0"); } public static VerifiableCredential.Builder createDismantlerCredential(String... brands) { diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/dismantler/DismantlerCredentialConstraintFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/dismantler/DismantlerCredentialConstraintFunctionTest.java index 01b9efe91a..eb54ee10a7 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/dismantler/DismantlerCredentialConstraintFunctionTest.java +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/dismantler/DismantlerCredentialConstraintFunctionTest.java @@ -37,7 +37,7 @@ import static org.eclipse.edc.policy.model.Operator.IN; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_NS; import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createDismantlerCredential; -import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createPcfCredential; +import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createMembershipCredential; import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createPlainDismantlerCredential; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -91,13 +91,13 @@ void evaluate_eq_withoutNamespace() { @Test void evaluate_eq_notSatisfied() { - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createMembershipCredential().build()))); assertThat(function.evaluate(CX_POLICY_NS + "Dismantler", Operator.EQ, "active", null, context)).isFalse(); } @Test void evaluate_neq_satisfied() { - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createMembershipCredential().build()))); assertThat(function.evaluate(CX_POLICY_NS + "Dismantler", Operator.NEQ, "active", null, context)).isTrue(); } @@ -112,7 +112,7 @@ void evaluate_invalidOperators() { var invalidOperators = new ArrayList<>(Arrays.asList(Operator.values())); invalidOperators.remove(Operator.EQ); invalidOperators.remove(Operator.NEQ); - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createMembershipCredential().build()))); assertThat(invalidOperators).allSatisfy(invalidOperator -> assertThat(function.evaluate(CX_POLICY_NS + "Dismantler", invalidOperator, "active", null, context)).isFalse()); @@ -120,7 +120,7 @@ void evaluate_invalidOperators() { @Test void evaluate_rightOperandInvalid() { - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createMembershipCredential().build()))); assertThat(function.evaluate(CX_POLICY_NS + "Dismantler", Operator.EQ, "invalid", null, context)).isFalse(); assertThat(context.getProblems()).containsOnly("Right-operand must be equal to 'active', but was 'invalid'"); } diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementCredentialConstraintFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunctionTest.java similarity index 77% rename from edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementCredentialConstraintFunctionTest.java rename to edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunctionTest.java index ab352c23a0..5d1cf76469 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementCredentialConstraintFunctionTest.java +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/FrameworkAgreementConstraintFunctionTest.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2025 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -34,14 +35,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_2025_09_NS; import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createCredential; -import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createPcfCredential; -import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createPlainPcfCredential; +import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createDataExchangeGovernanceCredential; +import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createPlainDataExchangeGovernanceCredential; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -class FrameworkAgreementCredentialConstraintFunctionTest { +class FrameworkAgreementConstraintFunctionTest { private final ParticipantAgent participantAgent = mock(); - private final FrameworkAgreementCredentialConstraintFunction function = new FrameworkAgreementCredentialConstraintFunction<>(); + private final FrameworkAgreementConstraintFunction function = new FrameworkAgreementConstraintFunction<>(); private final ParticipantAgentPolicyContext context = new TestParticipantAgentPolicyContext(participantAgent); @Test @@ -98,10 +99,10 @@ void evaluate_vcClaimCredentialsEmpty() { @Test void evaluate_rightOperandInvalidFormat() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.pcf", Operator.EQ, "/violate$", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "/violate$", null, context); assertThat(result).isFalse(); assertThat(context.getProblems()).containsOnly("Right-operand must contain the keyword 'active' followed by an optional version string: 'active'[:version], but was '/violate$'."); @@ -111,8 +112,8 @@ void evaluate_rightOperandInvalidFormat() { void evaluate_requiredCredentialNotFound() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - createCredential(CX_POLICY_2025_09_NS + "PcfCredential", "1.3.0").build(), - createCredential(CX_POLICY_2025_09_NS + "PcfCredential", "1.0.0").build()) + createCredential(CX_POLICY_2025_09_NS + "DataExchangeGovernanceCredential", "1.3.0").build(), + createCredential(CX_POLICY_2025_09_NS + "DataExchangeGovernanceCredential", "1.0.0").build()) )); var result = function.evaluate("FrameworkAgreement", Operator.EQ, "someOther:1.3.0", null, context); @@ -125,11 +126,11 @@ void evaluate_requiredCredential_wrongVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( createCredential("SomeOtherCredential", "2.0.0").build(), - createCredential("PcfCredential", "1.8.0").build(), - createCredential("PcfCredential", "1.0.0").build()) + createCredential("DataExchangeGovernanceCredential", "1.8.0").build(), + createCredential("DataExchangeGovernanceCredential", "1.0.0").build()) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "pcf:1.3.0", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "dataExchangeGovernance:1.3.0", null, context); assertThat(result).isFalse(); } @@ -138,12 +139,12 @@ void evaluate_requiredCredential_wrongVersion() { void evaluate_requiredCredentialFound() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - createCredential("PcfCredential", "6.0.0").build(), + createCredential("DataExchangeGovernanceCredential", "6.0.0").build(), createCredential("SomeOtherType", "3.4.1").build() ) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "pcf", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "dataExchangeGovernance", null, context); assertThat(result).isTrue(); } @@ -152,12 +153,12 @@ void evaluate_requiredCredentialFound() { void evaluate_requiredCredentialFound_withCorrectVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - createCredential("PcfCredential", "2.0.0").build(), - createCredential("PcfCredential", "1.3.0").build(), - createCredential("PcfCredential", "1.0.0").build()) + createCredential("DataExchangeGovernanceCredential", "2.0.0").build(), + createCredential("DataExchangeGovernanceCredential", "1.3.0").build(), + createCredential("DataExchangeGovernanceCredential", "1.0.0").build()) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "pcf:1.3.0", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "dataExchangeGovernance:1.3.0", null, context); assertThat(result).isTrue(); } @@ -166,7 +167,7 @@ void evaluate_requiredCredentialFound_withCorrectVersion() { void evaluate_neq_requiredCredentialFound() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - createCredential("PcfCredential", "6.0.0").build(), + createCredential("DataExchangeGovernanceCredential", "6.0.0").build(), createCredential("SomeOtherType", "3.4.1").build() ) )); @@ -180,7 +181,7 @@ void evaluate_neq_requiredCredentialFound() { void evaluate_neq_oneOfManyViolates() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - createCredential("PcfCredential", "6.0.0").build(), + createCredential("DataExchangeGovernanceCredential", "6.0.0").build(), createCredential("SustainabilityCredential", "6.0.0").build(), createCredential("SomeOtherType", "3.4.1").build() ) @@ -208,7 +209,7 @@ void evaluate_neq_oneViolates() { void evaluate_neq_requiredCredentialFound_withCorrectVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( "vc", List.of( - createCredential("PcfCredential", "2.0.0").build(), + createCredential("DataExchangeGovernanceCredential", "2.0.0").build(), createCredential("FooBarCredential", "1.3.0").build(), createCredential("BarBazCredential", "1.0.0").build()) )); @@ -224,7 +225,7 @@ class LegacyLeftOperand { @Test void evaluate_leftOperand_notContainSubtype() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.", Operator.EQ, "active:0.4.2", null, context); @@ -236,10 +237,10 @@ void evaluate_leftOperand_notContainSubtype() { @Test void evaluate_leftOperand_notContainFrameworkLiteral() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "foobar.pcf", Operator.EQ, "active:0.4.2", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "foobar.dataExchangeGovernance", Operator.EQ, "active:0.4.2", null, context); assertThat(result).isFalse(); assertThat(context.getProblems()).hasSize(1).allMatch(it -> it.startsWith("Constraint left-operand must start with 'FrameworkAgreement' but was")); @@ -248,10 +249,10 @@ void evaluate_leftOperand_notContainFrameworkLiteral() { @Test void evaluate_leftOperand_notStartsWithFrameworkLiteral() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "foobarFrameworkAgreement.pcf", Operator.EQ, "active:0.4.2", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "foobarFrameworkAgreement.dataExchangeGovernance", Operator.EQ, "active:0.4.2", null, context); assertThat(result).isFalse(); assertThat(context.getProblems()).hasSize(1).allMatch(it -> it.startsWith("Constraint left-operand must start with 'FrameworkAgreement' but was")); @@ -260,10 +261,10 @@ void evaluate_leftOperand_notStartsWithFrameworkLiteral() { @Test void evaluate_rightOperand_notStartWithActiveLiteral() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.pcf", Operator.EQ, "violates", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "violates", null, context); assertThat(result).isFalse(); assertThat(context.getProblems()).containsOnly("Right-operand must contain the keyword 'active' followed by an optional version string: 'active'[:version], but was 'violates'."); @@ -272,19 +273,19 @@ void evaluate_rightOperand_notStartWithActiveLiteral() { @Test void evaluate_rightOperandWithVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); - assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.pcf", Operator.EQ, "active:1.0.0", null, context)).isTrue(); - assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.pcf", Operator.EQ, "active:5.3.1", null, context)).isFalse(); + assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "active:1.0.0", null, context)).isTrue(); + assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "active:5.3.1", null, context)).isFalse(); } @Test void evaluate_rightOperand() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.pcf", Operator.EQ, "active", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement.dataExchangeGovernance", Operator.EQ, "active", null, context); assertThat(result).isTrue(); } @@ -295,7 +296,7 @@ class NewLeftOperand { @Test void evaluate_leftOperandNotFrameworkLiteral() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); var result = function.evaluate("Foobar", Operator.EQ, "active:0.4.2", null, context); @@ -307,10 +308,10 @@ void evaluate_leftOperandNotFrameworkLiteral() { @Test void evaluate_rightOperand_onlySubtype() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "pcf", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "dataExchangeGovernance", null, context); assertThat(result).isTrue(); } @@ -318,28 +319,28 @@ void evaluate_rightOperand_onlySubtype() { @Test void evaluate_rightOperand_withVersion() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); - var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "pcf:1.0.0", null, context); + var result = function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:1.0.0", null, context); assertThat(result).isTrue(); - assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "pcf:4.2.0", null, context)).isFalse(); + assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:4.2.0", null, context)).isFalse(); } @Test void evaluate_withoutNamespace() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPlainPcfCredential().build()) + "vc", List.of(createPlainDataExchangeGovernanceCredential().build()) )); - assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "pcf:1.0.0", null, context)).isTrue(); - assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "pcf:4.2.0", null, context)).isFalse(); + assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:1.0.0", null, context)).isTrue(); + assertThat(function.evaluate(CX_POLICY_2025_09_NS + "FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:4.2.0", null, context)).isFalse(); } @Test void evaluate_rightOperandMissesSubtype() { when(participantAgent.getClaims()).thenReturn(Map.of( - "vc", List.of(createPcfCredential().build()) + "vc", List.of(createDataExchangeGovernanceCredential().build()) )); var result = function.evaluate("FrameworkAgreement", Operator.EQ, ":1.0.0", null, context); diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/PcfCredential.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/PcfCredential.java deleted file mode 100644 index 37ee475003..0000000000 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/framework/PcfCredential.java +++ /dev/null @@ -1,64 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * 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. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -package org.eclipse.tractusx.edc.policy.cx.framework; - -public interface PcfCredential { - - String PCF_VP = """ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1" - ], - "type": "VerifiablePresentation", - "verifiableCredential": [ - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://w3id.org/2023/catenax/credentials/usecase/v1" - ], - "id": "urn:uuid:12345678-1234-1234-1234-123456789abc", - "type": [ - "VerifiableCredential", - "PcfCredential" - ], - "issuer": "did:web:issuer.example.com", - "issuanceDate": "2023-06-02T12:00:00Z", - "expirationDate": "2022-06-16T18:56:59Z", - "credentialSubject": { - "id": "did:web:example.com", - "holderIdentifier": "BPN of holder", - "usecaseAgreement": { - "value": "PCF", - "type": "PcfAgreement", - "contractTemplate": "https://public.catena-x.org/contracts/pcf.v1.pdf", - "contractVersion": "1.0.0" - } - }, - "proof": { - "type": "Ed25519Signature2018", - "created": "2023-06-02T12:00:00Z", - "proofPurpose": "assertionMethod", - "verificationMethod": "did:web:issuer.example.com#key-1", - "jws": "xxx" - } - } - ] - }"""; -} diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/membership/MembershipCredentialConstraintFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/membership/MembershipCredentialConstraintFunctionTest.java index f40d39e55e..8e5008c188 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/membership/MembershipCredentialConstraintFunctionTest.java +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/membership/MembershipCredentialConstraintFunctionTest.java @@ -30,8 +30,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_2025_09_NS; +import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createDataExchangeGovernanceCredential; import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createMembershipCredential; -import static org.eclipse.tractusx.edc.policy.cx.CredentialFunctions.createPcfCredential; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -102,7 +102,7 @@ void evaluate_whenSingleCredentialFound() { void evaluate_whenMultipleCredentialsFound() { when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createMembershipCredential().build(), createMembershipCredential().build(), - createPcfCredential().build()))); + createDataExchangeGovernanceCredential().build()))); var result = function.evaluate(CX_POLICY_2025_09_NS + "Membership", Operator.EQ, "active", null, context); @@ -111,7 +111,7 @@ void evaluate_whenMultipleCredentialsFound() { @Test void evaluate_whenCredentialNotFound() { - when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createPcfCredential().build()))); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of(createDataExchangeGovernanceCredential().build()))); var result = function.evaluate(CX_POLICY_2025_09_NS + "Membership", Operator.EQ, "active", null, context); diff --git a/edc-extensions/dcp/tx-dcp/build.gradle.kts b/edc-extensions/dcp/tx-dcp/build.gradle.kts index 00e1098768..5652a5068c 100644 --- a/edc-extensions/dcp/tx-dcp/build.gradle.kts +++ b/edc-extensions/dcp/tx-dcp/build.gradle.kts @@ -36,4 +36,5 @@ dependencies { implementation(project(":core:core-utils")) testImplementation(libs.edc.junit) + testImplementation(project(":edc-extensions:cx-policy-legacy")) } diff --git a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java index 1870d21c99..434a9751b3 100644 --- a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java +++ b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java @@ -1,6 +1,7 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * Copyright (c) 2025 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * Copyright (c) 2025 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -36,6 +37,7 @@ import static java.util.Collections.emptySet; import static org.eclipse.tractusx.edc.TxIatpConstants.CREDENTIAL_TYPE_NAMESPACE; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_2025_09_NS; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_NS; /** * Extract credentials from the policy constraints @@ -44,7 +46,8 @@ * The left operand should be bound to the namespace {@link org.eclipse.tractusx.edc.edr.spi.CoreConstants#CX_CREDENTIAL_NS} */ public class CredentialScopeExtractor implements ScopeExtractor { - public static final String FRAMEWORK_CREDENTIAL_PREFIX = "FrameworkAgreement"; + public static final String FRAMEWORK_AGREEMENT_LEFT_OPERAND = "FrameworkAgreement"; + public static final String DATA_EXCHANGE_GOVERNANCE = "DataExchangeGovernance"; public static final String SCOPE_FORMAT = "%s:%s:read"; public static final String CREDENTIAL_FORMAT = "%sCredential"; @@ -52,16 +55,7 @@ public class CredentialScopeExtractor implements ScopeExtractor { private static final Set CATENA_X_CREDENTIALS = Set.of( "MembershipCredential", "BpnCredential", - "FrameworkAgreementCredential", "DismantlerCredential", - "TraceabilityCredential", - "PcfCredential", - "QualityCredential", - "CircularEconomyCredential", - "DemandCapacityCredential", - "PurisCredential", - "BusinessPartnerCredential", - "BehavioralTwinCredential", "DataExchangeGovernanceCredential" ); @@ -77,9 +71,16 @@ public Set extractScopes(Object leftValue, Operator operator, Object rig if (requestContext != null) { - if (leftValue instanceof String leftOperand && leftOperand.startsWith(CX_POLICY_2025_09_NS) && isMessageSupported(requestContext)) { - leftOperand = leftOperand.replace(CX_POLICY_2025_09_NS, ""); - var credentialType = extractCredentialType(leftOperand, rightValue); + if (leftValue instanceof String leftOperand && isMessageSupported(requestContext)) { + if (leftOperand.startsWith(CX_POLICY_2025_09_NS)) { + leftOperand = leftOperand.replace(CX_POLICY_2025_09_NS, ""); + } else if (leftOperand.startsWith(CX_POLICY_NS)) { + leftOperand = leftOperand.replace(CX_POLICY_NS, ""); + } else { + return emptySet(); + } + + var credentialType = extractCredentialType(leftOperand); var scope = SCOPE_FORMAT.formatted(CREDENTIAL_TYPE_NAMESPACE, CREDENTIAL_FORMAT.formatted(capitalize(credentialType))); if (isSupportedScope(scope)) { return Set.of(scope); @@ -110,30 +111,8 @@ private boolean isMessageSupported(RequestContext ctx) { .orElse(false); } - /** - * Possible values for credential: - *

    - *
  • FrameworkAgreement -> subtype is encoded in rightValue, return subtype from rightOperand
  • - *
  • FrameworkAgreement.[subtype] -> return subtype
  • - *
  • Dismantler -> return "Dismantler"
  • - *
  • Dismantler.[expr] -> return "Dismantler"
  • - *
  • Membership -> return "Membership"
  • - *
- */ - private String extractCredentialType(String leftOperand, Object rightValue) { - if (leftOperand.equals(FRAMEWORK_CREDENTIAL_PREFIX)) { //this is the "new" notation, where the subtype is encoded in the right operand - var rightOperand = rightValue.toString(); - var ix = rightOperand.indexOf(":"); - return ix > 0 ? rightOperand.substring(0, ix) : rightOperand; - } - // for FrameworkAgreement.xyz we need the "xyz" part - if (leftOperand.startsWith(FRAMEWORK_CREDENTIAL_PREFIX + ".")) { - leftOperand = leftOperand.replace(FRAMEWORK_CREDENTIAL_PREFIX + ".", ""); - } else { //for all others, e.g. Dismantler.activityType, we only need the "Dismantler" part - var ix = leftOperand.indexOf("."); - leftOperand = ix > 0 ? leftOperand.substring(0, ix) : leftOperand; - } - return leftOperand; + private String extractCredentialType(String leftOperand) { + return leftOperand.equals(FRAMEWORK_AGREEMENT_LEFT_OPERAND) ? DATA_EXCHANGE_GOVERNANCE : leftOperand; } private String capitalize(String input) { diff --git a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractorTest.java b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractorTest.java index b6b2813838..21244a4451 100644 --- a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractorTest.java +++ b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractorTest.java @@ -1,6 +1,7 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * Copyright (c) 2025 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * Copyright (c) 2025 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -46,13 +47,20 @@ import java.util.stream.Stream; +import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.edc.TxIatpConstants.CREDENTIAL_TYPE_NAMESPACE; -import static org.eclipse.tractusx.edc.iam.iatp.scope.CredentialScopeExtractor.FRAMEWORK_CREDENTIAL_PREFIX; +import static org.eclipse.tractusx.edc.iam.iatp.scope.CredentialScopeExtractor.FRAMEWORK_AGREEMENT_LEFT_OPERAND; +import static org.eclipse.tractusx.edc.policy.cx.legacy.common.AbstractDynamicCredentialConstraintFunction.ACTIVE; +import static org.eclipse.tractusx.edc.policy.cx.legacy.dismantler.DismantlerCredentialConstraintFunction.DISMANTLER_LITERAL; +import static org.eclipse.tractusx.edc.policy.cx.legacy.membership.MembershipCredentialConstraintFunction.MEMBERSHIP_LITERAL; import static org.mockito.Mockito.mock; public class CredentialScopeExtractorTest { + private static final String DATA_EXCHANGE_GOVERNANCE_RIGHT_VALUE = "DataExchangeGovernance:1.0"; + private static final String DATA_EXCHANGE_GOVERNANCE_CREDENTIAL = "DataExchangeGovernanceCredential"; + private final Monitor monitor = mock(); private CredentialScopeExtractor extractor; @@ -68,9 +76,9 @@ void verify_extractScopes(RemoteMessage message) { var requestContext = RequestContext.Builder.newInstance().message(message).direction(RequestContext.Direction.Egress).build(); var ctx = new TestRequestPolicyContext(requestContext, null); - var scopes = extractor.extractScopes(CoreConstants.CX_POLICY_2025_09_NS + FRAMEWORK_CREDENTIAL_PREFIX + ".pcf", null, null, ctx); + var scopes = extractor.extractScopes(CoreConstants.CX_POLICY_2025_09_NS + FRAMEWORK_AGREEMENT_LEFT_OPERAND, Operator.EQ, DATA_EXCHANGE_GOVERNANCE_RIGHT_VALUE, ctx); - assertThat(scopes).contains(CREDENTIAL_TYPE_NAMESPACE + ":PcfCredential:read"); + assertThat(scopes).contains(format("%s:%s:read", CREDENTIAL_TYPE_NAMESPACE, DATA_EXCHANGE_GOVERNANCE_CREDENTIAL)); } @DisplayName("Scope extractor with not supported messages") @@ -80,20 +88,31 @@ void verify_extractScopes_isEmpty_whenNotSupportedMessages(RemoteMessage message var requestContext = RequestContext.Builder.newInstance().message(message).direction(RequestContext.Direction.Egress).build(); var ctx = new TestRequestPolicyContext(requestContext, null); - var scopes = extractor.extractScopes(CoreConstants.CX_POLICY_2025_09_NS + FRAMEWORK_CREDENTIAL_PREFIX + ".pfc", null, null, ctx); + var scopes = extractor.extractScopes(CoreConstants.CX_POLICY_2025_09_NS + FRAMEWORK_AGREEMENT_LEFT_OPERAND, Operator.EQ, DATA_EXCHANGE_GOVERNANCE_RIGHT_VALUE, ctx); assertThat(scopes).isEmpty(); } @Test void verify_extractScopes_isEmpty_whenLeftOperandDoesNotMapToCredential() { - var ctx = new TestRequestPolicyContext(null, null); + var ctx = new TestRequestPolicyContext(requestContextWithSupportedMessage(), null); var scopes = extractor.extractScopes(CoreConstants.CX_POLICY_2025_09_NS + "UsagePurpose", Operator.IS_ANY_OF, "cx.pcf.base:1", ctx); assertThat(scopes).isEmpty(); } + @DisplayName("Scope extractor with legacy constraints") + @ParameterizedTest(name = "{1}") + @ArgumentsSource(LegacyConstraints.class) + void verify_extractScopes_withLegacyConstraint(String leftOperand, Operator operator, String rightOperand, String credentialType) { + var ctx = new TestRequestPolicyContext(requestContextWithSupportedMessage(), null); + + var scopes = extractor.extractScopes(CoreConstants.CX_POLICY_NS + leftOperand, operator, rightOperand, ctx); + + assertThat(scopes).contains(format("%s:%s:read", CREDENTIAL_TYPE_NAMESPACE, credentialType)); + } + @Test void verify_extractScope_Empty() { var ctx = new TestRequestPolicyContext(null, null); @@ -103,6 +122,13 @@ void verify_extractScope_Empty() { assertThat(scopes).isEmpty(); } + private RequestContext requestContextWithSupportedMessage() { + return RequestContext.Builder.newInstance() + .message(TransferRequestMessage.Builder.newInstance().callbackAddress("cb").build()) + .direction(RequestContext.Direction.Egress) + .build(); + } + private static class SupportedMessages implements ArgumentsProvider { @Override public Stream provideArguments(ExtensionContext extensionContext) { @@ -126,6 +152,17 @@ public Stream provideArguments(ExtensionContext extensionCo } } + private static class LegacyConstraints implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + Arguments.of(FRAMEWORK_AGREEMENT_LEFT_OPERAND, Operator.EQ, DATA_EXCHANGE_GOVERNANCE_RIGHT_VALUE, DATA_EXCHANGE_GOVERNANCE_CREDENTIAL), + Arguments.of(DISMANTLER_LITERAL, Operator.EQ, ACTIVE, "DismantlerCredential"), + Arguments.of(MEMBERSHIP_LITERAL, Operator.EQ, ACTIVE, "MembershipCredential") + ); + } + } + private static class TestRequestPolicyContext extends RequestPolicyContext { protected TestRequestPolicyContext(RequestContext requestContext, RequestScope.Builder requestScopeBuilder) { diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/IatpParticipant.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/IatpParticipant.java index 7f41409f4a..b7b29b9188 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/IatpParticipant.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/IatpParticipant.java @@ -114,7 +114,6 @@ private List issueCredentials(DataspaceIssuer issu return List.of( issuer.issueMembershipCredential(getDid(), getBpn()), issuer.issueDismantlerCredential(getDid(), getBpn()), - issuer.issueFrameworkCredential(getDid(), getBpn(), "PcfCredential"), issuer.issueFrameworkCredential(getDid(), getBpn(), "BpnCredential"), issuer.issueFrameworkCredential(getDid(), getBpn(), "DataExchangeGovernanceCredential") ); diff --git a/edc-tests/runtime/iatp/iatp-extensions/src/main/resources/cx-credentials-context.json b/edc-tests/runtime/iatp/iatp-extensions/src/main/resources/cx-credentials-context.json index dd90f37457..c12e9a11a4 100644 --- a/edc-tests/runtime/iatp/iatp-extensions/src/main/resources/cx-credentials-context.json +++ b/edc-tests/runtime/iatp/iatp-extensions/src/main/resources/cx-credentials-context.json @@ -5,9 +5,6 @@ "cred": "https://www.w3.org/2018/credentials#", "xsd": "http://www.w3.org/2001/XMLSchema#", "cx-credentials": "https://w3id.org/catenax/credentials/", - "BehavioralTwinCredential": { - "@id": "cx-credentials:BehavioralTwinCredential" - }, "BpnCredential": { "@id": "cx-credentials:BpnCredential" }, @@ -17,21 +14,6 @@ "MembershipCredential": { "@id": "cx-credentials:MembershipCredential" }, - "PcfCredential": { - "@id": "cx-credentials:PcfCredential" - }, - "ResiliencyCredential": { - "@id": "cx-credentials:ResiliencyCredential" - }, - "QualityCredential": { - "@id": "cx-credentials:QualityCredential" - }, - "SustainabilityCredential": { - "@id": "cx-credentials:SustainabilityCredential" - }, - "TraceabilityCredential": { - "@id": "cx-credentials:TraceabilityCredential" - }, "activityType": { "@id": "cx-credentials:activityType", "@type": "xsd:string" From ca81f02afc51bdbbd1841e067e0e74a43961b7b5 Mon Sep 17 00:00:00 2001 From: Andrii Yurkevych Date: Wed, 26 Nov 2025 16:59:14 +0100 Subject: [PATCH 015/112] fix: OpenAPI spec - latest release (#2427) --- .github/workflows/publish-new-snapshot.yaml | 1 + .github/workflows/publish-openapi-ui.yml | 12 +++++++++++- .github/workflows/release.yml | 9 ++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-new-snapshot.yaml b/.github/workflows/publish-new-snapshot.yaml index b4f426da03..c58f55582c 100644 --- a/.github/workflows/publish-new-snapshot.yaml +++ b/.github/workflows/publish-new-snapshot.yaml @@ -123,6 +123,7 @@ jobs: secrets: inherit with: version: ${{ needs.determine-version.outputs.VERSION }} + latest: false publish-latest-versioned-snapshot: name: "Publish latest versioned snapshot in GitHub Pages" diff --git a/.github/workflows/publish-openapi-ui.yml b/.github/workflows/publish-openapi-ui.yml index f674ec4584..b261747b00 100644 --- a/.github/workflows/publish-openapi-ui.yml +++ b/.github/workflows/publish-openapi-ui.yml @@ -28,6 +28,11 @@ on: required: false description: "Version of the Tractus-X EDC API to be should be published" type: string + latest: + description: "Latest release" + required: true + type: boolean + default: false workflow_call: inputs: @@ -35,6 +40,11 @@ on: required: false description: "Version of the Tractus-X EDC API to be should be published" type: string + latest: + description: "Latest release" + required: true + type: boolean + default: false jobs: generate-openapi-spec: @@ -101,7 +111,7 @@ jobs: - name: Generate Swagger UI stable version uses: Legion2/swagger-ui-action@v1 - if: ${{ !endsWith( env.VERSION, '-SNAPSHOT') }} + if: ${{ inputs.latest }} with: output: dist spec-file: ${{ matrix.apiGroup.name }}.yaml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f42a7da69..c24033babe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,12 @@ run-name: "Release from ${{ github.ref_name }}" on: workflow_dispatch: + inputs: + latest: + description: "Latest release" + required: true + type: boolean + default: false jobs: @@ -165,7 +171,7 @@ jobs: generateReleaseNotes: true tag: ${{ needs.validation.outputs.RELEASE_VERSION }} token: ${{ secrets.GITHUB_TOKEN }} - makeLatest: ${{ needs.validation.outputs.update_main_branch_version == 'true' }} + makeLatest: ${{ inputs.latest }} removeArtifacts: true # Release: Publish specs to GitHub Pages @@ -178,6 +184,7 @@ jobs: secrets: inherit with: version: ${{ needs.validation.outputs.RELEASE_VERSION }} + latest: ${{ inputs.latest }} # Release: Update Release Notes with Allure Report Link publish-allure-report-link-to-release: From 19d21936ff702ac653b662b2f60afc5cab10989c Mon Sep 17 00:00:00 2001 From: Mathias Moser Date: Wed, 26 Nov 2025 17:13:17 +0100 Subject: [PATCH 016/112] chore: remove outdated context URL from JSON example (#2406) * chore: remove outdated context URL from JSON example Removed obsolete context URL from JSON example. * Remove an additional superfluous line --------- Co-authored-by: Lars Geyer-Blaumeiser --- docs/usage/management-api-walkthrough/04_catalog.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/usage/management-api-walkthrough/04_catalog.md b/docs/usage/management-api-walkthrough/04_catalog.md index f38acfdc6a..78246eab15 100644 --- a/docs/usage/management-api-walkthrough/04_catalog.md +++ b/docs/usage/management-api-walkthrough/04_catalog.md @@ -73,8 +73,6 @@ Content-Type: application/json ```json { "@context": [ - "https://w3id.org/catenax/2025/9/policy/odrl.jsonld", - "https://w3id.org/catenax/2025/9/policy/context.jsonld", { "@vocab":"https://w3id.org/edc/v0.0.1/ns/" } From 4e6fffa1ffa33416ae81dfec91f3c20df76afb68 Mon Sep 17 00:00:00 2001 From: Andrii Yurkevych Date: Wed, 26 Nov 2025 17:20:52 +0100 Subject: [PATCH 017/112] fix: Make a draft release with a failed dependency check (#2426) --- .github/workflows/draft-release.yaml | 26 ++++++++++---- .../generate-and-publish-dependencies.yaml | 35 +++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/.github/workflows/draft-release.yaml b/.github/workflows/draft-release.yaml index 2a9ef9b360..1b2260520a 100644 --- a/.github/workflows/draft-release.yaml +++ b/.github/workflows/draft-release.yaml @@ -110,12 +110,6 @@ jobs: - name: Create Release branch run: git checkout -b ${{ needs.validate-and-prepare.outputs.branch_name }} - uses: ./.github/actions/setup-java - - name: Check dependencies before release - uses: ./.github/actions/generate-and-check-dependencies - with: - run: ${{ needs.validate-and-prepare.outputs.is_official_release == true && 'strict' || 'standard' }} - - name: Replace published DEPENDENCIES file link in NOTICE with the one just created - run: sed -i "s#\[DEPENDENCIES\]\(.*\)#\[DEPENDENCIES\]\(DEPENDENCIES\)#g" NOTICE.md - name: Version and Chart Updates uses: ./.github/actions/update-version-and-charts with: @@ -123,3 +117,23 @@ jobs: bump_version: "false" - name: Push new branch run: git push origin ${{ needs.validate-and-prepare.outputs.branch_name }} + - name: Check dependencies before release + uses: ./.github/actions/generate-and-check-dependencies + with: + run: ${{ needs.validate-and-prepare.outputs.is_official_release == 'true' && 'strict' || 'standard' }} + - name: Replace published DEPENDENCIES file link in NOTICE with the one just created + run: sed -i "s#\[DEPENDENCIES\]\(.*\)#\[DEPENDENCIES\]\(DEPENDENCIES\)#g" NOTICE.md + - name: Commit DEPENDENCIES changes + shell: bash + run: | + if git diff --quiet -- DEPENDENCIES; then + echo "No changes in DEPENDENCIES, skipping commit." + exit 0 + fi + + git add DEPENDENCIES + git config user.name "eclipse-tractusx-bot" + git config user.email "tractusx-bot@eclipse.org" + git commit --message "Update DEPENDENCIES file" + git push origin ${{ needs.validate-and-prepare.outputs.branch_name }} + echo "commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/generate-and-publish-dependencies.yaml b/.github/workflows/generate-and-publish-dependencies.yaml index 2bbff80f70..907dcee055 100644 --- a/.github/workflows/generate-and-publish-dependencies.yaml +++ b/.github/workflows/generate-and-publish-dependencies.yaml @@ -25,6 +25,7 @@ on: push: branches: - main + - release/* permissions: contents: write @@ -35,15 +36,49 @@ jobs: steps: - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java + - name: Output release type + id: release_type + shell: bash + run: | + VERSION=$(grep "version" gradle.properties | awk -F= '{print $2}') + IFS=.- read -r MAJOR MINOR PATCH SNAPSHOT<<<"$VERSION" + + if [[ ! -z $SNAPSHOT ]] then + echo "is_official_release=false" >> "$GITHUB_OUTPUT" + else + echo "is_official_release=true" >> "$GITHUB_OUTPUT" + fi - uses: ./.github/actions/generate-and-check-dependencies + with: + run: ${{ steps.release_type.outputs.is_official_release == 'true' && 'strict' || 'standard' }} - name: Prepare to publish + if: ${{ github.ref_name == 'main' }} shell: bash run: | mkdir public cp DEPENDENCIES public/ - name: Publish to GitHub Pages + if: ${{ github.ref_name == 'main' }} uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: public keep_files: true + - name: Replace published DEPENDENCIES file link in NOTICE with the one just created + if: ${{ startsWith(github.ref_name, 'release/') }} + run: sed -i "s#\[DEPENDENCIES\]\(.*\)#\[DEPENDENCIES\]\(DEPENDENCIES\)#g" NOTICE.md + - name: Commit DEPENDENCIES changes + if: ${{ startsWith(github.ref_name, 'release/') }} + shell: bash + run: | + if git diff --quiet -- DEPENDENCIES; then + echo "No changes in DEPENDENCIES, skipping commit." + exit 0 + fi + + git add DEPENDENCIES + git config user.name "eclipse-tractusx-bot" + git config user.email "tractusx-bot@eclipse.org" + git commit --message "Update DEPENDENCIES file" + git push origin ${{ github.ref_name }} + echo "commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT From 1464f9332d5ca950e40e68fb445f1920b51fcba4 Mon Sep 17 00:00:00 2001 From: andrea bertagnolli Date: Thu, 27 Nov 2025 10:49:30 +0100 Subject: [PATCH 018/112] fix: remove asSequence call on downloadOpenapi (#2432) --- build.gradle.kts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f692f14375..5c90a1808f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -207,10 +207,9 @@ subprojects { .resolve("docs").resolve("openapi") configurations.asMap.values - .asSequence() .filter { it.isCanBeResolved } - .map { it.resolvedConfiguration.firstLevelModuleDependencies }.flatten() - .map { childrenDependencies(it) }.flatten() + .flatMap { it.resolvedConfiguration.firstLevelModuleDependencies } + .flatMap { childrenDependencies(it) } .distinct() .forEach { dep -> downloadYamlArtifact(dep, "management-api", destinationDirectory) @@ -241,7 +240,7 @@ tasks.register("aggregateAllureResults") { fun childrenDependencies(dependency: ResolvedDependency): List { - return listOf(dependency) + dependency.children.map { child -> childrenDependencies(child) }.flatten() + return listOf(dependency) + dependency.children.flatMap { child -> childrenDependencies(child) } } fun downloadYamlArtifact(dep: ResolvedDependency, classifier: String, destinationDirectory: java.nio.file.Path) { From d254a82dfa7f41c78f20303ae1a3996a07b6b1ea Mon Sep 17 00:00:00 2001 From: Konrad Geller <45310290+konradge@users.noreply.github.com> Date: Fri, 28 Nov 2025 09:25:53 +0100 Subject: [PATCH 019/112] fix: add missing comma to dspace.jsonld (#2431) --- core/json-ld-core/src/main/resources/document/dspace.jsonld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/json-ld-core/src/main/resources/document/dspace.jsonld b/core/json-ld-core/src/main/resources/document/dspace.jsonld index f475b23504..f26eaff028 100644 --- a/core/json-ld-core/src/main/resources/document/dspace.jsonld +++ b/core/json-ld-core/src/main/resources/document/dspace.jsonld @@ -55,7 +55,7 @@ "odrl:leftOperand": { "@type": "@id" }, "odrl:operator": { "@type": "@id" }, "odrl:rightOperandReference": { "@type": "@id" }, - "odrl:profile": { "@container": "@set" } + "odrl:profile": { "@container": "@set" }, "odrl:assigner": { "@type": "@id" }, "odrl:assignee": { "@type": "@id" } } From 2e95b3fd4d2ca55c8ce4ed0c7eeb5406c55cb832 Mon Sep 17 00:00:00 2001 From: andrea bertagnolli Date: Fri, 28 Nov 2025 11:30:14 +0100 Subject: [PATCH 020/112] refactor: set baseline schema for database migrations (#2425) * refactor: set baseline schema for database migrations * refactor: use specific tables for migrations * refactor: merge into a single migration module --- .../build.gradle.kts | 10 +- .../build.gradle.kts | 7 +- .../migrations/connector-migration/README.md | 4 + .../connector-migration/build.gradle.kts | 44 +++ .../ConnectorPostgresqlMigration.java | 79 +++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 21 ++ .../V1_0_0__Connector_Schema_Init.sql | 292 ++++++++++++++++++ .../ConnectorPostgresqlMigrationTest.java | 149 +++++++++ .../connector/V1_0_1__Add_Dummy_Column.sql | 1 + ...ementBpnsPostgresqlMigrationExtension.java | 1 + ...etirementPostgresqlMigrationExtension.java | 1 + .../AssetPostgresqlMigrationExtension.java | 1 + ...sinessGroupPostgresMigrationExtension.java | 1 + ...efinitionPostgresqlMigrationExtension.java | 1 + ...gotiationPostgresqlMigrationExtension.java | 1 + ...eInstancePostgresqlMigrationExtension.java | 1 + .../EdrIndexPostgresqlMigrationExtension.java | 1 + ...deratedCatalogCacheMigrationExtension.java | 1 + ...alidationPostgresqlMigrationExtension.java | 1 + ...cyMonitorPostgresqlMigrationExtension.java | 1 + .../PolicyPostgresqlMigrationExtension.java | 1 + ...erProcessPostgresqlMigrationExtension.java | 1 + .../data-plane-migration/build.gradle.kts | 1 + ...TokenDataPostgresqlMigrationExtension.java | 1 + ...DataPlanePostgresqlMigrationExtension.java | 3 +- .../AbstractPostgresqlMigrationExtension.java | 12 +- .../DatabaseMigrationConfiguration.java | 80 +++++ settings.gradle.kts | 1 + 28 files changed, 705 insertions(+), 13 deletions(-) create mode 100644 edc-extensions/migrations/connector-migration/README.md create mode 100644 edc-extensions/migrations/connector-migration/build.gradle.kts create mode 100644 edc-extensions/migrations/connector-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigration.java create mode 100644 edc-extensions/migrations/connector-migration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_0_0__Connector_Schema_Init.sql create mode 100644 edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java create mode 100644 edc-extensions/migrations/connector-migration/src/test/resources/migrations/connector/V1_0_1__Add_Dummy_Column.sql create mode 100644 edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DatabaseMigrationConfiguration.java diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts index 86ffa36730..0b878217a6 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts @@ -37,11 +37,11 @@ dependencies { runtimeOnly(libs.edc.bom.controlplane.feature.sql) runtimeOnly(libs.edc.bom.federatedcatalog.feature.sql) - runtimeOnly(project(":edc-extensions:agreements:retirement-evaluation-store-sql")) - runtimeOnly(project(":edc-extensions:agreements-bpns:bpns-evaluation-store-sql")) - runtimeOnly(project(":edc-extensions:bpn-validation:business-partner-store-sql")) - runtimeOnly(project(":edc-extensions:edr:edr-index-lock-sql")) - runtimeOnly(project(":edc-extensions:migrations::control-plane-migration")) + implementation(project(":edc-extensions:agreements:retirement-evaluation-store-sql")) + implementation(project(":edc-extensions:agreements-bpns:bpns-evaluation-store-sql")) + implementation(project(":edc-extensions:bpn-validation:business-partner-store-sql")) + implementation(project(":edc-extensions:edr:edr-index-lock-sql")) + implementation(project(":edc-extensions:migrations:connector-migration")) runtimeOnly(libs.edc.vault.hashicorp) } diff --git a/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts index e60452ea76..c799f9cf1d 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts @@ -24,13 +24,10 @@ plugins { } dependencies { - implementation(project(":edc-dataplane:edc-dataplane-base")) - runtimeOnly(libs.edc.bom.dataplane.feature.sql) - - runtimeOnly(project(":edc-extensions:migrations::data-plane-migration")) - runtimeOnly(libs.edc.vault.hashicorp) + implementation(project(":edc-dataplane:edc-dataplane-base")) + implementation(project(":edc-extensions:migrations:connector-migration")) } tasks.shadowJar { diff --git a/edc-extensions/migrations/connector-migration/README.md b/edc-extensions/migrations/connector-migration/README.md new file mode 100644 index 0000000000..26508902f7 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/README.md @@ -0,0 +1,4 @@ +# Postgresql SQL Migration Extension + +This extensions manages database schema migrations for a Connector in which control-plane and data-plane are using the +same schema (as in the official [`tractusx-connector`](../../../charts/tractusx-connector) chart). diff --git a/edc-extensions/migrations/connector-migration/build.gradle.kts b/edc-extensions/migrations/connector-migration/build.gradle.kts new file mode 100644 index 0000000000..d22d73eea4 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/build.gradle.kts @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2025 Think-it GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `maven-publish` + `java-library` +} + +dependencies { + implementation(project(":edc-extensions:migrations:postgresql-migration-lib")) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.transaction.datasource) + implementation(libs.edc.sql.assetindex) + implementation(libs.edc.lib.sql) + runtimeOnly(libs.postgres) + + implementation(libs.flyway.core) + // starting from flyway 10, they've moved to a more modular structure, + // so we need to add PG support explicitly + // https://documentation.red-gate.com/flyway/release-notes-and-older-versions/release-notes-for-flyway-engine + runtimeOnly(libs.flyway.database.postgres) + + testImplementation(libs.edc.junit) + testImplementation(testFixtures(libs.edc.sql.test.fixtures)) + testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) + testImplementation(project(":edc-extensions:migrations:control-plane-migration")) + testImplementation(project(":edc-extensions:migrations:data-plane-migration")) +} diff --git a/edc-extensions/migrations/connector-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigration.java b/edc-extensions/migrations/connector-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigration.java new file mode 100644 index 0000000000..9da25d04ea --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigration.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025 Think-it GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.postgresql.migration.connector; + +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.persistence.EdcPersistenceException; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.tractusx.edc.postgresql.migration.DatabaseMigrationConfiguration; +import org.flywaydb.core.Flyway; + +import static org.eclipse.tractusx.edc.postgresql.migration.connector.ConnectorPostgresqlMigration.NAME; +import static org.flywaydb.core.api.MigrationVersion.LATEST; + +@Extension(NAME) +public class ConnectorPostgresqlMigration implements ServiceExtension { + + public static final String NAME = "Connector Postgresql Schema Migration"; + + @Configuration + private DatabaseMigrationConfiguration configuration; + + @Inject + private Monitor monitor; + + @Override + public String name() { + return NAME; + } + + @Override + public void prepare() { + if (!configuration.enabled()) { + monitor.info("Migration for connector disabled"); + return; + } + + var dataSource = configuration.getDataSource(); + + var flyway = Flyway.configure() + .baselineVersion("1.0.0") + .baselineOnMigrate(true) + .failOnMissingLocations(true) + .dataSource(dataSource) + .table("flyway_schema_history") + .locations("classpath:migrations/connector") + .defaultSchema(configuration.schema()) + .target(LATEST) + .load(); + + var migrateResult = flyway.migrate(); + + if (!migrateResult.success) { + throw new EdcPersistenceException( + "Migrating connector failed: %s".formatted(String.join(", ", migrateResult.warnings)) + ); + } + } + +} diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/migrations/connector-migration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..f9b1612ae1 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,21 @@ +################################################################################# +# Copyright (c) 2025 Think-it GmbH +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# 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. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################# + + +org.eclipse.tractusx.edc.postgresql.migration.connector.ConnectorPostgresqlMigration diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_0_0__Connector_Schema_Init.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_0_0__Connector_Schema_Init.sql new file mode 100644 index 0000000000..f537064328 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_0_0__Connector_Schema_Init.sql @@ -0,0 +1,292 @@ +CREATE TABLE edc_agreement_retirement ( + contract_agreement_id character varying NOT NULL, + reason text NOT NULL, + agreement_retirement_date bigint NOT NULL +); + +CREATE TABLE edc_asset ( + asset_id character varying(255) NOT NULL, + created_at bigint, + properties json, + private_properties json, + data_address json +); + +CREATE TABLE edc_business_partner_group ( + bpn character varying NOT NULL, + groups json DEFAULT '[]'::json NOT NULL +); + +CREATE TABLE edc_contract_agreement ( + agr_id character varying NOT NULL, + provider_agent_id character varying(255), + consumer_agent_id character varying(255), + signing_date bigint, + start_date bigint, + end_date integer, + asset_id character varying(255) NOT NULL, + policy_id character varying(255), + serialized_policy text, + policy json +); + +CREATE TABLE edc_contract_agreement_bpns ( + agreement_id character varying NOT NULL, + provider_bpn character varying(255) NOT NULL, + consumer_bpn character varying(255) NOT NULL +); + +CREATE TABLE edc_contract_definitions ( + contract_definition_id character varying(255) NOT NULL, + assets_selector json NOT NULL, + access_policy_id character varying(255) DEFAULT NULL::character varying NOT NULL, + contract_policy_id character varying(255) DEFAULT NULL::character varying NOT NULL, + created_at bigint, + private_properties json +); + +CREATE TABLE edc_contract_negotiation ( + id character varying(255) NOT NULL, + correlation_id character varying(255), + counterparty_id character varying(255) NOT NULL, + counterparty_address character varying(255) NOT NULL, + protocol character varying(255) DEFAULT 'ids-multipart'::character varying NOT NULL, + type character varying DEFAULT 0 NOT NULL, + state integer DEFAULT 0 NOT NULL, + state_count integer DEFAULT 0, + state_timestamp bigint, + error_detail text, + agreement_id text, + contract_offers json, + trace_context json, + lease_id character varying(255), + created_at bigint, + updated_at bigint, + callback_addresses json, + pending boolean DEFAULT false, + protocol_messages json DEFAULT '{}'::json +); + +CREATE TABLE edc_data_plane_instance ( + id character varying NOT NULL, + data json, + lease_id character varying +); + +CREATE TABLE edc_edr_entry ( + transfer_process_id character varying NOT NULL, + agreement_id character varying NOT NULL, + asset_id character varying NOT NULL, + provider_id character varying NOT NULL, + contract_negotiation_id character varying, + created_at bigint NOT NULL +); + +CREATE TABLE edc_federated_catalog ( + id character varying NOT NULL, + catalog json, + marked boolean DEFAULT false +); + +CREATE TABLE edc_jti_validation ( + token_id character varying NOT NULL, + expires_at bigint +); + +CREATE TABLE edc_lease ( + leased_by character varying(255) NOT NULL, + leased_at bigint, + lease_duration integer DEFAULT 60000 NOT NULL, + lease_id character varying(255) NOT NULL +); + +CREATE TABLE edc_policy_monitor ( + entry_id character varying NOT NULL, + state integer NOT NULL, + created_at bigint NOT NULL, + updated_at bigint NOT NULL, + state_count integer DEFAULT 0 NOT NULL, + state_time_stamp bigint, + trace_context json, + error_detail character varying, + lease_id character varying, + properties json, + contract_id character varying +); + +CREATE TABLE edc_policydefinitions ( + policy_id character varying(255) NOT NULL, + permissions json, + prohibitions json, + duties json, + extensible_properties json, + inherits_from character varying(255), + assigner character varying(255), + assignee character varying(255), + target character varying(255), + policy_type character varying(255) NOT NULL, + created_at bigint, + private_properties json, + profiles json +); + +CREATE TABLE edc_transfer_process ( + transferprocess_id character varying(255) NOT NULL, + type character varying(255) NOT NULL, + state integer NOT NULL, + state_count integer DEFAULT 0 NOT NULL, + state_time_stamp bigint, + trace_context json, + error_detail text, + resource_manifest json, + provisioned_resource_set json, + lease_id character varying(255), + content_data_address json, + created_at bigint, + deprovisioned_resources json, + updated_at bigint, + private_properties text DEFAULT '{}'::text, + callback_addresses json, + pending boolean DEFAULT false, + transfer_type character varying, + protocol_messages json DEFAULT '{}'::json, + data_plane_id character varying, + correlation_id character varying, + counter_party_address character varying, + protocol character varying, + asset_id character varying, + contract_id character varying, + data_destination json +); + +ALTER TABLE ONLY edc_contract_agreement_bpns + ADD CONSTRAINT contract_agreement_bpns_contract_agreement_id_fk PRIMARY KEY (agreement_id); + +ALTER TABLE ONLY edc_contract_agreement + ADD CONSTRAINT contract_agreement_pk PRIMARY KEY (agr_id); + +ALTER TABLE ONLY edc_contract_negotiation + ADD CONSTRAINT contract_negotiation_pk PRIMARY KEY (id); + +ALTER TABLE ONLY edc_agreement_retirement + ADD CONSTRAINT edc_agreement_retirement_pkey PRIMARY KEY (contract_agreement_id); + +ALTER TABLE ONLY edc_asset + ADD CONSTRAINT edc_asset_pkey PRIMARY KEY (asset_id); + +ALTER TABLE ONLY edc_business_partner_group + ADD CONSTRAINT edc_business_partner_group_pk PRIMARY KEY (bpn); + +ALTER TABLE ONLY edc_contract_definitions + ADD CONSTRAINT edc_contract_definitions_pkey PRIMARY KEY (contract_definition_id); + +ALTER TABLE ONLY edc_data_plane_instance + ADD CONSTRAINT edc_data_plane_instance_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY edc_edr_entry + ADD CONSTRAINT edc_edr_entry_pkey PRIMARY KEY (transfer_process_id); + +ALTER TABLE ONLY edc_federated_catalog + ADD CONSTRAINT edc_federated_catalog_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY edc_jti_validation + ADD CONSTRAINT edc_jti_validation_pkey PRIMARY KEY (token_id); + +ALTER TABLE ONLY edc_policydefinitions + ADD CONSTRAINT edc_policies_pkey PRIMARY KEY (policy_id); + +ALTER TABLE ONLY edc_policy_monitor + ADD CONSTRAINT edc_policy_monitor_pkey PRIMARY KEY (entry_id); + +ALTER TABLE ONLY edc_lease + ADD CONSTRAINT lease_pk PRIMARY KEY (lease_id); + +ALTER TABLE ONLY edc_transfer_process + ADD CONSTRAINT transfer_process_pk PRIMARY KEY (transferprocess_id); + +CREATE INDEX asset_id_index ON edc_edr_entry USING btree (asset_id); + +CREATE UNIQUE INDEX contract_agreement_id_uindex ON edc_contract_agreement USING btree (agr_id); + +CREATE INDEX contract_negotiation_agreement_id_index ON edc_contract_negotiation USING btree (agreement_id); + +CREATE INDEX contract_negotiation_correlationid_index ON edc_contract_negotiation USING btree (correlation_id); + +CREATE UNIQUE INDEX contract_negotiation_id_uindex ON edc_contract_negotiation USING btree (id); + +CREATE INDEX contract_negotiation_lease_id_index ON edc_contract_negotiation USING btree (lease_id); + +CREATE INDEX contract_negotiation_state ON edc_contract_negotiation USING btree (state, state_timestamp); + +CREATE UNIQUE INDEX edc_policies_id_uindex ON edc_policydefinitions USING btree (policy_id); + +CREATE UNIQUE INDEX lease_lease_id_uindex ON edc_lease USING btree (lease_id); + +CREATE INDEX policy_monitor_lease_id_index ON edc_policy_monitor USING btree (lease_id); + +CREATE INDEX policy_monitor_state ON edc_policy_monitor USING btree (state, state_time_stamp); + +CREATE UNIQUE INDEX transfer_process_id_uindex ON edc_transfer_process USING btree (transferprocess_id); + +CREATE INDEX transfer_process_lease_id_index ON edc_transfer_process USING btree (lease_id); + +CREATE INDEX transfer_process_state ON edc_transfer_process USING btree (state, state_time_stamp); + +ALTER TABLE ONLY edc_contract_negotiation + ADD CONSTRAINT contract_negotiation_contract_agreement_id_fk FOREIGN KEY (agreement_id) REFERENCES edc_contract_agreement(agr_id); + +ALTER TABLE ONLY edc_contract_negotiation + ADD CONSTRAINT contract_negotiation_lease_lease_id_fk FOREIGN KEY (lease_id) REFERENCES edc_lease(lease_id) ON DELETE SET NULL; + +ALTER TABLE ONLY edc_data_plane_instance + ADD CONSTRAINT data_plane_instance_lease_id_fk FOREIGN KEY (lease_id) REFERENCES edc_lease(lease_id) ON DELETE SET NULL; + +ALTER TABLE ONLY edc_contract_agreement_bpns + ADD CONSTRAINT edc_contract_agreement_bpns_agreement_id_fkey FOREIGN KEY (agreement_id) REFERENCES edc_contract_agreement(agr_id); + +ALTER TABLE ONLY edc_policy_monitor + ADD CONSTRAINT policy_monitor_lease_lease_id_fk FOREIGN KEY (lease_id) REFERENCES edc_lease(lease_id) ON DELETE SET NULL; + +ALTER TABLE ONLY edc_transfer_process + ADD CONSTRAINT transfer_process_lease_lease_id_fk FOREIGN KEY (lease_id) REFERENCES edc_lease(lease_id) ON DELETE SET NULL; + + +CREATE TABLE edc_accesstokendata ( + id character varying NOT NULL, + claim_token json NOT NULL, + data_address json NOT NULL, + additional_properties json DEFAULT '{}'::json +); + +CREATE TABLE edc_data_plane ( + process_id character varying NOT NULL, + state integer NOT NULL, + created_at bigint NOT NULL, + updated_at bigint NOT NULL, + state_count integer DEFAULT 0 NOT NULL, + state_time_stamp bigint, + trace_context json, + error_detail character varying, + callback_address character varying, + lease_id character varying, + source json, + destination json, + properties json, + flow_type character varying, + transfer_type_destination character varying DEFAULT 'HttpData'::character varying, + runtime_id character varying, + resource_definitions json DEFAULT '[]'::json +); + +ALTER TABLE ONLY edc_accesstokendata + ADD CONSTRAINT edc_accesstokendata_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY edc_data_plane + ADD CONSTRAINT edc_data_plane_pkey PRIMARY KEY (process_id); + +CREATE INDEX data_plane_lease_id ON edc_data_plane USING btree (lease_id); + +CREATE INDEX data_plane_state ON edc_data_plane USING btree (state, state_time_stamp); + +ALTER TABLE ONLY edc_data_plane + ADD CONSTRAINT data_plane_lease_lease_id_fk FOREIGN KEY (lease_id) REFERENCES edc_lease(lease_id) ON DELETE SET NULL; diff --git a/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java b/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java new file mode 100644 index 0000000000..9fd05d350e --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2025 Think-it GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.postgresql.migration.connector; + +import org.eclipse.edc.boot.system.injection.ObjectFactory; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.eclipse.tractusx.edc.postgresql.migration.AbstractPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.AccessTokenDataPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.AgreementBpnsPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.AgreementRetirementPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.AssetPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.BusinessGroupPostgresMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.ContractDefinitionPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.ContractNegotiationPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.DataPlaneInstancePostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.EdrIndexPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.FederatedCatalogCacheMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.JtiValidationPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.PolicyMonitorPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.PolicyPostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.TransferProcessPostgresqlMigrationExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.postgresql.ds.PGSimpleDataSource; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import javax.sql.DataSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.tests.testcontainer.PostgresContainerManager.getPostgresTestContainerName; +import static org.flywaydb.core.api.CoreMigrationType.BASELINE; +import static org.flywaydb.core.api.CoreMigrationType.SQL; +import static org.mockito.Mockito.when; + +@Testcontainers +@ExtendWith(DependencyInjectionExtension.class) +public class ConnectorPostgresqlMigrationTest { + + @Container + private final PostgreSQLContainer postgresql = new PostgreSQLContainer<>(getPostgresTestContainerName()); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of( + "edc.datasource.default.url", postgresql.getJdbcUrl(), + "edc.datasource.default.user", postgresql.getUsername(), + "edc.datasource.default.password", postgresql.getPassword() + ))); + } + + @Test + void shouldRunOnEmptyDatabase(ObjectFactory objectFactory, ServiceExtensionContext context) { + var newMigrations = objectFactory.constructInstance(ConnectorPostgresqlMigration.class); + newMigrations.initialize(context); + newMigrations.prepare(); + + try (var connection = createDataSource().getConnection()) { + var callableStatement = connection.prepareCall("select * from flyway_schema_history;"); + callableStatement.execute(); + var resultSet = callableStatement.getResultSet(); + resultSet.next(); + assertThat(resultSet.getString("version")).isEqualTo("1.0.0"); + assertThat(resultSet.getString("type")).isEqualTo(SQL.toString()); + assertThat(testMigrationHasBeenApplied(connection)).isEqualTo(true); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Test + @Deprecated(since = "0.12.0") + void shouldUseMergedMigrationAsBaseline_whenSchemaAlreadyBuilt(ObjectFactory objectFactory, ServiceExtensionContext context) { + var currentMigrations = List.of( + objectFactory.constructInstance(AssetPostgresqlMigrationExtension.class), + objectFactory.constructInstance(AssetPostgresqlMigrationExtension.class), + objectFactory.constructInstance(ContractDefinitionPostgresqlMigrationExtension.class), + objectFactory.constructInstance(ContractNegotiationPostgresqlMigrationExtension.class), + objectFactory.constructInstance(DataPlaneInstancePostgresqlMigrationExtension.class), + objectFactory.constructInstance(EdrIndexPostgresqlMigrationExtension.class), + objectFactory.constructInstance(FederatedCatalogCacheMigrationExtension.class), + objectFactory.constructInstance(FederatedCatalogCacheMigrationExtension.class), + objectFactory.constructInstance(JtiValidationPostgresqlMigrationExtension.class), + objectFactory.constructInstance(PolicyMonitorPostgresqlMigrationExtension.class), + objectFactory.constructInstance(PolicyPostgresqlMigrationExtension.class), + objectFactory.constructInstance(TransferProcessPostgresqlMigrationExtension.class), + objectFactory.constructInstance(AgreementBpnsPostgresqlMigrationExtension.class), + objectFactory.constructInstance(AgreementRetirementPostgresqlMigrationExtension.class), + objectFactory.constructInstance(BusinessGroupPostgresMigrationExtension.class), + objectFactory.constructInstance(AccessTokenDataPostgresqlMigrationExtension.class), + objectFactory.constructInstance(DataPlaneInstancePostgresqlMigrationExtension.class) + ); + currentMigrations.forEach(e -> e.initialize(context)); + currentMigrations.forEach(AbstractPostgresqlMigrationExtension::prepare); + + var newMigrations = objectFactory.constructInstance(ConnectorPostgresqlMigration.class); + newMigrations.initialize(context); + newMigrations.prepare(); + + try (var connection = createDataSource().getConnection()) { + var callableStatement = connection.prepareCall("select * from flyway_schema_history;"); + callableStatement.execute(); + var resultSet = callableStatement.getResultSet(); + resultSet.next(); + assertThat(resultSet.getString("version")).isEqualTo("1.0.0"); + assertThat(resultSet.getString("type")).isEqualTo(BASELINE.toString()); + assertThat(testMigrationHasBeenApplied(connection)).isEqualTo(true); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + private DataSource createDataSource() { + var dataSource = new PGSimpleDataSource(); + dataSource.setUrl(postgresql.getJdbcUrl()); + dataSource.setUser(postgresql.getUsername()); + dataSource.setPassword(postgresql.getPassword()); + return dataSource; + } + + private boolean testMigrationHasBeenApplied(Connection connection) throws SQLException { + return connection.prepareCall("select dummy_column from edc_policydefinitions;").execute(); + } +} diff --git a/edc-extensions/migrations/connector-migration/src/test/resources/migrations/connector/V1_0_1__Add_Dummy_Column.sql b/edc-extensions/migrations/connector-migration/src/test/resources/migrations/connector/V1_0_1__Add_Dummy_Column.sql new file mode 100644 index 0000000000..f0329f8fb0 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/test/resources/migrations/connector/V1_0_1__Add_Dummy_Column.sql @@ -0,0 +1 @@ +ALTER TABLE edc_policydefinitions ADD dummy_column text; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AgreementBpnsPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AgreementBpnsPostgresqlMigrationExtension.java index fe483f8a92..d5961c59f7 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AgreementBpnsPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AgreementBpnsPostgresqlMigrationExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class AgreementBpnsPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "agreementbpns"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AgreementRetirementPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AgreementRetirementPostgresqlMigrationExtension.java index 0728c58e79..8b46e44cad 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AgreementRetirementPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AgreementRetirementPostgresqlMigrationExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class AgreementRetirementPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "agreementretirement"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AssetPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AssetPostgresqlMigrationExtension.java index a13a7a2e4c..b57b8166ab 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AssetPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AssetPostgresqlMigrationExtension.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class AssetPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "asset"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/BusinessGroupPostgresMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/BusinessGroupPostgresMigrationExtension.java index 585e5a9399..86feceb154 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/BusinessGroupPostgresMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/BusinessGroupPostgresMigrationExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class BusinessGroupPostgresMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME = "bpn"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/ContractDefinitionPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/ContractDefinitionPostgresqlMigrationExtension.java index c2a8f1aef8..72de0d815e 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/ContractDefinitionPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/ContractDefinitionPostgresqlMigrationExtension.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class ContractDefinitionPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "contractdefinition"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/ContractNegotiationPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/ContractNegotiationPostgresqlMigrationExtension.java index efd01f94e4..4a52e0d18a 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/ContractNegotiationPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/ContractNegotiationPostgresqlMigrationExtension.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class ContractNegotiationPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "contractnegotiation"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DataPlaneInstancePostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DataPlaneInstancePostgresqlMigrationExtension.java index 3b3ec3f448..fdbc1e63b1 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DataPlaneInstancePostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DataPlaneInstancePostgresqlMigrationExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class DataPlaneInstancePostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "dataplaneinstance"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/EdrIndexPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/EdrIndexPostgresqlMigrationExtension.java index 1e16364ae0..905d0b79ee 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/EdrIndexPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/EdrIndexPostgresqlMigrationExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class EdrIndexPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "edr"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/FederatedCatalogCacheMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/FederatedCatalogCacheMigrationExtension.java index 4e7b8e90db..f14bedeead 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/FederatedCatalogCacheMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/FederatedCatalogCacheMigrationExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class FederatedCatalogCacheMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "federatedcatalog"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/JtiValidationPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/JtiValidationPostgresqlMigrationExtension.java index f3f875fc48..5b8a04182b 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/JtiValidationPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/JtiValidationPostgresqlMigrationExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class JtiValidationPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "jti-validation"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/PolicyMonitorPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/PolicyMonitorPostgresqlMigrationExtension.java index 265f631e41..184ce208c9 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/PolicyMonitorPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/PolicyMonitorPostgresqlMigrationExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class PolicyMonitorPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "policy-monitor"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/PolicyPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/PolicyPostgresqlMigrationExtension.java index 2cc21812ad..5d95ab4550 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/PolicyPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/PolicyPostgresqlMigrationExtension.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class PolicyPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "policy"; diff --git a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/TransferProcessPostgresqlMigrationExtension.java b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/TransferProcessPostgresqlMigrationExtension.java index d13c096503..f603ff4285 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/TransferProcessPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/control-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/TransferProcessPostgresqlMigrationExtension.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class TransferProcessPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "transferprocess"; diff --git a/edc-extensions/migrations/data-plane-migration/build.gradle.kts b/edc-extensions/migrations/data-plane-migration/build.gradle.kts index 2ae75dfbee..5074a525ff 100644 --- a/edc-extensions/migrations/data-plane-migration/build.gradle.kts +++ b/edc-extensions/migrations/data-plane-migration/build.gradle.kts @@ -38,4 +38,5 @@ dependencies { testImplementation(libs.edc.junit) testImplementation(testFixtures(libs.edc.sql.test.fixtures)) + testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) } diff --git a/edc-extensions/migrations/data-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AccessTokenDataPostgresqlMigrationExtension.java b/edc-extensions/migrations/data-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AccessTokenDataPostgresqlMigrationExtension.java index b62754adfc..034a0ca121 100644 --- a/edc-extensions/migrations/data-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AccessTokenDataPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/data-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AccessTokenDataPostgresqlMigrationExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class AccessTokenDataPostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { private static final String NAME_SUBSYSTEM = "accesstokendata"; diff --git a/edc-extensions/migrations/data-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DataPlanePostgresqlMigrationExtension.java b/edc-extensions/migrations/data-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DataPlanePostgresqlMigrationExtension.java index bcbea63f93..4e1afbf819 100644 --- a/edc-extensions/migrations/data-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DataPlanePostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/data-plane-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DataPlanePostgresqlMigrationExtension.java @@ -19,8 +19,9 @@ package org.eclipse.tractusx.edc.postgresql.migration; +@Deprecated(since = "0.12.0") public class DataPlanePostgresqlMigrationExtension extends AbstractPostgresqlMigrationExtension { - private static final String NAME_SUBSYSTEM = "dataplane"; + public static final String NAME_SUBSYSTEM = "dataplane"; @Override protected String getSubsystemName() { diff --git a/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AbstractPostgresqlMigrationExtension.java b/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AbstractPostgresqlMigrationExtension.java index fe69d44a92..e04c6f917d 100644 --- a/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AbstractPostgresqlMigrationExtension.java +++ b/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/AbstractPostgresqlMigrationExtension.java @@ -35,7 +35,15 @@ import static org.flywaydb.core.api.MigrationVersion.LATEST; -abstract class AbstractPostgresqlMigrationExtension implements ServiceExtension { +/** + * Migration abstraction + * + * @deprecated please use connector-migration module instead. relative flyway_schema_history_* (except + * 'flyway_schema_history' that's the current source of truth) can be eventually removed + * with an additional migration cleanup on the current in-use migration stream + */ +@Deprecated(since = "0.12.0") +public abstract class AbstractPostgresqlMigrationExtension implements ServiceExtension { private static final String DEFAULT_MIGRATION_ENABLED_TEMPLATE = "true"; @Setting(value = "Enable/disables subsystem schema migration", defaultValue = DEFAULT_MIGRATION_ENABLED_TEMPLATE, type = "boolean") @@ -50,7 +58,7 @@ abstract class AbstractPostgresqlMigrationExtension implements ServiceExtension @Override public String name() { - return "Postgresql schema migration for subsystem " + getSubsystemName(); + return "DEPRECATED: Postgresql schema migration for subsystem " + getSubsystemName(); } @Override diff --git a/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DatabaseMigrationConfiguration.java b/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DatabaseMigrationConfiguration.java new file mode 100644 index 0000000000..cd983f42d9 --- /dev/null +++ b/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DatabaseMigrationConfiguration.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2025 Think-it GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.postgresql.migration; + +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; +import org.eclipse.edc.sql.DriverManagerConnectionFactory; +import org.eclipse.edc.sql.datasource.ConnectionFactoryDataSource; + +import java.util.Properties; +import javax.sql.DataSource; + +@Settings +public record DatabaseMigrationConfiguration( + + @Setting( + key = "tx.edc.postgresql.migration.enabled", + description = "Enable/disables data-plane schema migration", + defaultValue = DEFAULT_MIGRATION_ENABLED) + boolean enabled, + + @Setting( + key = "tx.edc.postgresql.migration.schema", + description = "Schema used for the migration", + defaultValue = DEFAULT_MIGRATION_SCHEMA + ) + String schema, + + @Setting( + key = "edc.datasource.default.url", + description = "DataSource JDBC url" + ) + String url, + + @Setting( + key = "edc.datasource.default.user", + description = "DataSource JDBC user" + ) + String user, + + @Setting( + key = "edc.datasource.default.password", + description = "DataSource JDBC password" + ) + String password +) { + private static final String DEFAULT_MIGRATION_ENABLED = "true"; + private static final String DEFAULT_MIGRATION_SCHEMA = "public"; + + /** + * Instance and return DataSource to be passed to Flyway for schema migrations + * + * @return the dataSource. + */ + public DataSource getDataSource() { + var jdbcProperties = new Properties(); + jdbcProperties.put("user", user); + jdbcProperties.put("password", password); + var driverManagerConnectionFactory = new DriverManagerConnectionFactory(); + return new ConnectionFactoryDataSource(driverManagerConnectionFactory, url, jdbcProperties); + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 27a0c60755..02510aab14 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -51,6 +51,7 @@ include(":edc-extensions:bpn-validation:bpn-validation-spi") include(":edc-extensions:bpn-validation:bpn-validation-core") include(":edc-extensions:bpn-validation:business-partner-store-sql") include(":edc-extensions:migrations:postgresql-migration-lib") +include(":edc-extensions:migrations:connector-migration") include(":edc-extensions:migrations:control-plane-migration") include(":edc-extensions:migrations:data-plane-migration") include(":edc-extensions:tokenrefresh-handler") From 0a414a9a5ba409fb8b8fe3fbb75806c9c726ff08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:05:50 +0100 Subject: [PATCH 021/112] chore(deps): bump io.qameta.allure:allure-junit5 from 2.30.0 to 2.31.0 (#2440) Bumps [io.qameta.allure:allure-junit5](https://github.com/allure-framework/allure-java) from 2.30.0 to 2.31.0. - [Release notes](https://github.com/allure-framework/allure-java/releases) - [Commits](https://github.com/allure-framework/allure-java/compare/2.30.0...2.31.0) --- updated-dependencies: - dependency-name: io.qameta.allure:allure-junit5 dependency-version: 2.31.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 13e627e405..581d2ce8c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ format.version = "1.1" [versions] edc = "0.14.1" edc-build = "1.1.2" -allure = "2.30.0" +allure = "2.31.0" awaitility = "4.3.0" aws = "2.39.2" azure-storage-blob = "12.32.0" From bf75adb51d1e351d0ea9ee233fdbc7bd819c0099 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:56:19 +0100 Subject: [PATCH 022/112] chore(deps): bump org.eclipse.edc.edc-build from 1.1.2 to 1.1.4 (#2439) Bumps org.eclipse.edc.edc-build from 1.1.2 to 1.1.4. --- updated-dependencies: - dependency-name: org.eclipse.edc.edc-build dependency-version: 1.1.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 581d2ce8c3..28c3ebabe5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ format.version = "1.1" [versions] edc = "0.14.1" -edc-build = "1.1.2" +edc-build = "1.1.4" allure = "2.31.0" awaitility = "4.3.0" aws = "2.39.2" From 6527cb85ec311bde10a0bd21a80a4e83fc76e048 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:46:51 +0100 Subject: [PATCH 023/112] chore(deps): bump mikefarah/yq (#2442) Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.48.2 to 4.49.2. - [Release notes](https://github.com/mikefarah/yq/releases) - [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt) - [Commits](https://github.com/mikefarah/yq/compare/v4.48.2...v4.49.2) --- updated-dependencies: - dependency-name: mikefarah/yq dependency-version: 4.49.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/update-version-and-charts/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/update-version-and-charts/action.yml b/.github/actions/update-version-and-charts/action.yml index 429c4d77b3..aa7385dccb 100644 --- a/.github/actions/update-version-and-charts/action.yml +++ b/.github/actions/update-version-and-charts/action.yml @@ -42,7 +42,7 @@ runs: fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Bump version in /charts - uses: mikefarah/yq@v4.48.2 + uses: mikefarah/yq@v4.49.2 with: cmd: | find charts -name Chart.yaml -maxdepth 3 | xargs -n1 yq -i '.appVersion = "${{ steps.resolver.outputs.version }}" From 43c562b4f9a0477b10785acfc8309b9db965beca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:47:39 +0100 Subject: [PATCH 024/112] chore(deps): bump io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17 (#2441) Bumps [io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17](https://github.com/open-telemetry/opentelemetry-java-instrumentation) from 2.21.0-alpha to 2.22.0-alpha. - [Release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java-instrumentation/commits) --- updated-dependencies: - dependency-name: io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17 dependency-version: 2.22.0-alpha dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 28c3ebabe5..9b3a877f7b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ netty-mockserver = "5.15.0" okhttp = "5.3.2" opentelemetry = "2.21.0" opentelemetry-instrumentation = "2.21.0" -opentelemetry-log4j-appender = "2.21.0-alpha" +opentelemetry-log4j-appender = "2.22.0-alpha" postgres = "42.7.8" restAssured = "5.5.6" rsApi = "4.0.0" From b79b1ab56b46f84ab31eb9b685f2d566e3f91b1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:48:22 +0100 Subject: [PATCH 025/112] chore(deps): bump com.github.dasniko:testcontainers-keycloak (#2438) Bumps [com.github.dasniko:testcontainers-keycloak](https://github.com/dasniko/testcontainers-keycloak) from 3.9.0 to 4.0.0. - [Release notes](https://github.com/dasniko/testcontainers-keycloak/releases) - [Commits](https://github.com/dasniko/testcontainers-keycloak/compare/v3.9.0...v4.0.0) --- updated-dependencies: - dependency-name: com.github.dasniko:testcontainers-keycloak dependency-version: 4.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9b3a877f7b..302cc6612f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ postgres = "42.7.8" restAssured = "5.5.6" rsApi = "4.0.0" testcontainers = "1.21.3" -testcontainers-keycloak = "3.9.0" +testcontainers-keycloak = "4.0.0" titanium = "1.7.0" log4j2 = "2.25.2" wiremock = "3.13.2" From acfcac96ed6ae115105e363cf9b00701d85c0cc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:48:56 +0100 Subject: [PATCH 026/112] chore(deps): bump flyway from 11.17.1 to 11.18.0 (#2437) Bumps `flyway` from 11.17.1 to 11.18.0. Updates `org.flywaydb:flyway-core` from 11.17.1 to 11.18.0 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-11.17.1...flyway-11.18.0) Updates `org.flywaydb:flyway-database-postgresql` from 11.17.1 to 11.18.0 --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-version: 11.18.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.flywaydb:flyway-database-postgresql dependency-version: 11.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 302cc6612f..6b0d152437 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.82" dcp-tck = "1.0.0-RC4" dsp-tck = "1.0.0-RC4" -flyway = "11.17.1" +flyway = "11.18.0" jackson = "2.20.1" jakarta-json = "2.0.1" junit = "6.0.1" From 497c34836d546d802e064e33bdacaebca8ee5432 Mon Sep 17 00:00:00 2001 From: Andrii Yurkevych Date: Wed, 3 Dec 2025 09:31:07 +0100 Subject: [PATCH 027/112] feat: Replace mockserver with wiremock (#2443) * feat: replace mockserver with wiremock * feat: fix e2e test * feat: correct md file --- docs/development/mock-edc.md | 4 +- edc-extensions/bdrs-client/build.gradle.kts | 2 +- .../identity/mapper/BdrsClientImplTest.java | 57 ++- .../dataplane-proxy-http/build.gradle.kts | 1 - .../dataplane-public-api-v2/build.gradle.kts | 1 - edc-tests/e2e-fixtures/build.gradle.kts | 2 +- .../tractusx/edc/tests/helpers/Functions.java | 6 +- .../participant/TransferParticipant.java | 27 +- .../tests/transfer/ProviderPushBaseTest.java | 54 ++- edc-tests/e2e/catalog-tests/build.gradle.kts | 2 +- edc-tests/e2e/dcp-tck-tests/build.gradle.kts | 2 +- .../tck/dcp/DcpPresentationFlowTest.java | 38 +- .../e2e/discovery-tests/build.gradle.kts | 2 +- edc-tests/e2e/edr-api-tests/build.gradle.kts | 2 +- .../tests/edrv2/EdrCacheApiEndToEndTest.java | 344 +++++++----------- .../edc/tests/edrv2/NegotiateEdrTest.java | 32 +- edc-tests/e2e/policy-tests/build.gradle.kts | 2 +- edc-tests/e2e/transfer-tests/build.gradle.kts | 2 +- .../transfer/DataFlowApiEndToEndTest.java | 21 +- .../tests/transfer/RetireAgreementTest.java | 17 +- .../transfer/TransferPullEndToEndTest.java | 24 +- .../TransferWithTokenRefreshTest.java | 41 ++- .../runtime/mock-connector/build.gradle.kts | 2 - gradle/libs.versions.toml | 12 +- .../build.gradle.kts | 2 +- 25 files changed, 291 insertions(+), 408 deletions(-) diff --git a/docs/development/mock-edc.md b/docs/development/mock-edc.md index 2efdaf995a..94001e93d3 100644 --- a/docs/development/mock-edc.md +++ b/docs/development/mock-edc.md @@ -152,8 +152,8 @@ different HTTP response code, i.e. 400, and the response body contains an error ## 4. Request pipeline and the instrumentation API -The Mock-Connector internally contains a pipeline of "recorded requests", much like mocked HTTP webservers, like Netty -Mockserver or OkHttp MockWebServer. Out-of-the-box, that pipeline is empty, which means the Management API would always +The Mock-Connector internally contains a pipeline of "recorded requests", much like mocked HTTP webservers, like +WireMock or OkHttp MockWebServer. Out-of-the-box, that pipeline is empty, which means the Management API would always respond with an error like the following: ```json diff --git a/edc-extensions/bdrs-client/build.gradle.kts b/edc-extensions/bdrs-client/build.gradle.kts index 2c88b482fa..c25d8334e6 100644 --- a/edc-extensions/bdrs-client/build.gradle.kts +++ b/edc-extensions/bdrs-client/build.gradle.kts @@ -35,7 +35,7 @@ dependencies { implementation(libs.edc.identity.trust.service) - testImplementation(libs.netty.mockserver) + testImplementation(libs.wiremock) testImplementation(libs.edc.junit) testImplementation(libs.awaitility) testImplementation(libs.edc.core.token) diff --git a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java index f990d54d18..ed4ebf2c56 100644 --- a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java +++ b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java @@ -21,6 +21,7 @@ package org.eclipse.tractusx.edc.identity.mapper; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import dev.failsafe.RetryPolicy; import okhttp3.OkHttpClient; import org.eclipse.edc.http.client.EdcHttpClientImpl; @@ -33,13 +34,11 @@ import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.model.HttpResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -48,12 +47,18 @@ import java.util.Map; import java.util.zip.GZIPOutputStream; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static java.time.Duration.ofSeconds; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.awaitility.Awaitility.await; -import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; @@ -61,9 +66,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.verify.VerificationTimes.exactly; -import static org.mockserver.verify.VerificationTimes.never; class BdrsClientImplTest { @@ -73,19 +75,18 @@ class BdrsClientImplTest { private final SecureTokenService stsMock = mock(); private final CredentialServiceClient csMock = mock(); private BdrsClientImpl client; - private ClientAndServer bdrsServer; + @RegisterExtension + static WireMockExtension bdrsServer = WireMockExtension.newInstance() + .options(wireMockConfig().dynamicPort()) + .build(); @BeforeEach void setup() { - bdrsServer = ClientAndServer.startClientAndServer(getFreePort()); - bdrsServer.when(request() - .withMethod("GET") - .withPath("/api/bpn-directory")) - .respond(HttpResponse.response() + bdrsServer.stubFor(get(urlPathEqualTo("/api/bpn-directory")) + .willReturn(aResponse() .withHeader("Content-Encoding", "gzip") .withBody(createGzipStream()) - .withStatusCode(200)); - + .withStatus(200))); client = new BdrsClientImpl("http://localhost:%d/api".formatted(bdrsServer.getPort()), 1, "did:web:self", () -> "http://credential.service", @@ -102,11 +103,6 @@ void setup() { } - @AfterEach - void teardown() { - bdrsServer.stop(); - } - @Test void getData_whenCacheCold_shouldHitServer() { var did = client.resolveDid("bpn1"); @@ -151,9 +147,8 @@ void getData_whenNotFound() { @ParameterizedTest(name = "HTTP Status {0}") @ValueSource(ints = { 400, 401, 403, 404, 405 }) void getData_bdrsReturnsError(int code) { - bdrsServer.reset(); - bdrsServer.when(request().withPath("/api/bpn-directory").withMethod("GET")) - .respond(HttpResponse.response().withStatusCode(code)); + bdrsServer.resetAll(); + bdrsServer.stubFor(get(urlPathEqualTo("/api/bpn-directory")).willReturn(aResponse().withStatus(code))); assertThatThrownBy(() -> client.resolveDid("bpn1")).isInstanceOf(EdcException.class); } @@ -163,7 +158,7 @@ void getData_whenStsFails() { assertThatThrownBy(() -> client.resolveDid("bpn1")) .isInstanceOf(EdcException.class) .hasMessage("test-failure"); - bdrsServer.verify(request(), never()); + bdrsServer.verify(0, getRequestedFor(anyUrl())); } @Test @@ -173,7 +168,7 @@ void getData_whenPresentationQueryFails() { assertThatThrownBy(() -> client.resolveDid("bpn1")) .isInstanceOf(EdcException.class) .hasMessage("test-failure"); - bdrsServer.verify(request(), never()); + bdrsServer.verify(0, getRequestedFor(anyUrl())); } @Test @@ -191,22 +186,18 @@ void getData_whenPresentationQueryReturnsTooManyVps() { @Test void getData_whenPresentationQueryReturnsEmpty() { - when(csMock.requestPresentation(anyString(), anyString(), anyList())).thenReturn(Result.success(Collections.emptyList())); assertThatThrownBy(() -> client.resolveDid("bpn1")) .isInstanceOf(EdcException.class) .hasMessage("Expected exactly 1 VP, but was empty"); - bdrsServer.verify(request(), never()); + bdrsServer.verify(0, getRequestedFor(anyUrl())); } private void verifyBdrsRequest(int count) { - bdrsServer.verify(request() - .withMethod("GET") - .withPath("/api/bpn-directory") - .withHeader("Authorization", "Bearer " + TEST_VP_CONTENT) - .withHeader("Accept-Encoding", "gzip"), - exactly(count)); + bdrsServer.verify(count, getRequestedFor(urlPathEqualTo("/api/bpn-directory")) + .withHeader("Authorization", equalTo("Bearer " + TEST_VP_CONTENT)) + .withHeader("Accept-Encoding", equalTo("gzip"))); } private byte[] createGzipStream() { diff --git a/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/build.gradle.kts b/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/build.gradle.kts index cfca94e6ce..b6fb5ac85c 100644 --- a/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/build.gradle.kts +++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/build.gradle.kts @@ -28,7 +28,6 @@ dependencies { testImplementation(libs.edc.dpf.core) testImplementation(libs.edc.ext.jsonld) testImplementation(libs.restAssured) - testImplementation(libs.netty.mockserver) testImplementation(testFixtures(libs.edc.lib.http)) testImplementation(testFixtures(libs.edc.spi.dataplane.dataplane)) diff --git a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/build.gradle.kts b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/build.gradle.kts index ac118b6692..4626ea704f 100644 --- a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/build.gradle.kts +++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/build.gradle.kts @@ -39,7 +39,6 @@ dependencies { testImplementation(libs.edc.core.jersey) testImplementation(libs.restAssured) - testImplementation(libs.netty.mockserver) testImplementation(testFixtures(libs.edc.core.jersey)) } edcBuild { diff --git a/edc-tests/e2e-fixtures/build.gradle.kts b/edc-tests/e2e-fixtures/build.gradle.kts index c7243d0c3b..7c6abb0fc9 100644 --- a/edc-tests/e2e-fixtures/build.gradle.kts +++ b/edc-tests/e2e-fixtures/build.gradle.kts @@ -52,7 +52,7 @@ dependencies { testFixturesApi(libs.aws.s3) testFixturesApi(libs.azure.storage.blob) testFixturesApi(libs.jakartaJson) - testFixturesApi(libs.netty.mockserver) + testFixturesApi(libs.wiremock) testFixturesApi(libs.postgres) testFixturesApi(libs.restAssured) testFixturesApi(libs.testcontainers.junit) diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/Functions.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/Functions.java index 8d5ebffc8d..016392b5e0 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/Functions.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/Functions.java @@ -20,8 +20,8 @@ package org.eclipse.tractusx.edc.tests.helpers; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.http.Request; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.mockserver.model.HttpRequest; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; @@ -34,9 +34,9 @@ public class Functions { private static final ObjectMapper MAPPER = new ObjectMapper(); - public static ReceivedEvent readEvent(HttpRequest request) { + public static ReceivedEvent readEvent(Request request) { try { - return MAPPER.readValue(request.getBody().getRawBytes(), ReceivedEvent.class); + return MAPPER.readValue(request.getBody(), ReceivedEvent.class); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TransferParticipant.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TransferParticipant.java index 65fb88d819..1f611d9595 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TransferParticipant.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TransferParticipant.java @@ -19,14 +19,15 @@ package org.eclipse.tractusx.edc.tests.participant; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.client.WireMock; import jakarta.json.Json; import jakarta.json.JsonObject; import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.edc.util.io.Ports; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.model.HttpResponse; import java.io.ByteArrayInputStream; import java.time.Duration; @@ -37,8 +38,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import static org.mockserver.model.HttpRequest.request; - /** * Extension of {@link TractusxParticipantBase} with Transfer specific configuration */ @@ -91,17 +90,25 @@ public Builder enableEventSubscription() { private static class EventSubscription { private final LazySupplier eventReceiverPort = new LazySupplier<>(Ports::getFreePort); - private final ClientAndServer server = ClientAndServer.startClientAndServer(eventReceiverPort.get()); + private WireMockServer server = new WireMockServer(eventReceiverPort.get()); private final BlockingQueue events = new LinkedBlockingQueue<>(); private final Duration timeout; EventSubscription(Duration timeout) { this.timeout = timeout; - server.when(request()).respond(httpRequest -> { - var bodyAsRawBytes = httpRequest.getBodyAsRawBytes(); - var event = Json.createReader(new ByteArrayInputStream(bodyAsRawBytes)).readObject(); - events.add(event); - return HttpResponse.response(); + server.start(); + server.stubFor( + WireMock.any(WireMock.anyUrl()) + .willReturn(ResponseDefinitionBuilder.responseDefinition().withStatus(200)) + ); + + // Capture every request body and add parsed JSON to events + server.addMockServiceRequestListener((request, response) -> { + byte[] body = request.getBody(); + if (body != null && body.length > 0) { + var json = Json.createReader(new ByteArrayInputStream(body)).readObject(); + events.add(json); + } }); } diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/transfer/ProviderPushBaseTest.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/transfer/ProviderPushBaseTest.java index 0e7380a5da..5af9f2628d 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/transfer/ProviderPushBaseTest.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/transfer/ProviderPushBaseTest.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.tests.transfer; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import jakarta.json.JsonObject; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; import org.eclipse.edc.connector.dataplane.spi.DataFlowStates; @@ -26,17 +27,19 @@ import org.eclipse.edc.policy.model.Operator; import org.eclipse.tractusx.edc.tests.ParticipantAwareTest; import org.eclipse.tractusx.edc.tests.RuntimeAwareTest; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.model.HttpResponse; +import org.junit.jupiter.api.extension.RegisterExtension; import java.time.Duration; import java.util.Map; import java.util.UUID; import java.util.stream.Stream; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static jakarta.json.Json.createObjectBuilder; import static java.time.Duration.ofSeconds; import static org.assertj.core.api.Assertions.assertThat; @@ -44,14 +47,11 @@ import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates.COMPLETED; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; -import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.FRAMEWORK_AGREEMENT_LITERAL; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.bpnPolicy; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkPolicy; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.legacyFrameworkPolicy; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.verify.VerificationTimes.exactly; /** * Base tests for Provider PUSH scenario @@ -63,12 +63,10 @@ public abstract class ProviderPushBaseTest implements ParticipantAwareTest, Runt public static final String MOCK_BACKEND_DESTINATION_PATH = "/mock/api/consumer"; public static final Duration POLL_DELAY = ofSeconds(3); - private ClientAndServer server; - - @BeforeEach - void setup() { - server = ClientAndServer.startClientAndServer(MOCK_BACKEND_REMOTE_HOST, getFreePort()); - } + @RegisterExtension + static WireMockExtension server = WireMockExtension.newInstance() + .options(wireMockConfig().bindAddress(MOCK_BACKEND_REMOTE_HOST).dynamicPort()) + .build(); @Test void httpPushDataTransfer() { @@ -94,8 +92,8 @@ void httpPushDataTransfer() { .execute(); await().atMost(ASYNC_TIMEOUT).untilAsserted(() -> transferProcessIsInState(transferProcessId, COMPLETED)); - server.verify(request().withPath(MOCK_BACKEND_SOURCE_PATH)); - server.verify(request().withPath(MOCK_BACKEND_DESTINATION_PATH)); + server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_SOURCE_PATH))); + server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_DESTINATION_PATH))); } @Test @@ -122,8 +120,8 @@ void httpPushDataTransfer_withLegacyUsagePolicy() { .execute(); await().atMost(ASYNC_TIMEOUT).untilAsserted(() -> transferProcessIsInState(transferProcessId, COMPLETED)); - server.verify(request().withPath(MOCK_BACKEND_SOURCE_PATH)); - server.verify(request().withPath(MOCK_BACKEND_DESTINATION_PATH)); + server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_SOURCE_PATH))); + server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_DESTINATION_PATH))); } @Test @@ -157,8 +155,8 @@ void httpPushNonFiniteDataTransfer() { POLL_DELAY, () -> transferProcessIsInState(consumerTransferProcessId, TransferProcessStates.STARTED), () -> dataFlowIsInState(providerTransferProcessId, DataFlowStates.STARTED)); - server.verify(request().withPath(MOCK_BACKEND_SOURCE_PATH)); - server.verify(request().withPath(MOCK_BACKEND_DESTINATION_PATH)); + server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_SOURCE_PATH))); + server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_DESTINATION_PATH))); provider().triggerDataTransfer(providerTransferProcessId); @@ -166,8 +164,9 @@ void httpPushNonFiniteDataTransfer() { POLL_DELAY, () -> transferProcessIsInState(consumerTransferProcessId, TransferProcessStates.STARTED), () -> dataFlowIsInState(providerTransferProcessId, DataFlowStates.STARTED)); - server.verify(request().withPath(MOCK_BACKEND_SOURCE_PATH), exactly(2)); - server.verify(request().withPath(MOCK_BACKEND_DESTINATION_PATH), exactly(2)); + + server.verify(2, anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_SOURCE_PATH))); + server.verify(2, anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_DESTINATION_PATH))); consumer().terminateTransfer(consumerTransferProcessId); consumer().awaitTransferToBeInState(consumerTransferProcessId, TransferProcessStates.TERMINATED); @@ -175,15 +174,8 @@ void httpPushNonFiniteDataTransfer() { .untilAsserted(() -> dataFlowIsInState(providerTransferProcessId, DataFlowStates.TERMINATED)); } - @AfterEach - void teardown() { - server.stop(); - } - private void waitAndAssert(Duration duration, Runnable... assertions) { - await().pollDelay(duration).atMost(ASYNC_TIMEOUT).untilAsserted(() -> { - Stream.of(assertions).forEach(Runnable::run); - }); + await().pollDelay(duration).atMost(ASYNC_TIMEOUT).untilAsserted(() -> Stream.of(assertions).forEach(Runnable::run)); } private void transferProcessIsInState(String transferProcessId, TransferProcessStates state) { @@ -196,8 +188,8 @@ private void dataFlowIsInState(String dataFlowId, DataFlowStates state) { } private String createMockHttpDataUrl(String path) { - server.when(request().withPath(path)) - .respond(HttpResponse.response().withStatusCode(200)); + server.stubFor(any(urlPathEqualTo(path)) + .willReturn(ok())); return "http://%s:%d%s".formatted(MOCK_BACKEND_REMOTE_HOST, server.getPort(), path); } diff --git a/edc-tests/e2e/catalog-tests/build.gradle.kts b/edc-tests/e2e/catalog-tests/build.gradle.kts index b4047d9574..f52e7ab054 100644 --- a/edc-tests/e2e/catalog-tests/build.gradle.kts +++ b/edc-tests/e2e/catalog-tests/build.gradle.kts @@ -25,7 +25,7 @@ plugins { dependencies { testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) - testImplementation(libs.netty.mockserver) + testImplementation(libs.wiremock) testImplementation(libs.edc.junit) testImplementation(libs.restAssured) testImplementation(libs.awaitility) diff --git a/edc-tests/e2e/dcp-tck-tests/build.gradle.kts b/edc-tests/e2e/dcp-tck-tests/build.gradle.kts index 74d586c490..6a00ce4e81 100644 --- a/edc-tests/e2e/dcp-tck-tests/build.gradle.kts +++ b/edc-tests/e2e/dcp-tck-tests/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { testImplementation(libs.dsp.tck.runtime) testImplementation(libs.dcp.system) testImplementation(libs.dsp.tck.core) - testImplementation(libs.netty.mockserver) + testImplementation(libs.wiremock) testImplementation(libs.junit.platform.launcher) testImplementation(libs.testcontainers.junit) } diff --git a/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java b/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java index f8469a9bdd..915612454f 100644 --- a/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java +++ b/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; @@ -50,13 +51,11 @@ import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.eclipse.tractusx.edc.tests.MockBdrsClient; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.integration.ClientAndServer; import java.util.HashMap; import java.util.List; @@ -64,6 +63,10 @@ import java.util.UUID; import java.util.stream.Collectors; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry.WILDCARD; import static org.eclipse.edc.spi.result.Result.success; import static org.eclipse.edc.util.io.Ports.getFreePort; @@ -74,8 +77,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; @EndToEndTest public class DcpPresentationFlowTest { @@ -94,25 +95,21 @@ public class DcpPresentationFlowTest { .registerServiceMock(DataspaceProfileContextRegistry.class, DATASPACE_PROFILE_CONTEXT_REGISTRY_SPY) .registerServiceMock(BdrsClient.class, new MockBdrsClient((s) -> s, (s) -> s)) .configurationProvider(DcpPresentationFlowTest::runtimeConfiguration)); - private ClientAndServer didServer; + @RegisterExtension + protected static WireMockExtension didServer = WireMockExtension.newInstance() + .options(wireMockConfig().bindAddress("localhost").port(DID_SERVER_PORT)) + .build(); private ECKey verifierKey; @BeforeEach void setUp(TrustedIssuerRegistry trustedIssuerRegistry) throws JOSEException { verifierKey = generateEcKey(); trustedIssuerRegistry.register(new Issuer(formatDid(CALLBACK_PORT, "issuer"), Map.of()), WILDCARD); - startDidServer(); + configureDidMock(); configureStsMock(); configureIdExtractionMock(); } - @AfterEach - void teardown() { - if (didServer != null && didServer.hasStarted()) { - didServer.stop(); - } - } - @DisplayName("Run TCK Presentation Flow tests") @Test void runPresentationFlowTests() { @@ -154,16 +151,13 @@ private ECKey generateEcKey() throws JOSEException { .generate(); } - private void startDidServer() { - didServer = ClientAndServer.startClientAndServer(DID_SERVER_PORT); - didServer.when( - request().withMethod("GET").withPath("/verifier/did.json") - ).respond( - response() + private void configureDidMock() { + didServer.stubFor(get(urlPathEqualTo("/verifier/did.json")) + .willReturn(aResponse() + .withStatus(200) .withHeader("Content-Type", "application/json") - .withStatusCode(200) - .withBody(createDidDocumentJson()) - ); + .withBody(createDidDocumentJson() + ))); } private void configureStsMock() { diff --git a/edc-tests/e2e/discovery-tests/build.gradle.kts b/edc-tests/e2e/discovery-tests/build.gradle.kts index 0a0e0b0fc7..5884064895 100644 --- a/edc-tests/e2e/discovery-tests/build.gradle.kts +++ b/edc-tests/e2e/discovery-tests/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) - testImplementation(libs.netty.mockserver) + testImplementation(libs.wiremock) testImplementation(libs.edc.junit) testImplementation(libs.restAssured) testImplementation(libs.awaitility) diff --git a/edc-tests/e2e/edr-api-tests/build.gradle.kts b/edc-tests/e2e/edr-api-tests/build.gradle.kts index 71b5efaa44..b19a422be0 100644 --- a/edc-tests/e2e/edr-api-tests/build.gradle.kts +++ b/edc-tests/e2e/edr-api-tests/build.gradle.kts @@ -28,7 +28,7 @@ dependencies { testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) testImplementation(libs.edc.spi.edrstore) - testImplementation(libs.netty.mockserver) + testImplementation(libs.wiremock) testImplementation(libs.edc.junit) testImplementation(libs.restAssured) testImplementation(libs.awaitility) diff --git a/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/EdrCacheApiEndToEndTest.java b/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/EdrCacheApiEndToEndTest.java index 8ca82f2b13..057d296810 100644 --- a/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/EdrCacheApiEndToEndTest.java +++ b/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/EdrCacheApiEndToEndTest.java @@ -22,6 +22,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; @@ -40,16 +42,11 @@ import org.eclipse.tractusx.edc.spi.tokenrefresh.dataplane.model.TokenResponse; import org.eclipse.tractusx.edc.tests.participant.TransferParticipant; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.client.MockServerClient; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.model.Delay; -import org.mockserver.verify.VerificationTimes; import java.time.Clock; import java.time.Instant; @@ -59,9 +56,14 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.IntStream; +import static com.github.tomakehurst.wiremock.client.WireMock.absent; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; -import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.EDR_PROPERTY_EXPIRES_IN; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.EDR_PROPERTY_REFRESH_AUDIENCE; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.EDR_PROPERTY_REFRESH_ENDPOINT; @@ -72,16 +74,11 @@ import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.equalTo; -import static org.mockserver.integration.ClientAndServer.startClientAndServer; -import static org.mockserver.matchers.Times.exactly; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; -import static org.mockserver.model.StringBody.exact; /** * This End-To-End test spins up a consumer control plane and verifies that the EDR Cache API * performs as expected. - * The provider data plane is mocked with a {@link ClientAndServer}. + * The provider data plane is mocked with a {@link WireMock}. */ @EndToEndTest public class EdrCacheApiEndToEndTest { @@ -99,9 +96,13 @@ public class EdrCacheApiEndToEndTest { @RegisterExtension private static final RuntimeExtension CONSUMER_RUNTIME = pgRuntime(CONSUMER, POSTGRES); + @RegisterExtension + protected static WireMockExtension mockedRefreshApi = WireMockExtension.newInstance() + .options(wireMockConfig().dynamicPort()) + .build(); + private final Random random = new Random(); private final ObjectMapper mapper = new ObjectMapper(); - private ClientAndServer mockedRefreshApi; private ECKey providerSigningKey; private String refreshEndpoint; private String refreshAudience; @@ -109,216 +110,141 @@ public class EdrCacheApiEndToEndTest { @BeforeEach void setup() throws JOSEException { providerSigningKey = new ECKeyGenerator(Curve.P_256).keyID("did:web:provider#key-1").generate(); - var port = getFreePort(); - refreshEndpoint = "http://localhost:%s/refresh".formatted(port); + refreshEndpoint = "http://localhost:%s/refresh".formatted(mockedRefreshApi.getPort()); refreshAudience = "did:web:consumer"; - mockedRefreshApi = startClientAndServer(port); - } - - @AfterEach - void teardown() { - mockedRefreshApi.stop(); } @DisplayName("Verify HTTP 200 response and body when refreshing succeeds") @Test void getEdrWithRefresh_success() { + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")).withRequestBody(absent()) + .willReturn(ok(tokenResponseBody()))); - try (var client = new MockServerClient("localhost", mockedRefreshApi.getPort())) { - // mock the provider dataplane's refresh endpoint - client.when(request() - .withMethod("POST") - .withPath("/refresh/token") - .withBody(exact("")), - exactly(1)) - .respond(response() - .withStatusCode(200) - .withBody(tokenResponseBody()) - ); - - storeEdr("test-id", true); - var edr = CONSUMER.edrs().getEdrWithRefresh("test-id", true) - .statusCode(200) - .extract().body().as(JsonObject.class); - assertThat(edr).isNotNull(); - - // assert the correct endpoint was called - client.verify( - request() - .withQueryStringParameter("grant_type", "refresh_token") - .withMethod("POST") - .withPath("/refresh/token"), - VerificationTimes.exactly(1)); - } + storeEdr("test-id", true); + var edr = CONSUMER.edrs().getEdrWithRefresh("test-id", true) + .statusCode(200) + .extract().body().as(JsonObject.class); + assertThat(edr).isNotNull(); + + mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token")) + .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); } @DisplayName("When multiple requests to refresh, to different edrs, verify all return non expired token") @Test void getEdrWithRefresh_subsequentRequestReturn() throws InterruptedException { - - try (var client = new MockServerClient("localhost", mockedRefreshApi.getPort())) { - var claims = new JWTClaimsSet.Builder().claim("iss", "did:web:provider").build(); - var accessToken = createJwt(providerSigningKey, claims); - var refreshToken = createJwt(providerSigningKey, new JWTClaimsSet.Builder().build()); - var tokenResponseBodyString = tokenResponseBody(accessToken, refreshToken); - - client.when(request().withMethod("POST").withPath("/refresh/token").withBody(exact(""))) - .respond(response().withStatusCode(200).withDelay(Delay.milliseconds(5000)).withBody(tokenResponseBodyString)); - - storeEdr("test-id-1", true); - storeEdr("test-id-2", true); - var numThreads = 50; - var jitter = 20; // maximum time between threads are spawned - var latch = new CountDownLatch(numThreads); - - var failed = new AtomicBoolean(false); - - IntStream.range(0, numThreads) - .parallel() - .forEach(i -> { - var wait = random.nextInt(1, jitter); - try { - Thread.sleep(wait); - new Thread(() -> { - var edrNumber = random.nextInt(1, 3); - try { - var tr = CONSUMER.edrs().getEdrWithRefresh("test-id-%s".formatted(edrNumber), true) - .assertThat() - .log().ifValidationFails() - .statusCode(anyOf(equalTo(200), equalTo(409))) - .extract().asString(); - - assertThat(tr).contains(accessToken); - } catch (AssertionError e) { - failed.set(true); - } finally { - latch.countDown(); - } - - - }).start(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - }); - - latch.await(); - assertThat(failed.get()).isFalse(); - - client.verify(request() - .withQueryStringParameter("grant_type", "refresh_token") - .withMethod("POST") - .withPath("/refresh/token"), VerificationTimes.exactly(2)); - - } + var claims = new JWTClaimsSet.Builder().claim("iss", "did:web:provider").build(); + var accessToken = createJwt(providerSigningKey, claims); + var refreshToken = createJwt(providerSigningKey, new JWTClaimsSet.Builder().build()); + var tokenResponseBodyString = tokenResponseBody(accessToken, refreshToken); + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")).withRequestBody(absent()) + .willReturn(WireMock.aResponse() + .withStatus(200) + .withFixedDelay(5000) + .withBody(tokenResponseBodyString) + )); + + storeEdr("test-id-1", true); + storeEdr("test-id-2", true); + var numThreads = 50; + var jitter = 20; // maximum time between threads are spawned + var latch = new CountDownLatch(numThreads); + + var failed = new AtomicBoolean(false); + + IntStream.range(0, numThreads) + .parallel() + .forEach(i -> { + var wait = random.nextInt(1, jitter); + try { + Thread.sleep(wait); + new Thread(() -> { + var edrNumber = random.nextInt(1, 3); + try { + var tr = CONSUMER.edrs().getEdrWithRefresh("test-id-%s".formatted(edrNumber), true) + .assertThat() + .log().ifValidationFails() + .statusCode(anyOf(equalTo(200), equalTo(409))) + .extract().asString(); + + assertThat(tr).contains(accessToken); + } catch (AssertionError e) { + failed.set(true); + } finally { + latch.countDown(); + } + + + }).start(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + latch.await(); + assertThat(failed.get()).isFalse(); + + mockedRefreshApi.verify(2, postRequestedFor(urlPathEqualTo("/refresh/token")) + .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); } @DisplayName("Verify the refresh endpoint is not called when token not yet expired") @Test void getEdrWithRefresh_notExpired_shouldNotCallEndpoint() { - - try (var client = new MockServerClient("localhost", mockedRefreshApi.getPort())) { - // mock the provider dataplane's refresh endpoint - - storeEdr("test-id", false); - var edr = CONSUMER.edrs().getEdrWithRefresh("test-id", true) - .statusCode(200) - .extract().body().as(JsonObject.class); - assertThat(edr).isNotNull(); - - // assert the correct endpoint was called - client.verify( - request() - .withQueryStringParameter("grant_type", "refresh_token") - .withMethod("POST") - .withPath("/refresh/token"), - VerificationTimes.never()); - } + storeEdr("test-id", false); + var edr = CONSUMER.edrs().getEdrWithRefresh("test-id", true) + .statusCode(200) + .extract().body().as(JsonObject.class); + assertThat(edr).isNotNull(); + + mockedRefreshApi.verify(0, postRequestedFor(urlPathEqualTo("/refresh/token")) + .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); } @DisplayName("Verify the refresh endpoint is not called when auto_refresh=false") @Test void getEdrWithRefresh_whenNotAutorefresh_shouldNotCallEndpoint() { - - try (var client = new MockServerClient("localhost", mockedRefreshApi.getPort())) { - // mock the provider dataplane's refresh endpoint - - storeEdr("test-id", true); - var edr = CONSUMER.edrs() - .getEdrWithRefresh("test-id", false) - .statusCode(200) - .extract().body().as(JsonObject.class); - assertThat(edr).isNotNull(); - - // assert the correct endpoint was called - client.verify( - request() - .withQueryStringParameter("grant_type", "refresh_token") - .withMethod("POST") - .withPath("/refresh/token"), - VerificationTimes.never()); - } + storeEdr("test-id", true); + var edr = CONSUMER.edrs() + .getEdrWithRefresh("test-id", false) + .statusCode(200) + .extract().body().as(JsonObject.class); + assertThat(edr).isNotNull(); + + mockedRefreshApi.verify(0, postRequestedFor(urlPathEqualTo("/refresh/token")) + .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); } @DisplayName("Verify HTTP 403 response when refreshing the token is not allowed") @Test void getEdrWithRefresh_unauthorized() { - - try (var client = new MockServerClient("localhost", mockedRefreshApi.getPort())) { - // mock the provider dataplane's refresh endpoint - client.when(request() - .withMethod("POST") - .withPath("/refresh/token") - .withBody(exact("")), - exactly(1)) - .respond(response() - .withStatusCode(401) - .withBody("unauthorized") - ); - - storeEdr("test-id", true); - CONSUMER.edrs().getEdrWithRefresh("test-id", true) - .statusCode(403); - - // assert the correct endpoint was called - client.verify( - request() - .withQueryStringParameter("grant_type", "refresh_token") - .withMethod("POST") - .withPath("/refresh/token"), - VerificationTimes.exactly(1)); - } + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")).withRequestBody(absent()) + .willReturn(WireMock.aResponse() + .withStatus(401) + .withBody("unauthorized") + )); + + storeEdr("test-id", true); + CONSUMER.edrs().getEdrWithRefresh("test-id", true) + .statusCode(403); + + mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token")) + .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); } @Test void refreshEdr() { - try (var client = new MockServerClient("localhost", mockedRefreshApi.getPort())) { - // mock the provider dataplane's refresh endpoint - client.when(request() - .withMethod("POST") - .withPath("/refresh/token") - .withBody(exact("")), - exactly(1)) - .respond(response() - .withStatusCode(200) - .withBody(tokenResponseBody()) - ); - - storeEdr("test-id", true); - var edr = CONSUMER.edrs().refreshEdr("test-id") - .statusCode(200) - .extract().body().as(JsonObject.class); - assertThat(edr).isNotNull(); - - // assert the correct endpoint was called - client.verify( - request() - .withQueryStringParameter("grant_type", "refresh_token") - .withMethod("POST") - .withPath("/refresh/token"), - VerificationTimes.exactly(1)); + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")).withRequestBody(absent()) + .willReturn(ok(tokenResponseBody()))); - } + storeEdr("test-id", true); + var edr = CONSUMER.edrs().refreshEdr("test-id") + .statusCode(200) + .extract().body().as(JsonObject.class); + assertThat(edr).isNotNull(); + + mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token")) + .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); } @Test @@ -329,30 +255,18 @@ void refreshEdr_whenNotFound() { @Test void refreshEdr_whenNotAuthorized() { - try (var client = new MockServerClient("localhost", mockedRefreshApi.getPort())) { - // mock the provider dataplane's refresh endpoint - client.when(request() - .withMethod("POST") - .withPath("/refresh/token") - .withBody(exact("")), - exactly(1)) - .respond(response() - .withStatusCode(401) - .withBody("unauthorized") - ); - - storeEdr("test-id", true); - CONSUMER.edrs().refreshEdr("test-id") - .statusCode(403); - - // assert the correct endpoint was called - client.verify( - request() - .withQueryStringParameter("grant_type", "refresh_token") - .withMethod("POST") - .withPath("/refresh/token"), - VerificationTimes.exactly(1)); - } + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")).withRequestBody(absent()) + .willReturn(WireMock.aResponse() + .withStatus(401) + .withBody("unauthorized") + )); + + storeEdr("test-id", true); + CONSUMER.edrs().refreshEdr("test-id") + .statusCode(403); + + mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token")) + .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); } private String tokenResponseBody() { diff --git a/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/NegotiateEdrTest.java b/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/NegotiateEdrTest.java index 61cc341015..b2bd59b4b6 100644 --- a/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/NegotiateEdrTest.java +++ b/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/NegotiateEdrTest.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.tests.edrv2; +import com.github.tomakehurst.wiremock.WireMockServer; import jakarta.json.Json; import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAgreed; import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationFinalized; @@ -44,17 +45,18 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.model.HttpResponse; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_BPN; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_DID; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; @@ -68,7 +70,6 @@ import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_POLL_INTERVAL; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; -import static org.mockserver.model.HttpRequest.request; @EndToEndTest public class NegotiateEdrTest { @@ -99,11 +100,12 @@ public class NegotiateEdrTest { @RegisterExtension private static final RuntimeExtension PROVIDER_RUNTIME = pgRuntime(PROVIDER, POSTGRES); - private ClientAndServer server; + private static WireMockServer server; @BeforeEach void setup() { - server = ClientAndServer.startClientAndServer("localhost", getFreePort()); + server = new WireMockServer(options().bindAddress("localhost").dynamicPort()); + server.start(); CONSUMER.setJsonLd(CONSUMER_RUNTIME.getService(JsonLd.class)); } @@ -122,7 +124,7 @@ void negotiateEdr_shouldInvokeCallbacks() { createEvent(TransferProcessRequested.class), createEvent(TransferProcessStarted.class)); - var url = "http://%s:%d%s".formatted("localhost", server.getPort(), "/mock/api"); + var url = "http://%s:%d%s".formatted("localhost", server.port(), "/mock/api"); var assetId = "api-asset-1"; @@ -146,13 +148,15 @@ void negotiateEdr_shouldInvokeCallbacks() { var events = new ArrayList(); - server.when(request().withPath("/mock/api")) - .respond(request -> { - var event = readEvent(request); - events.add(event); - return HttpResponse.response().withStatusCode(200); - }); + server.stubFor( + any(urlPathEqualTo("/mock/api")) + .willReturn(aResponse().withStatus(200)) + ); + server.addMockServiceRequestListener((request, response) -> { + var event = readEvent(request); + events.add(event); + }); var callbacks = Json.createArrayBuilder() .add(EdrNegotiationHelperFunctions.createCallback(url, true, Set.of("contract.negotiation", "transfer.process"))) @@ -194,12 +198,10 @@ void negotiateEdr_shouldInvokeCallbacks() { assertThat(edr.getJsonString("endpoint").getString()).isNotNull(); assertThat(edr.getJsonString("endpointType").getString()).isEqualTo(edr.getJsonString("type").getString()); assertThat(edr.getJsonString("authorization").getString()).isNotNull(); - } @AfterEach void teardown() { server.stop(); } - } diff --git a/edc-tests/e2e/policy-tests/build.gradle.kts b/edc-tests/e2e/policy-tests/build.gradle.kts index f015c19a5a..d51250bdba 100644 --- a/edc-tests/e2e/policy-tests/build.gradle.kts +++ b/edc-tests/e2e/policy-tests/build.gradle.kts @@ -25,7 +25,7 @@ plugins { dependencies { testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) - testImplementation(libs.netty.mockserver) + testImplementation(libs.wiremock) testImplementation(libs.edc.junit) testImplementation(libs.restAssured) testImplementation(libs.awaitility) diff --git a/edc-tests/e2e/transfer-tests/build.gradle.kts b/edc-tests/e2e/transfer-tests/build.gradle.kts index c5f8993eb1..307ad673b2 100644 --- a/edc-tests/e2e/transfer-tests/build.gradle.kts +++ b/edc-tests/e2e/transfer-tests/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { testImplementation(project(":spi:bdrs-client-spi")) testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) - testImplementation(libs.netty.mockserver) + testImplementation(libs.wiremock) testImplementation(libs.edc.junit) testImplementation(libs.restAssured) testImplementation(libs.awaitility) diff --git a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DataFlowApiEndToEndTest.java b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DataFlowApiEndToEndTest.java index 2f03b94a62..e814d0bb1d 100644 --- a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DataFlowApiEndToEndTest.java +++ b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DataFlowApiEndToEndTest.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.tests.transfer; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import io.restassured.path.json.JsonPath; import io.restassured.response.ValidatableResponse; import org.eclipse.edc.connector.dataplane.spi.DataFlow; @@ -29,15 +30,13 @@ import org.eclipse.edc.spi.types.domain.transfer.TransferType; import org.eclipse.tractusx.edc.tests.participant.TransferParticipant; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.integration.ClientAndServer; import java.util.UUID; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static io.restassured.http.ContentType.JSON; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.connector.dataplane.spi.DataFlowStates.STARTED; @@ -45,7 +44,6 @@ import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; import static org.eclipse.edc.spi.types.domain.transfer.FlowType.PULL; import static org.eclipse.edc.spi.types.domain.transfer.FlowType.PUSH; -import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_BPN; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_DID; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_NAME; @@ -67,12 +65,10 @@ public class DataFlowApiEndToEndTest { @RegisterExtension private static final RuntimeExtension RUNTIME = pgRuntime(PARTICIPANT, POSTGRES); - private ClientAndServer server; - - @BeforeEach - void setup() { - server = ClientAndServer.startClientAndServer("localhost", getFreePort()); - } + @RegisterExtension + protected static WireMockExtension server = WireMockExtension.newInstance() + .options(wireMockConfig().bindAddress("localhost").dynamicPort()) + .build(); @Test void triggerDataTransfer_shouldFail_whenDataFlowDoesNotExist() { @@ -164,11 +160,6 @@ void trigger_shouldReturnSuccess_whenAllValidationsSucceed() { PARTICIPANT.triggerDataTransfer(dataFlow.getId()); } - @AfterEach - void teardown() { - server.stop(); - } - @SuppressWarnings("rawtypes") private DataAddress.Builder dataAddressBuilder() { return DataAddress.Builder.newInstance() diff --git a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/RetireAgreementTest.java b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/RetireAgreementTest.java index dfbaed7b05..16ccfce4e5 100644 --- a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/RetireAgreementTest.java +++ b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/RetireAgreementTest.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.edc.tests.transfer; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import jakarta.json.Json; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; import org.eclipse.edc.jsonld.spi.JsonLd; @@ -29,20 +30,18 @@ import org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions; import org.eclipse.tractusx.edc.tests.participant.TransferParticipant; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.integration.ClientAndServer; import java.util.Map; import java.util.UUID; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_2025_09_NS; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_BPN; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_DID; @@ -86,11 +85,13 @@ public class RetireAgreementTest { @RegisterExtension private static final RuntimeExtension PROVIDER_RUNTIME = pgRuntime(PROVIDER, POSTGRES); - private ClientAndServer server; + @RegisterExtension + protected static WireMockExtension server = WireMockExtension.newInstance() + .options(wireMockConfig().bindAddress("localhost").dynamicPort()) + .build(); @BeforeEach void setup() { - server = ClientAndServer.startClientAndServer("localhost", getFreePort()); CONSUMER.setJsonLd(CONSUMER_RUNTIME.getService(JsonLd.class)); } @@ -150,10 +151,4 @@ void retireAgreement_shouldCloseTransferProcesses() { void retireAgreement_shouldFail_whenAgreementDoesNotExist() { PROVIDER.retireProviderAgreement(UUID.randomUUID().toString()).statusCode(404); } - - @AfterEach - void teardown() { - server.stop(); - } - } diff --git a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPullEndToEndTest.java b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPullEndToEndTest.java index c38e773991..f658ec1e5c 100644 --- a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPullEndToEndTest.java +++ b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPullEndToEndTest.java @@ -33,7 +33,6 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.model.HttpStatusCode; import java.util.Map; @@ -43,6 +42,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.moreThanOrExactly; import static com.github.tomakehurst.wiremock.client.WireMock.ok; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static jakarta.ws.rs.core.Response.Status; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_BPN; @@ -213,10 +213,10 @@ void transferData_successful_notReturnOriginalSourceResponseCode_withTerminate() var edr = CONSUMER.edrs().waitForEdr(transferProcessId); - var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(HttpStatusCode.OK_200.code()); + var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(Status.OK.getStatusCode()); var header = response.extract().headers().get("to-be-returned"); assertThat(header).isNull(); - assertThat(response.extract().statusLine()).contains(HttpStatusCode.OK_200.reasonPhrase()); + assertThat(response.extract().statusLine()).contains(Status.OK.getReasonPhrase()); var data = response.extract().body().asString(); assertThat(data).isNotNull().isEqualTo("test response"); @@ -250,7 +250,7 @@ void transferData_unsuccessful_notReturnOriginalSourceResponseCode_withTerminate var edr = CONSUMER.edrs().waitForEdr(transferProcessId); - CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(HttpStatusCode.INTERNAL_SERVER_ERROR_500.code()); + CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(Status.INTERNAL_SERVER_ERROR.getStatusCode()); server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } @@ -284,9 +284,9 @@ void transferData_success_withProxyOriginalResponse() { var edr = CONSUMER.edrs().waitForEdr(transferProcessId); // consumer can fetch data with a valid token - var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(HttpStatusCode.CREATED_201.code()); + var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(Status.CREATED.getStatusCode()); var header = response.extract().headers().get("to-be-returned"); - assertThat(response.extract().statusLine()).contains(HttpStatusCode.CREATED_201.reasonPhrase()); + assertThat(response.extract().statusLine()).contains(Status.CREATED.getReasonPhrase()); assertThat(header.getValue()).isNotNull().isEqualTo("true"); var data = response.extract().body().asString(); assertThat(data).isNotNull().isEqualTo("test created"); @@ -323,10 +323,10 @@ void transferData_success_withProxyOriginalResponse_withoutSourceResponseBody() var edr = CONSUMER.edrs().waitForEdr(transferProcessId); // consumer can fetch data with a valid token - var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(HttpStatusCode.NO_CONTENT_204.code()); + var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(Status.NO_CONTENT.getStatusCode()); var header = response.extract().headers().get("to-be-returned"); assertThat(header.getValue()).isNotNull().isEqualTo("true"); - assertThat(response.extract().statusLine()).contains(HttpStatusCode.NO_CONTENT_204.reasonPhrase()); + assertThat(response.extract().statusLine()).contains(Status.NO_CONTENT.getReasonPhrase()); var data = response.extract().body().asString(); assertThat(data).isEmpty(); @@ -360,8 +360,8 @@ void transferData_failing_withProxyOriginalResponse() { var edr = CONSUMER.edrs().waitForEdr(transferProcessId); - var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(HttpStatusCode.EXPECTATION_FAILED_417.code()); - assertThat(response.extract().statusLine()).contains(HttpStatusCode.EXPECTATION_FAILED_417.reasonPhrase()); + var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(Status.EXPECTATION_FAILED.getStatusCode()); + assertThat(response.extract().statusLine()).contains(Status.EXPECTATION_FAILED.getReasonPhrase()); var data = response.extract().body().asString(); assertThat(data).isNotNull().isEqualTo("test failed response"); @@ -395,8 +395,8 @@ void transferData_failing_withProxyOriginalResponse_withoutSourceResponseBody() var edr = CONSUMER.edrs().waitForEdr(transferProcessId); - var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(HttpStatusCode.GATEWAY_TIMEOUT_504.code()); - assertThat(response.extract().statusLine()).contains(HttpStatusCode.GATEWAY_TIMEOUT_504.reasonPhrase()); + var response = CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(Status.GATEWAY_TIMEOUT.getStatusCode()); + assertThat(response.extract().statusLine()).contains(Status.GATEWAY_TIMEOUT.getReasonPhrase()); var data = response.extract().body().asString(); assertThat(data).isNotNull().isEmpty(); diff --git a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferWithTokenRefreshTest.java b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferWithTokenRefreshTest.java index ee88ba2b3b..53c7af70b0 100644 --- a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferWithTokenRefreshTest.java +++ b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferWithTokenRefreshTest.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.tests.transfer; +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import jakarta.json.JsonObject; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; import org.eclipse.edc.jsonld.spi.JsonLd; @@ -29,17 +30,20 @@ import org.eclipse.tractusx.edc.tests.MockBdrsClient; import org.eclipse.tractusx.edc.tests.participant.TransferParticipant; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockserver.integration.ClientAndServer; -import org.mockserver.verify.VerificationTimes; import java.time.Duration; import java.util.Map; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static io.restassured.RestAssured.given; import static jakarta.json.Json.createObjectBuilder; import static org.assertj.core.api.Assertions.assertThat; @@ -47,7 +51,6 @@ import static org.awaitility.pollinterval.FibonacciPollInterval.fibonacci; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; -import static org.eclipse.edc.util.io.Ports.getFreePort; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_BPN; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_DID; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; @@ -59,8 +62,6 @@ import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.bpnPolicy; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; -import static org.mockserver.model.HttpRequest.request; -import static org.mockserver.model.HttpResponse.response; /** * this test uses in-mem runtimes to negotiate and perform a data transfer, but the EDR expires before the consumer has a @@ -104,12 +105,15 @@ public class TransferWithTokenRefreshTest { )))) .registerServiceMock(BdrsClient.class, new MockBdrsClient((c) -> CONSUMER.getDid(), (c) -> CONSUMER.getBpn())); - private ClientAndServer server; + @RegisterExtension + protected static WireMockExtension server = WireMockExtension.newInstance() + .options(wireMockConfig().bindAddress(MOCK_BACKEND_REMOTE_HOST).dynamicPort()) + .build(); + private String privateBackendUrl; @BeforeEach void setup() { - server = ClientAndServer.startClientAndServer(MOCK_BACKEND_REMOTE_HOST, getFreePort()); privateBackendUrl = "http://%s:%d%s".formatted(MOCK_BACKEND_REMOTE_HOST, server.getPort(), MOCK_BACKEND_PATH); CONSUMER.setJsonLd(CONSUMER_RUNTIME.getService(JsonLd.class)); } @@ -136,7 +140,8 @@ void transferData_withExpiredEdr_shouldReturn4xx() { CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); // wait until EDC is available on the consumer side - server.when(request().withMethod("GET").withPath(MOCK_BACKEND_PATH)).respond(response().withStatusCode(200).withBody("test response")); + server.stubFor(get(urlPathEqualTo(MOCK_BACKEND_PATH)) + .willReturn(ok("test response"))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); @@ -158,7 +163,7 @@ void transferData_withExpiredEdr_shouldReturn4xx() { }); // assert the data has not been fetched - server.verify(request().withPath(MOCK_BACKEND_PATH), VerificationTimes.never()); + server.verify(0, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); // renew EDR explicitly var renewedEdr = CONSUMER.edrs().refreshEdr(transferProcessId) @@ -170,7 +175,7 @@ void transferData_withExpiredEdr_shouldReturn4xx() { var data = CONSUMER.data().pullData(renewedEdr, Map.of()); assertThat(data).isNotNull().isEqualTo("test response"); - server.verify(request().withPath(MOCK_BACKEND_PATH), VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } @Test @@ -194,7 +199,10 @@ void transferData_withAutomaticRefresh() { CONSUMER.waitForTransferProcess(transferProcessId, TransferProcessStates.STARTED); // wait until EDC is available on the consumer side - server.when(request().withMethod("GET").withPath(MOCK_BACKEND_PATH)).respond(response().withStatusCode(200).withBody("test response")); + server.stubFor(get(urlPathEqualTo(MOCK_BACKEND_PATH)) + .willReturn(aResponse() + .withStatus(200) + .withBody("test response"))); var edr = CONSUMER.edrs().waitForEdr(transferProcessId); @@ -215,7 +223,7 @@ void transferData_withAutomaticRefresh() { }); // assert the data has not been fetched - server.verify(request().withPath(MOCK_BACKEND_PATH), VerificationTimes.never()); + server.verify(0, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); // get EDR with automatic refresh var renewedEdr = CONSUMER.edrs().getEdrWithRefresh(transferProcessId, true) @@ -227,7 +235,7 @@ void transferData_withAutomaticRefresh() { var data = CONSUMER.data().pullData(renewedEdr, Map.of()); assertThat(data).isNotNull().isEqualTo("test response"); - server.verify(request().withPath(MOCK_BACKEND_PATH), VerificationTimes.exactly(1)); + server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH))); } private JsonObject httpDataDestination() { @@ -240,11 +248,6 @@ private JsonObject httpDataDestination() { .build(); } - @AfterEach - void teardown() { - server.stop(); - } - protected JsonObject createAccessPolicy(String bpn) { return bpnPolicy(bpn); } diff --git a/edc-tests/runtime/mock-connector/build.gradle.kts b/edc-tests/runtime/mock-connector/build.gradle.kts index 3a4b1c59bb..825ddff791 100644 --- a/edc-tests/runtime/mock-connector/build.gradle.kts +++ b/edc-tests/runtime/mock-connector/build.gradle.kts @@ -1,5 +1,3 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - /******************************************************************************** * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6b0d152437..6656872bb2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,6 @@ jackson = "2.20.1" jakarta-json = "2.0.1" junit = "6.0.1" nimbus = "10.6" -netty-mockserver = "5.15.0" okhttp = "5.3.2" opentelemetry = "2.21.0" opentelemetry-instrumentation = "2.21.0" @@ -24,7 +23,7 @@ opentelemetry-log4j-appender = "2.22.0-alpha" postgres = "42.7.8" restAssured = "5.5.6" rsApi = "4.0.0" -testcontainers = "1.21.3" +testcontainers = "2.0.2" testcontainers-keycloak = "4.0.0" titanium = "1.7.0" log4j2 = "2.25.2" @@ -212,14 +211,13 @@ nimbus-jwt = { module = "com.nimbusds:nimbus-jose-jwt", version.ref = "nimbus" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } opentelemetry-javaagent = { module = "io.opentelemetry.javaagent:opentelemetry-javaagent", version.ref = "opentelemetry" } opentelemetry-instrumentation-annotations = { module = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations", version.ref = "opentelemetry-instrumentation" } -netty-mockserver = { module = "org.mock-server:mockserver-netty", version.ref = "netty-mockserver" } postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" } restAssured = { module = "io.rest-assured:rest-assured", version.ref = "restAssured" } -testcontainers-junit = { module = "org.testcontainers:junit-jupiter", version.ref = "testcontainers" } +testcontainers-junit = { module = "org.testcontainers:testcontainers-junit-jupiter", version.ref = "testcontainers" } testcontainers-keycloak = { module = "com.github.dasniko:testcontainers-keycloak", version.ref = "testcontainers-keycloak" } -testcontainers-minio = { module = "org.testcontainers:minio", version.ref = "testcontainers" } -testcontainers-localstack = { module = "org.testcontainers:localstack", version.ref = "testcontainers" } -testcontainers-postgres = { module = "org.testcontainers:postgresql", version.ref = "testcontainers" } +testcontainers-minio = { module = "org.testcontainers:testcontainers-minio", version.ref = "testcontainers" } +testcontainers-localstack = { module = "org.testcontainers:testcontainers-localstack", version.ref = "testcontainers" } +testcontainers-postgres = { module = "org.testcontainers:testcontainers-postgresql", version.ref = "testcontainers" } titaniumJsonLd = { module = "com.apicatalog:titanium-json-ld", version.ref = "titanium" } wiremock = { module = "org.wiremock:wiremock-jetty12", version.ref = "wiremock" } diff --git a/samples/testing-with-mocked-connector/build.gradle.kts b/samples/testing-with-mocked-connector/build.gradle.kts index 2edcaf610b..c66ca63fd4 100644 --- a/samples/testing-with-mocked-connector/build.gradle.kts +++ b/samples/testing-with-mocked-connector/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) testImplementation(libs.testcontainers.junit) - testImplementation(libs.netty.mockserver) + testImplementation(libs.wiremock) testImplementation(libs.edc.junit) testImplementation(libs.restAssured) testImplementation(libs.awaitility) From 99b34838cf78c27e368de78a1d2c99ef8f98f582 Mon Sep 17 00:00:00 2001 From: DanielaWuensch <107044426+DanielaWuensch@users.noreply.github.com> Date: Wed, 3 Dec 2025 18:13:11 +0100 Subject: [PATCH 028/112] Propose small adaptations to migration guide for version 0.10.x to 0.11.x (#2445) * Propose small adaptations to migration guide for version 0.10.x to 0.11.x Updated references to policy builder Adapted naming of usage policy valid in Jupiter * small changes --- docs/migration/2025-09-Version_0.10.x_0.11.x.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/migration/2025-09-Version_0.10.x_0.11.x.md b/docs/migration/2025-09-Version_0.10.x_0.11.x.md index cbd90acfcf..d2be834af0 100644 --- a/docs/migration/2025-09-Version_0.10.x_0.11.x.md +++ b/docs/migration/2025-09-Version_0.10.x_0.11.x.md @@ -114,7 +114,7 @@ standard are handled by the DSP version 2025-1 support. A consequence of this is that a consumer sees both offers and has to decide based on his own capabilities (Jupiter or Saturn), which offer to negotiate for. The difference can be determined by the namespace used for defining the operands. -A policy defined by CX-0152 will have a context binding like this: +A policy fulfilling the Saturn standard CX-0152 will have a context binding like this: ```json { @@ -163,6 +163,5 @@ part in that case. There is an extensive documentation on contracting in Catena-X in the regulatory framework: - [Catena-X Regulatory Framework - Contracting](https://catenax-ev.github.io/docs/next/regulatory-framework/Contracting/Guidance:%20Contract%20Modularization) -A recommended playground to experiment with the new policies is the Policy Builder which will soon be an Eclipse -Tractus-X offer, but so far, it can be found here: -- [Catena-X Policy Builder](https://fraunhoferisst.github.io/edc-dashboard/policy-builder/) +A recommended playground to experiment with the new policies is the Tractus-X Policy Builder which can be found here: +- [Catena-X Policy Builder](https://eclipse-tractusx.github.io/tractusx-edc-dashboard/policy-builder/) From eaa7084e83912e6dae42c13c948607a68d85ffa7 Mon Sep 17 00:00:00 2001 From: andrea bertagnolli Date: Fri, 5 Dec 2025 17:02:45 +0100 Subject: [PATCH 029/112] build: bump EDC to 0.15.0 (#2434) * build: bump EDC to 0.15.0 * introduce dedicated inmemory vault * fix iatp tests * Fix retirement test * fix iatp tests again * fix dcp tck test * fix azure tests * fix deployment tests --- core/core-utils/build.gradle.kts | 2 +- .../edc-controlplane-base/build.gradle.kts | 6 +- .../build.gradle.kts | 2 +- .../edc-runtime-memory/build.gradle.kts | 6 +- .../edc/vault/memory/VaultSeedExtension.java | 20 +++-- .../vault/memory/VaultSeedExtensionTest.java | 10 ++- .../edc-dataplane-base/build.gradle.kts | 29 ++++--- .../build.gradle.kts | 4 +- .../api/AgreementsRetirementApiExtension.java | 13 ++- edc-extensions/bdrs-client/build.gradle.kts | 5 +- .../mapper/BdrsClientAudienceMapper.java | 3 +- .../identity/mapper/BdrsClientExtension.java | 16 ++-- .../edc/identity/mapper/BdrsClientImpl.java | 22 +++-- .../mapper/BdrsClientAudienceMapperTest.java | 11 ++- .../mapper/BdrsClientImplComponentTest.java | 14 ++- .../identity/mapper/BdrsClientImplTest.java | 19 ++-- .../bpn-validation-api/build.gradle.kts | 1 + .../bpn/BusinessPartnerGroupApiExtension.java | 15 +++- .../connector-discovery-api/build.gradle.kts | 6 +- .../v4alpha/ConnectorDiscoveryExtension.java | 11 ++- .../cx-policy-legacy/build.gradle.kts | 2 +- edc-extensions/cx-policy/build.gradle.kts | 2 +- .../service/DataFlowServiceImplTest.java | 2 - .../api/DataPlanePublicApiV2Extension.java | 12 +-- .../token-refresh-core/build.gradle.kts | 1 + ...DataPlaneTokenRefreshServiceExtension.java | 6 +- .../DataPlaneTokenRefreshServiceImpl.java | 41 +++++++-- .../rules/RefreshTokenValidationRule.java | 7 +- ...eTokenRefreshServiceImplComponentTest.java | 23 +++-- .../DataPlaneTokenRefreshServiceImplTest.java | 46 +++++----- .../rules/RefreshTokenValidationRuleTest.java | 20 +++-- .../dataspace-protocol/build.gradle.kts | 3 +- .../protocol/DataspaceProtocolExtension.java | 29 +++++-- .../CatenaxParticipantIdentityResolver.java | 49 +++++++++++ .../DataspaceProtocolExtensionTest.java | 11 +-- .../dcp/tx-dcp-sts-dim/build.gradle.kts | 5 +- .../RemoteTokenServiceClientExtension.java | 21 ++--- .../sts/StsClientConfigurationExtension.java | 2 +- .../dcp/sts/dim/DimSecureTokenService.java | 6 +- .../dcp/sts/dim/oauth/DimOauthClientImpl.java | 15 +++- ...RemoteTokenServiceClientExtensionTest.java | 2 +- .../sts/dim/DimSecureTokenServiceTest.java | 21 +++-- .../sts/dim/oauth/DimOauthClientImplTest.java | 26 +++--- edc-extensions/dcp/tx-dcp/build.gradle.kts | 2 +- .../iam/iatp/IatpScopeExtractorExtension.java | 2 +- .../iatp/scope/CredentialScopeExtractor.java | 2 +- .../iatp/IatpScopeExtractorExtensionTest.java | 2 +- .../edr/edr-api-v2/build.gradle.kts | 1 + .../api/edr/BaseEdrCacheApiController.java | 11 ++- .../edc/api/edr/EdrCacheApiExtension.java | 27 +++--- .../api/edr/v2/EdrCacheApiV2Controller.java | 6 +- .../api/edr/v3/EdrCacheApiV3Controller.java | 6 +- .../edr/BaseEdrCacheApiControllerTest.java | 9 +- .../edr/v2/EdrCacheApiV2ControllerTest.java | 2 +- .../edr/v3/EdrCacheApiV3ControllerTest.java | 2 +- .../edr/edr-callback/build.gradle.kts | 6 +- .../callback/ContractNegotiationCallback.java | 11 ++- .../InProcessCallbackMessageDispatcher.java | 5 +- .../edc/callback/LocalCallbackExtension.java | 10 +-- .../ContractNegotiationCallbackTest.java | 19 ++-- ...nProcessCallbackMessageDispatcherTest.java | 11 +-- .../src/test/resources/schema.sql | 3 +- .../log4j2-monitor/build.gradle.kts | 2 - .../connector/V1_1_0__Lease_Fix.sql | 14 +++ .../V1_2_0__Add_ParticipantContextId.sql | 9 ++ .../V1_3_0__Add_DataplaneMetadata.sql | 2 + .../V1_4_0__Add_AgreementIdColumn.sql | 6 ++ .../ConnectorPostgresqlMigrationTest.java | 3 +- ...AssetPostgresqlMigrationExtensionTest.java | 87 ------------------- .../single-participant-vault/build.gradle.kts | 27 ++++++ .../InMemorySingleParticipantVault.java | 86 ++++++++++++++++++ ...MemorySingleParticipantVaultExtension.java | 34 ++++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 20 +++++ .../tokenrefresh-handler/build.gradle.kts | 6 +- .../TokenRefreshHandlerExtension.java | 8 +- .../tokenrefresh/TokenRefreshHandlerImpl.java | 45 +++++----- .../TokenRefreshHandlerImplTest.java | 22 +++-- edc-tests/e2e-fixtures/build.gradle.kts | 3 +- .../edc/tests/MockVcIdentityService.java | 4 +- .../ParticipantConsumerDataPlaneApi.java | 2 +- .../edc/tests/aws/LocalstackExtension.java | 2 +- .../edc/tests/aws/MinioExtension.java | 2 +- .../tests/extension/VaultSeedExtension.java | 11 ++- .../tractusx/edc/tests/helpers/Functions.java | 13 --- .../TractusxIatpParticipantBase.java | 2 +- .../participant/TractusxParticipantBase.java | 4 +- .../participant/TransferParticipant.java | 2 +- .../edc/tests/runtimes/DataWiper.java | 2 +- .../runtimes/ParticipantRuntimeExtension.java | 36 ++++++-- .../e2e/bpn-event-tests/build.gradle.kts | 2 + .../tests/catalog/FederatedCatalogTest.java | 10 +-- .../transfer/test/AzureToAzureTest.java | 2 +- .../transfer/test/MultiCloudTest.java | 2 +- .../transfer/test/RuntimeConfig.java | 2 +- .../dataplane/transfer/test/S3ToS3Test.java | 2 +- edc-tests/e2e/dcp-tck-tests/build.gradle.kts | 2 +- .../tck/dcp/DcpPresentationFlowTest.java | 32 ++++--- .../dsp-compatibility-tests/build.gradle.kts | 2 +- .../tck/dsp/EdcCompatibilityPostgresTest.java | 15 ++-- .../build.gradle.kts | 1 + .../DataPlaneTokenRefreshEndToEndTest.java | 10 ++- .../tokenrefresh/e2e/RuntimeConfig.java | 2 +- edc-tests/e2e/edr-api-tests/build.gradle.kts | 1 + .../edc/tests/edrv2/NegotiateEdrTest.java | 12 ++- .../end2end-transfer-cloud/build.gradle.kts | 2 + .../transfer/AzureToAzureEndToEndTest.java | 4 +- edc-tests/e2e/iatp-tests/build.gradle.kts | 17 +++- .../AbstractIatpConsumerPullTest.java | 26 +++--- .../tests/transfer/CredentialSpoofTest.java | 18 ++-- .../tests/transfer/DimConsumerPullTest.java | 54 ++++++++---- .../transfer/IdentityExtractionTest.java | 2 +- .../tests/transfer/StsConsumerPullTest.java | 19 ++-- .../extension/BdrsServerExtension.java | 2 +- .../extension/DidServerExtension.java | 2 +- .../iatp/dispatchers/DimDispatcher.java | 4 +- .../iatp/harness/IatpParticipant.java | 76 ++++++++++------ .../transfer/iatp/harness/StsParticipant.java | 11 ++- .../IatpParticipantRuntimeExtension.java | 7 +- .../tests/transfer/iatp/runtime/Runtimes.java | 11 ++- .../tests/transfer/RetireAgreementTest.java | 11 ++- .../runtime/dataplane-cloud/build.gradle.kts | 1 + .../iatp/iatp-extensions/build.gradle.kts | 3 + .../edc/iatp/CredentialsJsonLdExtension.java | 8 ++ .../build.gradle.kts | 5 +- .../runtime-memory-iatp-ih/build.gradle.kts | 11 ++- .../iatp/runtime-memory-sts/build.gradle.kts | 6 +- .../runtime/mock-connector/build.gradle.kts | 1 + .../edc/mock/MockServiceExtension.java | 13 +-- .../ContractNegotiationServiceStub.java | 3 +- .../services/TransferProcessServiceStub.java | 9 +- .../edc/mock/services/VersionServiceStub.java | 58 ------------- .../runtime-discovery-base/build.gradle.kts | 5 +- .../build.gradle.kts | 5 +- .../build.gradle.kts | 5 +- .../runtime/runtime-dsp/build.gradle.kts | 2 +- .../runtime-postgresql/build.gradle.kts | 5 +- gradle/libs.versions.toml | 51 ++++++----- .../mockedc/UseMockConnectorSampleTest.java | 35 +------- .../src/test/resources/versions.request.json | 26 ------ settings.gradle.kts | 1 + 140 files changed, 1063 insertions(+), 711 deletions(-) create mode 100644 edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/CatenaxParticipantIdentityResolver.java create mode 100644 edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_1_0__Lease_Fix.sql create mode 100644 edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_2_0__Add_ParticipantContextId.sql create mode 100644 edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_3_0__Add_DataplaneMetadata.sql create mode 100644 edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_4_0__Add_AgreementIdColumn.sql delete mode 100644 edc-extensions/migrations/control-plane-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/AssetPostgresqlMigrationExtensionTest.java create mode 100644 edc-extensions/single-participant-vault/build.gradle.kts create mode 100644 edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVault.java create mode 100644 edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVaultExtension.java create mode 100644 edc-extensions/single-participant-vault/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension delete mode 100644 edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/VersionServiceStub.java delete mode 100644 samples/testing-with-mocked-connector/src/test/resources/versions.request.json diff --git a/core/core-utils/build.gradle.kts b/core/core-utils/build.gradle.kts index 5a8a90ae24..d55e072147 100644 --- a/core/core-utils/build.gradle.kts +++ b/core/core-utils/build.gradle.kts @@ -23,6 +23,6 @@ plugins { dependencies { implementation(libs.edc.spi.core) - implementation(libs.edc.spi.identitytrust) + implementation(libs.edc.spi.decentralized.claims) implementation(libs.edc.spi.vc) } diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index 866fea5179..18e8f4631a 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -27,8 +27,8 @@ configurations.all { // edr-cache-api excluded due to edr controller signature clash with tx-edr-api-v2 that provides same functionality with token auto_refresh capability exclude(group = "org.eclipse.edc", module = "edr-cache-api") - // identity-trust-sts-remote-client excluded because we have the tx-dcp-sts-dim that takes care to define the correct client in case of DIM - exclude("org.eclipse.edc", "identity-trust-sts-remote-client") + // decentralized-claims-sts-remote-client excluded because we have the tx-dcp-sts-dim that takes care to define the correct client in case of DIM + exclude("org.eclipse.edc", "decentralized-claims-sts-remote-client") } dependencies { @@ -59,7 +59,7 @@ dependencies { implementation(project(":edc-extensions:connector-discovery:connector-discovery-api")) implementation(project(":edc-extensions:dataspace-protocol")) implementation(project(":edc-extensions:token-interceptor")) - runtimeOnly(project(":edc-extensions:event-subscriber")) + implementation(project(":edc-extensions:event-subscriber")) runtimeOnly(libs.bundles.edc.monitoring) runtimeOnly(libs.edc.aws.validator.data.address.s3) diff --git a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts index 0b878217a6..379efb01e7 100644 --- a/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts @@ -32,7 +32,7 @@ configurations.all { } dependencies { - runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) + implementation(project(":edc-controlplane:edc-controlplane-base")) runtimeOnly(libs.edc.bom.controlplane.feature.sql) runtimeOnly(libs.edc.bom.federatedcatalog.feature.sql) diff --git a/edc-controlplane/edc-runtime-memory/build.gradle.kts b/edc-controlplane/edc-runtime-memory/build.gradle.kts index c68d0f3b64..53f6fb28e2 100644 --- a/edc-controlplane/edc-runtime-memory/build.gradle.kts +++ b/edc-controlplane/edc-runtime-memory/build.gradle.kts @@ -24,13 +24,15 @@ plugins { } dependencies { - runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) - runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) { + implementation(project(":edc-controlplane:edc-controlplane-base")) + implementation(project(":edc-dataplane:edc-dataplane-base")) { exclude("org.eclipse.edc", "data-plane-selector-client") } implementation(project(":core:core-utils")) + implementation(project(":edc-extensions:single-participant-vault")) implementation(libs.edc.spi.core) + implementation(libs.edc.spi.participant.context.single) testImplementation(libs.edc.junit) testImplementation(libs.edc.lib.boot) diff --git a/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtension.java b/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtension.java index d4136860db..e0e43c2d00 100644 --- a/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtension.java +++ b/edc-controlplane/edc-runtime-memory/src/main/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtension.java @@ -19,11 +19,14 @@ package org.eclipse.tractusx.edc.vault.memory; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.runtime.metamodel.annotation.BaseExtension; 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.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; @@ -40,6 +43,8 @@ public class VaultSeedExtension implements ServiceExtension { @Inject private Vault vault; + @Inject + private SingleParticipantContextSupplier singleParticipantContextSupplier; @Override public String name() { @@ -51,11 +56,16 @@ public Vault createInMemVault(ServiceExtensionContext context) { var seedSecrets = context.getSetting(VAULT_MEMORY_SECRETS_PROPERTY, null); if (seedSecrets != null) { - Stream.of(seedSecrets.split(";")) - .filter(pair -> pair.contains(":")) - .map(kvp -> kvp.split(":", 2)) - .filter(kvp -> kvp.length >= 2) - .forEach(pair -> vault.storeSecret(pair[0], pair[1])); + singleParticipantContextSupplier.get().map(ParticipantContext::getParticipantContextId) + .onSuccess(participantContextId -> { + Stream.of(seedSecrets.split(";")) + .filter(pair -> pair.contains(":")) + .map(kvp -> kvp.split(":", 2)) + .filter(kvp -> kvp.length >= 2) + .forEach(pair -> vault.storeSecret(participantContextId, pair[0], pair[1])); + }) + .orElseThrow(f -> new EdcException("Cannot get the participant context: " + f.getFailureDetail())); + } return vault; } diff --git a/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtensionTest.java b/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtensionTest.java index d01e055d3b..a1ffc30169 100644 --- a/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtensionTest.java +++ b/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtensionTest.java @@ -21,7 +21,10 @@ import org.eclipse.edc.boot.vault.InMemoryVault; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.junit.jupiter.api.BeforeEach; @@ -40,13 +43,16 @@ @ExtendWith(DependencyInjectionExtension.class) class VaultSeedExtensionTest { - private Monitor monitor; + private final Monitor monitor = mock(); + private final SingleParticipantContextSupplier participantContextSupplier = () -> ServiceResult.success( + ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build() + ); @BeforeEach void setup(ServiceExtensionContext context) { - monitor = mock(Monitor.class); context.registerService(Monitor.class, monitor); context.registerService(Vault.class, new InMemoryVault(monitor)); + context.registerService(SingleParticipantContextSupplier.class, participantContextSupplier); } @Test diff --git a/edc-dataplane/edc-dataplane-base/build.gradle.kts b/edc-dataplane/edc-dataplane-base/build.gradle.kts index ff4299593f..ee07823f24 100644 --- a/edc-dataplane/edc-dataplane-base/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-base/build.gradle.kts @@ -20,25 +20,26 @@ plugins { `java-library` + id(libs.plugins.swagger.get().pluginId) } dependencies { runtimeOnly(libs.edc.bom.dataplane.base) - runtimeOnly(project(":core:edr-core")) - runtimeOnly(project(":edc-extensions:log4j2-monitor")) - runtimeOnly(project(":edc-extensions:dataplane:dataplane-proxy:dataplane-proxy-http")) - runtimeOnly(project(":edc-extensions:dataplane:dataplane-proxy:dataplane-public-api-v2")) - runtimeOnly(project(":edc-extensions:dataplane:dataplane-proxy:edc-dataplane-proxy-consumer-api")) - runtimeOnly(project(":edc-extensions:dataplane:dataplane-token-refresh:token-refresh-api")) - runtimeOnly(project(":edc-extensions:dataplane:dataplane-token-refresh:token-refresh-core")) - runtimeOnly(project(":edc-extensions:dcp:tx-dcp-sts-dim")) - runtimeOnly(project(":edc-extensions:tokenrefresh-handler")) - runtimeOnly(project(":edc-extensions:event-subscriber")) - runtimeOnly(project(":edc-extensions:non-finite-provider-push:non-finite-provider-push-core")) + implementation(project(":core:edr-core")) + implementation(project(":edc-extensions:log4j2-monitor")) + implementation(project(":edc-extensions:dataplane:dataplane-proxy:dataplane-proxy-http")) + implementation(project(":edc-extensions:dataplane:dataplane-proxy:dataplane-public-api-v2")) + implementation(project(":edc-extensions:dataplane:dataplane-proxy:edc-dataplane-proxy-consumer-api")) + implementation(project(":edc-extensions:dataplane:dataplane-token-refresh:token-refresh-api")) + implementation(project(":edc-extensions:dataplane:dataplane-token-refresh:token-refresh-core")) + implementation(project(":edc-extensions:dcp:tx-dcp-sts-dim")) + implementation(project(":edc-extensions:tokenrefresh-handler")) + implementation(project(":edc-extensions:event-subscriber")) + implementation(project(":edc-extensions:non-finite-provider-push:non-finite-provider-push-core")) - runtimeOnly(project(":edc-extensions:dataplane:dataflow:dataflow-api")) - runtimeOnly(project(":edc-extensions:dataplane:dataflow:dataflow-service")) + implementation(project(":edc-extensions:dataplane:dataflow:dataflow-api")) + implementation(project(":edc-extensions:dataplane:dataflow:dataflow-service")) runtimeOnly(libs.edc.api.management.config) runtimeOnly(libs.edc.auth.tokenbased) runtimeOnly(libs.edc.auth.configuration) @@ -48,6 +49,8 @@ dependencies { runtimeOnly(libs.edc.aws.validator.data.address.s3) runtimeOnly(libs.edc.core.did) // for the DID Public Key Resolver runtimeOnly(libs.edc.core.edrstore) + runtimeOnly(libs.edc.core.participant.context.config) + runtimeOnly(libs.edc.core.participant.context.single) runtimeOnly(libs.edc.dpf.awss3) runtimeOnly(libs.edc.dpf.azblob) runtimeOnly(libs.edc.identity.did.web) diff --git a/edc-extensions/agreements/retirement-evaluation-api/build.gradle.kts b/edc-extensions/agreements/retirement-evaluation-api/build.gradle.kts index cdb7bac443..36dcaad02d 100644 --- a/edc-extensions/agreements/retirement-evaluation-api/build.gradle.kts +++ b/edc-extensions/agreements/retirement-evaluation-api/build.gradle.kts @@ -22,10 +22,12 @@ plugins { `maven-publish` id(libs.plugins.swagger.get().pluginId) } + dependencies { implementation(project(":edc-extensions:agreements:retirement-evaluation-spi")) implementation(libs.edc.api.management.config) + implementation(libs.edc.lib.jersey.providers) implementation(libs.jakarta.rsApi) @@ -40,4 +42,4 @@ edcBuild { swagger { apiGroup.set("control-plane") } -} \ No newline at end of file +} diff --git a/edc-extensions/agreements/retirement-evaluation-api/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/api/AgreementsRetirementApiExtension.java b/edc-extensions/agreements/retirement-evaluation-api/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/api/AgreementsRetirementApiExtension.java index 39c9a3badd..01f8a10f11 100644 --- a/edc-extensions/agreements/retirement-evaluation-api/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/api/AgreementsRetirementApiExtension.java +++ b/edc-extensions/agreements/retirement-evaluation-api/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/api/AgreementsRetirementApiExtension.java @@ -20,13 +20,16 @@ package org.eclipse.tractusx.edc.agreements.retirement.api; import jakarta.json.Json; +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.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; import org.eclipse.tractusx.edc.agreements.retirement.api.transform.JsonObjectFromAgreementRetirementTransformer; @@ -36,6 +39,7 @@ import java.util.Map; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; import static org.eclipse.tractusx.edc.agreements.retirement.api.AgreementsRetirementApiExtension.NAME; @@ -59,6 +63,10 @@ public String name() { private AgreementsRetirementService agreementsRetirementService; @Inject private Monitor monitor; + @Inject + private JsonLd jsonLd; + @Inject + private TypeManager typeManager; @Override public void initialize(ServiceExtensionContext context) { @@ -68,7 +76,10 @@ public void initialize(ServiceExtensionContext context) { managementTypeTransformerRegistry.register(new JsonObjectFromAgreementRetirementTransformer(jsonFactory)); managementTypeTransformerRegistry.register(new JsonObjectToAgreementsRetirementEntryTransformer()); - webService.registerResource(ApiContext.MANAGEMENT, new AgreementsRetirementApiV3Controller(agreementsRetirementService, managementTypeTransformerRegistry, validator, monitor)); + webService.registerResource(ApiContext.MANAGEMENT, new AgreementsRetirementApiV3Controller(agreementsRetirementService, + managementTypeTransformerRegistry, validator, monitor)); + webService.registerDynamicResource(ApiContext.MANAGEMENT, AgreementsRetirementApiV3Controller.class, + new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, "MANAGEMENT_API")); } } diff --git a/edc-extensions/bdrs-client/build.gradle.kts b/edc-extensions/bdrs-client/build.gradle.kts index c25d8334e6..a657e4dcb6 100644 --- a/edc-extensions/bdrs-client/build.gradle.kts +++ b/edc-extensions/bdrs-client/build.gradle.kts @@ -29,11 +29,12 @@ dependencies { implementation(libs.edc.spi.boot) implementation(libs.edc.spi.core) implementation(libs.edc.spi.http) - implementation(libs.edc.spi.identitytrust) + implementation(libs.edc.spi.decentralized.claims) implementation(libs.edc.spi.identity.did) implementation(libs.edc.spi.jwt) //JwtRegisteredClaimNames + implementation(libs.edc.spi.participant.context.single) - implementation(libs.edc.identity.trust.service) + implementation(libs.edc.decentralized.claims.service) testImplementation(libs.wiremock) testImplementation(libs.edc.junit) diff --git a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapper.java b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapper.java index 70e1b8b5be..b9c29181cc 100644 --- a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapper.java +++ b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapper.java @@ -22,6 +22,7 @@ import org.eclipse.edc.spi.iam.AudienceResolver; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.types.domain.message.ProtocolRemoteMessage; import org.eclipse.edc.spi.types.domain.message.RemoteMessage; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; @@ -43,7 +44,7 @@ class BdrsClientAudienceMapper implements AudienceResolver { } @Override - public Result resolve(RemoteMessage remoteMessage) { + public Result resolve(ProtocolRemoteMessage remoteMessage) { try { var counterPartyId = remoteMessage.getCounterPartyId(); if (counterPartyId.startsWith(DID_PREFIX)) { diff --git a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientExtension.java b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientExtension.java index 46b9594d01..c1c60d59bb 100644 --- a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientExtension.java +++ b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientExtension.java @@ -20,10 +20,11 @@ package org.eclipse.tractusx.edc.identity.mapper; import org.eclipse.edc.http.spi.EdcHttpClient; +import org.eclipse.edc.iam.decentralizedclaims.service.DidCredentialServiceUrlResolver; +import org.eclipse.edc.iam.decentralizedclaims.spi.CredentialServiceClient; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; -import org.eclipse.edc.iam.identitytrust.service.DidCredentialServiceUrlResolver; -import org.eclipse.edc.iam.identitytrust.spi.CredentialServiceClient; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; @@ -57,18 +58,16 @@ public class BdrsClientExtension implements ServiceExtension { @Inject private EdcHttpClient httpClient; - @Inject private TypeManager typeManager; - @Inject private SecureTokenService secureTokenService; - @Inject private CredentialServiceClient credentialServiceClient; - @Inject private DidResolverRegistry didResolverRegistry; + @Inject + private SingleParticipantContextSupplier participantContextSupplier; @Override public String name() { @@ -105,7 +104,8 @@ public BdrsClient getBdrsClient(ServiceExtensionContext context) { } - return new BdrsClientImpl(baseUrl, cacheValidity, ownDid, urlSupplier, httpClient, monitor, typeManager.getMapper(), secureTokenService, credentialServiceClient); + return new BdrsClientImpl(baseUrl, cacheValidity, ownDid, urlSupplier, httpClient, monitor, typeManager.getMapper(), + secureTokenService, credentialServiceClient, participantContextSupplier); } } diff --git a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java index 9aab3b9a02..7527f11492 100644 --- a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java +++ b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java @@ -23,8 +23,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.Request; import org.eclipse.edc.http.spi.EdcHttpClient; -import org.eclipse.edc.iam.identitytrust.spi.CredentialServiceClient; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.spi.CredentialServiceClient; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; @@ -66,6 +68,7 @@ class BdrsClientImpl implements BdrsClient { private final String ownDid; private final Supplier ownCredentialServiceUrl; private final CredentialServiceClient credentialServiceClient; + private final ParticipantContextSupplier participantContextSupplier; private Map cacheBpnDid = new HashMap<>(); private Map cacheDidBpn = new HashMap<>(); private Instant lastCacheUpdate; @@ -78,7 +81,7 @@ class BdrsClientImpl implements BdrsClient { Monitor monitor, ObjectMapper mapper, SecureTokenService secureTokenService, - CredentialServiceClient credentialServiceClient) { + CredentialServiceClient credentialServiceClient, ParticipantContextSupplier participantContextSupplier) { this.serverUrl = baseUrl; this.cacheValidity = cacheValidity; this.httpClient = httpClient; @@ -88,6 +91,7 @@ class BdrsClientImpl implements BdrsClient { this.ownDid = ownDid; this.ownCredentialServiceUrl = ownCredentialServiceUrl; this.credentialServiceClient = credentialServiceClient; + this.participantContextSupplier = participantContextSupplier; } @Override @@ -151,7 +155,7 @@ private Result updateCache() { .get() .build(); try (var response = httpClient.execute(request)) { - if (response.isSuccessful() && response.body() != null) { + if (response.isSuccessful()) { var body = response.body().byteStream(); try (var gz = new GZIPInputStream(body)) { var bytes = gz.readAllBytes(); @@ -185,7 +189,15 @@ private Result createMembershipPresentation() { ); var scope = TxIatpConstants.MEMBERSHIP_SCOPE; - return secureTokenService.createToken(claims, scope) + return participantContextSupplier.get().map(ParticipantContext::getParticipantContextId) + .flatMap(result -> { + if (result.succeeded()) { + return Result.success(result.getContent()); + } else { + return Result.failure(result.getFailureDetail()); + } + }) + .compose(id -> secureTokenService.createToken(id, claims, scope)) .compose(sit -> credentialServiceClient.requestPresentation(ownCredentialServiceUrl.get(), sit.getToken(), List.of(scope))) .compose(pres -> { if (pres.isEmpty()) { diff --git a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapperTest.java b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapperTest.java index 5d60c15b8e..9b4b9a3208 100644 --- a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapperTest.java +++ b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapperTest.java @@ -20,7 +20,7 @@ package org.eclipse.tractusx.edc.identity.mapper; -import org.eclipse.edc.spi.types.domain.message.RemoteMessage; +import org.eclipse.edc.spi.types.domain.message.ProtocolRemoteMessage; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.junit.jupiter.api.Test; @@ -68,7 +68,13 @@ void shouldFail_whenResolutionThrowsException() { assertThat(did).isFailed().detail().contains("exception"); } - private record TestMessage(String counterPartyId) implements RemoteMessage { + private static final class TestMessage extends ProtocolRemoteMessage { + private final String counterPartyId; + + private TestMessage(String counterPartyId) { + this.counterPartyId = counterPartyId; + } + @Override public String getProtocol() { return "test-proto"; @@ -83,5 +89,6 @@ public String getCounterPartyAddress() { public String getCounterPartyId() { return counterPartyId; } + } } diff --git a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplComponentTest.java b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplComponentTest.java index cba9a39ce2..2b5b6e57bc 100644 --- a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplComponentTest.java +++ b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplComponentTest.java @@ -35,16 +35,19 @@ import dev.failsafe.RetryPolicy; import okhttp3.OkHttpClient; import org.eclipse.edc.http.client.EdcHttpClientImpl; -import org.eclipse.edc.iam.identitytrust.spi.CredentialServiceClient; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.spi.CredentialServiceClient; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentation; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; import org.eclipse.edc.junit.annotations.ComponentTest; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.ServiceResult; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -137,8 +140,11 @@ void setup() throws IOException, ParseException { vpHolderKey = ECKey.parse(Files.readString(Path.of(SHARED_TEMP_DIR, HOLDER_NAME + "/key.json"))); SecureTokenService secureTokenService = mock(); - when(secureTokenService.createToken(any(), any())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("token").build())); + when(secureTokenService.createToken(any(), any(), any())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("token").build())); var directoryPort = BDRS_SERVER_CONTAINER.getMappedPort(8082); + ParticipantContextSupplier participantContextSupplier = mock(); + var participantContext = ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build(); + when(participantContextSupplier.get()).thenReturn(ServiceResult.success(participantContext)); client = new BdrsClientImpl("http://%s:%d/api/directory".formatted(BDRS_SERVER_CONTAINER.getHost(), directoryPort), 1, "did:web:self", () -> "http://credential.service", @@ -146,7 +152,7 @@ void setup() throws IOException, ParseException { monitor, mapper, secureTokenService, - csMock); + csMock, participantContextSupplier); // need to wait until healthy, otherwise BDRS will respond with a 404 await().atMost(Duration.ofSeconds(20)).untilAsserted(() -> { diff --git a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java index ed4ebf2c56..02ed849db0 100644 --- a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java +++ b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java @@ -25,15 +25,18 @@ import dev.failsafe.RetryPolicy; import okhttp3.OkHttpClient; import org.eclipse.edc.http.client.EdcHttpClientImpl; -import org.eclipse.edc.iam.identitytrust.spi.CredentialServiceClient; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.spi.CredentialServiceClient; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentation; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.ServiceResult; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -59,6 +62,7 @@ import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; @@ -74,6 +78,7 @@ class BdrsClientImplTest { private final ObjectMapper mapper = new ObjectMapper(); private final SecureTokenService stsMock = mock(); private final CredentialServiceClient csMock = mock(); + private final ParticipantContextSupplier participantContextSupplier = mock(); private BdrsClientImpl client; @RegisterExtension static WireMockExtension bdrsServer = WireMockExtension.newInstance() @@ -87,6 +92,8 @@ void setup() { .withHeader("Content-Encoding", "gzip") .withBody(createGzipStream()) .withStatus(200))); + var participantContext = ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build(); + when(participantContextSupplier.get()).thenReturn(ServiceResult.success(participantContext)); client = new BdrsClientImpl("http://localhost:%d/api".formatted(bdrsServer.getPort()), 1, "did:web:self", () -> "http://credential.service", @@ -94,10 +101,10 @@ void setup() { monitor, mapper, stsMock, - csMock); + csMock, participantContextSupplier); // prime STS and CS - when(stsMock.createToken(anyMap(), notNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("my-fancy-sitoken").build())); + when(stsMock.createToken(any(), anyMap(), notNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("my-fancy-sitoken").build())); when(csMock.requestPresentation(anyString(), anyString(), anyList())) .thenReturn(Result.success(List.of(new VerifiablePresentationContainer(TEST_VP_CONTENT, CredentialFormat.VC1_0_JWT, VerifiablePresentation.Builder.newInstance().type("VerifiableCredential").build())))); @@ -154,7 +161,7 @@ void getData_bdrsReturnsError(int code) { @Test void getData_whenStsFails() { - when(stsMock.createToken(anyMap(), notNull())).thenReturn(Result.failure("test-failure")); + when(stsMock.createToken(any(), anyMap(), notNull())).thenReturn(Result.failure("test-failure")); assertThatThrownBy(() -> client.resolveDid("bpn1")) .isInstanceOf(EdcException.class) .hasMessage("test-failure"); @@ -214,4 +221,4 @@ private byte[] createGzipStream() { return bas.toByteArray(); } -} \ No newline at end of file +} diff --git a/edc-extensions/bpn-validation/bpn-validation-api/build.gradle.kts b/edc-extensions/bpn-validation/bpn-validation-api/build.gradle.kts index 8c853e39b8..6dccca2ac9 100644 --- a/edc-extensions/bpn-validation/bpn-validation-api/build.gradle.kts +++ b/edc-extensions/bpn-validation/bpn-validation-api/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(libs.edc.api.management) { exclude("org.eclipse.edc", "edr-cache-api") } + implementation(libs.edc.lib.jersey.providers) implementation(libs.jakarta.rsApi) testImplementation(testFixtures(libs.edc.core.jersey)) diff --git a/edc-extensions/bpn-validation/bpn-validation-api/src/main/java/org/eclipse/tractusx/edc/api/bpn/BusinessPartnerGroupApiExtension.java b/edc-extensions/bpn-validation/bpn-validation-api/src/main/java/org/eclipse/tractusx/edc/api/bpn/BusinessPartnerGroupApiExtension.java index af8c5840ff..8a5a34545e 100644 --- a/edc-extensions/bpn-validation/bpn-validation-api/src/main/java/org/eclipse/tractusx/edc/api/bpn/BusinessPartnerGroupApiExtension.java +++ b/edc-extensions/bpn-validation/bpn-validation-api/src/main/java/org/eclipse/tractusx/edc/api/bpn/BusinessPartnerGroupApiExtension.java @@ -25,6 +25,8 @@ import org.eclipse.edc.spi.event.EventRouter; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; import org.eclipse.tractusx.edc.api.bpn.v1.BusinessPartnerGroupApiV1Controller; @@ -34,16 +36,19 @@ import java.time.Clock; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; + @Extension(value = "Registers the Business Partner Group API") public class BusinessPartnerGroupApiExtension implements ServiceExtension { @Inject private WebService webService; @Inject - private JsonLd jsonLdService; + private JsonLd jsonLd; @Inject private BusinessPartnerStore businessPartnerStore; - + @Inject + private TypeManager typeManager; @Inject private Clock clock; @Inject @@ -57,8 +62,14 @@ public void initialize(ServiceExtensionContext context) { webService.registerResource(ApiContext.MANAGEMENT, new BusinessPartnerGroupApiV1Controller( businessPartnerStore, businessPartnerObservable, context.getMonitor() )); + webService.registerDynamicResource(ApiContext.MANAGEMENT, BusinessPartnerGroupApiV1Controller.class, + new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, "MANAGEMENT_API")); + + webService.registerResource(ApiContext.MANAGEMENT, new BusinessPartnerGroupApiV3Controller( businessPartnerStore, businessPartnerObservable )); + webService.registerDynamicResource(ApiContext.MANAGEMENT, BusinessPartnerGroupApiV3Controller.class, + new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, "MANAGEMENT_API")); } } diff --git a/edc-extensions/connector-discovery/connector-discovery-api/build.gradle.kts b/edc-extensions/connector-discovery/connector-discovery-api/build.gradle.kts index e950a17871..ca14c0280e 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/build.gradle.kts +++ b/edc-extensions/connector-discovery/connector-discovery-api/build.gradle.kts @@ -18,7 +18,7 @@ */ plugins { - "java-library" + `java-library` id(libs.plugins.swagger.get().pluginId) } @@ -32,10 +32,10 @@ dependencies { api(libs.edc.spi.http) api(libs.edc.spi.jsonld) api(libs.edc.spi.controlplane) - api(libs.edc.spi.protocolversion) api(libs.dsp.spi.v08) api(libs.dsp.spi.v2025) + implementation(libs.edc.lib.jersey.providers) implementation(libs.edc.lib.validator) implementation(libs.edc.lib.util) implementation(libs.edc.boot) @@ -51,4 +51,4 @@ edcBuild { swagger { apiGroup.set("control-plane") } -} \ No newline at end of file +} diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryExtension.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryExtension.java index ad467d3d25..cf9a61eb95 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryExtension.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryExtension.java @@ -19,12 +19,15 @@ package org.eclipse.tractusx.edc.discovery.v4alpha; +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.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; import org.eclipse.tractusx.edc.discovery.v4alpha.api.ConnectorDiscoveryV4AlphaController; @@ -33,6 +36,7 @@ import org.eclipse.tractusx.edc.discovery.v4alpha.transformers.JsonObjectToConnectorDiscoveryRequest; import org.eclipse.tractusx.edc.discovery.v4alpha.validators.ConnectorDiscoveryRequestValidator; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; import static org.eclipse.tractusx.edc.discovery.v4alpha.ConnectorDiscoveryExtension.NAME; @Extension(value = NAME) @@ -53,6 +57,10 @@ public String name() { private JsonObjectValidatorRegistry validatorRegistry; @Inject private ConnectorDiscoveryService connectorDiscoveryService; + @Inject + private JsonLd jsonLd; + @Inject + private TypeManager typeManager; @Override public void initialize(ServiceExtensionContext context) { @@ -62,5 +70,6 @@ public void initialize(ServiceExtensionContext context) { validatorRegistry.register(ConnectorParamsDiscoveryRequest.TYPE, ConnectorDiscoveryRequestValidator.instance()); webService.registerResource(ApiContext.MANAGEMENT, new ConnectorDiscoveryV4AlphaController(connectorDiscoveryService, managementTypeTransformerRegistry, validatorRegistry)); + webService.registerDynamicResource(ApiContext.MANAGEMENT, ConnectorDiscoveryV4AlphaController.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, "MANAGEMENT_API")); } -} \ No newline at end of file +} diff --git a/edc-extensions/cx-policy-legacy/build.gradle.kts b/edc-extensions/cx-policy-legacy/build.gradle.kts index 8f58405b72..d16c8d0020 100644 --- a/edc-extensions/cx-policy-legacy/build.gradle.kts +++ b/edc-extensions/cx-policy-legacy/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { implementation(project(":core:core-utils")) implementation(libs.edc.spi.catalog) implementation(libs.edc.spi.contract) - implementation(libs.edc.spi.identitytrust) + implementation(libs.edc.spi.decentralized.claims) implementation(libs.edc.spi.policyengine) implementation(libs.edc.spi.vc) implementation(libs.jakartaJson) diff --git a/edc-extensions/cx-policy/build.gradle.kts b/edc-extensions/cx-policy/build.gradle.kts index 1cd2385ebf..91cf71c45d 100644 --- a/edc-extensions/cx-policy/build.gradle.kts +++ b/edc-extensions/cx-policy/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { implementation(project(":spi:bdrs-client-spi")) implementation(libs.edc.spi.catalog) implementation(libs.edc.spi.contract) - implementation(libs.edc.spi.identitytrust) + implementation(libs.edc.spi.decentralized.claims) implementation(libs.edc.spi.policyengine) implementation(libs.edc.spi.vc) implementation(libs.jakartaJson) diff --git a/edc-extensions/dataplane/dataflow/dataflow-service/src/test/java/org/eclipse/tractusx/edc/dataflow/service/DataFlowServiceImplTest.java b/edc-extensions/dataplane/dataflow/dataflow-service/src/test/java/org/eclipse/tractusx/edc/dataflow/service/DataFlowServiceImplTest.java index 9c63dfce43..5617dd3165 100644 --- a/edc-extensions/dataplane/dataflow/dataflow-service/src/test/java/org/eclipse/tractusx/edc/dataflow/service/DataFlowServiceImplTest.java +++ b/edc-extensions/dataplane/dataflow/dataflow-service/src/test/java/org/eclipse/tractusx/edc/dataflow/service/DataFlowServiceImplTest.java @@ -36,7 +36,6 @@ import static org.eclipse.edc.spi.result.ServiceFailure.Reason.NOT_FOUND; import static org.eclipse.edc.spi.types.domain.transfer.FlowType.PULL; import static org.eclipse.edc.spi.types.domain.transfer.FlowType.PUSH; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -129,7 +128,6 @@ public void trigger_shouldReturnSuccess_whenAllValidationsSucceed() { when(store.findByIdAndLease(DATAFLOW_ID)).thenReturn(StoreResult.success(dataFlow)); when(finitenessEvaluator.isNonFinite(dataFlow)).thenReturn(true); - doNothing().when(store).save(dataFlow); var result = service.trigger(DATAFLOW_ID); diff --git a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/DataPlanePublicApiV2Extension.java b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/DataPlanePublicApiV2Extension.java index 5b2d8150e7..39106e1b50 100644 --- a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/DataPlanePublicApiV2Extension.java +++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/DataPlanePublicApiV2Extension.java @@ -36,7 +36,6 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.ApiContext; import org.eclipse.edc.web.spi.configuration.PortMapping; import org.eclipse.edc.web.spi.configuration.PortMappingRegistry; import org.eclipse.tractusx.edc.dataplane.proxy.api.controller.DataPlanePublicApiV2Controller; @@ -56,6 +55,8 @@ public class DataPlanePublicApiV2Extension implements ServiceExtension { private static final int DEFAULT_PUBLIC_PORT = 8185; private static final String DEFAULT_PUBLIC_PATH = "/api/public"; private static final int DEFAULT_THREAD_POOL = 10; + private static final String API_CONTEXT = "public"; + @Setting(description = "Base url of the public API endpoint without the trailing slash. This should point to the public endpoint configured.", required = false, key = "edc.dataplane.api.public.baseurl", warnOnMissingConfig = true) @@ -64,6 +65,7 @@ public class DataPlanePublicApiV2Extension implements ServiceExtension { private String publicApiResponseUrl; @Configuration private PublicApiConfiguration apiConfiguration; + @Inject private PortMappingRegistry portMappingRegistry; @Inject @@ -88,7 +90,7 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var portMapping = new PortMapping(ApiContext.PUBLIC, apiConfiguration.port(), apiConfiguration.path()); + var portMapping = new PortMapping(API_CONTEXT, apiConfiguration.port(), apiConfiguration.path()); portMappingRegistry.register(portMapping); var executorService = executorInstrumentation.instrument( Executors.newFixedThreadPool(DEFAULT_THREAD_POOL), @@ -115,14 +117,14 @@ public void initialize(ServiceExtensionContext context) { } var publicApiController = new DataPlanePublicApiV2Controller(pipelineService, executorService, authorizationService); - webService.registerResource(ApiContext.PUBLIC, publicApiController); + webService.registerResource(API_CONTEXT, publicApiController); } @Settings record PublicApiConfiguration( - @Setting(key = "web.http." + ApiContext.PUBLIC + ".port", description = "Port for " + ApiContext.PUBLIC + " api context", defaultValue = DEFAULT_PUBLIC_PORT + "") + @Setting(key = "web.http." + API_CONTEXT + ".port", description = "Port for " + API_CONTEXT + " api context", defaultValue = DEFAULT_PUBLIC_PORT + "") int port, - @Setting(key = "web.http." + ApiContext.PUBLIC + ".path", description = "Path for " + ApiContext.PUBLIC + " api context", defaultValue = DEFAULT_PUBLIC_PATH) + @Setting(key = "web.http." + API_CONTEXT + ".path", description = "Path for " + API_CONTEXT + " api context", defaultValue = DEFAULT_PUBLIC_PATH) String path ) { diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/build.gradle.kts b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/build.gradle.kts index be1e935dd8..814b5029b5 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/build.gradle.kts +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { implementation(libs.edc.spi.jwt) implementation(libs.edc.spi.jwt.signer) implementation(libs.edc.spi.keys) + implementation(libs.edc.spi.participant.context.single) implementation(libs.edc.spi.token) implementation(libs.edc.core.token) implementation(libs.edc.lib.cryptocommon) diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceExtension.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceExtension.java index f0c483939b..dfb31794a0 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceExtension.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceExtension.java @@ -24,6 +24,7 @@ import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; import org.eclipse.edc.jwt.signer.spi.JwsSignerProvider; import org.eclipse.edc.keys.spi.LocalPublicKeyService; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; @@ -83,9 +84,10 @@ public class DataPlaneTokenRefreshServiceExtension implements ServiceExtension { private TypeManager typeManager; @Inject private Hostname hostname; - @Inject private JwsSignerProvider jwsSignerProvider; + @Inject + private SingleParticipantContextSupplier singleParticipantContextSupplier; private DataPlaneTokenRefreshServiceImpl tokenRefreshService; @@ -121,7 +123,7 @@ private DataPlaneTokenRefreshServiceImpl getTokenRefreshService(ServiceExtension monitor.debug("Token refresh time tolerance: %d s".formatted(expiryTolerance)); tokenRefreshService = new DataPlaneTokenRefreshServiceImpl(clock, tokenValidationService, didPkResolver, localPublicKeyService, accessTokenDataStore, new JwtGenerationService(jwsSignerProvider), () -> context.getConfig().getString(TOKEN_SIGNER_PRIVATE_KEY_ALIAS), context.getMonitor(), refreshEndpoint, getOwnDid(context), expiryTolerance, tokenExpiry, - () -> context.getConfig().getString(TOKEN_VERIFIER_PUBLIC_KEY_ALIAS), vault, typeManager.getMapper()); + () -> context.getConfig().getString(TOKEN_VERIFIER_PUBLIC_KEY_ALIAS), vault, typeManager.getMapper(), singleParticipantContextSupplier); } return tokenRefreshService; } diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImpl.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImpl.java index 57eb968181..f44550cff4 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImpl.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImpl.java @@ -28,6 +28,8 @@ import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames; import org.eclipse.edc.keys.spi.LocalPublicKeyService; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.iam.TokenRepresentation; @@ -78,6 +80,7 @@ public class DataPlaneTokenRefreshServiceImpl implements DataPlaneTokenRefreshSe public static final String TOKEN_ID_CLAIM = "jti"; private final long tokenExpirySeconds; private final List authenticationTokenValidationRules; + private final ParticipantContextSupplier participantContextSupplier; private final List accessTokenAuthorizationRules; private final TokenValidationService tokenValidationService; private final DidPublicKeyResolver publicKeyResolver; @@ -108,7 +111,8 @@ public DataPlaneTokenRefreshServiceImpl(Clock clock, long tokenExpirySeconds, Supplier publicKeyIdSupplier, Vault vault, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + ParticipantContextSupplier participantContextSupplier) { this.tokenValidationService = tokenValidationService; this.publicKeyResolver = publicKeyResolver; this.localPublicKeyService = localPublicKeyService; @@ -128,6 +132,7 @@ public DataPlaneTokenRefreshServiceImpl(Clock clock, new ClaimIsPresentRule(ACCESS_TOKEN_CLAIM), new ClaimIsPresentRule(TOKEN_ID_CLAIM), new AuthTokenAudienceRule(accessTokenDataStore)); + this.participantContextSupplier = participantContextSupplier; accessTokenAuthorizationRules = List.of(new IssuerEqualsSubjectRule(), new ClaimIsPresentRule(AUDIENCE), new ClaimIsPresentRule(TOKEN_ID_CLAIM), @@ -160,9 +165,16 @@ public Result refreshToken(String refreshToken, String authentica return Result.failure("Authentication token validation failed: %s".formatted(authTokenRes.getFailureDetail())); } + var participantContextServiceResult = participantContextSupplier.get(); + if (participantContextServiceResult.failed()) { + return Result.failure("Cannot retrieve ParticipantContext: " + participantContextServiceResult.getFailureDetail()); + } + var participantContext = participantContextServiceResult.getContent(); + // 2. extract access token and validate it var accessToken = authTokenRes.getContent().getStringClaim("token"); - var accessTokenDataResult = tokenValidationService.validate(accessToken, localPublicKeyService, new RefreshTokenValidationRule(vault, refreshToken, objectMapper)) + var refreshTokenValidationRule = new RefreshTokenValidationRule(vault, refreshToken, objectMapper, participantContext); + var accessTokenDataResult = tokenValidationService.validate(accessToken, localPublicKeyService, refreshTokenValidationRule) .map(accessTokenClaims -> accessTokenDataStore.getById(accessTokenClaims.getStringClaim(JwtRegisteredClaimNames.JWT_ID))); if (accessTokenDataResult.failed()) { @@ -183,7 +195,7 @@ public Result refreshToken(String refreshToken, String authentica return Result.failure("Failed to regenerate access/refresh token pair: %s".formatted(errors)); } - storeRefreshToken(existingAccessTokenData.id(), new RefreshToken(newRefreshToken.getContent(), tokenExpirySeconds, refreshEndpoint)); + storeRefreshToken(existingAccessTokenData.id(), new RefreshToken(newRefreshToken.getContent(), tokenExpirySeconds, refreshEndpoint), participantContext); // the ClaimToken is created based solely on the TokenParameters. The additional information (refresh token...) is persisted separately var claimToken = ClaimToken.Builder.newInstance().claims(newTokenParams.getClaims()).build(); @@ -223,8 +235,14 @@ public Result obtainToken(TokenParameters tokenParameters, var accessTokenData = new AccessTokenData(accessTokenResult.getContent().id(), claimToken, backendDataAddress, additionalDataForStorage); var storeResult = accessTokenDataStore.store(accessTokenData); + var participantContextServiceResult = participantContextSupplier.get(); + if (participantContextServiceResult.failed()) { + return Result.failure("Cannot retrieve ParticipantContext: " + participantContextServiceResult.getFailureDetail()); + } + var participantContext = participantContextServiceResult.getContent(); + storeRefreshToken(accessTokenResult.getContent().id(), new RefreshToken(refreshTokenResult.getContent().tokenRepresentation().getToken(), - tokenExpirySeconds, refreshEndpoint)); + tokenExpirySeconds, refreshEndpoint), participantContext); // the refresh token information must be returned in the EDR var audience = additionalDataForStorage.get(AUDIENCE_PROPERTY); @@ -289,7 +307,7 @@ private Result deleteTokenData(AccessTokenData tokenData) { * Creates a token that has an ID based on the given token parameters. If the token parameters don't contain a "jti" claim, one * will be generated at random. */ - private Result createToken(TokenParameters tokenParameters) { + private ServiceResult createToken(TokenParameters tokenParameters) { var claims = new HashMap<>(tokenParameters.getClaims()); claims.put(JwtRegisteredClaimNames.ISSUED_AT, clock.instant().getEpochSecond()); // iat is millis in upstream -> bug var claimDecorators = claims.entrySet().stream().map(e -> (TokenDecorator) claimDecorator -> claimDecorator.claims(e.getKey(), e.getValue())); @@ -313,13 +331,20 @@ private Result createToken(TokenParameters tokenParam allDecorators.add(tp -> tp.claims(JwtRegisteredClaimNames.EXPIRATION_TIME, exp)); } - return tokenGenerationService.generate(privateKeyIdSupplier.get(), allDecorators.toArray(new TokenDecorator[0])) + return participantContextSupplier.get().map(ParticipantContext::getParticipantContextId) + .compose(participantContextId -> tokenGenerationService.generate(participantContextId, + privateKeyIdSupplier.get(), allDecorators.toArray(new TokenDecorator[0])).flatMap(ServiceResult::from)) .map(tr -> new TokenRepresentationWithId(tokenId.get(), tr)); } - private Result storeRefreshToken(String id, RefreshToken refreshToken) { + private Result storeRefreshToken(String id, RefreshToken refreshToken, ParticipantContext participantContext) { + return toJson(refreshToken) + .compose(json -> vault.storeSecret(participantContext.getParticipantContextId(), id, json)); + } + + private Result toJson(Object object) { try { - return vault.storeSecret(id, objectMapper.writeValueAsString(refreshToken)); + return Result.success(objectMapper.writeValueAsString(object)); } catch (JsonProcessingException e) { return Result.failure(e.getMessage()); } diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/rules/RefreshTokenValidationRule.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/rules/RefreshTokenValidationRule.java index be7ad813b8..63b7c53bac 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/rules/RefreshTokenValidationRule.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/rules/RefreshTokenValidationRule.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jwt.JWTClaimNames; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.security.Vault; @@ -43,18 +44,20 @@ public class RefreshTokenValidationRule implements TokenValidationRule { private final Vault vault; private final String incomingRefreshToken; private final ObjectMapper objectMapper; + private final ParticipantContext participantContext; - public RefreshTokenValidationRule(Vault vault, String incomingRefreshToken, ObjectMapper objectMapper) { + public RefreshTokenValidationRule(Vault vault, String incomingRefreshToken, ObjectMapper objectMapper, ParticipantContext participantContext) { this.vault = vault; this.incomingRefreshToken = incomingRefreshToken; this.objectMapper = objectMapper; + this.participantContext = participantContext; } @Override public Result checkRule(@NotNull ClaimToken accessToken, @Nullable Map additional) { var tokenId = accessToken.getStringClaim(JWTClaimNames.JWT_ID); - var storedRefreshTokenJson = vault.resolveSecret(tokenId); + var storedRefreshTokenJson = vault.resolveSecret(participantContext.getParticipantContextId(), tokenId); if (storedRefreshTokenJson == null) { return failure("No refresh token with the ID '%s' was found in the vault.".formatted(tokenId)); } diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java index 1f93cdec3d..a12b7eb8c5 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java @@ -38,12 +38,15 @@ import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames; import org.eclipse.edc.keys.spi.LocalPublicKeyService; import org.eclipse.edc.keys.spi.PrivateKeyResolver; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.query.CriterionOperatorRegistryImpl; import org.eclipse.edc.security.token.jwt.CryptoConverter; import org.eclipse.edc.security.token.jwt.DefaultJwsSignerProvider; import org.eclipse.edc.spi.iam.ClaimToken; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.token.JwtGenerationService; import org.eclipse.edc.token.TokenValidationServiceImpl; @@ -78,25 +81,22 @@ class DataPlaneTokenRefreshServiceImplComponentTest { private final DidPublicKeyResolver didPkResolverMock = mock(); private final LocalPublicKeyService localPublicKeyService = mock(); private final PrivateKeyResolver privateKeyResolver = mock(); + private final ParticipantContextSupplier participantContextSupplier = () -> ServiceResult.success( + ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build() + ); private DataPlaneTokenRefreshServiceImpl tokenRefreshService; - private InMemoryAccessTokenDataStore tokenDataStore; - private InMemoryVault vault; - private ObjectMapper objectMapper; + private final InMemoryAccessTokenDataStore tokenDataStore = new InMemoryAccessTokenDataStore(CriterionOperatorRegistryImpl.ofDefaults()); + private final InMemoryVault vault = new InMemoryVault(mock()); + private final ObjectMapper objectMapper = new ObjectMapper(); private ECKey consumerKey; private ECKey providerKey; @BeforeEach void setup() throws JOSEException { - var privateKeyAlias = "privateKeyAlias"; providerKey = new ECKeyGenerator(Curve.P_384).keyID(PROVIDER_BPN + "#provider-key").keyUse(KeyUse.SIGNATURE).generate(); consumerKey = new ECKeyGenerator(Curve.P_384).keyID(CONSUMER_DID + "#consumer-key").keyUse(KeyUse.SIGNATURE).generate(); - var privateKey = providerKey.toPrivateKey(); - - objectMapper = new ObjectMapper(); - tokenDataStore = new InMemoryAccessTokenDataStore(CriterionOperatorRegistryImpl.ofDefaults()); - vault = new InMemoryVault(mock()); tokenRefreshService = new DataPlaneTokenRefreshServiceImpl(Clock.systemUTC(), new TokenValidationServiceImpl(), didPkResolverMock, @@ -111,9 +111,9 @@ void setup() throws JOSEException { 300L, () -> providerKey.getKeyID(), vault, - objectMapper); + objectMapper, participantContextSupplier); - when(privateKeyResolver.resolvePrivateKey(privateKeyAlias)).thenReturn(Result.success(privateKey)); + when(privateKeyResolver.resolvePrivateKey("participantContextId", privateKeyAlias)).thenReturn(Result.success(providerKey.toPrivateKey())); when(localPublicKeyService.resolveKey(eq(consumerKey.getKeyID()))).thenReturn(Result.success(consumerKey.toPublicKey())); when(localPublicKeyService.resolveKey(eq(providerKey.getKeyID()))).thenReturn(Result.success(providerKey.toPublicKey())); @@ -147,7 +147,6 @@ void obtainToken() { .hasSize(2) .containsEntry(AUDIENCE_PROPERTY, CONSUMER_DID) .containsEntry("authType", "bearer"); - } @DisplayName("Verify that a token can be refreshed") diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java index 1a7ec0ca7e..e92b47491d 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java @@ -24,10 +24,13 @@ import org.eclipse.edc.connector.dataplane.spi.store.AccessTokenDataStore; import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; import org.eclipse.edc.keys.spi.LocalPublicKeyService; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; 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.result.Result; +import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.result.StoreResult; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.token.spi.TokenDecorator; @@ -66,21 +69,22 @@ class DataPlaneTokenRefreshServiceImplTest { private final TokenGenerationService tokenGenService = mock(); private final TokenValidationService tokenValidationService = mock(); private final DidPublicKeyResolver didPublicKeyResolver = mock(); - private final LocalPublicKeyService localPublicKeyService = mock(); + private final ParticipantContextSupplier participantContextSupplier = () -> ServiceResult.success( + ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build() + ); private final DataPlaneTokenRefreshServiceImpl accessTokenService = new DataPlaneTokenRefreshServiceImpl(Clock.systemUTC(), tokenValidationService, didPublicKeyResolver, localPublicKeyService, accessTokenDataStore, tokenGenService, mock(), mock(), "https://example.com", "did:web:provider", 1, 300L, - () -> "keyid", mock(), new ObjectMapper()); - + () -> "keyid", mock(), new ObjectMapper(), participantContextSupplier); @Test void obtainToken() { var params = TokenParameters.Builder.newInstance().claims("foo", "bar").claims("jti", "baz").header("qux", "quz").build(); var address = DataAddress.Builder.newInstance().type("test-type").build(); - when(tokenGenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-token").build())); + when(tokenGenService.generate(any(), any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-token").build())); when(accessTokenDataStore.store(any(AccessTokenData.class))).thenReturn(StoreResult.success()); var result = accessTokenService.obtainToken(params, address, Map.of("fizz", "buzz", "refreshToken", "getsOverwritten", AUDIENCE_PROPERTY, "audience")); @@ -89,7 +93,7 @@ void obtainToken() { .containsKeys("fizz", EDR_PROPERTY_REFRESH_TOKEN, EDR_PROPERTY_EXPIRES_IN, EDR_PROPERTY_REFRESH_ENDPOINT) .containsEntry(EDR_PROPERTY_REFRESH_TOKEN, "foo-token"); - verify(tokenGenService, times(2)).generate(any(), any(TokenDecorator[].class)); + verify(tokenGenService, times(2)).generate(any(), any(), any(TokenDecorator[].class)); verify(accessTokenDataStore).store(any(AccessTokenData.class)); } @@ -98,13 +102,13 @@ void obtainToken_withAdditionalProperties() { var params = TokenParameters.Builder.newInstance().claims("foo", "bar").claims("jti", "baz").header("qux", "quz").build(); var address = DataAddress.Builder.newInstance().type("test-type").build(); - when(tokenGenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-token").build())); + when(tokenGenService.generate(any(), any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-token").build())); when(accessTokenDataStore.store(any(AccessTokenData.class))).thenReturn(StoreResult.success()); var result = accessTokenService.obtainToken(params, address, Map.of("foo", "bar", AUDIENCE_PROPERTY, "audience")); assertThat(result).isSucceeded().extracting(TokenRepresentation::getToken).isEqualTo("foo-token"); - verify(tokenGenService, times(2)).generate(any(), any(TokenDecorator[].class)); + verify(tokenGenService, times(2)).generate(any(), any(), any(TokenDecorator[].class)); verify(accessTokenDataStore).store(argThat(accessTokenData -> accessTokenData.additionalProperties().get("foo").equals("bar"))); } @@ -122,13 +126,13 @@ void obtainToken_noTokenId() { var params = TokenParameters.Builder.newInstance().claims("foo", "bar")/* missing: .claims("jti", "baz")*/.header("qux", "quz").build(); var address = DataAddress.Builder.newInstance().type("test-type").build(); - when(tokenGenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-token").build())); + when(tokenGenService.generate(any(), any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-token").build())); when(accessTokenDataStore.store(any(AccessTokenData.class))).thenReturn(StoreResult.success()); var result = accessTokenService.obtainToken(params, address, Map.of(AUDIENCE_PROPERTY, "audience")); assertThat(result).isSucceeded().extracting(TokenRepresentation::getToken).isEqualTo("foo-token"); - verify(tokenGenService, times(2)).generate(any(), any(TokenDecorator[].class)); + verify(tokenGenService, times(2)).generate(any(), any(), any(TokenDecorator[].class)); verify(accessTokenDataStore).store(argThat(accessTokenData -> UUID_PATTERN.matcher(accessTokenData.id()).matches())); } @@ -137,12 +141,12 @@ void obtainToken_creationFails() { var params = TokenParameters.Builder.newInstance().claims("foo", "bar").claims("jti", "baz").header("qux", "quz").build(); var address = DataAddress.Builder.newInstance().type("test-type").build(); - when(tokenGenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.failure("test failure")); + when(tokenGenService.generate(any(), any(), any(TokenDecorator[].class))).thenReturn(Result.failure("test failure")); var result = accessTokenService.obtainToken(params, address, Map.of()); assertThat(result).isFailed().detail().contains("test failure"); - verify(tokenGenService).generate(any(), any(TokenDecorator[].class)); + verify(tokenGenService).generate(any(), any(), any(TokenDecorator[].class)); verifyNoMoreInteractions(accessTokenDataStore); } @@ -151,13 +155,13 @@ void obtainToken_storingFails() { var params = TokenParameters.Builder.newInstance().claims("foo", "bar").claims("jti", "baz").header("qux", "quz").build(); var address = DataAddress.Builder.newInstance().type("test-type").build(); - when(tokenGenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-token").build())); + when(tokenGenService.generate(any(), any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-token").build())); when(accessTokenDataStore.store(any(AccessTokenData.class))).thenReturn(StoreResult.alreadyExists("test failure")); var result = accessTokenService.obtainToken(params, address, Map.of(AUDIENCE_PROPERTY, "audience")); assertThat(result).isFailed().detail().isEqualTo("test failure"); - verify(tokenGenService, times(2)).generate(any(), any(TokenDecorator[].class)); + verify(tokenGenService, times(2)).generate(any(), any(), any(TokenDecorator[].class)); verify(accessTokenDataStore).store(any(AccessTokenData.class)); } @@ -179,8 +183,6 @@ void resolve() { @Test void resolve_whenValidationFails() { - var tokenId = "test-id"; - var claimToken = ClaimToken.Builder.newInstance().claim("jti", tokenId).build(); when(tokenValidationService.validate(anyString(), any(), anyList())) .thenReturn(Result.failure("test-failure")); @@ -238,7 +240,7 @@ void refresh_whenRegeneratingTokenFails() { when(accessTokenDataStore.getById(eq(tokenId))).thenReturn(new AccessTokenData(tokenId, ClaimToken.Builder.newInstance().claim("claim1", "value1").build(), DataAddress.Builder.newInstance().type("type").build(), Map.of("fizz", "buzz"))); - when(tokenGenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.failure("generator-failure")); + when(tokenGenService.generate(any(), any(), any(TokenDecorator[].class))).thenReturn(Result.failure("generator-failure")); assertThat(accessTokenService.refreshToken(refreshToken, authenticationToken)) @@ -249,7 +251,7 @@ void refresh_whenRegeneratingTokenFails() { verify(tokenValidationService).validate(eq(accessToken), any(), any(TokenValidationRule[].class)); verify(tokenValidationService).validate(eq(authenticationToken), any(), anyList()); verify(accessTokenDataStore).getById(tokenId); - verify(tokenGenService, times(2)).generate(any(), any(TokenDecorator[].class)); + verify(tokenGenService, times(2)).generate(any(), any(), any(TokenDecorator[].class)); verifyNoMoreInteractions(tokenValidationService, tokenGenService, didPublicKeyResolver, accessTokenDataStore); } @@ -270,7 +272,7 @@ void refresh_whenStoreFails() { when(accessTokenDataStore.getById(eq(tokenId))).thenReturn(new AccessTokenData(tokenId, ClaimToken.Builder.newInstance().claim("claim1", "value1").build(), DataAddress.Builder.newInstance().type("type").build(), Map.of("fizz", "buzz"))); - when(tokenGenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().build())); + when(tokenGenService.generate(any(), any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().build())); when(accessTokenDataStore.update(any())).thenReturn(StoreResult.alreadyExists("test-failure")); @@ -282,7 +284,7 @@ void refresh_whenStoreFails() { verify(tokenValidationService).validate(eq(authenticationToken), any(), anyList()); verify(tokenValidationService).validate(eq(accessToken), any(), any(TokenValidationRule[].class)); verify(accessTokenDataStore).getById(tokenId); - verify(tokenGenService, times(2)).generate(any(), any(TokenDecorator[].class)); + verify(tokenGenService, times(2)).generate(any(), any(), any(TokenDecorator[].class)); verify(accessTokenDataStore).update(any()); verifyNoMoreInteractions(tokenValidationService, tokenGenService, didPublicKeyResolver, accessTokenDataStore); } @@ -303,7 +305,7 @@ void refresh_successful() { when(accessTokenDataStore.getById(eq(tokenId))).thenReturn(new AccessTokenData(tokenId, ClaimToken.Builder.newInstance().claim("claim1", "value1").build(), DataAddress.Builder.newInstance().type("type").build(), Map.of("fizz", "buzz"))); - when(tokenGenService.generate(any(), any(TokenDecorator[].class))) + when(tokenGenService.generate(any(), any(), any(TokenDecorator[].class))) .thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("fizz-token").build())) .thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("buzz-token").build())); @@ -319,8 +321,8 @@ void refresh_successful() { verify(tokenValidationService).validate(eq(authenticationToken), any(), anyList()); verify(tokenValidationService).validate(eq(accessToken), any(), any(TokenValidationRule[].class)); verify(accessTokenDataStore).getById(tokenId); - verify(tokenGenService, times(2)).generate(any(), any(TokenDecorator[].class)); + verify(tokenGenService, times(2)).generate(any(), any(), any(TokenDecorator[].class)); verify(accessTokenDataStore).update(any()); verifyNoMoreInteractions(tokenValidationService, tokenGenService, didPublicKeyResolver, accessTokenDataStore); } -} \ No newline at end of file +} diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/rules/RefreshTokenValidationRuleTest.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/rules/RefreshTokenValidationRuleTest.java index 2de8f641e2..c7ed1a2a1a 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/rules/RefreshTokenValidationRuleTest.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/rules/RefreshTokenValidationRuleTest.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.edc.dataplane.tokenrefresh.core.rules; import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.security.Vault; import org.junit.jupiter.api.Test; @@ -35,11 +36,14 @@ class RefreshTokenValidationRuleTest { private static final String TEST_TOKEN_ID = "test-jti"; private static final String TEST_REFRESH_TOKEN = "test-refresh-token"; private final Vault vault = mock(); - private final RefreshTokenValidationRule rule = new RefreshTokenValidationRule(vault, TEST_REFRESH_TOKEN, new ObjectMapper()); + private final String participantContextId = "participantContextId"; + private final ParticipantContext participantContext = ParticipantContext.Builder.newInstance() + .participantContextId(participantContextId).identity("identity").build(); + private final RefreshTokenValidationRule rule = new RefreshTokenValidationRule(vault, TEST_REFRESH_TOKEN, new ObjectMapper(), participantContext); @Test void checkRule_noAccessTokenDataEntryFound() { - when(vault.resolveSecret(TEST_TOKEN_ID)).thenReturn(null); + when(vault.resolveSecret(participantContextId, TEST_TOKEN_ID)).thenReturn(null); assertThat(rule.checkRule(createAccessToken(TEST_TOKEN_ID), Map.of())) .isFailed() @@ -49,7 +53,7 @@ void checkRule_noAccessTokenDataEntryFound() { @Test void checkRule_noRefreshTokenStored() { - when(vault.resolveSecret(TEST_TOKEN_ID)).thenReturn(null); + when(vault.resolveSecret(participantContextId, TEST_TOKEN_ID)).thenReturn(null); assertThat(rule.checkRule(createAccessToken(TEST_TOKEN_ID), Map.of())) .isFailed() @@ -59,7 +63,7 @@ void checkRule_noRefreshTokenStored() { @Test void checkRule_refreshTokenNotString() { - when(vault.resolveSecret(TEST_TOKEN_ID)).thenReturn( + when(vault.resolveSecret(participantContextId, TEST_TOKEN_ID)).thenReturn( """ { "refreshToken": 42 @@ -74,7 +78,7 @@ void checkRule_refreshTokenNotString() { @Test void checkRule_refreshTokenDoesNotMatch() { - when(vault.resolveSecret(TEST_TOKEN_ID)).thenReturn( + when(vault.resolveSecret(participantContextId, TEST_TOKEN_ID)).thenReturn( """ { "refreshToken": "someRefreshToken" @@ -89,7 +93,7 @@ void checkRule_refreshTokenDoesNotMatch() { @Test void checkRule_success() { - when(vault.resolveSecret(TEST_TOKEN_ID)).thenReturn( + when(vault.resolveSecret(participantContextId, TEST_TOKEN_ID)).thenReturn( """ { "refreshToken": "%s" @@ -102,7 +106,7 @@ void checkRule_success() { @Test void checkRule_invalidJson() { - when(vault.resolveSecret(TEST_TOKEN_ID)).thenReturn( + when(vault.resolveSecret(participantContextId, TEST_TOKEN_ID)).thenReturn( "nope-thats-not-json"); assertThat(rule.checkRule(createAccessToken(TEST_TOKEN_ID), Map.of())) @@ -111,4 +115,4 @@ void checkRule_invalidJson() { .startsWith("Failed to parse stored secret"); } -} \ No newline at end of file +} diff --git a/edc-extensions/dataspace-protocol/build.gradle.kts b/edc-extensions/dataspace-protocol/build.gradle.kts index 2376d52a42..54234e86d4 100644 --- a/edc-extensions/dataspace-protocol/build.gradle.kts +++ b/edc-extensions/dataspace-protocol/build.gradle.kts @@ -27,8 +27,9 @@ dependencies { implementation(libs.edc.spi.boot) implementation(libs.edc.spi.core) + implementation(libs.edc.spi.decentralized.claims) + implementation(libs.edc.spi.participant.context.single) implementation(libs.edc.ih.spi.credentials) - implementation(libs.edc.spi.identitytrust) implementation(libs.dsp.spi.http) implementation(libs.dsp.spi.v08) diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java index 3b65157fcf..e78d04a380 100644 --- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java +++ b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java @@ -19,16 +19,22 @@ package org.eclipse.tractusx.edc.protocol; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.identity.ParticipantIdentityResolver; import org.eclipse.edc.protocol.dsp.http.spi.api.DspBaseWebhookAddress; import org.eclipse.edc.protocol.spi.DataspaceProfileContext; import org.eclipse.edc.protocol.spi.DataspaceProfileContextRegistry; import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.tractusx.edc.protocol.identifier.BpnExtractionFunction; +import org.eclipse.tractusx.edc.protocol.identifier.CatenaxParticipantIdentityResolver; import org.eclipse.tractusx.edc.protocol.identifier.DidExtractionFunction; +import java.util.stream.Stream; + import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.V_08; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2024Constants.DATASPACE_PROTOCOL_HTTP_V_2024_1; @@ -39,7 +45,7 @@ import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1_PATH; public class DataspaceProtocolExtension implements ServiceExtension { - + @Setting(description = "the BPN of the participant", key = "tractusx.edc.participant.bpn") private String bpn; @@ -47,13 +53,22 @@ public class DataspaceProtocolExtension implements ServiceExtension { private DataspaceProfileContextRegistry contextRegistry; @Inject private DspBaseWebhookAddress dspWebhookAddress; - + @Inject + private SingleParticipantContextSupplier singleParticipantContextSupplier; + @Override public void initialize(ServiceExtensionContext context) { - contextRegistry.register(new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP, V_08, () -> dspWebhookAddress.get(), bpn, new BpnExtractionFunction())); - contextRegistry.register(new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2025_1, V_2025_1, () -> dspWebhookAddress.get() + V_2025_1_PATH, context.getParticipantId(), new DidExtractionFunction())); - - // currently required for DCP TCK tests - contextRegistry.register(new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2024_1, V_2024_1, () -> dspWebhookAddress.get() + V_2024_1_PATH, bpn, new BpnExtractionFunction())); + Stream.of( + new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP, V_08, () -> dspWebhookAddress.get(), new BpnExtractionFunction()), + new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2025_1, V_2025_1, () -> dspWebhookAddress.get() + V_2025_1_PATH, new DidExtractionFunction()), + // currently required for DCP TCK tests + new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2024_1, V_2024_1, () -> dspWebhookAddress.get() + V_2024_1_PATH, new BpnExtractionFunction()) + ).forEach(contextRegistry::register); + } + + @Provider + public ParticipantIdentityResolver participantIdentityResolver() { + return new CatenaxParticipantIdentityResolver(bpn, singleParticipantContextSupplier); } + } diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/CatenaxParticipantIdentityResolver.java b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/CatenaxParticipantIdentityResolver.java new file mode 100644 index 0000000000..4d2e31646a --- /dev/null +++ b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/CatenaxParticipantIdentityResolver.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Think-it GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.protocol.identifier; + +import org.eclipse.edc.participantcontext.spi.identity.ParticipantIdentityResolver; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; +import org.eclipse.edc.spi.EdcException; +import org.jetbrains.annotations.Nullable; + +import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2024Constants.DATASPACE_PROTOCOL_HTTP_V_2024_1; + +public class CatenaxParticipantIdentityResolver implements ParticipantIdentityResolver { + private final String bpn; + private final ParticipantContextSupplier participantContextSupplier; + + public CatenaxParticipantIdentityResolver(String bpn, ParticipantContextSupplier participantContextSupplier) { + this.bpn = bpn; + this.participantContextSupplier = participantContextSupplier; + } + + @Nullable + @Override + public String getParticipantId(String participantContextId, String protocol) { + if (DATASPACE_PROTOCOL_HTTP.equals(protocol) || DATASPACE_PROTOCOL_HTTP_V_2024_1.equals(protocol)) { + return bpn; + } + return participantContextSupplier.get().map(ParticipantContext::getIdentity) + .orElseThrow(f -> new EdcException("Cannot get the participant context: " + f.getFailureDetail())); + } +} diff --git a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtensionTest.java b/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtensionTest.java index fc07b1a5ab..bb638af0f1 100644 --- a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtensionTest.java +++ b/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtensionTest.java @@ -48,10 +48,9 @@ class DataspaceProtocolExtensionTest { private final String webhook = "https://webhook"; private final String bpn = "bpn"; - private final String did = "did:web:example"; - private DataspaceProfileContextRegistry dataspaceProfileContextRegistry = mock(); - private DspBaseWebhookAddress dspBaseWebhookAddress = mock(); + private final DataspaceProfileContextRegistry dataspaceProfileContextRegistry = mock(); + private final DspBaseWebhookAddress dspBaseWebhookAddress = mock(); @BeforeEach void setup(ServiceExtensionContext context) { @@ -63,7 +62,6 @@ void setup(ServiceExtensionContext context) { @Test void initialize_shouldRegisterProfileContexts(ObjectFactory factory, ServiceExtensionContext context) { - when(context.getParticipantId()).thenReturn(did); when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of("tractusx.edc.participant.bpn", bpn))); factory.constructInstance(DataspaceProtocolExtension.class).initialize(context); @@ -72,13 +70,12 @@ void initialize_shouldRegisterProfileContexts(ObjectFactory factory, ServiceExte dataspaceProfileContext -> dataspaceProfileContext.name().equals(DATASPACE_PROTOCOL_HTTP) && dataspaceProfileContext.protocolVersion().equals(V_08) && dataspaceProfileContext.webhook().url().equals(webhook) && - dataspaceProfileContext.participantId().equals(bpn) && dataspaceProfileContext.idExtractionFunction() instanceof BpnExtractionFunction)); verify(dataspaceProfileContextRegistry).register(argThat( dataspaceProfileContext -> dataspaceProfileContext.name().equals(DATASPACE_PROTOCOL_HTTP_V_2025_1) && dataspaceProfileContext.protocolVersion().equals(V_2025_1) && dataspaceProfileContext.webhook().url().equals(webhook + V_2025_1_PATH) && - dataspaceProfileContext.participantId().equals(did) && - dataspaceProfileContext.idExtractionFunction() instanceof DidExtractionFunction)); + dataspaceProfileContext.idExtractionFunction() instanceof DidExtractionFunction + )); } } diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/build.gradle.kts b/edc-extensions/dcp/tx-dcp-sts-dim/build.gradle.kts index 541eb0e967..45cbeedf6d 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/build.gradle.kts +++ b/edc-extensions/dcp/tx-dcp-sts-dim/build.gradle.kts @@ -25,8 +25,9 @@ plugins { dependencies { implementation(project(":spi:core-spi")) implementation(project(":core:core-utils")) - implementation(libs.edc.identity.trust.sts.remote.lib) - implementation(libs.edc.spi.identitytrust) + implementation(libs.edc.decentralized.claims.sts.remote.lib) + implementation(libs.edc.spi.decentralized.claims) + implementation(libs.edc.spi.participant.context.single) implementation(libs.edc.auth.oauth2.client) diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtension.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtension.java index 8865042a0e..a0c5afd175 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtension.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtension.java @@ -20,10 +20,11 @@ package org.eclipse.tractusx.edc.iam.dcp.sts; import org.eclipse.edc.http.spi.EdcHttpClient; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; -import org.eclipse.edc.iam.identitytrust.sts.remote.RemoteSecureTokenService; -import org.eclipse.edc.iam.identitytrust.sts.remote.StsRemoteClientConfiguration; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.sts.remote.RemoteSecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.sts.remote.StsRemoteClientConfiguration; import org.eclipse.edc.iam.oauth2.spi.client.Oauth2Client; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; @@ -51,24 +52,20 @@ public class RemoteTokenServiceClientExtension implements ServiceExtension { @Inject private EdcHttpClient httpClient; - @Inject private Monitor monitor; - @Inject private TypeManager typeManager; - @Inject private StsRemoteClientConfiguration clientConfiguration; - @Inject private Oauth2Client oauth2Client; - @Inject private Vault vault; - @Inject private Clock clock; + @Inject + private SingleParticipantContextSupplier singleParticipantContextSupplier; @Override public String name() { @@ -86,12 +83,12 @@ public SecureTokenService secureTokenService(ServiceExtensionContext context) { }) .orElseGet(() -> { monitor.info("DIM URL not configured, will use the standard EDC Remote STS client"); - return new RemoteSecureTokenService(oauth2Client, clientConfiguration, vault); + return new RemoteSecureTokenService(oauth2Client, participantContextId -> clientConfiguration, vault); }); } private DimOauth2Client oauth2Client() { - return new DimOauthClientImpl(oauth2Client, vault, clientConfiguration, clock, monitor); + return new DimOauthClientImpl(oauth2Client, vault, clientConfiguration, clock, monitor, singleParticipantContextSupplier); } -} \ No newline at end of file +} diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtension.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtension.java index d1fa83f35c..f9c3075aed 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtension.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtension.java @@ -19,7 +19,7 @@ package org.eclipse.tractusx.edc.iam.dcp.sts; -import org.eclipse.edc.iam.identitytrust.sts.remote.StsRemoteClientConfiguration; +import org.eclipse.edc.iam.decentralizedclaims.sts.remote.StsRemoteClientConfiguration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Setting; diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java index 7e02f29fa1..4b3693a863 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java @@ -27,7 +27,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import org.eclipse.edc.http.spi.EdcHttpClient; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; @@ -48,7 +48,7 @@ import static java.lang.String.format; import static org.eclipse.edc.http.spi.FallbackFactories.retryWhenStatusIsNotIn; -import static org.eclipse.edc.iam.identitytrust.spi.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM; +import static org.eclipse.edc.iam.decentralizedclaims.spi.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT; @@ -95,7 +95,7 @@ public DimSecureTokenService(EdcHttpClient httpClient, String dimUrl, DimOauth2C } @Override - public Result createToken(Map claims, @Nullable String bearerAccessScope) { + public Result createToken(String participantContextId, Map claims, @Nullable String bearerAccessScope) { return Optional.ofNullable(bearerAccessScope) .map(scope -> grantAccessRequest(claims, scope)) .orElseGet(() -> signTokenRequest(claims)); diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java index 14a0e81679..7361b9142a 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java @@ -19,10 +19,11 @@ package org.eclipse.tractusx.edc.iam.dcp.sts.dim.oauth; -import org.eclipse.edc.iam.identitytrust.sts.remote.StsRemoteClientConfiguration; +import org.eclipse.edc.iam.decentralizedclaims.sts.remote.StsRemoteClientConfiguration; import org.eclipse.edc.iam.oauth2.spi.client.Oauth2Client; import org.eclipse.edc.iam.oauth2.spi.client.Oauth2CredentialsRequest; import org.eclipse.edc.iam.oauth2.spi.client.SharedSecretOauth2CredentialsRequest; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; @@ -43,15 +44,18 @@ public class DimOauthClientImpl implements DimOauth2Client { private final Vault vault; private final Clock clock; private final Monitor monitor; + private final ParticipantContextSupplier participantContextSupplier; private volatile TimestampedToken authToken; - public DimOauthClientImpl(Oauth2Client oauth2Client, Vault vault, StsRemoteClientConfiguration configuration, Clock clock, Monitor monitor) { + public DimOauthClientImpl(Oauth2Client oauth2Client, Vault vault, StsRemoteClientConfiguration configuration, Clock clock, + Monitor monitor, ParticipantContextSupplier participantContextSupplier) { this.configuration = configuration; this.oauth2Client = oauth2Client; this.vault = vault; this.clock = clock; this.monitor = monitor; + this.participantContextSupplier = participantContextSupplier; } @Override @@ -85,7 +89,12 @@ private boolean isExpired() { @NotNull private Result createRequest() { - var secret = vault.resolveSecret(configuration.clientSecretAlias()); + var participantContextServiceResult = participantContextSupplier.get(); + if (participantContextServiceResult.failed()) { + return Result.failure("Cannot retrieve Participant Context"); + } + + var secret = vault.resolveSecret(participantContextServiceResult.getContent().getParticipantContextId(), configuration.clientSecretAlias()); if (secret != null) { var builder = SharedSecretOauth2CredentialsRequest.Builder.newInstance() .url(configuration.tokenUrl()) diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtensionTest.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtensionTest.java index 8a2f72d188..1e79ceba34 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtensionTest.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtensionTest.java @@ -19,7 +19,7 @@ package org.eclipse.tractusx.edc.iam.dcp.sts; -import org.eclipse.edc.iam.identitytrust.sts.remote.RemoteSecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.sts.remote.RemoteSecureTokenService; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtensionContext; diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenServiceTest.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenServiceTest.java index fc1399e746..b7ce1bd6d5 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenServiceTest.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenServiceTest.java @@ -46,7 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.http.client.testfixtures.HttpTestUtils.testHttpClient; -import static org.eclipse.edc.iam.identitytrust.spi.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM; +import static org.eclipse.edc.iam.decentralizedclaims.spi.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER; @@ -96,7 +96,7 @@ void createToken_grantAccess() throws IOException { .thenAnswer(invocation -> createResponse(200, invocation, requestAcceptor, response)); var input = Map.of(ISSUER, "issuer", AUDIENCE, "audience"); - var result = client.createToken(input, "namespace:TestCredential:read"); + var result = client.createToken("ignored", input, "namespace:TestCredential:read"); assertThat(result).isSucceeded() @@ -130,7 +130,7 @@ void createToken_signToken() throws IOException { .thenAnswer(invocation -> createResponse(200, invocation, requestAcceptor, response)); var input = Map.of(ISSUER, "issuer", SUBJECT, "issuer", AUDIENCE, "audience", PRESENTATION_TOKEN_CLAIM, "accessToken"); - var result = client.createToken(input, null); + var result = client.createToken("ignored", input, null); assertThat(result).isSucceeded() .extracting(TokenRepresentation::getToken) @@ -145,7 +145,7 @@ void createToken_grantFails_withDimFailure() throws IOException { .thenAnswer(invocation -> createResponse(500, invocation)); var input = Map.of(ISSUER, "issuer", AUDIENCE, "audience"); - var result = client.createToken(input, "namespace:TestCredential:read"); + var result = client.createToken("ignored", input, "namespace:TestCredential:read"); assertThat(result).isFailed() .satisfies(failure -> assertThat(failure.getFailureDetail()).contains("grantAccess")); @@ -160,7 +160,7 @@ void createToken_grantFails_whenClaimsMissing() throws IOException { .thenAnswer(invocation -> createResponse(500, invocation)); var input = Map.of("foo", "bar"); - var result = client.createToken(input, "namespace:TestCredential:read"); + var result = client.createToken("ignored", input, "namespace:TestCredential:read"); assertThat(result).isFailed() .satisfies(failure -> assertThat(failure.getFailureDetail()) @@ -177,7 +177,7 @@ void createToken_grantFails_whenBadResponsePayload() throws IOException { .thenAnswer(invocation -> createResponse(200, invocation, Map.of())); var input = Map.of(ISSUER, "issuer", AUDIENCE, "audience"); - var result = client.createToken(input, "namespace:TestCredential:read"); + var result = client.createToken("ignored", input, "namespace:TestCredential:read"); assertThat(result).isFailed() .satisfies(failure -> assertThat(failure.getFailureDetail()) @@ -193,7 +193,7 @@ void createToken_grantFails_whenInvalidScope() throws IOException { .thenAnswer(invocation -> createResponse(500, invocation)); var input = Map.of(ISSUER, "issuer", AUDIENCE, "audience"); - var result = client.createToken(input, "invalidScope"); + var result = client.createToken("ignored", input, "invalidScope"); assertThat(result).isFailed() .satisfies(failure -> assertThat(failure.getFailureDetail()) @@ -209,7 +209,7 @@ void createToken_signFails_withDimFailure() throws IOException { .thenAnswer(invocation -> createResponse(500, invocation)); var input = Map.of(ISSUER, "issuer", SUBJECT, "issuer", AUDIENCE, "audience", PRESENTATION_TOKEN_CLAIM, "token"); - var result = client.createToken(input, null); + var result = client.createToken("ignored", input, null); assertThat(result).isFailed() .satisfies(failure -> assertThat(failure.getFailureDetail()).contains("signToken")); @@ -224,7 +224,7 @@ void createToken_signFails_whenBadResponsePayload() throws IOException { .thenAnswer(invocation -> createResponse(200, invocation, Map.of())); var input = Map.of(ISSUER, "issuer", SUBJECT, "issuer", AUDIENCE, "audience", PRESENTATION_TOKEN_CLAIM, "token"); - var result = client.createToken(input, null); + var result = client.createToken("ignored", input, null); assertThat(result).isFailed() .satisfies(failure -> assertThat(failure.getFailureDetail()) @@ -240,7 +240,7 @@ void createToken_signFails_whenClaimsMissing() throws IOException { .thenAnswer(invocation -> createResponse(500, invocation)); var input = Map.of("foo", "bar"); - var result = client.createToken(input, null); + var result = client.createToken("ignored", input, null); assertThat(result).isFailed() .satisfies(failure -> assertThat(failure.getFailureDetail()) @@ -261,7 +261,6 @@ private Response createResponse(int code, InvocationOnMock invocation, Object bo }, body); } - private Response createResponse(int code, InvocationOnMock invocation, Consumer consumer, Object body) { var bodyString = Optional.ofNullable(body).map(this::toJson).orElse(""); var request = getRequest(invocation); diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImplTest.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImplTest.java index d9a43533b9..eac5efe362 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImplTest.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImplTest.java @@ -19,12 +19,15 @@ package org.eclipse.tractusx.edc.iam.dcp.sts.dim.oauth; -import org.eclipse.edc.iam.identitytrust.sts.remote.StsRemoteClientConfiguration; +import org.eclipse.edc.iam.decentralizedclaims.sts.remote.StsRemoteClientConfiguration; import org.eclipse.edc.iam.oauth2.spi.client.Oauth2Client; import org.eclipse.edc.iam.oauth2.spi.client.SharedSecretOauth2CredentialsRequest; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.security.Vault; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -41,18 +44,18 @@ public class DimOauthClientImplTest { private final Oauth2Client oauth2Client = mock(); - private final Vault vault = mock(); - private final Monitor monitor = mock(); + private final ParticipantContextSupplier participantContextSupplier = () -> ServiceResult.success( + ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build()); @Test void obtainRequestToken_withNoExpiration() { var config = new StsRemoteClientConfiguration("http://localhost:8081/token", "clientId", "client_secret_alias"); var tokenRepresentation = TokenRepresentation.Builder.newInstance().token("token").build(); - when(vault.resolveSecret("client_secret_alias")).thenReturn("client_secret"); + when(vault.resolveSecret("participantContextId", "client_secret_alias")).thenReturn("client_secret"); when(oauth2Client.requestToken(any())).thenReturn(Result.success(tokenRepresentation)); - var client = new DimOauthClientImpl(oauth2Client, vault, config, Clock.systemUTC(), monitor); + var client = new DimOauthClientImpl(oauth2Client, vault, config, Clock.systemUTC(), monitor, participantContextSupplier); var response = client.obtainRequestToken(); assertThat(response).isNotNull().extracting(Result::getContent).isEqualTo(tokenRepresentation); @@ -70,16 +73,15 @@ void obtainRequestToken_withNoExpiration() { assertThat(response).isNotNull().extracting(Result::getContent).isEqualTo(tokenRepresentation); verify(oauth2Client, times(2)).requestToken(any()); - } @Test void obtainRequestToken_withExpiration_whenNotExpired() { var config = new StsRemoteClientConfiguration("http://localhost:8081/token", "clientId", "client_secret_alias"); var tokenRepresentation = TokenRepresentation.Builder.newInstance().token("token").expiresIn(10L).build(); - when(vault.resolveSecret("client_secret_alias")).thenReturn("client_secret"); + when(vault.resolveSecret("participantContextId", "client_secret_alias")).thenReturn("client_secret"); when(oauth2Client.requestToken(any())).thenReturn(Result.success(tokenRepresentation)); - var client = new DimOauthClientImpl(oauth2Client, vault, config, Clock.systemUTC(), monitor); + var client = new DimOauthClientImpl(oauth2Client, vault, config, Clock.systemUTC(), monitor, participantContextSupplier); var response = client.obtainRequestToken(); assertThat(response).isNotNull().extracting(Result::getContent).isEqualTo(tokenRepresentation); @@ -97,16 +99,15 @@ void obtainRequestToken_withExpiration_whenNotExpired() { assertThat(response).isNotNull().extracting(Result::getContent).isEqualTo(tokenRepresentation); verify(oauth2Client, times(1)).requestToken(any()); - } @Test void obtainRequestToken_withExpiration_whenExpired() throws InterruptedException { var config = new StsRemoteClientConfiguration("http://localhost:8081/token", "clientId", "client_secret_alias"); var tokenRepresentation = TokenRepresentation.Builder.newInstance().token("token").expiresIn(2L).build(); - when(vault.resolveSecret("client_secret_alias")).thenReturn("client_secret"); + when(vault.resolveSecret("participantContextId", "client_secret_alias")).thenReturn("client_secret"); when(oauth2Client.requestToken(any())).thenReturn(Result.success(tokenRepresentation)); - var client = new DimOauthClientImpl(oauth2Client, vault, config, Clock.systemUTC(), monitor); + var client = new DimOauthClientImpl(oauth2Client, vault, config, Clock.systemUTC(), monitor, participantContextSupplier); var response = client.obtainRequestToken(); assertThat(response).isNotNull().extracting(Result::getContent).isEqualTo(tokenRepresentation); @@ -126,7 +127,6 @@ void obtainRequestToken_withExpiration_whenExpired() throws InterruptedException assertThat(response).isNotNull().extracting(Result::getContent).isEqualTo(tokenRepresentation); verify(oauth2Client, times(2)).requestToken(any()); - } @Test @@ -134,7 +134,7 @@ void obtainRequestToken_failed() { var config = new StsRemoteClientConfiguration("http://localhost:8081/token", "clientId", "client_secret"); when(oauth2Client.requestToken(any())).thenReturn(Result.failure("failure")); - var client = new DimOauthClientImpl(oauth2Client, vault, config, Clock.systemUTC(), monitor); + var client = new DimOauthClientImpl(oauth2Client, vault, config, Clock.systemUTC(), monitor, participantContextSupplier); var response = client.obtainRequestToken(); assertThat(response).isNotNull().matches(Result::failed); diff --git a/edc-extensions/dcp/tx-dcp/build.gradle.kts b/edc-extensions/dcp/tx-dcp/build.gradle.kts index 5652a5068c..3b8eb84641 100644 --- a/edc-extensions/dcp/tx-dcp/build.gradle.kts +++ b/edc-extensions/dcp/tx-dcp/build.gradle.kts @@ -25,7 +25,7 @@ plugins { dependencies { implementation(libs.edc.spi.core) implementation(libs.edc.spi.policyengine) - implementation(libs.edc.spi.identitytrust) + implementation(libs.edc.spi.decentralized.claims) implementation(libs.edc.spi.contract) implementation(libs.edc.spi.transfer) implementation(libs.edc.spi.catalog) diff --git a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/IatpScopeExtractorExtension.java b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/IatpScopeExtractorExtension.java index 4963aa4b92..ce868198c6 100644 --- a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/IatpScopeExtractorExtension.java +++ b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/IatpScopeExtractorExtension.java @@ -19,7 +19,7 @@ package org.eclipse.tractusx.edc.iam.iatp; -import org.eclipse.edc.iam.identitytrust.spi.scope.ScopeExtractorRegistry; +import org.eclipse.edc.iam.decentralizedclaims.spi.scope.ScopeExtractorRegistry; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.monitor.Monitor; diff --git a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java index 434a9751b3..28ce59ca8f 100644 --- a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java +++ b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java @@ -24,7 +24,7 @@ import org.eclipse.edc.connector.controlplane.catalog.spi.CatalogRequestMessage; import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequestMessage; import org.eclipse.edc.connector.controlplane.transfer.spi.types.protocol.TransferRequestMessage; -import org.eclipse.edc.iam.identitytrust.spi.scope.ScopeExtractor; +import org.eclipse.edc.iam.decentralizedclaims.spi.scope.ScopeExtractor; import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.spi.iam.RequestContext; diff --git a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/IatpScopeExtractorExtensionTest.java b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/IatpScopeExtractorExtensionTest.java index d75d366efa..164178b436 100644 --- a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/IatpScopeExtractorExtensionTest.java +++ b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/IatpScopeExtractorExtensionTest.java @@ -19,7 +19,7 @@ package org.eclipse.tractusx.edc.iam.iatp; -import org.eclipse.edc.iam.identitytrust.spi.scope.ScopeExtractorRegistry; +import org.eclipse.edc.iam.decentralizedclaims.spi.scope.ScopeExtractorRegistry; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.tractusx.edc.iam.iatp.scope.CredentialScopeExtractor; diff --git a/edc-extensions/edr/edr-api-v2/build.gradle.kts b/edc-extensions/edr/edr-api-v2/build.gradle.kts index 819c8c52a3..254c9b258c 100644 --- a/edc-extensions/edr/edr-api-v2/build.gradle.kts +++ b/edc-extensions/edr/edr-api-v2/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { } implementation(libs.edc.lib.api) implementation(libs.edc.lib.validator) + implementation(libs.edc.lib.jersey.providers) implementation(libs.edc.spi.edrstore) implementation(libs.jakarta.rsApi) diff --git a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiController.java b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiController.java index 6aceff6a9f..01e2061313 100644 --- a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiController.java +++ b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiController.java @@ -23,9 +23,11 @@ import jakarta.json.JsonObject; import org.eclipse.edc.api.model.IdResponse; import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest; +import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore; import org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.query.QuerySpec; @@ -64,6 +66,7 @@ public class BaseEdrCacheApiController { private final JsonObjectValidatorRegistry validator; protected final Monitor monitor; private final EdrService edrService; + private final ParticipantContextSupplier participantContextSupplier; private final ContractNegotiationService contractNegotiationService; @@ -71,12 +74,13 @@ public BaseEdrCacheApiController(EndpointDataReferenceStore edrStore, TypeTransformerRegistry transformerRegistry, JsonObjectValidatorRegistry validator, Monitor monitor, - EdrService edrService, ContractNegotiationService contractNegotiationService) { + EdrService edrService, ParticipantContextSupplier participantContextSupplier, ContractNegotiationService contractNegotiationService) { this.edrStore = edrStore; this.transformerRegistry = transformerRegistry; this.validator = validator; this.monitor = monitor; this.edrService = edrService; + this.participantContextSupplier = participantContextSupplier; this.contractNegotiationService = contractNegotiationService; } @@ -88,7 +92,10 @@ public JsonObject initiateEdrNegotiation(JsonObject requestObject) { var contractRequest = transformerRegistry.transform(requestObject, ContractRequest.class) .orElseThrow(InvalidRequestException::new); - var contractNegotiation = contractNegotiationService.initiateNegotiation(enrichContractRequest(contractRequest)); + var participantContext = participantContextSupplier.get() + .orElseThrow(exceptionMapper(ContractDefinition.class)); + + var contractNegotiation = contractNegotiationService.initiateNegotiation(participantContext, enrichContractRequest(contractRequest)); var idResponse = IdResponse.Builder.newInstance() .id(contractNegotiation.getId()) diff --git a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/EdrCacheApiExtension.java b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/EdrCacheApiExtension.java index 2b1b981425..ee2aa6e917 100644 --- a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/EdrCacheApiExtension.java +++ b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/EdrCacheApiExtension.java @@ -23,12 +23,15 @@ import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore; import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; import org.eclipse.tractusx.edc.api.edr.transform.JsonObjectFromEndpointDataReferenceEntryTransformer; @@ -38,36 +41,40 @@ import java.util.Map; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; +import static org.eclipse.edc.web.spi.configuration.ApiContext.MANAGEMENT; + public class EdrCacheApiExtension implements ServiceExtension { @Inject private WebService webService; - @Inject private EdrService edrService; - @Inject private TypeTransformerRegistry transformerRegistry; - - @Inject - private JsonLd jsonLdService; - @Inject private JsonObjectValidatorRegistry validatorRegistry; - @Inject private ContractNegotiationService contractNegotiationService; - @Inject private Monitor monitor; @Inject private EndpointDataReferenceStore edrStore; + @Inject + private SingleParticipantContextSupplier singleParticipantContextSupplier; + @Inject + private JsonLd jsonLd; + @Inject + private TypeManager typeManager; @Override public void initialize(ServiceExtensionContext context) { var mgmtApiTransformerRegistry = transformerRegistry.forContext("management-api"); mgmtApiTransformerRegistry.register(new JsonObjectFromEndpointDataReferenceEntryTransformer(Json.createBuilderFactory(Map.of()))); - webService.registerResource(ApiContext.MANAGEMENT, new EdrCacheApiV2Controller(edrStore, mgmtApiTransformerRegistry, validatorRegistry, monitor, edrService, contractNegotiationService)); - webService.registerResource(ApiContext.MANAGEMENT, new EdrCacheApiV3Controller(edrStore, mgmtApiTransformerRegistry, validatorRegistry, monitor, edrService, contractNegotiationService)); + webService.registerResource(MANAGEMENT, new EdrCacheApiV2Controller(edrStore, mgmtApiTransformerRegistry, validatorRegistry, monitor, edrService, contractNegotiationService, singleParticipantContextSupplier)); + webService.registerDynamicResource(ApiContext.MANAGEMENT, EdrCacheApiV2Controller.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, "MANAGEMENT_API")); + + webService.registerResource(MANAGEMENT, new EdrCacheApiV3Controller(edrStore, mgmtApiTransformerRegistry, validatorRegistry, monitor, edrService, contractNegotiationService, singleParticipantContextSupplier)); + webService.registerDynamicResource(ApiContext.MANAGEMENT, EdrCacheApiV3Controller.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, "MANAGEMENT_API")); } } diff --git a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2Controller.java b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2Controller.java index 4f0220d483..75636876ae 100644 --- a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2Controller.java +++ b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2Controller.java @@ -33,6 +33,7 @@ import jakarta.ws.rs.core.MediaType; import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; @@ -51,8 +52,9 @@ public EdrCacheApiV2Controller(EndpointDataReferenceStore edrStore, TypeTransformerRegistry transformerRegistry, JsonObjectValidatorRegistry validator, Monitor monitor, - EdrService edrService, ContractNegotiationService contractNegotiationService) { - super(edrStore, transformerRegistry, validator, monitor, edrService, contractNegotiationService); + EdrService edrService, ContractNegotiationService contractNegotiationService, + ParticipantContextSupplier participantContextSupplier) { + super(edrStore, transformerRegistry, validator, monitor, edrService, participantContextSupplier, contractNegotiationService); } @POST diff --git a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3Controller.java b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3Controller.java index 7b07f324f7..630bd9dc4e 100644 --- a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3Controller.java +++ b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3Controller.java @@ -33,6 +33,7 @@ import jakarta.ws.rs.core.MediaType; import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; @@ -48,8 +49,9 @@ public EdrCacheApiV3Controller(EndpointDataReferenceStore edrStore, TypeTransformerRegistry transformerRegistry, JsonObjectValidatorRegistry validator, Monitor monitor, - EdrService edrService, ContractNegotiationService contractNegotiationService) { - super(edrStore, transformerRegistry, validator, monitor, edrService, contractNegotiationService); + EdrService edrService, ContractNegotiationService contractNegotiationService, + ParticipantContextSupplier participantContextSupplier) { + super(edrStore, transformerRegistry, validator, monitor, edrService, participantContextSupplier, contractNegotiationService); } @POST diff --git a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiControllerTest.java b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiControllerTest.java index 7d83f609a7..a4817967cd 100644 --- a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiControllerTest.java +++ b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiControllerTest.java @@ -30,6 +30,8 @@ import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore; import org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry; import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.Result; @@ -90,7 +92,7 @@ public abstract class BaseEdrCacheApiControllerTest extends RestControllerTestBa protected final EndpointDataReferenceStore edrStore = mock(); protected final EdrService edrService = mock(); protected final ContractNegotiationService contractNegotiationService = mock(); - + protected final ParticipantContextSupplier participantContextSupplier = mock(); @Test void initEdrNegotiation_shouldWork_whenValidRequest() { @@ -100,8 +102,10 @@ void initEdrNegotiation_shouldWork_whenValidRequest() { var responseBody = Json.createObjectBuilder().add(TYPE, ID_RESPONSE_TYPE).add(ID, contractNegotiation.getId()).build(); when(transformerRegistry.transform(any(JsonObject.class), eq(ContractRequest.class))).thenReturn(Result.success(createContractRequest())); - when(contractNegotiationService.initiateNegotiation(any())).thenReturn(contractNegotiation); + when(contractNegotiationService.initiateNegotiation(any(), any())).thenReturn(contractNegotiation); when(transformerRegistry.transform(any(IdResponse.class), eq(JsonObject.class))).thenReturn(Result.success(responseBody)); + var participantContext = ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build(); + when(participantContextSupplier.get()).thenReturn(ServiceResult.success(participantContext)); var request = negotiationRequest(); @@ -127,7 +131,6 @@ void initEdrNegotiation_shouldReturnBadRequest_whenValidInvalidRequest() { .post("/edrs") .then() .statusCode(400); - } @Test diff --git a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2ControllerTest.java b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2ControllerTest.java index 24d4495417..48362a50e0 100644 --- a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2ControllerTest.java +++ b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2ControllerTest.java @@ -31,7 +31,7 @@ public class EdrCacheApiV2ControllerTest extends BaseEdrCacheApiControllerTest { @Override protected Object controller() { - return new EdrCacheApiV2Controller(edrStore, transformerRegistry, validator, mock(), edrService, contractNegotiationService); + return new EdrCacheApiV2Controller(edrStore, transformerRegistry, validator, mock(), edrService, contractNegotiationService, participantContextSupplier); } protected RequestSpecification baseRequest() { diff --git a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3ControllerTest.java b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3ControllerTest.java index ce7d196849..996cf77db3 100644 --- a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3ControllerTest.java +++ b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3ControllerTest.java @@ -31,7 +31,7 @@ public class EdrCacheApiV3ControllerTest extends BaseEdrCacheApiControllerTest { @Override protected Object controller() { - return new EdrCacheApiV3Controller(edrStore, transformerRegistry, validator, mock(), edrService, contractNegotiationService); + return new EdrCacheApiV3Controller(edrStore, transformerRegistry, validator, mock(), edrService, contractNegotiationService, participantContextSupplier); } protected RequestSpecification baseRequest() { diff --git a/edc-extensions/edr/edr-callback/build.gradle.kts b/edc-extensions/edr/edr-callback/build.gradle.kts index 6d87b53f0a..640c6010d1 100644 --- a/edc-extensions/edr/edr-callback/build.gradle.kts +++ b/edc-extensions/edr/edr-callback/build.gradle.kts @@ -27,11 +27,13 @@ dependencies { implementation(project(":spi:edr-spi")) implementation(project(":spi:core-spi")) + implementation(libs.edc.spi.controlplane) implementation(libs.edc.spi.core) + implementation(libs.edc.spi.participant.context.single) + implementation(libs.edc.spi.transactionspi) implementation(libs.edc.spi.transfer) implementation(libs.edc.spi.transform) - implementation(libs.edc.spi.transactionspi) - implementation(libs.edc.spi.controlplane) + implementation(libs.nimbus.jwt) testImplementation(libs.edc.junit) diff --git a/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallback.java b/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallback.java index b401b2887e..9562fbb24b 100644 --- a/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallback.java +++ b/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallback.java @@ -23,6 +23,7 @@ import org.eclipse.edc.connector.controlplane.services.spi.callback.CallbackEventRemoteMessage; import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; import org.eclipse.edc.spi.event.Event; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; @@ -37,13 +38,16 @@ public class ContractNegotiationCallback implements InProcessCallback { public static final DataAddress DATA_DESTINATION = DataAddress.Builder.newInstance().type("HttpProxy").build(); private static final String TRANSFER_TYPE = "HttpData-PULL"; - private final TransferProcessService transferProcessService; + private final TransferProcessService transferProcessService; private final Monitor monitor; + private final ParticipantContextSupplier participantContextSupplier; - public ContractNegotiationCallback(TransferProcessService transferProcessService, Monitor monitor) { + public ContractNegotiationCallback(TransferProcessService transferProcessService, Monitor monitor, + ParticipantContextSupplier participantContextSupplier) { this.transferProcessService = transferProcessService; this.monitor = monitor; + this.participantContextSupplier = participantContextSupplier; } @Override @@ -66,7 +70,8 @@ private Result initiateTransfer(ContractNegotiationFinalized negotiationFi .callbackAddresses(negotiationFinalized.getCallbackAddresses()) .build(); - var result = transferProcessService.initiateTransfer(transferRequest); + var result = participantContextSupplier.get() + .compose(participantContext -> transferProcessService.initiateTransfer(participantContext, transferRequest)); if (result.failed()) { var msg = format("Failed to initiate a transfer for contract %s and asset %s, error: %s", negotiationFinalized.getContractAgreement().getId(), negotiationFinalized.getContractAgreement().getAssetId(), result.getFailureDetail()); diff --git a/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/InProcessCallbackMessageDispatcher.java b/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/InProcessCallbackMessageDispatcher.java index 0b8e947a46..e2be7d196b 100644 --- a/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/InProcessCallbackMessageDispatcher.java +++ b/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/InProcessCallbackMessageDispatcher.java @@ -32,7 +32,7 @@ import static java.lang.String.format; -public class InProcessCallbackMessageDispatcher implements RemoteMessageDispatcher { +public class InProcessCallbackMessageDispatcher implements RemoteMessageDispatcher { public static final String CALLBACK_EVENT_LOCAL = "callback-event-local"; @@ -43,7 +43,7 @@ public InProcessCallbackMessageDispatcher(InProcessCallbackRegistry registry) { } @Override - public CompletableFuture> dispatch(Class responseType, M message) { + public CompletableFuture> dispatch(String participantContextId, Class responseType, M message) { if (message instanceof CallbackEventRemoteMessage) { var result = registry.handleMessage((CallbackEventRemoteMessage) message); if (result.succeeded()) { @@ -54,4 +54,5 @@ public CompletableFuture> dispatch( } return CompletableFuture.failedFuture(new EdcException(format("Message of type %s not supported", message.getClass().getSimpleName()))); } + } diff --git a/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/LocalCallbackExtension.java b/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/LocalCallbackExtension.java index 73f77c3afc..169226d6f7 100644 --- a/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/LocalCallbackExtension.java +++ b/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/LocalCallbackExtension.java @@ -21,6 +21,7 @@ import org.eclipse.edc.connector.controlplane.services.spi.callback.CallbackProtocolResolverRegistry; import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; @@ -36,20 +37,19 @@ public class LocalCallbackExtension implements ServiceExtension { public static final String NAME = "Local callbacks extension"; public static final String LOCAL = "local"; + @Inject private RemoteMessageDispatcherRegistry registry; - @Inject private CallbackProtocolResolverRegistry resolverRegistry; - @Inject private TransferProcessService transferProcessService; - @Inject private InProcessCallbackRegistry callbackRegistry; - @Inject private Monitor monitor; + @Inject + private SingleParticipantContextSupplier singleParticipantContextSupplier; @Override public String name() { @@ -59,7 +59,7 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - callbackRegistry.registerHandler(new ContractNegotiationCallback(transferProcessService, monitor)); + callbackRegistry.registerHandler(new ContractNegotiationCallback(transferProcessService, monitor, singleParticipantContextSupplier)); resolverRegistry.registerResolver(this::resolveProtocol); registry.register(CALLBACK_EVENT_LOCAL, new InProcessCallbackMessageDispatcher(callbackRegistry)); diff --git a/edc-extensions/edr/edr-callback/src/test/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallbackTest.java b/edc-extensions/edr/edr-callback/src/test/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallbackTest.java index 88f6983f72..4d60d068be 100644 --- a/edc-extensions/edr/edr-callback/src/test/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallbackTest.java +++ b/edc-extensions/edr/edr-callback/src/test/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallbackTest.java @@ -32,6 +32,8 @@ import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.ServiceResult; @@ -80,7 +82,10 @@ private static dispatcher.dispatch(Object.class, new TestMessage()).join()) + assertThatThrownBy(() -> dispatcher.dispatch("any", Object.class, new TestMessage()).join()) .hasCauseInstanceOf(EdcException.class); - verifyNoInteractions(callback); } - private static class TestMessage implements RemoteMessage { + private static class TestMessage extends ProtocolRemoteMessage { @Override public String getProtocol() { diff --git a/edc-extensions/edr/edr-index-lock-sql/src/test/resources/schema.sql b/edc-extensions/edr/edr-index-lock-sql/src/test/resources/schema.sql index a400c505d2..be63be29f8 100644 --- a/edc-extensions/edr/edr-index-lock-sql/src/test/resources/schema.sql +++ b/edc-extensions/edr/edr-index-lock-sql/src/test/resources/schema.sql @@ -24,5 +24,6 @@ CREATE TABLE IF NOT EXISTS edc_edr_entry asset_id VARCHAR NOT NULL, provider_id VARCHAR NOT NULL, contract_negotiation_id VARCHAR, - created_at BIGINT NOT NULL + created_at BIGINT NOT NULL, + participant_context_id VARCHAR ); diff --git a/edc-extensions/log4j2-monitor/build.gradle.kts b/edc-extensions/log4j2-monitor/build.gradle.kts index aa3f676057..8bd06c66fd 100644 --- a/edc-extensions/log4j2-monitor/build.gradle.kts +++ b/edc-extensions/log4j2-monitor/build.gradle.kts @@ -11,5 +11,3 @@ dependencies { testImplementation(libs.edc.junit) testImplementation(libs.log4j2.core.test) } - - diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_1_0__Lease_Fix.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_1_0__Lease_Fix.sql new file mode 100644 index 0000000000..372f3f9f1b --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_1_0__Lease_Fix.sql @@ -0,0 +1,14 @@ +ALTER TABLE edc_contract_negotiation DROP CONSTRAINT contract_negotiation_lease_lease_id_fk; +ALTER TABLE edc_data_plane DROP CONSTRAINT data_plane_lease_lease_id_fk; +ALTER TABLE edc_data_plane_instance DROP CONSTRAINT data_plane_instance_lease_id_fk; +ALTER TABLE edc_policy_monitor DROP CONSTRAINT policy_monitor_lease_lease_id_fk; +ALTER TABLE edc_transfer_process DROP CONSTRAINT transfer_process_lease_lease_id_fk; + +DELETE FROM edc_lease WHERE lease_id IS NULL; + +ALTER TABLE edc_lease + ADD COLUMN resource_id varchar NOT NULL, + ADD COLUMN resource_kind varchar NOT NULL, + DROP CONSTRAINT lease_pk, + ALTER lease_id DROP NOT NULL, + ADD CONSTRAINT lease_pk PRIMARY KEY (resource_id, resource_kind); diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_2_0__Add_ParticipantContextId.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_2_0__Add_ParticipantContextId.sql new file mode 100644 index 0000000000..4ab2c33bb8 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_2_0__Add_ParticipantContextId.sql @@ -0,0 +1,9 @@ +ALTER TABLE edc_asset ADD COLUMN participant_context_id varchar; +ALTER TABLE edc_contract_agreement ADD COLUMN agr_participant_context_id varchar; +ALTER TABLE edc_contract_definitions ADD COLUMN participant_context_id varchar; +ALTER TABLE edc_contract_negotiation ADD COLUMN participant_context_id varchar; +ALTER TABLE edc_edr_entry ADD COLUMN participant_context_id varchar; +ALTER TABLE edc_policy_monitor ADD COLUMN participant_context_id varchar; +ALTER TABLE edc_policydefinitions ADD COLUMN participant_context_id varchar; +ALTER TABLE edc_transfer_process ADD COLUMN participant_context_id varchar; +ALTER TABLE edc_data_plane ADD COLUMN participant_context_id varchar; diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_3_0__Add_DataplaneMetadata.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_3_0__Add_DataplaneMetadata.sql new file mode 100644 index 0000000000..986c7b9beb --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_3_0__Add_DataplaneMetadata.sql @@ -0,0 +1,2 @@ +ALTER TABLE edc_asset ADD COLUMN dataplane_metadata JSON; +ALTER TABLE edc_transfer_process ADD COLUMN dataplane_metadata JSON; diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_4_0__Add_AgreementIdColumn.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_4_0__Add_AgreementIdColumn.sql new file mode 100644 index 0000000000..5a559012a2 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_4_0__Add_AgreementIdColumn.sql @@ -0,0 +1,6 @@ +ALTER TABLE edc_contract_agreement ADD COLUMN agr_agreement_id varchar; + +UPDATE edc_contract_agreement SET agr_agreement_id = agr_id WHERE agr_agreement_id IS NULL; + +ALTER TABLE edc_contract_agreement ALTER COLUMN agr_agreement_id SET NOT NULL; + diff --git a/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java b/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java index 9fd05d350e..d00e26529b 100644 --- a/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java +++ b/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java @@ -32,6 +32,7 @@ import org.eclipse.tractusx.edc.postgresql.migration.ContractDefinitionPostgresqlMigrationExtension; import org.eclipse.tractusx.edc.postgresql.migration.ContractNegotiationPostgresqlMigrationExtension; import org.eclipse.tractusx.edc.postgresql.migration.DataPlaneInstancePostgresqlMigrationExtension; +import org.eclipse.tractusx.edc.postgresql.migration.DataPlanePostgresqlMigrationExtension; import org.eclipse.tractusx.edc.postgresql.migration.EdrIndexPostgresqlMigrationExtension; import org.eclipse.tractusx.edc.postgresql.migration.FederatedCatalogCacheMigrationExtension; import org.eclipse.tractusx.edc.postgresql.migration.JtiValidationPostgresqlMigrationExtension; @@ -113,7 +114,7 @@ void shouldUseMergedMigrationAsBaseline_whenSchemaAlreadyBuilt(ObjectFactory obj objectFactory.constructInstance(AgreementRetirementPostgresqlMigrationExtension.class), objectFactory.constructInstance(BusinessGroupPostgresMigrationExtension.class), objectFactory.constructInstance(AccessTokenDataPostgresqlMigrationExtension.class), - objectFactory.constructInstance(DataPlaneInstancePostgresqlMigrationExtension.class) + objectFactory.constructInstance(DataPlanePostgresqlMigrationExtension.class) ); currentMigrations.forEach(e -> e.initialize(context)); currentMigrations.forEach(AbstractPostgresqlMigrationExtension::prepare); diff --git a/edc-extensions/migrations/control-plane-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/AssetPostgresqlMigrationExtensionTest.java b/edc-extensions/migrations/control-plane-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/AssetPostgresqlMigrationExtensionTest.java deleted file mode 100644 index 6c46412bfd..0000000000 --- a/edc-extensions/migrations/control-plane-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/AssetPostgresqlMigrationExtensionTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * 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. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -package org.eclipse.tractusx.edc.postgresql.migration; - -import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; -import org.eclipse.edc.connector.controlplane.store.sql.assetindex.SqlAssetIndex; -import org.eclipse.edc.connector.controlplane.store.sql.assetindex.schema.postgres.PostgresDialectStatements; -import org.eclipse.edc.json.JacksonTypeManager; -import org.eclipse.edc.junit.annotations.PostgresqlIntegrationTest; -import org.eclipse.edc.sql.QueryExecutor; -import org.eclipse.edc.sql.testfixtures.PostgresqlStoreSetupExtension; -import org.flywaydb.core.api.MigrationVersion; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.tractusx.edc.tests.testcontainer.PostgresContainerManager.getPostgresTestContainerName; - -@PostgresqlIntegrationTest -class AssetPostgresqlMigrationExtensionTest { - private SqlAssetIndex store; - - @RegisterExtension - static PostgresqlStoreSetupExtension extension = - new PostgresqlStoreSetupExtension(getPostgresTestContainerName()); - - @BeforeEach - void setUp(PostgresqlStoreSetupExtension extension, QueryExecutor queryExecutor) { - store = new SqlAssetIndex(extension.getDataSourceRegistry(), extension.getDatasourceName(), - extension.getTransactionContext(), new JacksonTypeManager().getMapper(), new PostgresDialectStatements(), - queryExecutor); - } - - // bugfix https://github.com/eclipse-tractusx/tractusx-edc/issues/1003 - @Test - void version006shouldTransformPropertiesListToMap(PostgresqlStoreSetupExtension extension) { - var dataSource = extension.getDataSourceRegistry().resolve(extension.getDatasourceName()); - FlywayManager.migrate(dataSource, "asset", "public", MigrationVersion.fromVersion("0.0.5")); - - insertAsset(extension, "1"); - insertAsset(extension, "2"); - - FlywayManager.migrate(dataSource, "asset", "public", MigrationVersion.fromVersion("0.0.6")); - - var result = store.findById("1"); - - assertThat(result).isNotNull(); - assertThat(result.getProperties()).containsExactlyInAnyOrderEntriesOf( - Map.of(Asset.PROPERTY_ID, "1", "key", "value1", "anotherKey", "anotherValue1")); - assertThat(result.getPrivateProperties()).containsExactlyInAnyOrderEntriesOf( - Map.of("privateKey", "privateValue1", "anotherPrivateKey", "anotherPrivateValue1")); - } - - private void insertAsset(PostgresqlStoreSetupExtension extension, String id) { - var propertiesArray = "[ %s, %s ]".formatted(propertyJsonMap("key", "value" + id), propertyJsonMap("anotherKey", "anotherValue" + id)); - var privatePropertiesArray = "[ %s, %s ]".formatted(propertyJsonMap("privateKey", "privateValue" + id), propertyJsonMap("anotherPrivateKey", "anotherPrivateValue" + id)); - - extension.runQuery(("insert into edc_asset (asset_id, properties, private_properties, data_address) " + - "values ('%s', '%s'::json, '%s'::json, '{\"type\":\"type\"}'::json)") - .formatted(id, propertiesArray, privatePropertiesArray)); - } - - private String propertyJsonMap(String key, String value) { - return "{\"property_name\" : \"%s\", \"property_value\" : \"%s\", \"property_type\" : \"java.lang.String\"}".formatted(key, value); - } - -} diff --git a/edc-extensions/single-participant-vault/build.gradle.kts b/edc-extensions/single-participant-vault/build.gradle.kts new file mode 100644 index 0000000000..9f1bc3c9ad --- /dev/null +++ b/edc-extensions/single-participant-vault/build.gradle.kts @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2025 Think-it GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + + +plugins { + `java-library` +} + +dependencies { + implementation(libs.edc.spi.core) +} diff --git a/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVault.java b/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVault.java new file mode 100644 index 0000000000..c641ce7aa8 --- /dev/null +++ b/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVault.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025 Think-it GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.connector; + +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.security.Vault; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static java.util.Optional.ofNullable; + +@Deprecated(since = "0.12.0") // can be removed once https://github.com/eclipse-edc/Connector/pull/5396 is merged and released +public class InMemorySingleParticipantVault implements Vault { + private static final String DEFAULT_PARTITION = "default"; + private final Map> secrets = new ConcurrentHashMap<>(); + private final Monitor monitor; + + public InMemorySingleParticipantVault(Monitor monitor) { + this.monitor = monitor; + } + + @Override + public @Nullable String resolveSecret(String key) { + return resolveSecret(DEFAULT_PARTITION, key); + } + + @Override + public Result storeSecret(String key, String value) { + return storeSecret(DEFAULT_PARTITION, key, value); + } + + @Override + public Result deleteSecret(String key) { + return deleteSecret(DEFAULT_PARTITION, key); + } + + @Override + public @Nullable String resolveSecret(String vaultPartition, String s) { + monitor.debug("Resolving secret " + s); + if (s == null) { + monitor.warning("Secret name is null - skipping"); + return null; + } + return ofNullable(secrets.get(DEFAULT_PARTITION)).map(map -> map.getOrDefault(s, null)).orElse(null); + } + + @Override + public Result storeSecret(String vaultPartition, String s, String s1) { + monitor.debug("Storing secret " + s); + + var partition = secrets.computeIfAbsent(DEFAULT_PARTITION, k -> new ConcurrentHashMap<>()); + partition.put(s, s1); + return Result.success(); + } + + @Override + public Result deleteSecret(String vaultPartition, String s) { + monitor.debug("Deleting secret " + s); + + var result = ofNullable(secrets.get(DEFAULT_PARTITION)).map(map -> map.remove(s)).orElse(null); + + return result == null ? + Result.failure("Secret with key " + s + " does not exist") : + Result.success(); + } +} diff --git a/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVaultExtension.java b/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVaultExtension.java new file mode 100644 index 0000000000..226617bdae --- /dev/null +++ b/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVaultExtension.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Think-it GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.connector; + +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +@Deprecated(since = "0.12.0") // can be removed once https://github.com/eclipse-edc/Connector/pull/5396 is merged and released +public class InMemorySingleParticipantVaultExtension implements ServiceExtension { + + @Provider + public Vault vault(ServiceExtensionContext context) { + return new InMemorySingleParticipantVault(context.getMonitor()); + } +} diff --git a/edc-extensions/single-participant-vault/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/single-participant-vault/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..eccc606c8f --- /dev/null +++ b/edc-extensions/single-participant-vault/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2025 Think-it GmbH +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# 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. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################# + +org.eclipse.tractusx.edc.connector.InMemorySingleParticipantVaultExtension diff --git a/edc-extensions/tokenrefresh-handler/build.gradle.kts b/edc-extensions/tokenrefresh-handler/build.gradle.kts index 535170d42e..6edcd26f81 100644 --- a/edc-extensions/tokenrefresh-handler/build.gradle.kts +++ b/edc-extensions/tokenrefresh-handler/build.gradle.kts @@ -27,15 +27,15 @@ dependencies { implementation(project(":spi:core-spi")) implementation(project(":spi:tokenrefresh-spi")) implementation(libs.edc.spi.core) + implementation(libs.edc.spi.decentralized.claims) implementation(libs.edc.spi.edrstore) implementation(libs.edc.spi.http) - implementation(libs.edc.spi.token) implementation(libs.edc.spi.jwt) - implementation(libs.edc.spi.identitytrust) + implementation(libs.edc.spi.participant.context.single) + implementation(libs.edc.spi.token) implementation(libs.edc.lib.util) implementation(libs.nimbus.jwt) - testImplementation(libs.edc.junit) testImplementation(libs.restAssured) } diff --git a/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerExtension.java b/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerExtension.java index f1c2b3d85d..2c8f469b4b 100644 --- a/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerExtension.java +++ b/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerExtension.java @@ -21,7 +21,8 @@ import org.eclipse.edc.edr.spi.store.EndpointDataReferenceCache; import org.eclipse.edc.http.spi.EdcHttpClient; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; @@ -47,6 +48,8 @@ public class TokenRefreshHandlerExtension implements ServiceExtension { private SecureTokenService secureTokenService; @Inject private TypeManager typeManager; + @Inject + private SingleParticipantContextSupplier participantContextSupplier; @Override public String name() { @@ -55,7 +58,8 @@ public String name() { @Provider public TokenRefreshHandler createTokenRefreshHander(ServiceExtensionContext context) { - return new TokenRefreshHandlerImpl(edrStore, httpClient, getOwnDid(context), context.getMonitor(), secureTokenService, typeManager.getMapper()); + return new TokenRefreshHandlerImpl(edrStore, httpClient, getOwnDid(context), context.getMonitor(), + secureTokenService, typeManager.getMapper(), participantContextSupplier); } private String getOwnDid(ServiceExtensionContext context) { diff --git a/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java b/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java index f395aee277..5977188b6c 100644 --- a/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java +++ b/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java @@ -25,7 +25,9 @@ import okhttp3.RequestBody; import org.eclipse.edc.edr.spi.store.EndpointDataReferenceCache; import org.eclipse.edc.http.spi.EdcHttpClient; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceResult; @@ -57,28 +59,31 @@ public class TokenRefreshHandlerImpl implements TokenRefreshHandler { private final Monitor monitor; private final SecureTokenService secureTokenService; private final ObjectMapper objectMapper; + private final ParticipantContextSupplier participantContextSupplier; /** * Creates a new TokenRefreshHandler * - * @param edrCache a persistent storage where {@link DataAddress} objects are stored. - * @param httpClient needed to make the actual refresh call against the refresh endpoint - * @param ownDid the DID of this connector - * @param secureTokenService Service to generate the authentication token - * @param objectMapper ObjectMapper to interpret JSON responses + * @param edrCache a persistent storage where {@link DataAddress} objects are stored. + * @param httpClient needed to make the actual refresh call against the refresh endpoint + * @param ownDid the DID of this connector + * @param secureTokenService Service to generate the authentication token + * @param objectMapper ObjectMapper to interpret JSON responses + * @param participantContextSupplier the participant context supplier. */ public TokenRefreshHandlerImpl(EndpointDataReferenceCache edrCache, EdcHttpClient httpClient, String ownDid, Monitor monitor, SecureTokenService secureTokenService, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, ParticipantContextSupplier participantContextSupplier) { this.edrCache = edrCache; this.httpClient = httpClient; this.ownDid = ownDid; this.monitor = monitor; this.secureTokenService = secureTokenService; this.objectMapper = objectMapper; + this.participantContextSupplier = participantContextSupplier; } @Override @@ -119,14 +124,13 @@ public ServiceResult refreshToken(String tokenId, DataAddress edr) "token", accessToken ); - var result = secureTokenService.createToken(claims, null) - .compose(authToken -> createTokenRefreshRequest(refreshEndpoint.toString(), refreshToken.toString(), "Bearer %s".formatted(authToken.getToken()))); - - if (result.failed()) { - return ServiceResult.badRequest("Could not execute token refresh: " + result.getFailureDetail()); - } - - return executeRequest(result.getContent()) + return participantContextSupplier.get().map(ParticipantContext::getParticipantContextId) + .compose(participantContextId -> secureTokenService.createToken(participantContextId, claims, null) + .flatMap(ServiceResult::from)) + .compose(authToken -> createTokenRefreshRequest(refreshEndpoint.toString(), refreshToken.toString(), "Bearer %s".formatted(authToken.getToken())) + .flatMap(ServiceResult::from)) + .recover(f -> ServiceResult.badRequest("Could not execute token refresh: " + f.getFailureDetail())) + .compose(this::executeRequest) .map(tr -> createNewEdr(edr, tr)); } @@ -143,13 +147,10 @@ private DataAddress createNewEdr(DataAddress oldEdr, TokenResponse tokenResponse private ServiceResult executeRequest(Request tokenRefreshRequest) { try (var response = httpClient.execute(tokenRefreshRequest)) { if (response.isSuccessful()) { - if (response.body() != null) { - - var jsonBody = response.body().string(); - if (!StringUtils.isNullOrEmpty(jsonBody)) { - var tokenResponse = objectMapper.readValue(jsonBody, TokenResponse.class); - return ServiceResult.success(tokenResponse); - } + var jsonBody = response.body().string(); + if (!StringUtils.isNullOrEmpty(jsonBody)) { + var tokenResponse = objectMapper.readValue(jsonBody, TokenResponse.class); + return ServiceResult.success(tokenResponse); } return ServiceResult.badRequest("Token refresh successful, but body was empty."); } diff --git a/edc-extensions/tokenrefresh-handler/src/test/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImplTest.java b/edc-extensions/tokenrefresh-handler/src/test/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImplTest.java index 4676ed3786..9b08e08d97 100644 --- a/edc-extensions/tokenrefresh-handler/src/test/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImplTest.java +++ b/edc-extensions/tokenrefresh-handler/src/test/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImplTest.java @@ -37,9 +37,12 @@ import org.assertj.core.api.Assertions; import org.eclipse.edc.edr.spi.store.EndpointDataReferenceCache; import org.eclipse.edc.http.spi.EdcHttpClient; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; +import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.result.StoreResult; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.tractusx.edc.spi.tokenrefresh.dataplane.model.TokenResponse; @@ -81,6 +84,7 @@ class TokenRefreshHandlerImplTest { private final EndpointDataReferenceCache edrCache = mock(); private final EdcHttpClient mockedHttpClient = mock(); private final SecureTokenService mockedTokenService = mock(); + private final ParticipantContextSupplier participantContextSupplier = mock(); private TokenRefreshHandlerImpl tokenRefreshHandler; private ObjectMapper objectMapper; @@ -97,15 +101,17 @@ private static String createJwt() { @BeforeEach void setup() { + var participantContext = ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build(); + when(participantContextSupplier.get()).thenReturn(ServiceResult.success(participantContext)); objectMapper = new ObjectMapper(); tokenRefreshHandler = new TokenRefreshHandlerImpl(edrCache, mockedHttpClient, CONSUMER_DID, mock(), - mockedTokenService, objectMapper); + mockedTokenService, objectMapper, participantContextSupplier); } @Test void refresh_validateCorrectRequest() throws IOException { when(edrCache.get(anyString())).thenReturn(StoreResult.success(createEdr().build())); - when(mockedTokenService.createToken(anyMap(), isNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build())); + when(mockedTokenService.createToken(any(), anyMap(), isNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build())); var tokenResponse = new TokenResponse("new-access-token", "new-refresh-token", 60 * 5L, "bearer"); var successResponse = createResponse(tokenResponse, 200, ""); when(mockedHttpClient.execute(any())).thenReturn(successResponse); @@ -153,7 +159,7 @@ void refresh_edrLacksRequiredProperties(String authorization, String refreshToke @Test void refresh_endpointReturnsFailure() throws IOException { when(edrCache.get(anyString())).thenReturn(StoreResult.success(createEdr().build())); - when(mockedTokenService.createToken(anyMap(), isNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build())); + when(mockedTokenService.createToken(any(), anyMap(), isNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build())); var response401 = createResponse(null, 401, "Not authorized"); when(mockedHttpClient.execute(any())).thenReturn(response401); @@ -166,7 +172,7 @@ void refresh_endpointReturnsFailure() throws IOException { @Test void refresh_endpointReturnsEmptyBody() throws IOException { when(edrCache.get(anyString())).thenReturn(StoreResult.success(createEdr().build())); - when(mockedTokenService.createToken(anyMap(), isNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build())); + when(mockedTokenService.createToken(any(), anyMap(), isNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build())); var successResponse = createResponse(null, 200, ""); when(mockedHttpClient.execute(any())).thenReturn(successResponse); @@ -178,7 +184,7 @@ void refresh_endpointReturnsEmptyBody() throws IOException { @Test void refresh_ioException() throws IOException { when(edrCache.get(anyString())).thenReturn(StoreResult.success(createEdr().build())); - when(mockedTokenService.createToken(anyMap(), isNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build())); + when(mockedTokenService.createToken(any(), anyMap(), isNull())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build())); when(mockedHttpClient.execute(any())).thenThrow(new IOException("test exception")); assertThat(tokenRefreshHandler.refreshToken("token-id")).isFailed() @@ -188,7 +194,7 @@ void refresh_ioException() throws IOException { @Test void refresh_tokenGenerationFailed() { when(edrCache.get(anyString())).thenReturn(StoreResult.success(createEdr().build())); - when(mockedTokenService.createToken(anyMap(), isNull())).thenReturn(Result.failure("foobar")); + when(mockedTokenService.createToken(any(), anyMap(), isNull())).thenReturn(Result.failure("foobar")); assertThat(tokenRefreshHandler.refreshToken("token-id")).isFailed() .detail().isEqualTo("Could not execute token refresh: foobar"); } @@ -228,4 +234,4 @@ public Stream provideArguments(ExtensionContext extensionCo ); } } -} \ No newline at end of file +} diff --git a/edc-tests/e2e-fixtures/build.gradle.kts b/edc-tests/e2e-fixtures/build.gradle.kts index 7c6abb0fc9..a3bac351c3 100644 --- a/edc-tests/e2e-fixtures/build.gradle.kts +++ b/edc-tests/e2e-fixtures/build.gradle.kts @@ -41,11 +41,12 @@ dependencies { testFixturesApi(libs.edc.aws.s3.core) testFixturesApi(libs.edc.spi.edrstore) testFixturesApi(libs.edc.spi.jsonld) - testFixturesApi(libs.edc.spi.identity.trust) + testFixturesApi(libs.edc.spi.decentralized.claims) testFixturesApi(libs.edc.spi.identity.did) testFixturesApi(libs.edc.spi.policy) testFixturesApi(libs.edc.spi.transfer) testFixturesApi(libs.edc.spi.dataplane.dataplane) + testFixturesApi(libs.edc.spi.participant.context.single) testFixturesApi(testFixtures(libs.edc.api.management.test.fixtures)) testFixturesApi(libs.awaitility) diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/MockVcIdentityService.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/MockVcIdentityService.java index f1930cb8a9..f525f333ae 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/MockVcIdentityService.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/MockVcIdentityService.java @@ -56,7 +56,7 @@ public MockVcIdentityService(String businessPartnerNumber, String did) { } @Override - public Result obtainClientCredentials(TokenParameters parameters) { + public Result obtainClientCredentials(String participantContextId, TokenParameters parameters) { var credentials = List.of(membershipCredential(), dataExchangeGovernanceCredential()); var token = Map.of(VC_CLAIM, credentials); @@ -67,7 +67,7 @@ public Result obtainClientCredentials(TokenParameters param } @Override - public Result verifyJwtToken(TokenRepresentation tokenRepresentation, VerificationContext verificationContext) { + public Result verifyJwtToken(String participantContextId, TokenRepresentation tokenRepresentation, VerificationContext verificationContext) { var token = tokenRepresentation.getToken().replace("Bearer ", ""); var tokenParsed = typeManager.readValue(token, Map.class); diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/ParticipantConsumerDataPlaneApi.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/ParticipantConsumerDataPlaneApi.java index 3f92caa57d..5216c3efc0 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/ParticipantConsumerDataPlaneApi.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/ParticipantConsumerDataPlaneApi.java @@ -20,7 +20,7 @@ package org.eclipse.tractusx.edc.tests; import io.restassured.http.ContentType; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.junit.utils.LazySupplier; import java.net.URI; import java.util.Map; diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/LocalstackExtension.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/LocalstackExtension.java index 57445aa4b8..3eeb8a777e 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/LocalstackExtension.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/LocalstackExtension.java @@ -23,7 +23,7 @@ import org.eclipse.edc.aws.s3.AwsClientProviderConfiguration; import org.eclipse.edc.aws.s3.AwsClientProviderImpl; import org.eclipse.edc.aws.s3.S3ClientRequest; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.junit.utils.LazySupplier; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/MinioExtension.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/MinioExtension.java index 497ce53ef2..f7fec61c28 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/MinioExtension.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/MinioExtension.java @@ -23,7 +23,7 @@ import org.eclipse.edc.aws.s3.AwsClientProviderConfiguration; import org.eclipse.edc.aws.s3.AwsClientProviderImpl; import org.eclipse.edc.aws.s3.S3ClientRequest; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.junit.utils.LazySupplier; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/extension/VaultSeedExtension.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/extension/VaultSeedExtension.java index b060865968..2fc35c0a30 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/extension/VaultSeedExtension.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/extension/VaultSeedExtension.java @@ -19,7 +19,9 @@ package org.eclipse.tractusx.edc.tests.extension; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; @@ -31,6 +33,8 @@ public class VaultSeedExtension implements ServiceExtension { private final Map secrets; @Inject private Vault vault; + @Inject(required = false) + private SingleParticipantContextSupplier singleParticipantContextSupplier; public VaultSeedExtension(Map secrets) { this.secrets = secrets; @@ -43,6 +47,11 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - secrets.forEach((key, value) -> vault.storeSecret(key, value)); + if (singleParticipantContextSupplier == null) { + secrets.forEach((key, value) -> vault.storeSecret(key, value)); + } else { + var participantContext = singleParticipantContextSupplier.get().orElseThrow(f -> new EdcException(f.getFailureDetail())); + secrets.forEach((key, value) -> vault.storeSecret(participantContext.getParticipantContextId(), key, value)); + } } } diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/Functions.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/Functions.java index 016392b5e0..d84b233d7f 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/Functions.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/Functions.java @@ -19,11 +19,8 @@ package org.eclipse.tractusx.edc.tests.helpers; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.tomakehurst.wiremock.http.Request; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -32,16 +29,6 @@ public class Functions { - private static final ObjectMapper MAPPER = new ObjectMapper(); - - public static ReceivedEvent readEvent(Request request) { - try { - return MAPPER.readValue(request.getBody(), ReceivedEvent.class); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - public static KeyPair generateKeyPair() { try { KeyPairGenerator gen = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider()); diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxIatpParticipantBase.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxIatpParticipantBase.java index 5a7668ce4f..cffa1c07a1 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxIatpParticipantBase.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxIatpParticipantBase.java @@ -19,7 +19,7 @@ package org.eclipse.tractusx.edc.tests.participant; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxParticipantBase.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxParticipantBase.java index 09d1fc6ecd..58ea61539e 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxParticipantBase.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxParticipantBase.java @@ -22,9 +22,9 @@ import io.restassured.response.ValidatableResponse; import jakarta.json.Json; import jakarta.json.JsonObject; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.connector.controlplane.test.system.utils.Participant; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.tractusx.edc.tests.IdentityParticipant; @@ -115,8 +115,6 @@ public Config getConfig() { put("web.http.management.auth.key", MANAGEMENT_API_KEY); put("web.http.control.port", String.valueOf(getFreePort())); put("web.http.control.path", "/control"); - put("web.http.version.port", String.valueOf(getFreePort())); - put("web.http.version.path", "/version"); put("web.http.catalog.port", String.valueOf(federatedCatalog.get().getPort())); put("web.http.catalog.path", federatedCatalog.get().getPath()); put("web.http.catalog.auth.type", "tokenbased"); diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TransferParticipant.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TransferParticipant.java index 1f611d9595..9bf07db3c2 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TransferParticipant.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TransferParticipant.java @@ -24,7 +24,7 @@ import com.github.tomakehurst.wiremock.client.WireMock; import jakarta.json.Json; import jakarta.json.JsonObject; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.edc.util.io.Ports; diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/DataWiper.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/DataWiper.java index 64cef308a9..db8a739d2b 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/DataWiper.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/DataWiper.java @@ -67,7 +67,7 @@ public void clearPolicies() { } public void clearAssetIndex() { - var index = context.getService(AssetIndex.class); + var index = context.getService(AssetIndex.class, true); index.queryAssets(QuerySpec.max()).forEach(asset -> index.deleteById(asset.getId())); } diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/ParticipantRuntimeExtension.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/ParticipantRuntimeExtension.java index af054f6c0e..39d149e308 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/ParticipantRuntimeExtension.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/ParticipantRuntimeExtension.java @@ -22,11 +22,13 @@ import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.Curve; import com.nimbusds.jose.jwk.gen.ECKeyGenerator; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; +import org.eclipse.edc.keys.spi.PrivateKeyResolver; import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.security.token.jwt.DefaultJwsSignerProvider; import org.eclipse.edc.spi.iam.TokenParameters; import org.eclipse.edc.spi.result.Result; @@ -39,6 +41,7 @@ import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; +import java.security.PrivateKey; import java.util.concurrent.atomic.AtomicReference; /** @@ -63,6 +66,9 @@ public void afterEach(ExtensionContext extensionContext) { public static class SignServicesExtension implements ServiceExtension { private final ParticipantRuntimeExtension participantRuntimeExtension; + @Setting(key = "edc.participant.id") + private String participantContextId; + @Inject private Vault vault; @@ -81,8 +87,8 @@ public void initialize(ServiceExtensionContext context) { KeyPool.register(kid, runtimeKeyPair.toKeyPair()); var privateKey = runtimeKeyPair.toPrivateKey(); - var jwtGenerationService = new JwtGenerationService(new DefaultJwsSignerProvider(s -> Result.success(privateKey))); - participantRuntimeExtension.registerServiceMock(SecureTokenService.class, (claims, bearerAccessScope) -> { + var jwtGenerationService = new JwtGenerationService(new DefaultJwsSignerProvider(new StaticPrivateKeyResolver(privateKey))); + participantRuntimeExtension.registerServiceMock(SecureTokenService.class, (participantContextId, claims, bearerAccessScope) -> { var decorator = new TokenDecorator() { @Override public TokenParameters.Builder decorate(TokenParameters.Builder tokenParameters) { @@ -90,16 +96,34 @@ public TokenParameters.Builder decorate(TokenParameters.Builder tokenParameters) return tokenParameters; } }; - return jwtGenerationService.generate(privateAlias, new KeyIdDecorator(kid), decorator); + return jwtGenerationService.generate(participantContextId, privateAlias, new KeyIdDecorator(kid), decorator); }); participantRuntimeExtension.registerServiceMock(DidPublicKeyResolver.class, keyId -> Result.success(KeyPool.forId(keyId).getPublic())); - vault.storeSecret(privateAlias, runtimeKeyPair.toJSONString()); - vault.storeSecret(publicAlias, runtimeKeyPair.toPublicJWK().toJSONString()); + vault.storeSecret(participantContextId, privateAlias, runtimeKeyPair.toJSONString()); + vault.storeSecret(participantContextId, publicAlias, runtimeKeyPair.toPublicJWK().toJSONString()); } catch (JOSEException e) { throw new RuntimeException(e); } } + + private static class StaticPrivateKeyResolver implements PrivateKeyResolver { + private final PrivateKey privateKey; + + public StaticPrivateKeyResolver(PrivateKey privateKey) { + this.privateKey = privateKey; + } + + @Override + public Result resolvePrivateKey(String id) { + return Result.success(privateKey); + } + + @Override + public Result resolvePrivateKey(String participantContextId, String id) { + return Result.success(privateKey); + } + } } } diff --git a/edc-tests/e2e/bpn-event-tests/build.gradle.kts b/edc-tests/e2e/bpn-event-tests/build.gradle.kts index 98effa2c56..3c6acb337f 100644 --- a/edc-tests/e2e/bpn-event-tests/build.gradle.kts +++ b/edc-tests/e2e/bpn-event-tests/build.gradle.kts @@ -24,6 +24,8 @@ plugins { dependencies { testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) + + testCompileOnly(project(":edc-tests:runtime:runtime-postgresql")) } // do not publish diff --git a/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/FederatedCatalogTest.java b/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/FederatedCatalogTest.java index b462c6363f..10ba1282f9 100644 --- a/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/FederatedCatalogTest.java +++ b/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/FederatedCatalogTest.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import java.util.List; -import java.util.stream.Collectors; import static io.restassured.http.ContentType.JSON; import static org.awaitility.Awaitility.await; @@ -77,14 +76,11 @@ public class FederatedCatalogTest { @Test @DisplayName("Consumer gets cached catalog with provider entry") void requestCatalog_fulfillsPolicy_shouldReturnOffer() { - - // arrange PROVIDER.createAsset("test-asset"); var ap = PROVIDER.createPolicyDefinition(noConstraintPolicy()); var cp = PROVIDER.createPolicyDefinition(noConstraintPolicy()); PROVIDER.createContractDefinition("test-asset", "test-def", ap, cp); - await().pollInterval(ASYNC_POLL_INTERVAL) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { @@ -93,7 +89,7 @@ void requestCatalog_fulfillsPolicy_shouldReturnOffer() { .contentType(JSON) .log().ifValidationFails() .body("size()", is(1)) - .body("[0].'dcat:dataset'.'@id'", equalTo("test-asset")); + .body("[0].'dataset'[0].'@id'", equalTo("test-asset")); }); } @@ -108,8 +104,8 @@ static class TestTargetNodeDirectory implements TargetNodeDirectory { @Override public List getAll() { return participants.stream() - .map(p -> new TargetNode(p.getDid(), p.getBpn(), p.getProtocolUrl(), List.of("dataspace-protocol-http"))) - .collect(Collectors.toList()); + .map(p -> new TargetNode(p.getDid(), p.getBpn(), p.getProtocolUrl() + "/2025-1", List.of("dataspace-protocol-http:2025-1"))) + .toList(); } @Override diff --git a/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/AzureToAzureTest.java b/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/AzureToAzureTest.java index d7be2525c4..caa3c7de9e 100644 --- a/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/AzureToAzureTest.java +++ b/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/AzureToAzureTest.java @@ -23,12 +23,12 @@ import io.restassured.http.ContentType; import jakarta.json.Json; import jakarta.json.JsonObjectBuilder; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimeExtension; import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; import org.eclipse.edc.junit.testfixtures.TestUtils; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.monitor.ConsoleMonitor; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.security.Vault; diff --git a/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/MultiCloudTest.java b/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/MultiCloudTest.java index 136d88032b..5aa89193f3 100644 --- a/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/MultiCloudTest.java +++ b/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/MultiCloudTest.java @@ -24,12 +24,12 @@ import jakarta.json.Json; import jakarta.json.JsonObjectBuilder; import org.eclipse.edc.aws.s3.spi.S3BucketSchema; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimeExtension; import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; import org.eclipse.edc.junit.testfixtures.TestUtils; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.security.Vault; import org.eclipse.tractusx.edc.tests.aws.MinioExtension; import org.eclipse.tractusx.edc.tests.azure.AzureBlobClient; diff --git a/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/RuntimeConfig.java b/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/RuntimeConfig.java index e666e4ee91..744afcc3fc 100644 --- a/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/RuntimeConfig.java +++ b/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/RuntimeConfig.java @@ -19,7 +19,7 @@ package org.eclipse.tractusx.edc.dataplane.transfer.test; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; diff --git a/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/S3ToS3Test.java b/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/S3ToS3Test.java index dadbc9957c..0c1050ee22 100644 --- a/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/S3ToS3Test.java +++ b/edc-tests/e2e/cloud-transfer-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/transfer/test/S3ToS3Test.java @@ -24,12 +24,12 @@ import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import org.eclipse.edc.aws.s3.spi.S3BucketSchema; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.connector.dataplane.spi.store.DataPlaneStore; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimeExtension; import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.tractusx.edc.tests.aws.MinioExtension; import org.junit.jupiter.api.Test; diff --git a/edc-tests/e2e/dcp-tck-tests/build.gradle.kts b/edc-tests/e2e/dcp-tck-tests/build.gradle.kts index 6a00ce4e81..bbd70097bc 100644 --- a/edc-tests/e2e/dcp-tck-tests/build.gradle.kts +++ b/edc-tests/e2e/dcp-tck-tests/build.gradle.kts @@ -25,7 +25,7 @@ dependencies { testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) testRuntimeOnly(libs.dcp.testcases) testImplementation(libs.edc.junit) - testImplementation(libs.edc.spi.identity.trust) + testImplementation(libs.edc.spi.decentralized.claims) testImplementation(libs.edc.spi.identity.did) testImplementation(libs.edc.core.controlplane) testImplementation(libs.nimbus.jwt) diff --git a/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java b/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java index 915612454f..8eb98a1fa2 100644 --- a/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java +++ b/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java @@ -35,10 +35,10 @@ import org.eclipse.dataspacetck.core.system.ConsoleMonitor; import org.eclipse.dataspacetck.runtime.TckRuntime; import org.eclipse.edc.connector.controlplane.profile.DataspaceProfileContextRegistryImpl; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; import org.eclipse.edc.iam.did.spi.document.DidDocument; import org.eclipse.edc.iam.did.spi.document.Service; import org.eclipse.edc.iam.did.spi.document.VerificationMethod; -import org.eclipse.edc.iam.identitytrust.spi.SecureTokenService; import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; import org.eclipse.edc.junit.annotations.EndToEndTest; @@ -51,22 +51,26 @@ import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.eclipse.tractusx.edc.tests.MockBdrsClient; -import org.junit.jupiter.api.Assertions; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.platform.launcher.listeners.TestExecutionSummary; +import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Supplier; import java.util.stream.Collectors; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry.WILDCARD; import static org.eclipse.edc.spi.result.Result.success; import static org.eclipse.edc.util.io.Ports.getFreePort; @@ -90,14 +94,14 @@ public class DcpPresentationFlowTest { @RegisterExtension static final RuntimePerClassExtension RUNTIME = new RuntimePerClassExtension( - new EmbeddedRuntime("Connector-under-test", ":edc-controlplane:edc-controlplane-base") + new EmbeddedRuntime("Connector-under-test", ":edc-controlplane:edc-controlplane-base", ":edc-extensions:single-participant-vault") .registerServiceMock(SecureTokenService.class, STS_MOCK) .registerServiceMock(DataspaceProfileContextRegistry.class, DATASPACE_PROFILE_CONTEXT_REGISTRY_SPY) .registerServiceMock(BdrsClient.class, new MockBdrsClient((s) -> s, (s) -> s)) .configurationProvider(DcpPresentationFlowTest::runtimeConfiguration)); @RegisterExtension protected static WireMockExtension didServer = WireMockExtension.newInstance() - .options(wireMockConfig().bindAddress("localhost").port(DID_SERVER_PORT)) + .options(wireMockConfig().port(DID_SERVER_PORT)) .build(); private ECKey verifierKey; @@ -119,9 +123,12 @@ void runPresentationFlowTests() { var holderDid = formatDid(CALLBACK_PORT, "holder"); var thirdPartyDid = formatDid(CALLBACK_PORT, "thirdparty"); var baseCallbackUrl = "http://localhost:%s".formatted(CALLBACK_PORT); + var baseCallbackUri = URI.create(baseCallbackUrl); var result = TckRuntime.Builder.newInstance() .properties(Map.of( "dataspacetck.callback.address", baseCallbackUrl, + "dataspacetck.host", baseCallbackUri.getHost(), + "dataspacetck.port", String.valueOf(baseCallbackUri.getPort()), "dataspacetck.launcher", "org.eclipse.dataspacetck.dcp.system.DcpSystemLauncher", "dataspacetck.did.verifier", VERIFIER_DID, "dataspacetck.did.holder", holderDid, @@ -137,12 +144,13 @@ void runPresentationFlowTests() { result.getTestsSucceededCount(), result.getTotalFailureCount() )).resetMode(); - if (!result.getFailures().isEmpty()) { - var failures = result.getFailures().stream() - .map(f -> "- " + f.getTestIdentifier().getDisplayName() + " (" + f.getException() + ")") - .collect(Collectors.joining("\n")); - Assertions.fail(result.getTotalFailureCount() + " TCK test cases failed:\n" + failures); - } + assertThat(result.getFailures()).withFailMessage(errorMessageSupplier(result)).isEmpty(); + } + + private @NotNull Supplier errorMessageSupplier(TestExecutionSummary result) { + return () -> result.getFailures().stream() + .map(f -> "- " + f.getTestIdentifier().getDisplayName() + " (" + f.getException() + ")") + .collect(Collectors.joining("\n")); } private ECKey generateEcKey() throws JOSEException { @@ -161,9 +169,9 @@ private void configureDidMock() { } private void configureStsMock() { - when(STS_MOCK.createToken(anyMap(), isNull())) + when(STS_MOCK.createToken(any(), anyMap(), isNull())) .thenAnswer(i -> { - Map claims = new HashMap<>(i.getArgument(0)); + Map claims = new HashMap<>(i.getArgument(1)); var header = new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(verifierKey.getKeyID()).build(); var claimsSet = new JWTClaimsSet.Builder(JWTClaimsSet.parse(claims)) .jwtID(UUID.randomUUID().toString()) diff --git a/edc-tests/e2e/dsp-compatibility-tests/build.gradle.kts b/edc-tests/e2e/dsp-compatibility-tests/build.gradle.kts index 69415de3c5..1a75639378 100644 --- a/edc-tests/e2e/dsp-compatibility-tests/build.gradle.kts +++ b/edc-tests/e2e/dsp-compatibility-tests/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { testRuntimeOnly(libs.dsp.tck.transferprocess) testRuntimeOnly(libs.dsp.tck.contractnegotiation) testImplementation(libs.junit.platform.launcher) - testImplementation(libs.edc.spi.identitytrust) + testImplementation(libs.edc.spi.decentralized.claims) testImplementation(libs.nimbus.jwt) } diff --git a/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java b/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java index b7c9409198..c90e795b78 100644 --- a/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java +++ b/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java @@ -61,13 +61,12 @@ public class EdcCompatibilityPostgresTest { private static final URI PROTOCOL_URL = URI.create("http://host.docker.internal:8282/protocol"); private static final URI MANAGEMENT_URL = URI.create("http://localhost:" + getFreePort() + "/management"); private static final URI CONTROL_URL = URI.create("http://localhost:" + getFreePort() + "/control"); - private static final URI VERSION_URL = URI.create("http://localhost:" + getFreePort() + "/version"); private static final URI WEBHOOK_URL = URI.create("http://localhost:8687/tck"); private static final String API_KEY = "password"; private static final URI DATA_PLANE_PROXY = URI.create("http://localhost:" + getFreePort()); private static final URI DATA_PLANE_PUBLIC = URI.create("http://localhost:" + getFreePort() + "/public"); private static final URI FEDERATED_CATALOG = URI.create("http://localhost:" + getFreePort() + "/api/catalog"); - private static final String CONNECTOR_UNDER_TEST = "CONNECTOR_UNDER_TEST"; + private static final String CONNECTOR_UNDER_TEST = "participantContextId"; private static final DataspaceProfileContextRegistry DATASPACE_PROFILE_CONTEXT_REGISTRY_SPY = spy(DataspaceProfileContextRegistryImpl.class); @@ -77,7 +76,7 @@ public class EdcCompatibilityPostgresTest { @RegisterExtension private static final RuntimeExtension RUNTIME = new RuntimePerClassExtension(new EmbeddedRuntime(CONNECTOR_UNDER_TEST, - ":edc-tests:runtime:runtime-dsp") + ":edc-tests:runtime:runtime-dsp", ":edc-extensions:single-participant-vault") .registerServiceMock(BdrsClient.class, new MockBdrsClient(s -> s, s -> s)) .registerServiceMock(DataspaceProfileContextRegistry.class, DATASPACE_PROFILE_CONTEXT_REGISTRY_SPY) .configurationProvider(() -> EdcCompatibilityPostgresTest.runtimeConfiguration().merge(POSTGRES.getConfig(CONNECTOR_UNDER_TEST)))); @@ -96,8 +95,6 @@ private static Config runtimeConfiguration() { put("edc.participant.id", CONNECTOR_UNDER_TEST); put("web.http.port", "8080"); put("web.http.path", "/api"); - put("web.http.version.port", String.valueOf(VERSION_URL.getPort())); - put("web.http.version.path", VERSION_URL.getPath()); put("web.http.control.port", String.valueOf(CONTROL_URL.getPort())); put("web.http.control.path", CONTROL_URL.getPath()); put("web.http.management.port", String.valueOf(MANAGEMENT_URL.getPort())); @@ -140,10 +137,6 @@ private static Config runtimeConfiguration() { }); } - private static String resourceConfig(String resource) { - return Path.of(TestUtils.getResource(resource)).toString(); - } - @Timeout(500) @Test void assertDspCompatibility() { @@ -171,5 +164,9 @@ void assertDspCompatibility() { assertThat(failures).isEmpty(); } + + private String resourceConfig(String resource) { + return Path.of(TestUtils.getResource(resource)).toString(); + } } diff --git a/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/build.gradle.kts b/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/build.gradle.kts index 4ab8bca335..0b60f7b34a 100644 --- a/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/build.gradle.kts +++ b/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { testImplementation(testFixtures(libs.edc.api.management.test.fixtures)) testImplementation(libs.edc.dpf.http) testImplementation(libs.edc.spi.identity.did) + testImplementation(libs.edc.spi.participant.context.single) testImplementation(libs.bouncyCastle.bcpkixJdk18on) testImplementation(libs.nimbus.jwt) diff --git a/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/e2e/DataPlaneTokenRefreshEndToEndTest.java b/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/e2e/DataPlaneTokenRefreshEndToEndTest.java index 5c1530d673..556cda0463 100644 --- a/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/e2e/DataPlaneTokenRefreshEndToEndTest.java +++ b/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/e2e/DataPlaneTokenRefreshEndToEndTest.java @@ -36,6 +36,8 @@ import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimeExtension; import org.eclipse.edc.junit.extensions.RuntimePerMethodExtension; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.configuration.ConfigFactory; @@ -261,7 +263,6 @@ void refresh_withWrongRefreshToken() { .body(containsString("Provided refresh token does not match the stored refresh token.")); } - @DisplayName("The authentication token misses required claims: token") @Test void refresh_invalidAuthenticationToken_missingAccessToken() { @@ -341,7 +342,6 @@ void refresh_invalidTokenId() { var refreshToken = edr.getStringProperty(TX_AUTH_NS + "refreshToken"); var accessToken = edr.getStringProperty(EDC_NAMESPACE + "authorization"); - edrService.revoke(dataFlow, "Revoked"); var tokenId = getJwtId(accessToken); @@ -368,8 +368,10 @@ void refresh_invalidTokenId() { private void prepareDataplaneRuntime() { var vault = DATAPLANE_RUNTIME.getService(Vault.class); - vault.storeSecret(PROVIDER_KEY_ID, providerKey.toJSONString()); - vault.storeSecret(PROVIDER_KEY_ID_PUBLIC, providerKey.toPublicJWK().toJSONString()); + var participantContext = DATAPLANE_RUNTIME.getService(SingleParticipantContextSupplier.class).get() + .orElseThrow(f -> new EdcException(f.getFailureDetail())); + vault.storeSecret(participantContext.getParticipantContextId(), PROVIDER_KEY_ID, providerKey.toJSONString()); + vault.storeSecret(participantContext.getParticipantContextId(), PROVIDER_KEY_ID_PUBLIC, providerKey.toPublicJWK().toJSONString()); } private String createAuthToken(String accessToken, ECKey signerKey) { diff --git a/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/e2e/RuntimeConfig.java b/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/e2e/RuntimeConfig.java index 17d5e48094..901288138e 100644 --- a/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/e2e/RuntimeConfig.java +++ b/edc-tests/e2e/edc-dataplane-tokenrefresh-tests/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/e2e/RuntimeConfig.java @@ -20,7 +20,7 @@ package org.eclipse.tractusx.edc.dataplane.tokenrefresh.e2e; import io.restassured.specification.RequestSpecification; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; diff --git a/edc-tests/e2e/edr-api-tests/build.gradle.kts b/edc-tests/e2e/edr-api-tests/build.gradle.kts index b19a422be0..9adf646bd5 100644 --- a/edc-tests/e2e/edr-api-tests/build.gradle.kts +++ b/edc-tests/e2e/edr-api-tests/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { testImplementation(libs.awaitility) testRuntimeOnly(libs.edc.transaction.local) + testCompileOnly(project(":edc-tests:runtime:runtime-postgresql")) } // do not publish diff --git a/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/NegotiateEdrTest.java b/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/NegotiateEdrTest.java index b2bd59b4b6..effefbb47c 100644 --- a/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/NegotiateEdrTest.java +++ b/edc-tests/e2e/edr-api-tests/src/test/java/org/eclipse/tractusx/edc/tests/edrv2/NegotiateEdrTest.java @@ -19,7 +19,9 @@ package org.eclipse.tractusx.edc.tests.edrv2; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.http.Request; import jakarta.json.Json; import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationAgreed; import org.eclipse.edc.connector.controlplane.contract.spi.event.contractnegotiation.ContractNegotiationFinalized; @@ -46,6 +48,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -66,7 +69,6 @@ import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_DID; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_NAME; import static org.eclipse.tractusx.edc.tests.helpers.EdrNegotiationHelperFunctions.createEvent; -import static org.eclipse.tractusx.edc.tests.helpers.Functions.readEvent; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_POLL_INTERVAL; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; @@ -204,4 +206,12 @@ void negotiateEdr_shouldInvokeCallbacks() { void teardown() { server.stop(); } + + public static ReceivedEvent readEvent(Request request) { + try { + return new ObjectMapper().readValue(request.getBody(), ReceivedEvent.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/edc-tests/e2e/end2end-transfer-cloud/build.gradle.kts b/edc-tests/e2e/end2end-transfer-cloud/build.gradle.kts index b2bdf34487..c109b4572f 100644 --- a/edc-tests/e2e/end2end-transfer-cloud/build.gradle.kts +++ b/edc-tests/e2e/end2end-transfer-cloud/build.gradle.kts @@ -33,6 +33,8 @@ dependencies { testImplementation(libs.edc.aws.s3.core) testImplementation(libs.aws.s3) testImplementation(libs.aws.s3transfer) + + testCompileOnly(project(":edc-tests:runtime:runtime-postgresql")) } // do not publish diff --git a/edc-tests/e2e/end2end-transfer-cloud/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AzureToAzureEndToEndTest.java b/edc-tests/e2e/end2end-transfer-cloud/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AzureToAzureEndToEndTest.java index 996013d9a2..1a04021698 100644 --- a/edc-tests/e2e/end2end-transfer-cloud/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AzureToAzureEndToEndTest.java +++ b/edc-tests/e2e/end2end-transfer-cloud/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AzureToAzureEndToEndTest.java @@ -122,10 +122,10 @@ public TractusxParticipantBase consumer() { @BeforeEach void setup() { PROVIDER_RUNTIME.getService(Vault.class) - .storeSecret(PROVIDER_KEY_ALIAS, PROVIDER_AZURITE_ACCOUNT.key()); + .storeSecret(PROVIDER_NAME, PROVIDER_KEY_ALIAS, PROVIDER_AZURITE_ACCOUNT.key()); CONSUMER_RUNTIME.getService(Vault.class) - .storeSecret("%s-key1".formatted(CONSUMER_AZURITE_ACCOUNT.name()), CONSUMER_AZURITE_ACCOUNT.key()); + .storeSecret(CONSUMER_NAME, CONSUMER_AZURITE_ACCOUNT.name(), CONSUMER_AZURITE_ACCOUNT.key()); providerBlobHelper = AZURITE_CONTAINER.getClientFor(PROVIDER_AZURITE_ACCOUNT); consumerBlobHelper = AZURITE_CONTAINER.getClientFor(CONSUMER_AZURITE_ACCOUNT); diff --git a/edc-tests/e2e/iatp-tests/build.gradle.kts b/edc-tests/e2e/iatp-tests/build.gradle.kts index 89087872ef..9de9307243 100644 --- a/edc-tests/e2e/iatp-tests/build.gradle.kts +++ b/edc-tests/e2e/iatp-tests/build.gradle.kts @@ -22,9 +22,19 @@ plugins { `java-test-fixtures` } +configurations.all { + exclude("com.networknt", "json-schema-validator") +} + dependencies { + constraints { + testImplementation("com.networknt:json-schema-validator:2.0.0") { + because("older versions cause runtime issues") + } + } + testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) - testImplementation(libs.edc.ih.did) + testImplementation(libs.edc.spi.keypair) testImplementation(libs.edc.ih.spi) testImplementation(libs.edc.ih.spi.participant.context) testImplementation(libs.edc.ih.spi.credentials) @@ -35,13 +45,16 @@ dependencies { testImplementation(libs.edc.sts.core) testRuntimeOnly(libs.edc.transaction.local) - testImplementation(libs.wiremock) + testImplementation(libs.wiremock) { + exclude("com.networknt", "json-schema-validator") + } testImplementation(libs.restAssured) testImplementation(libs.awaitility) testImplementation(libs.bouncyCastle.bcpkixJdk18on) testCompileOnly(project(":edc-tests:runtime:iatp:runtime-memory-iatp-dim-ih")) testCompileOnly(project(":edc-tests:runtime:iatp:runtime-memory-iatp-ih")) + testCompileOnly(project(":edc-tests:runtime:iatp:runtime-memory-sts")) } // do not publish diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java index b7d69958e9..e9ba3241fe 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java @@ -124,7 +124,6 @@ void transferData_whenContractPolicyFulfilled(JsonObject contractPolicy, String // Prov-DP -> Prov-backend assertThat(consumer().data().pullData(edr.get(), Map.of())).isEqualTo("test response"); - server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH)).withHeader("Edc-Bpn", equalTo(consumer().getBpn())).withHeader("Edc-Contract-Agreement-Id", matching(".+"))); } @@ -134,12 +133,13 @@ void transferData_whenContractPolicyFulfilled(JsonObject contractPolicy, String @Test void catalogRequest_whenCredentialExpired() { //update the membership credential to an expirationDate that is in the past - var store = consumerRuntime().getService(CredentialStore.class); + var store = credentialStoreRuntime().getService(CredentialStore.class); - var existingCred = store.query(QuerySpec.Builder.newInstance().filter(new Criterion("verifiableCredential.credential.type", "contains", "MembershipCredential")).build()) + var existingCred = store.query(QuerySpec.Builder.newInstance() + .filter(new Criterion("verifiableCredential.credential.type", "contains", "MembershipCredential")).build()) .orElseThrow(f -> new RuntimeException(f.getFailureDetail())) - .stream().findFirst() - .orElseThrow(RuntimeException::new); + .stream().filter(it -> it.getHolderId().equals(consumer().getBpn())) + .findFirst().orElseThrow(RuntimeException::new); var expirationDate = Instant.now().minus(1, ChronoUnit.DAYS); var newCred = VerifiableCredential.Builder.newInstance() @@ -182,13 +182,13 @@ void catalogRequest_whenCredentialExpired() { @Test void catalogRequest_whenCredentialRevoked() { //update the membership credential to contain a `credentialStatus` with a revocation - var store = consumerRuntime().getService(CredentialStore.class); + var store = credentialStoreRuntime().getService(CredentialStore.class); var port = getFreePort(); - - var existingCred = store.query(QuerySpec.Builder.newInstance().filter(new Criterion("verifiableCredential.credential.type", "contains", "MembershipCredential")).build()) + var isMembershipCredential = new Criterion("verifiableCredential.credential.type", "contains", "MembershipCredential"); + var existingCred = store.query(QuerySpec.Builder.newInstance().filter(isMembershipCredential).build()) .orElseThrow(f -> new RuntimeException(f.getFailureDetail())) - .stream().findFirst() - .orElseThrow(RuntimeException::new); + .stream().filter(it -> it.getHolderId().equals(consumer().getBpn())) + .findAny().orElseThrow(RuntimeException::new); var newCred = VerifiableCredential.Builder.newInstance() .id(existingCred.getVerifiableCredential().credential().getId()) @@ -222,7 +222,7 @@ void catalogRequest_whenCredentialRevoked() { store.update(VerifiableCredentialResource.Builder.newInstance() .id(existingCred.getId()) .issuerId(dataspaceIssuer().didUrl()) - .participantContextId(did) + .participantContextId(existingCred.getParticipantContextId()) .holderId(bpn) .credential(new VerifiableCredentialContainer(newVcString, CredentialFormat.VC1_0_JWT, newCred)) .build()) @@ -255,9 +255,7 @@ protected JsonObject createContractPolicy(String bpn) { return frameworkPolicy(Map.of(CX_POLICY_2025_09_NS + "Membership", "active"), CX_POLICY_2025_09_NS + "access"); } - protected abstract RuntimeExtension consumerRuntime(); - - protected abstract RuntimeExtension providerRuntime(); + protected abstract RuntimeExtension credentialStoreRuntime(); protected abstract DataspaceIssuer dataspaceIssuer(); diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java index f666348f7c..c32e95685a 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java @@ -21,16 +21,16 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import jakarta.json.JsonObject; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.iam.decentralizedclaims.spi.model.PresentationResponseMessage; import org.eclipse.edc.iam.did.spi.document.DidDocument; import org.eclipse.edc.iam.did.spi.document.Service; -import org.eclipse.edc.iam.identitytrust.spi.model.PresentationResponseMessage; import org.eclipse.edc.identityhub.spi.verifiablecredentials.generator.VerifiablePresentationService; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.verifiablecredentials.store.CredentialStore; import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.RuntimeExtension; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.Result; @@ -41,7 +41,6 @@ import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.IatpParticipant; import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.StsParticipant; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -123,10 +122,7 @@ static void beforeAll() { BDRS_SERVER_EXTENSION.addMapping(CONSUMER.getBpn(), CONSUMER.getDid()); BDRS_SERVER_EXTENSION.addMapping(PROVIDER.getBpn(), PROVIDER.getDid()); - } - @BeforeEach - void setup() { CONSUMER.configureParticipant(DATASPACE_ISSUER_PARTICIPANT, CONSUMER_RUNTIME, STS_RUNTIME); PROVIDER.configureParticipant(DATASPACE_ISSUER_PARTICIPANT, PROVIDER_RUNTIME, STS_RUNTIME); MALICIOUS_ACTOR.configureParticipant(DATASPACE_ISSUER_PARTICIPANT, MALICIOUS_ACTOR_RUNTIME, STS_RUNTIME); @@ -143,7 +139,7 @@ void shouldNotImpersonateConsumer_withWrappedConsumerCredential() { "contentType", "application/json" ); - var presentationService = MALICIOUS_ACTOR_RUNTIME.getService(VerifiablePresentationService.class); + var presentationService = STS_RUNTIME.getService(VerifiablePresentationService.class); withMock((membershipCredential) -> presentationService.createPresentation(MALICIOUS_ACTOR.getDid(), List.of(membershipCredential.getVerifiableCredential()), null, PROVIDER.getDid())); @@ -169,7 +165,7 @@ void shouldNotImpersonateConsumer_withConsumerPresentation() { "contentType", "application/json" ); - var presentationService = CONSUMER_RUNTIME.getService(VerifiablePresentationService.class); + var presentationService = STS_RUNTIME.getService(VerifiablePresentationService.class); withMock((membershipCredential) -> presentationService.createPresentation(CONSUMER.getDid(), List.of(membershipCredential.getVerifiableCredential()), null, PROVIDER.getDid())); @@ -199,10 +195,10 @@ private static DidDocument maliciousActorDidDocument(DidDocument didDocument) { void withMock(Function> response) { - var store = CONSUMER_RUNTIME.getService(CredentialStore.class); + var store = STS_RUNTIME.getService(CredentialStore.class); - var sokratesMembershipCredential = store.query(QuerySpec.max()).getContent() - .stream().filter(c -> c.getVerifiableCredential().credential().getType().contains("MembershipCredential")) + var sokratesMembershipCredential = store.query(QuerySpec.max()).getContent().stream() + .filter(c -> c.getVerifiableCredential().credential().getType().contains("MembershipCredential")) .findFirst() .orElseThrow(); diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DimConsumerPullTest.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DimConsumerPullTest.java index 990067c205..9886f9d860 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DimConsumerPullTest.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DimConsumerPullTest.java @@ -20,22 +20,25 @@ package org.eclipse.tractusx.edc.tests.transfer; import com.github.tomakehurst.wiremock.WireMockServer; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; -import org.eclipse.edc.iam.identitytrust.sts.service.EmbeddedSecureTokenService; -import org.eclipse.edc.iam.identitytrust.sts.spi.model.StsAccount; -import org.eclipse.edc.iam.identitytrust.sts.spi.service.StsAccountService; +import org.eclipse.edc.iam.decentralizedclaims.sts.service.EmbeddedSecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.sts.spi.model.StsAccount; +import org.eclipse.edc.iam.decentralizedclaims.sts.spi.service.StsAccountService; +import org.eclipse.edc.identityhub.spi.keypair.KeyPairService; +import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext; +import org.eclipse.edc.identityhub.spi.participantcontext.store.ParticipantContextStore; import org.eclipse.edc.json.JacksonTypeManager; import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.RuntimeExtension; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.keys.spi.PrivateKeyResolver; import org.eclipse.edc.security.token.jwt.DefaultJwsSignerProvider; -import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.token.JwtGenerationService; import org.eclipse.edc.token.spi.TokenGenerationService; import org.eclipse.edc.transaction.spi.NoopTransactionContext; import org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase; +import org.eclipse.tractusx.edc.tests.runtimes.KeyPool; import org.eclipse.tractusx.edc.tests.transfer.extension.BdrsServerExtension; import org.eclipse.tractusx.edc.tests.transfer.extension.DidServerExtension; import org.eclipse.tractusx.edc.tests.transfer.iatp.dispatchers.DimDispatcher; @@ -48,6 +51,7 @@ import java.net.URI; import java.time.Clock; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -64,6 +68,7 @@ import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_BPN; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_NAME; import static org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.Runtimes.dimRuntime; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -115,6 +120,7 @@ public class DimConsumerPullTest extends AbstractIatpConsumerPullTest { @BeforeAll static void prepare() { + KeyPool.register(DATASPACE_ISSUER_PARTICIPANT.getFullKeyId(), DATASPACE_ISSUER_PARTICIPANT.getKeyPair()); DID_SERVER.register(CONSUMER_NAME, CONSUMER.getDidDocument()); DID_SERVER.register(PROVIDER_NAME, PROVIDER.getDidDocument()); DID_SERVER.register("issuer", DATASPACE_ISSUER_PARTICIPANT.didDocument()); @@ -126,8 +132,8 @@ static void prepare() { var providerTokenGeneration = new JwtGenerationService(new DefaultJwsSignerProvider(PROVIDER_RUNTIME.getService(PrivateKeyResolver.class))); var generatorServices = Map.of( - CONSUMER.getDid(), tokenServiceFor(consumerTokenGeneration, CONSUMER), - PROVIDER.getDid(), tokenServiceFor(providerTokenGeneration, PROVIDER)); + CONSUMER.getDid(), tokenServiceFor(consumerTokenGeneration, CONSUMER, CONSUMER_RUNTIME), + PROVIDER.getDid(), tokenServiceFor(providerTokenGeneration, PROVIDER, PROVIDER_RUNTIME)); var stsUri = STS.stsUri().get(); @@ -149,28 +155,43 @@ static void unwind() { dimServer.stop(); } - private static EmbeddedSecureTokenService tokenServiceFor(TokenGenerationService tokenGenerationService, IatpParticipant participant) { + private static EmbeddedSecureTokenService tokenServiceFor(TokenGenerationService tokenGenerationService, IatpParticipant participant, + RuntimeExtension runtime) { StsAccountService stsAccountService = mock(); - when(stsAccountService.findById(participant.getDid())).thenAnswer(i -> { + when(stsAccountService.queryAccounts(any())).thenAnswer(i -> { var dummyId = UUID.randomUUID().toString(); var account = StsAccount.Builder.newInstance() .id(dummyId) + .participantContextId(participant.getDid()) .clientId(participant.getDid()) .name(participant.getName()) .did(participant.getDid()) .secretAlias(dummyId) - .privateKeyAlias(participant.getPrivateKeyAlias()) - .publicKeyReference(participant.verificationId()) .build(); - return ServiceResult.success(account); + return List.of(account); }); + + var participantContextStore = runtime.getService(ParticipantContextStore.class); + participantContextStore.create(ParticipantContext.Builder.newInstance() + .participantContextId(participant.getDid()) + .did(participant.getDid()) + .apiTokenAlias(participant.getDid()).build()); + + var keyPairService = runtime.getService(KeyPairService.class); + var keyDescriptor = participant.createKeyDescriptor(); + KeyPool.register(participant.getFullKeyId(), participant.getKeyPair()); + + keyPairService.addKeyPair(participant.getDid(), keyDescriptor, true) + .orElseThrow(f -> new RuntimeException("Cannot add key pair: " + f.getFailureDetail())); + return new EmbeddedSecureTokenService( new NoopTransactionContext(), 60 * 60, tokenGenerationService, Clock.systemUTC(), - stsAccountService + stsAccountService, + keyPairService ); } @@ -182,15 +203,10 @@ void setupParticipants() { } @Override - protected RuntimeExtension consumerRuntime() { + protected RuntimeExtension credentialStoreRuntime() { return CONSUMER_RUNTIME; } - @Override - protected RuntimeExtension providerRuntime() { - return PROVIDER_RUNTIME; - } - @Override protected DataspaceIssuer dataspaceIssuer() { return DATASPACE_ISSUER_PARTICIPANT; diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java index 309da0512d..2329a977d9 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java @@ -19,12 +19,12 @@ package org.eclipse.tractusx.edc.tests.transfer; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.RuntimeExtension; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.protocol.spi.DataspaceProfileContextRegistry; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.iam.ClaimToken; diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java index 743161c974..b49192fbf5 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java @@ -25,12 +25,12 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.tractusx.edc.tests.extension.VaultSeedExtension; import org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase; +import org.eclipse.tractusx.edc.tests.runtimes.KeyPool; import org.eclipse.tractusx.edc.tests.transfer.extension.BdrsServerExtension; import org.eclipse.tractusx.edc.tests.transfer.extension.DidServerExtension; import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.DataspaceIssuer; import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.IatpParticipant; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.RegisterExtension; import java.util.Map; @@ -57,6 +57,7 @@ public class StsConsumerPullTest extends AbstractIatpConsumerPullTest { .id(DID_SERVER.didFor(CONSUMER_NAME)) .stsUri(STS.stsUri()) .stsClientId(CONSUMER_BPN) + .credentialServiceUri(STS.credentialServiceUri()) .trustedIssuer(DATASPACE_ISSUER_PARTICIPANT.didUrl()) .bpn(CONSUMER_BPN) .protocol(DSP_2025) @@ -67,6 +68,7 @@ public class StsConsumerPullTest extends AbstractIatpConsumerPullTest { .id(DID_SERVER.didFor(PROVIDER_NAME)) .stsUri(STS.stsUri()) .stsClientId(PROVIDER_BPN) + .credentialServiceUri(STS.credentialServiceUri()) .trustedIssuer(DATASPACE_ISSUER_PARTICIPANT.didUrl()) .bpn(PROVIDER_BPN) .protocol(DSP_2025) @@ -92,31 +94,22 @@ public class StsConsumerPullTest extends AbstractIatpConsumerPullTest { @BeforeAll static void beforeAll() { + KeyPool.register(DATASPACE_ISSUER_PARTICIPANT.getFullKeyId(), DATASPACE_ISSUER_PARTICIPANT.getKeyPair()); DID_SERVER.register(CONSUMER_NAME, CONSUMER.getDidDocument()); DID_SERVER.register(PROVIDER_NAME, PROVIDER.getDidDocument()); DID_SERVER.register("issuer", DATASPACE_ISSUER_PARTICIPANT.didDocument()); BDRS_SERVER_EXTENSION.addMapping(CONSUMER.getBpn(), CONSUMER.getDid()); BDRS_SERVER_EXTENSION.addMapping(PROVIDER.getBpn(), PROVIDER.getDid()); - } - // credentials etc get wiped after every, so the need to be created before every test - @BeforeEach - void setUp() { CONSUMER.configureParticipant(DATASPACE_ISSUER_PARTICIPANT, CONSUMER_RUNTIME, STS_RUNTIME); PROVIDER.configureParticipant(DATASPACE_ISSUER_PARTICIPANT, PROVIDER_RUNTIME, STS_RUNTIME); - CONSUMER.setJsonLd(CONSUMER_RUNTIME.getService(JsonLd.class)); } @Override - protected RuntimeExtension consumerRuntime() { - return CONSUMER_RUNTIME; - } - - @Override - protected RuntimeExtension providerRuntime() { - return PROVIDER_RUNTIME; + protected RuntimeExtension credentialStoreRuntime() { + return STS_RUNTIME; } @Override diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java index d91b7fc90e..bd82116d9f 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java @@ -19,7 +19,7 @@ package org.eclipse.tractusx.edc.tests.transfer.extension; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.junit.jupiter.api.extension.AfterAllCallback; diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java index e80aa0d284..58b5fdf2f4 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java @@ -20,8 +20,8 @@ package org.eclipse.tractusx.edc.tests.transfer.extension; import com.github.tomakehurst.wiremock.WireMockServer; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; import org.eclipse.edc.iam.did.spi.document.DidDocument; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.util.io.Ports; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DimDispatcher.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DimDispatcher.java index f6a7e8c05c..4e16ace22b 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DimDispatcher.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DimDispatcher.java @@ -24,7 +24,7 @@ import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.http.Response; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; -import org.eclipse.edc.iam.identitytrust.sts.service.EmbeddedSecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.sts.service.EmbeddedSecureTokenService; import org.eclipse.edc.json.JacksonTypeManager; import org.eclipse.edc.spi.iam.TokenRepresentation; import org.eclipse.edc.spi.types.TypeManager; @@ -34,7 +34,7 @@ import java.util.Optional; import java.util.stream.Collectors; -import static org.eclipse.edc.iam.identitytrust.spi.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM; +import static org.eclipse.edc.iam.decentralizedclaims.spi.SelfIssuedTokenConstants.PRESENTATION_TOKEN_CLAIM; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT; diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/IatpParticipant.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/IatpParticipant.java index b7b29b9188..6bdbd96164 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/IatpParticipant.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/IatpParticipant.java @@ -19,27 +19,31 @@ package org.eclipse.tractusx.edc.tests.transfer.iatp.harness; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.iam.decentralizedclaims.sts.spi.model.StsAccount; +import org.eclipse.edc.iam.decentralizedclaims.sts.spi.store.StsAccountStore; import org.eclipse.edc.iam.did.spi.document.DidDocument; import org.eclipse.edc.iam.did.spi.document.Service; import org.eclipse.edc.iam.did.spi.document.VerificationMethod; -import org.eclipse.edc.iam.identitytrust.sts.spi.model.StsAccount; -import org.eclipse.edc.iam.identitytrust.sts.spi.store.StsAccountStore; +import org.eclipse.edc.identityhub.spi.keypair.KeyPairService; import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService; import org.eclipse.edc.identityhub.spi.participantcontext.model.KeyDescriptor; import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantManifest; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; import org.eclipse.edc.identityhub.spi.verifiablecredentials.store.CredentialStore; import org.eclipse.edc.junit.extensions.RuntimeExtension; +import org.eclipse.edc.junit.utils.LazySupplier; +import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.tractusx.edc.tests.participant.TractusxIatpParticipantBase; +import org.eclipse.tractusx.edc.tests.runtimes.KeyPool; import java.net.URI; import java.util.Base64; import java.util.HashMap; import java.util.List; +import java.util.Objects; import static org.eclipse.edc.util.io.Ports.getFreePort; @@ -47,6 +51,7 @@ public class IatpParticipant extends TractusxIatpParticipantBase { protected final LazySupplier csService = new LazySupplier<>(() -> URI.create("http://localhost:" + getFreePort() + "/api/resolution")); protected LazySupplier dimUri; + protected LazySupplier credentialServiceUri; private DidDocument didDocument; @@ -70,41 +75,47 @@ public Config getConfig() { } public void configureParticipant(DataspaceIssuer issuer, RuntimeExtension runtimeExtension) { - var participantContextService = runtimeExtension.getService(ParticipantContextService.class); - var vault = runtimeExtension.getService(Vault.class); + runtimeExtension.getService(Vault.class).storeSecret(getDid(), getPrivateKeyAlias(), getPrivateKeyAsString()); + + try { + // runtime has CredentialStore, DIM tests cases + var credentialStore = runtimeExtension.getService(CredentialStore.class); + issueCredentials(issuer).forEach(credentialStore::create); + } catch (EdcException e) { + // runtime has no CredentialStore, STS tests cases + } + } - var participantKey = getKeyPairAsJwk(); - var key = KeyDescriptor.Builder.newInstance() - .keyId(getKeyId()) - .publicKeyJwk(participantKey.toPublicJWK().toJSONObject()) - .privateKeyAlias(getPrivateKeyAlias()) - .build(); + public void configureParticipant(DataspaceIssuer issuer, RuntimeExtension runtimeExtension, RuntimeExtension stsRuntimeExtension) { + configureParticipant(issuer, runtimeExtension); + + var credentialStore = stsRuntimeExtension.getService(CredentialStore.class); + issueCredentials(issuer).forEach(credentialStore::create); + + stsRuntimeExtension.getService(Vault.class).storeSecret(verificationId(), getPrivateKeyAsString()); + stsRuntimeExtension.getService(Vault.class).storeSecret(getPrivateKeyAlias(), getPrivateKeyAsString()); var participantManifest = ParticipantManifest.Builder.newInstance() - .participantId(getDid()) + .participantContextId(getDid()) .did(getDid()) - .key(key) .build(); + var participantContextService = stsRuntimeExtension.getService(ParticipantContextService.class); + var createParticipantContextResponse = participantContextService.createParticipantContext(participantManifest) + .orElseThrow(f -> new EdcException("cannot create participant context: " + f.getFailureDetail())); - participantContextService.createParticipantContext(participantManifest); - vault.storeSecret(getPrivateKeyAlias(), getPrivateKeyAsString()); + runtimeExtension.getService(Vault.class).storeSecret("client_secret_alias", createParticipantContextResponse.clientSecret()); - var credentialStore = runtimeExtension.getService(CredentialStore.class); - issueCredentials(issuer).forEach(credentialStore::create); - } + stsRuntimeExtension.getService(KeyPairService.class).addKeyPair(getDid(), createKeyDescriptor(), true) + .orElseThrow(f -> new EdcException("Cannot store key pair: " + f.getFailureDetail())); - public void configureParticipant(DataspaceIssuer issuer, RuntimeExtension runtimeExtension, RuntimeExtension stsRuntimeExtension) { - configureParticipant(issuer, runtimeExtension); + KeyPool.register(getFullKeyId(), getKeyPair()); - stsRuntimeExtension.getService(Vault.class).storeSecret(verificationId(), getPrivateKeyAsString()); - stsRuntimeExtension.getService(Vault.class).storeSecret(getPrivateKeyAlias(), getPrivateKeyAsString()); var account = StsAccount.Builder.newInstance() .id(getId()) + .participantContextId(getDid()) .name(getName()) .clientId(getDid()) .did(getDid()) - .privateKeyAlias(getPrivateKeyAlias()) - .publicKeyReference(getFullKeyId()) .secretAlias("client_secret_alias") .build(); stsRuntimeExtension.getService(StsAccountStore.class).create(account); @@ -119,6 +130,14 @@ private List issueCredentials(DataspaceIssuer issu ); } + public KeyDescriptor createKeyDescriptor() { + return KeyDescriptor.Builder.newInstance() + .keyId(getFullKeyId()) + .privateKeyAlias(getPrivateKeyAlias()) + .publicKeyJwk(getKeyPairAsJwk().toPublicJWK().toJSONObject()) + .build(); + } + public static class Builder extends TractusxIatpParticipantBase.Builder { protected Builder() { @@ -141,11 +160,17 @@ public Builder dimUri(LazySupplier dimUri) { return self(); } + public Builder credentialServiceUri(LazySupplier credentialServiceUri) { + participant.credentialServiceUri = credentialServiceUri; + return self(); + } + private DidDocument generateDidDocument() { var service = new Service(); service.setId("#credential-service"); service.setType("CredentialService"); - service.setServiceEndpoint(participant.csService.get() + "/v1/participants/" + toBase64(participant.did)); + var credentialServiceBaseUri = Objects.requireNonNullElse(participant.credentialServiceUri, participant.csService); + service.setServiceEndpoint(credentialServiceBaseUri.get() + "/v1/participants/" + toBase64(participant.did)); var ecKey = participant.getKeyPairAsJwk(); @@ -167,6 +192,5 @@ private DidDocument generateDidDocument() { private String toBase64(String s) { return Base64.getUrlEncoder().encodeToString(s.getBytes()); } - } } diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/StsParticipant.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/StsParticipant.java index 86a4f42dbf..20dab84547 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/StsParticipant.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/StsParticipant.java @@ -20,7 +20,7 @@ package org.eclipse.tractusx.edc.tests.transfer.iatp.harness; -import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier; +import org.eclipse.edc.junit.utils.LazySupplier; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase; @@ -39,6 +39,7 @@ public class StsParticipant extends TractusxParticipantBase { protected final LazySupplier stsUri = new LazySupplier<>(() -> URI.create("http://localhost:" + getFreePort() + "/api/v1/sts")); + protected final LazySupplier credentialServiceUri = new LazySupplier<>(() -> URI.create("http://localhost:" + getFreePort() + "/api/resolution")); private StsParticipant() { } @@ -46,7 +47,9 @@ private StsParticipant() { public Config stsConfig(IatpParticipant... participants) { var additionalSettings = Map.of( "web.http.sts.port", String.valueOf(stsUri.get().getPort()), - "web.http.sts.path", stsUri.get().getPath() + "web.http.sts.path", stsUri.get().getPath(), + "web.http.credentials.port", String.valueOf(credentialServiceUri.get().getPort()), + "web.http.credentials.path", credentialServiceUri.get().getPath() ); var baseConfig = super.getConfig() @@ -78,6 +81,10 @@ public LazySupplier stsUri() { return stsUri; } + public LazySupplier credentialServiceUri() { + return credentialServiceUri; + } + public static class Builder extends TractusxParticipantBase.Builder { protected Builder() { diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/IatpParticipantRuntimeExtension.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/IatpParticipantRuntimeExtension.java index 830c8ef06f..4bc18735a8 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/IatpParticipantRuntimeExtension.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/IatpParticipantRuntimeExtension.java @@ -22,6 +22,7 @@ import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.security.token.jwt.CryptoConverter; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.ServiceExtension; @@ -42,6 +43,8 @@ public IatpParticipantRuntimeExtension(EmbeddedRuntime runtime, KeyPair keyPair) super(runtime); registerSystemExtension(ServiceExtension.class, new ServiceExtension() { + @Setting(key = "edc.participant.id") + private String participantContextId; @Inject private Vault vault; @@ -52,8 +55,8 @@ public void initialize(ServiceExtensionContext context) { var config = context.getConfig(); var privateAlias = config.getString("edc.transfer.proxy.token.signer.privatekey.alias"); var publicAlias = config.getString("edc.transfer.proxy.token.verifier.publickey.alias"); - vault.storeSecret(privateAlias, runtimeKeyPair.toJSONString()); - vault.storeSecret(publicAlias, runtimeKeyPair.toPublicJWK().toJSONString()); + vault.storeSecret(participantContextId, privateAlias, runtimeKeyPair.toJSONString()); + vault.storeSecret(participantContextId, publicAlias, runtimeKeyPair.toPublicJWK().toJSONString()); } }); registerSystemExtension(ServiceExtension.class, new DataWiperExtension(wiper, CredentialWiper::new)); diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/Runtimes.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/Runtimes.java index 4407f222d3..ae70d10a55 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/Runtimes.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/Runtimes.java @@ -19,11 +19,15 @@ package org.eclipse.tractusx.edc.tests.transfer.iatp.runtime; +import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimeExtension; +import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; +import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.tractusx.edc.tests.extension.VaultSeedExtension; +import org.eclipse.tractusx.edc.tests.runtimes.KeyPool; import java.security.KeyPair; import java.util.Map; @@ -42,15 +46,16 @@ static RuntimeExtension iatpRuntime(String name, KeyPair keyPair, Supplier configurationProvider) { - return genericRuntime(name, ":edc-tests:runtime:iatp:runtime-memory-sts", keyPair, configurationProvider) - .registerSystemExtension(ServiceExtension.class, new VaultSeedExtension(Map.of("client_secret_alias", "client_secret"))); + return new RuntimePerClassExtension(new EmbeddedRuntime(name, ":edc-tests:runtime:iatp:runtime-memory-sts").configurationProvider(configurationProvider) + .registerSystemExtension(ServiceExtension.class, new VaultSeedExtension(Map.of("client_secret_alias", "client_secret")))) + .registerServiceMock(DidPublicKeyResolver.class, keyId -> Result.success(KeyPool.forId(keyId).getPublic())); } private static RuntimeExtension genericRuntime(String name, String moduleName, KeyPair keyPair, Supplier configurationProvider) { return new IatpParticipantRuntimeExtension( new EmbeddedRuntime(name, moduleName).configurationProvider(configurationProvider), keyPair - ); + ).registerServiceMock(DidPublicKeyResolver.class, keyId -> Result.success(KeyPool.forId(keyId).getPublic())); } } diff --git a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/RetireAgreementTest.java b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/RetireAgreementTest.java index 16ccfce4e5..761a005493 100644 --- a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/RetireAgreementTest.java +++ b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/RetireAgreementTest.java @@ -124,11 +124,16 @@ void retireAgreement_shouldCloseTransferProcesses() { .until(() -> CONSUMER.edrs().getEdrEntriesByAssetId(assetId), it -> it.size() == 1) .get(0).asJsonObject(); - var agreementId = edrCache.getString("agreementId"); var transferProcessId = edrCache.getString("transferProcessId"); + var agreementId = CONSUMER.getTransferProcessField(transferProcessId, "contractId"); - var response = PROVIDER.retireProviderAgreement(agreementId); - response.statusCode(204); + var providerTransferProcess = PROVIDER.getTransferProcesses().stream() + .filter(it -> it.asJsonObject().getString("correlationId").equals(transferProcessId)).findFirst().get(); + + await().untilAsserted(() -> { + var response = PROVIDER.retireProviderAgreement(providerTransferProcess.asJsonObject().getString("contractId")); + response.statusCode(204); + }); var event = PROVIDER.waitForEvent("ContractAgreementRetired"); assertThat(event).isNotNull(); diff --git a/edc-tests/runtime/dataplane-cloud/build.gradle.kts b/edc-tests/runtime/dataplane-cloud/build.gradle.kts index 2d02b7b954..5f1c2881f8 100644 --- a/edc-tests/runtime/dataplane-cloud/build.gradle.kts +++ b/edc-tests/runtime/dataplane-cloud/build.gradle.kts @@ -31,6 +31,7 @@ dependencies { exclude("org.eclipse.edc", "data-plane-selector-client") exclude("org.eclipse.edc", "data-plane-self-registration") } + implementation(project(":edc-extensions:single-participant-vault")) } application { diff --git a/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts b/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts index 4a53d5fa6f..cf871f4de6 100644 --- a/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts +++ b/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts @@ -25,6 +25,9 @@ dependencies { implementation(libs.edc.ih.spi) implementation(libs.edc.spi.jsonld) implementation(project(":spi:core-spi")) + + // TODO: test + implementation("com.networknt:json-schema-validator:2.0.0") } // do not publish diff --git a/edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/CredentialsJsonLdExtension.java b/edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/CredentialsJsonLdExtension.java index 584ae5308b..ff5f270118 100644 --- a/edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/CredentialsJsonLdExtension.java +++ b/edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/CredentialsJsonLdExtension.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.iatp; +import com.networknt.schema.format.PatternFormat; import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; @@ -43,5 +44,12 @@ public void initialize(ServiceExtensionContext context) { } catch (URISyntaxException e) { throw new RuntimeException(e); } + + // TODO: test + try { + getClass().getClassLoader().loadClass(PatternFormat.class.getName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } } } diff --git a/edc-tests/runtime/iatp/runtime-memory-iatp-dim-ih/build.gradle.kts b/edc-tests/runtime/iatp/runtime-memory-iatp-dim-ih/build.gradle.kts index dacac4a578..fce26d0467 100644 --- a/edc-tests/runtime/iatp/runtime-memory-iatp-dim-ih/build.gradle.kts +++ b/edc-tests/runtime/iatp/runtime-memory-iatp-dim-ih/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { // use basic (all in-mem) control plane implementation(project(":edc-controlplane:edc-controlplane-base")) + implementation(project(":edc-extensions:single-participant-vault")) implementation(project(":core:json-ld-core")) implementation(project(":edc-extensions:cx-policy")) implementation(project(":edc-extensions:cx-policy-legacy")) @@ -35,14 +36,14 @@ dependencies { implementation(project(":edc-tests:runtime:iatp:iatp-extensions")) // use basic (all in-mem) data plane - runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) { + implementation(project(":edc-dataplane:edc-dataplane-base")) { exclude("org.eclipse.edc", "api-observability") exclude("org.eclipse.edc", "data-plane-selector-client") } implementation(libs.edc.core.controlplane) implementation(libs.edc.core.did) - implementation(libs.edc.identity.trust.transform) + implementation(libs.edc.decentralized.claims.transform) implementation(libs.edc.auth.oauth2.client) implementation(libs.edc.ih.api.presentation) implementation(libs.edc.ih.keypairs) diff --git a/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts b/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts index b6d7cbf53d..322718d83b 100644 --- a/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts +++ b/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { // use basic (all in-mem) control plane implementation(project(":edc-controlplane:edc-controlplane-base")) + implementation(project(":edc-extensions:single-participant-vault")) implementation(project(":edc-extensions:cx-policy")) implementation(project(":edc-extensions:cx-policy-legacy")) implementation(project(":core:json-ld-core")) @@ -34,14 +35,20 @@ dependencies { implementation(project(":edc-tests:runtime:iatp:iatp-extensions")) // use basic (all in-mem) data plane - runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) { + implementation(project(":edc-dataplane:edc-dataplane-base")) { exclude("org.eclipse.edc", "api-observability") exclude("org.eclipse.edc", "data-plane-selector-client") } + constraints { + implementation("com.networknt:json-schema-validator:2.0.0") { + because("older versions cause runtime issues") + } + } + implementation(libs.edc.core.controlplane) implementation(libs.edc.core.did) - implementation(libs.edc.identity.trust.transform) + implementation(libs.edc.decentralized.claims.transform) implementation(libs.edc.auth.oauth2.client) // IH dependencies implementation(libs.edc.ih.api.presentation) diff --git a/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts b/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts index 75d1b61961..82df91b908 100644 --- a/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts +++ b/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts @@ -23,10 +23,12 @@ plugins { } dependencies { - // use basic (all in-mem) control plane - implementation(project(":edc-controlplane:edc-controlplane-base")) +// implementation(project(":edc-controlplane:edc-controlplane-base")) + implementation("org.eclipse.edc:identityhub-bom:0.15.0") // TODO: put in version catalog + implementation(project(":edc-extensions:single-participant-vault")) implementation(project(":core:json-ld-core")) + implementation(project(":edc-tests:runtime:iatp:iatp-extensions")) implementation(libs.edc.iam.mock) implementation(libs.edc.spi.keys) diff --git a/edc-tests/runtime/mock-connector/build.gradle.kts b/edc-tests/runtime/mock-connector/build.gradle.kts index 825ddff791..ff423b513d 100644 --- a/edc-tests/runtime/mock-connector/build.gradle.kts +++ b/edc-tests/runtime/mock-connector/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { // runtime dependencies runtimeOnly(libs.edc.core.runtime) runtimeOnly(libs.edc.core.connector) + runtimeOnly(libs.edc.core.participant.context.single) runtimeOnly(libs.edc.boot) runtimeOnly(libs.edc.api.management) { exclude("org.eclipse.edc", "edr-cache-api") diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MockServiceExtension.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MockServiceExtension.java index 2faa203f88..eb3c788cee 100644 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MockServiceExtension.java +++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MockServiceExtension.java @@ -26,8 +26,8 @@ import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; -import org.eclipse.edc.connector.controlplane.services.spi.protocol.VersionService; import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.spi.monitor.Monitor; @@ -45,7 +45,6 @@ import org.eclipse.tractusx.edc.mock.services.ContractNegotiationServiceStub; import org.eclipse.tractusx.edc.mock.services.PolicyDefinitionServiceStub; import org.eclipse.tractusx.edc.mock.services.TransferProcessServiceStub; -import org.eclipse.tractusx.edc.mock.services.VersionServiceStub; import java.util.Queue; import java.util.concurrent.CompletableFuture; @@ -87,14 +86,13 @@ public AssetService mockAssetService(ServiceExtensionContext context) { @Provider public CatalogService mockCatalogService() { return new CatalogService() { - @Override - public CompletableFuture> requestCatalog(String s, String s1, String s2, QuerySpec querySpec, String... strings) { + public CompletableFuture> requestCatalog(ParticipantContext participantContext, String counterPartyId, String counterPartyAddress, String protocol, QuerySpec querySpec, String... additionalScopes) { return null; } @Override - public CompletableFuture> requestDataset(String s, String s1, String s2, String s3) { + public CompletableFuture> requestDataset(ParticipantContext participantContext, String id, String counterPartyId, String counterPartyAddress, String protocol) { return null; } }; @@ -125,9 +123,4 @@ public TransferProcessService mockTransferProcessService() { return new TransferProcessServiceStub(new ResponseQueue(recordedRequests, monitor)); } - @Provider - public VersionService mockVersionService() { - return new VersionServiceStub(new ResponseQueue(recordedRequests, monitor)); - } - } diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractNegotiationServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractNegotiationServiceStub.java index bf7c5e7eac..d4009dcd67 100644 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractNegotiationServiceStub.java +++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractNegotiationServiceStub.java @@ -24,6 +24,7 @@ import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractRequest; import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationService; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.web.spi.exception.InvalidRequestException; @@ -66,7 +67,7 @@ public ContractAgreement getForNegotiation(String negotiationId) { } @Override - public ContractNegotiation initiateNegotiation(ContractRequest request) { + public ContractNegotiation initiateNegotiation(ParticipantContext participantContext, ContractRequest request) { return responseQueue.getNext(ContractNegotiation.class, "Error initiating ContractNegotiation: %s") .orElseThrow(InvalidRequestException::new); } diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/TransferProcessServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/TransferProcessServiceStub.java index 3926ecc43e..2bf880c54e 100644 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/TransferProcessServiceStub.java +++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/TransferProcessServiceStub.java @@ -24,10 +24,11 @@ import org.eclipse.edc.connector.controlplane.transfer.spi.types.ProvisionResponse; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferRequest; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.CompleteProvisionCommand; +import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.NotifyPreparedCommand; import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.ResumeTransferCommand; import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.SuspendTransferCommand; import org.eclipse.edc.connector.controlplane.transfer.spi.types.command.TerminateTransferCommand; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.web.spi.exception.InvalidRequestException; @@ -91,13 +92,13 @@ public ServiceResult> search(QuerySpec query) { } @Override - public @NotNull ServiceResult initiateTransfer(TransferRequest request) { + public @NotNull ServiceResult initiateTransfer(ParticipantContext participantContext, TransferRequest request) { return responseQueue.getNext(TransferProcess.class, "Error initiating TransferProcess: %s"); } @Override - public ServiceResult completeProvision(CompleteProvisionCommand completeProvisionCommand) { - return responseQueue.getNext(Void.class, "Error completing provisioning"); + public ServiceResult notifyPrepared(NotifyPreparedCommand command) { + return responseQueue.getNext(Void.class, "Error notifying prepared on TransferProcess: %s"); } @Override diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/VersionServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/VersionServiceStub.java deleted file mode 100644 index fb63962714..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/VersionServiceStub.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * 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. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.mock.services; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.edc.connector.controlplane.protocolversion.spi.ProtocolVersionRequest; -import org.eclipse.edc.connector.controlplane.services.spi.protocol.VersionService; -import org.eclipse.edc.protocol.spi.ProtocolVersions; -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.response.StatusResult; -import org.eclipse.tractusx.edc.mock.ResponseQueue; - -import java.util.concurrent.CompletableFuture; - -/** - * Stub implementation of the VersionService. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class VersionServiceStub extends AbstractServiceStub implements VersionService { - - private final ObjectMapper objectMapper = new ObjectMapper(); - - public VersionServiceStub(ResponseQueue responseQueue) { - super(responseQueue); - } - - @Override - public CompletableFuture> requestVersions(ProtocolVersionRequest request) { - var nextInQueue = responseQueue.getNext(ProtocolVersions.class, "Error retrieving VersionService status result: %s"); - try { - var protocolVersions = objectMapper.writeValueAsString(nextInQueue.getContent()); - var result = StatusResult.success(protocolVersions.getBytes()); - return CompletableFuture.completedFuture(result); - } catch (JsonProcessingException e) { - throw new EdcException(e); - } - } -} diff --git a/edc-tests/runtime/runtime-discovery/runtime-discovery-base/build.gradle.kts b/edc-tests/runtime/runtime-discovery/runtime-discovery-base/build.gradle.kts index 60a9506d2a..547ba3234a 100644 --- a/edc-tests/runtime/runtime-discovery/runtime-discovery-base/build.gradle.kts +++ b/edc-tests/runtime/runtime-discovery/runtime-discovery-base/build.gradle.kts @@ -24,14 +24,15 @@ plugins { dependencies { - runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) { + implementation(project(":edc-controlplane:edc-controlplane-base")) { exclude(module = "tx-dcp") exclude(module = "tx-dcp-sts-dim") } - runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) { + implementation(project(":edc-dataplane:edc-dataplane-base")) { exclude("org.eclipse.edc", "data-plane-selector-client") } + implementation(project(":edc-extensions:single-participant-vault")) } application { diff --git a/edc-tests/runtime/runtime-discovery/runtime-discovery-no-protocols/build.gradle.kts b/edc-tests/runtime/runtime-discovery/runtime-discovery-no-protocols/build.gradle.kts index dd9acefcf6..f3d8c81a19 100644 --- a/edc-tests/runtime/runtime-discovery/runtime-discovery-no-protocols/build.gradle.kts +++ b/edc-tests/runtime/runtime-discovery/runtime-discovery-no-protocols/build.gradle.kts @@ -24,7 +24,7 @@ plugins { dependencies { - runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) { + implementation(project(":edc-controlplane:edc-controlplane-base")) { exclude(module = "tx-dcp") exclude(module = "tx-dcp-sts-dim") exclude(module = "dataspace-protocol") @@ -34,9 +34,10 @@ dependencies { exclude(module = "dsp-http-api-configuration-2025") } - runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) { + implementation(project(":edc-dataplane:edc-dataplane-base")) { exclude("org.eclipse.edc", "data-plane-selector-client") } + implementation(project(":edc-extensions:single-participant-vault")) } diff --git a/edc-tests/runtime/runtime-discovery/runtime-discovery-with-dsp-v08/build.gradle.kts b/edc-tests/runtime/runtime-discovery/runtime-discovery-with-dsp-v08/build.gradle.kts index 2e67f1c1be..ddb0d5e975 100644 --- a/edc-tests/runtime/runtime-discovery/runtime-discovery-with-dsp-v08/build.gradle.kts +++ b/edc-tests/runtime/runtime-discovery/runtime-discovery-with-dsp-v08/build.gradle.kts @@ -24,7 +24,7 @@ plugins { dependencies { - runtimeOnly(project(":edc-controlplane:edc-controlplane-base")) { + implementation(project(":edc-controlplane:edc-controlplane-base")) { exclude(module = "tx-dcp") exclude(module = "tx-dcp-sts-dim") exclude(module = "dataspace-protocol") @@ -33,9 +33,10 @@ dependencies { exclude(module = "dsp-http-api-configuration-2025") } - runtimeOnly(project(":edc-dataplane:edc-dataplane-base")) { + implementation(project(":edc-dataplane:edc-dataplane-base")) { exclude("org.eclipse.edc", "data-plane-selector-client") } + implementation(project(":edc-extensions:single-participant-vault")) } application { diff --git a/edc-tests/runtime/runtime-dsp/build.gradle.kts b/edc-tests/runtime/runtime-dsp/build.gradle.kts index 35c7796108..f5c326313d 100644 --- a/edc-tests/runtime/runtime-dsp/build.gradle.kts +++ b/edc-tests/runtime/runtime-dsp/build.gradle.kts @@ -24,7 +24,7 @@ plugins { dependencies { - runtimeOnly(project(":edc-tests:runtime:runtime-postgresql")) + implementation(project(":edc-tests:runtime:runtime-postgresql")) runtimeOnly(libs.tck.extension) } diff --git a/edc-tests/runtime/runtime-postgresql/build.gradle.kts b/edc-tests/runtime/runtime-postgresql/build.gradle.kts index 486a92ab49..0652ce4ac8 100644 --- a/edc-tests/runtime/runtime-postgresql/build.gradle.kts +++ b/edc-tests/runtime/runtime-postgresql/build.gradle.kts @@ -24,16 +24,17 @@ plugins { dependencies { - runtimeOnly(project(":edc-controlplane:edc-controlplane-postgresql-hashicorp-vault")) { + implementation(project(":edc-controlplane:edc-controlplane-postgresql-hashicorp-vault")) { exclude("org.eclipse.edc", "vault-hashicorp") exclude(module = "tx-dcp") exclude(module = "tx-dcp-sts-dim") } - runtimeOnly(project(":edc-dataplane:edc-dataplane-hashicorp-vault")) { + implementation(project(":edc-dataplane:edc-dataplane-hashicorp-vault")) { exclude("org.eclipse.edc", "data-plane-selector-client") exclude("org.eclipse.edc", "vault-hashicorp") } + implementation(project(":edc-extensions:single-participant-vault")) } application { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6656872bb2..7d98299c8f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,14 +2,14 @@ format.version = "1.1" [versions] -edc = "0.14.1" +edc = "0.15.0" edc-build = "1.1.4" allure = "2.31.0" awaitility = "4.3.0" aws = "2.39.2" azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.82" -dcp-tck = "1.0.0-RC4" +dcp-tck = "1.0.0-SNAPSHOT" dsp-tck = "1.0.0-RC4" flyway = "11.18.0" jackson = "2.20.1" @@ -41,39 +41,43 @@ edc-bom-federatedcatalog-base = { module = "org.eclipse.edc:federatedcatalog-bas edc-bom-federatedcatalog-dcp = { module = "org.eclipse.edc:federatedcatalog-dcp-bom", version.ref = "edc" } edc-bom-federatedcatalog-feature-sql = { module = "org.eclipse.edc:federatedcatalog-feature-sql-bom", version.ref = "edc" } -edc-spi-catalog = { module = "org.eclipse.edc:catalog-spi", version.ref = "edc" } edc-spi-auth = { module = "org.eclipse.edc:auth-spi", version.ref = "edc" } -edc-spi-transfer = { module = "org.eclipse.edc:transfer-spi", version.ref = "edc" } -edc-spi-core = { module = "org.eclipse.edc:core-spi", version.ref = "edc" } -edc-spi-policy = { module = "org.eclipse.edc:policy-spi", version.ref = "edc" } +edc-spi-boot = { module = "org.eclipse.edc:web-spi", version.ref = "edc" } +edc-spi-catalog = { module = "org.eclipse.edc:catalog-spi", version.ref = "edc" } edc-spi-contract = { module = "org.eclipse.edc:contract-spi", version.ref = "edc" } -edc-spi-participant = { module = "org.eclipse.edc:participant-spi", version.ref = "edc" } -edc-spi-protocol = { module = "org.eclipse.edc:protocol-spi", version.ref = "edc" } -edc-spi-policyengine = { module = "org.eclipse.edc:policy-engine-spi", version.ref = "edc" } -edc-spi-request-policy-context = { module = "org.eclipse.edc:request-policy-context-spi", version.ref = "edc" } -edc-spi-transaction-datasource = { module = "org.eclipse.edc:transaction-datasource-spi", version.ref = "edc" } -edc-spi-transactionspi = { module = "org.eclipse.edc:transaction-spi", version.ref = "edc" } edc-spi-controlplane = { module = "org.eclipse.edc:control-plane-spi", version.ref = "edc" } -edc-spi-protocolversion = { module = "org.eclipse.edc:protocol-version-spi", version.ref = "edc" } -edc-spi-boot = { module = "org.eclipse.edc:web-spi", version.ref = "edc" } -edc-spi-web = { module = "org.eclipse.edc:web-spi", version.ref = "edc" } +edc-spi-core = { module = "org.eclipse.edc:core-spi", version.ref = "edc" } +edc-spi-decentralized-claims = { module = "org.eclipse.edc:decentralized-claims-spi", version.ref = "edc" } +edc-spi-edrstore = { module = "org.eclipse.edc:edr-store-spi", version.ref = "edc" } edc-spi-http = { module = "org.eclipse.edc:http-spi", version.ref = "edc" } -edc-spi-keys = { module = "org.eclipse.edc:keys-spi", version.ref = "edc" } +edc-spi-identity-did = { module = "org.eclipse.edc:identity-did-spi", version.ref = "edc" } edc-spi-jsonld = { module = "org.eclipse.edc:json-ld-spi", version.ref = "edc" } edc-spi-jwt = { module = "org.eclipse.edc:jwt-spi", version.ref = "edc" } edc-spi-jwt-signer = { module = "org.eclipse.edc:jwt-signer-spi", version.ref = "edc" } +edc-spi-keypair = { module = "org.eclipse.edc:keypair-spi", version.ref = "edc" } +edc-spi-keys = { module = "org.eclipse.edc:keys-spi", version.ref = "edc" } +edc-spi-participant = { module = "org.eclipse.edc:participant-spi", version.ref = "edc" } +edc-spi-participant-context-single = { module = "org.eclipse.edc:participant-context-single-spi", version.ref = "edc" } +edc-spi-policy = { module = "org.eclipse.edc:policy-spi", version.ref = "edc" } +edc-spi-policyengine = { module = "org.eclipse.edc:policy-engine-spi", version.ref = "edc" } +edc-spi-protocol = { module = "org.eclipse.edc:protocol-spi", version.ref = "edc" } +edc-spi-request-policy-context = { module = "org.eclipse.edc:request-policy-context-spi", version.ref = "edc" } edc-spi-token = { module = "org.eclipse.edc:token-spi", version.ref = "edc" } +edc-spi-transaction-datasource = { module = "org.eclipse.edc:transaction-datasource-spi", version.ref = "edc" } +edc-spi-transactionspi = { module = "org.eclipse.edc:transaction-spi", version.ref = "edc" } +edc-spi-transfer = { module = "org.eclipse.edc:transfer-spi", version.ref = "edc" } edc-spi-transform = { module = "org.eclipse.edc:transform-spi", version.ref = "edc" } -edc-spi-identity-did = { module = "org.eclipse.edc:identity-did-spi", version.ref = "edc" } -edc-spi-identity-trust = { module = "org.eclipse.edc:identity-trust-spi", version.ref = "edc" } edc-spi-vc = { module = "org.eclipse.edc:verifiable-credentials-spi", version.ref = "edc" } -edc-spi-edrstore = { module = "org.eclipse.edc:edr-store-spi", version.ref = "edc" } +edc-spi-web = { module = "org.eclipse.edc:web-spi", version.ref = "edc" } + edc-boot = { module = "org.eclipse.edc:boot", version.ref = "edc" } edc-vault-hashicorp = { module = "org.eclipse.edc:vault-hashicorp", version.ref = "edc" } edc-core-connector = { module = "org.eclipse.edc:connector-core", version.ref = "edc" } edc-core-controlplane = { module = "org.eclipse.edc:control-plane-core", version.ref = "edc" } edc-core-edrstore = { module = "org.eclipse.edc:edr-store-core", version.ref = "edc" } edc-core-jersey = { module = "org.eclipse.edc:jersey-core", version.ref = "edc" } +edc-core-participant-context-config = { module = "org.eclipse.edc:participant-context-config-core", version.ref = "edc" } +edc-core-participant-context-single = { module = "org.eclipse.edc:participant-context-single-core", version.ref = "edc" } edc-core-policy-monitor = { module = "org.eclipse.edc:policy-monitor-core", version.ref = "edc" } edc-core-runtime = { module = "org.eclipse.edc:runtime-core", version.ref = "edc" } edc-core-token = { module = "org.eclipse.edc:token-core", version.ref = "edc" } @@ -130,17 +134,16 @@ edc-aws-validator-data-address-s3 = { module = "org.eclipse.edc.aws:validator-da edc-aws-provision-s3 = { module = "org.eclipse.edc.aws:provision-aws-s3", version.ref = "edc" } # DCP Modules -edc-spi-identitytrust = { module = "org.eclipse.edc:identity-trust-spi", version.ref = "edc" } edc-core-did = { module = "org.eclipse.edc:identity-did-core", version.ref = "edc" } edc-identity-did-web = { module = "org.eclipse.edc:identity-did-web", version.ref = "edc" } edc-identity-vc-ldp = { module = "org.eclipse.edc:ldp-verifiable-credentials", version.ref = "edc" } edc-identity-vc-jwt = { module = "org.eclipse.edc:jwt-verifiable-credentials", version.ref = "edc" } -edc-identity-trust-service = { module = "org.eclipse.edc:identity-trust-service", version.ref = "edc" } -edc-identity-trust-transform = { module = "org.eclipse.edc:identity-trust-transform", version.ref = "edc" } +edc-decentralized-claims-service = { module = "org.eclipse.edc:decentralized-claims-service", version.ref = "edc" } +edc-decentralized-claims-transform = { module = "org.eclipse.edc:decentralized-claims-transform", version.ref = "edc" } # DCP for Testing -edc-identity-trust-sts-remote-lib = { module = "org.eclipse.edc:identity-trust-sts-remote-lib", version.ref = "edc" } +edc-decentralized-claims-sts-remote-lib = { module = "org.eclipse.edc:decentralized-claims-sts-remote-lib", version.ref = "edc" } edc-sts-core = { module = "org.eclipse.edc:sts-core", version.ref = "edc" } edc-sts-api = { module = "org.eclipse.edc:sts-api", version.ref = "edc" } edc-sts-account-provisioner = { module = "org.eclipse.edc:sts-account-provisioner", version.ref = "edc" } @@ -238,5 +241,5 @@ edc-sts = [ [plugins] docker = { id = "com.bmuschko.docker-remote-api", version = "10.0.0" } shadow = { id = "com.gradleup.shadow", version = "9.2.2" } -swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.40" } +swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.26" } edc-build = { id = "org.eclipse.edc.edc-build", version.ref = "edc-build" } diff --git a/samples/testing-with-mocked-connector/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockConnectorSampleTest.java b/samples/testing-with-mocked-connector/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockConnectorSampleTest.java index 6a37af72d9..93756903e1 100644 --- a/samples/testing-with-mocked-connector/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockConnectorSampleTest.java +++ b/samples/testing-with-mocked-connector/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockConnectorSampleTest.java @@ -22,7 +22,6 @@ import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import jakarta.json.JsonArray; -import jakarta.json.JsonObject; import org.eclipse.edc.junit.annotations.ComponentTest; import org.eclipse.edc.junit.testfixtures.TestUtils; import org.junit.jupiter.api.Test; @@ -53,6 +52,7 @@ public class UseMockConnectorSampleTest { .withEnv("WEB_HTTP_MANAGEMENT_PORT", String.valueOf(MANAGEMENT_PORT)) .withEnv("WEB_HTTP_MANAGEMENT_PATH", "/api/management") .withExposedPorts(DEFAULT_PORT, MANAGEMENT_PORT) + .withLogConsumer(o -> System.out.println(o.getUtf8StringWithoutLineEnding())) .waitingFor(Wait.forLogMessage(".* ready.*", 1)); @Test @@ -115,39 +115,6 @@ void test_apiNotAuthenticated_expect400() { assertThat(errorObject.get("message").toString()).contains("This user is not authorized, This is just a second error message"); } - @Test - void test_getProtocolVersions() { - setupNextResponse("versions.request.json"); - var response = mgmtRequest() - .contentType(ContentType.JSON) - .body(""" - { - "@context": { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/" - }, - "@type": "QuerySpec", - "https://w3id.org/edc/v0.0.1/ns/counterPartyAddress": "http://provider-control-plane:8282/api/v1/dsp", - "https://w3id.org/edc/v0.0.1/ns/counterPartyId": "providerId", - "https://w3id.org/edc/v0.0.1/ns/protocol": "dataspace-protocol-http" - } - """) - .post("/v4alpha/protocol-versions/request") - .then() - .log().ifValidationFails() - .statusCode(200) - .extract() - .body() - .as(JsonObject.class); - - var protocolVersions = response.get("protocolVersions").asJsonArray(); - - assertThat(protocolVersions).hasSize(2); - assertThat(protocolVersions.getJsonObject(0).getJsonString("version").getString()).isEqualTo("2024/1"); - assertThat(protocolVersions.getJsonObject(0).getJsonString("path").getString()).isEqualTo("/2024/1"); - assertThat(protocolVersions.getJsonObject(1).getJsonString("version").getString()).isEqualTo("v0.8"); - assertThat(protocolVersions.getJsonObject(1).getJsonString("path").getString()).isEqualTo("/"); - } - private void setupNextResponse(String resourceFileName) { var json = TestUtils.getResourceFileContentAsString(resourceFileName); diff --git a/samples/testing-with-mocked-connector/src/test/resources/versions.request.json b/samples/testing-with-mocked-connector/src/test/resources/versions.request.json deleted file mode 100644 index 5bec1ceb38..0000000000 --- a/samples/testing-with-mocked-connector/src/test/resources/versions.request.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "Version API", - "description": "test description", - "input": { - "class": "org.eclipse.edc.connector.controlplane.protocolversion.spi.ProtocolVersionRequest", - "data": { - "offset": 0, - "sortOrder": "DESC" - } - }, - "output": { - "class": "org.eclipse.edc.protocol.spi.ProtocolVersions", - "data": { - "protocolVersions": [ - { - "version": "2024/1", - "path": "/2024/1" - }, - { - "version": "v0.8", - "path": "/" - } - ] - } - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 02510aab14..2fa6510fd9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -96,6 +96,7 @@ include(":edc-extensions:agreements-bpns:bpns-evaluation-core") include(":edc-extensions:agreements-bpns:bpns-evaluation-store-sql") include(":edc-extensions:agreements-bpns:bpns-evaluation-spi") include(":edc-extensions:token-interceptor") +include(":edc-extensions:single-participant-vault") // test modules include(":edc-tests:e2e-fixtures") From 2fefc444592587ade33beb2e041cdfeddebb22c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:22:01 +0100 Subject: [PATCH 030/112] chore(deps): bump actions/setup-java in /.github/actions/setup-java (#2455) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.0.0 to 5.1.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v5.0.0...v5.1.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 5.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/setup-java/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-java/action.yml b/.github/actions/setup-java/action.yml index 6220c98509..00ad411c72 100644 --- a/.github/actions/setup-java/action.yml +++ b/.github/actions/setup-java/action.yml @@ -26,7 +26,7 @@ runs: using: "composite" steps: - name: Setup JDK 21 - uses: actions/setup-java@v5.0.0 + uses: actions/setup-java@v5.1.0 with: java-version: '21' distribution: 'temurin' From 1853b4e16d3b6056287cf6c787d18f37e3db5c03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:30:43 +0100 Subject: [PATCH 031/112] chore(deps): bump trufflesecurity/trufflehog from 3.91.1 to 3.91.2 (#2454) Bumps [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) from 3.91.1 to 3.91.2. - [Release notes](https://github.com/trufflesecurity/trufflehog/releases) - [Commits](https://github.com/trufflesecurity/trufflehog/compare/aade3bff5594fe8808578dd4db3dfeae9bf2abdc...821e8b9e5cdf8dc484dd23e06f78941fcf6b9191) --- updated-dependencies: - dependency-name: trufflesecurity/trufflehog dependency-version: 3.91.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/secrets-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml index 5d2db3a398..890900bd5e 100644 --- a/.github/workflows/secrets-scan.yml +++ b/.github/workflows/secrets-scan.yml @@ -46,7 +46,7 @@ jobs: - name: TruffleHog OSS id: trufflehog - uses: trufflesecurity/trufflehog@aade3bff5594fe8808578dd4db3dfeae9bf2abdc + uses: trufflesecurity/trufflehog@821e8b9e5cdf8dc484dd23e06f78941fcf6b9191 continue-on-error: true with: path: ./ # Scan the entire repository From 5ecb74a3579d5a3785b30782f2f0bd0bf3342e49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:31:16 +0100 Subject: [PATCH 032/112] chore(deps): bump checkmarx/kics-github-action from 2.1.16 to 2.1.17 (#2453) Bumps [checkmarx/kics-github-action](https://github.com/checkmarx/kics-github-action) from 2.1.16 to 2.1.17. - [Release notes](https://github.com/checkmarx/kics-github-action/releases) - [Commits](https://github.com/checkmarx/kics-github-action/compare/v2.1.16...v2.1.17) --- updated-dependencies: - dependency-name: checkmarx/kics-github-action dependency-version: 2.1.17 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/kics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/kics.yml b/.github/workflows/kics.yml index acaf45bc5b..66c01de2bc 100644 --- a/.github/workflows/kics.yml +++ b/.github/workflows/kics.yml @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v6 - name: KICS scan - uses: checkmarx/kics-github-action@v2.1.16 + uses: checkmarx/kics-github-action@v2.1.17 with: path: "." fail_on: high From 204bfeb74b00ab8548aeb2e904b8e3f229134d8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:31:45 +0100 Subject: [PATCH 033/112] chore(deps): bump io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations (#2452) Bumps [io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations](https://github.com/open-telemetry/opentelemetry-java-instrumentation) from 2.21.0 to 2.22.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java-instrumentation/compare/v2.21.0...v2.22.0) --- updated-dependencies: - dependency-name: io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations dependency-version: 2.22.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7d98299c8f..8a8f533cc6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ junit = "6.0.1" nimbus = "10.6" okhttp = "5.3.2" opentelemetry = "2.21.0" -opentelemetry-instrumentation = "2.21.0" +opentelemetry-instrumentation = "2.22.0" opentelemetry-log4j-appender = "2.22.0-alpha" postgres = "42.7.8" restAssured = "5.5.6" From b5bbe6bbf5cbe8369ce4c57e967e0c8af7655c89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:32:29 +0100 Subject: [PATCH 034/112] chore(deps): bump io.swagger.core.v3.swagger-gradle-plugin (#2451) Bumps io.swagger.core.v3.swagger-gradle-plugin from 2.2.26 to 2.2.41. --- updated-dependencies: - dependency-name: io.swagger.core.v3.swagger-gradle-plugin dependency-version: 2.2.41 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a8f533cc6..46da662415 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -241,5 +241,5 @@ edc-sts = [ [plugins] docker = { id = "com.bmuschko.docker-remote-api", version = "10.0.0" } shadow = { id = "com.gradleup.shadow", version = "9.2.2" } -swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.26" } +swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.41" } edc-build = { id = "org.eclipse.edc.edc-build", version.ref = "edc-build" } From 28860b1a52fdc133b83906da2b27cc58f4d2126b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:32:59 +0100 Subject: [PATCH 035/112] chore(deps): bump aws from 2.39.2 to 2.40.3 (#2450) Bumps `aws` from 2.39.2 to 2.40.3. Updates `software.amazon.awssdk:s3` from 2.39.2 to 2.40.3 Updates `software.amazon.awssdk:s3-transfer-manager` from 2.39.2 to 2.40.3 --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.40.3 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: software.amazon.awssdk:s3-transfer-manager dependency-version: 2.40.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 46da662415..6029fbe78e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ edc = "0.15.0" edc-build = "1.1.4" allure = "2.31.0" awaitility = "4.3.0" -aws = "2.39.2" +aws = "2.40.3" azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.82" dcp-tck = "1.0.0-SNAPSHOT" From 72ad569d894242f485bd1889651dcb0cc7a09e23 Mon Sep 17 00:00:00 2001 From: Felix Gerbig <48456355+gerbigf@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:35:27 +0100 Subject: [PATCH 036/112] [Fix] Update Contract Request Message with missing ODRL Context (#2446) * [Fix] Update Contract Request Message with missing ODRL Context * Update 05_contractnegotiations.md --- .../05_contractnegotiations.md | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/docs/usage/management-api-walkthrough/05_contractnegotiations.md b/docs/usage/management-api-walkthrough/05_contractnegotiations.md index 1f01fb4442..98cfbe5611 100644 --- a/docs/usage/management-api-walkthrough/05_contractnegotiations.md +++ b/docs/usage/management-api-walkthrough/05_contractnegotiations.md @@ -34,18 +34,9 @@ Content-Type: application/json "@id": "{{OFFER_ID}}", "target": "{{ASSET_ID}}", "assigner": "{{PROVIDER_IDENTIFIER}}", - "permission": { - "action": "use", - "constraint": { - "leftOperand": "FrameworkAgreement", - "operand": "eq", - "rightOperand": "DataExchangeGovernance:1.0" - }, - "prohibition": [], - "obligation": [] - }, - "prohibition": [], - "obligation": [] + "permission": {{OFFER_ODRL_PERMISSION}}, + "prohibition": {{OFFER_ODRL_PROHIBITION}}, + "obligation": {{OFFER_ODRL_OBLIGATION}} }, "callbackAddresses": [ { From a8619284bf48d20f66eba04ddf95a12b197f4583 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:48:01 +0100 Subject: [PATCH 037/112] chore(deps): bump io.opentelemetry.javaagent:opentelemetry-javaagent (#2449) Bumps [io.opentelemetry.javaagent:opentelemetry-javaagent](https://github.com/open-telemetry/opentelemetry-java-instrumentation) from 2.21.0 to 2.22.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java-instrumentation/compare/v2.21.0...v2.22.0) --- updated-dependencies: - dependency-name: io.opentelemetry.javaagent:opentelemetry-javaagent dependency-version: 2.22.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6029fbe78e..0ce67b49df 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ jakarta-json = "2.0.1" junit = "6.0.1" nimbus = "10.6" okhttp = "5.3.2" -opentelemetry = "2.21.0" +opentelemetry = "2.22.0" opentelemetry-instrumentation = "2.22.0" opentelemetry-log4j-appender = "2.22.0-alpha" postgres = "42.7.8" From bfffb9991fe5a7432116199edfcebaf64d6cfd29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 10:48:36 +0100 Subject: [PATCH 038/112] chore(deps): bump com.gradleup.shadow from 9.2.2 to 9.3.0 (#2448) Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 9.2.2 to 9.3.0. - [Release notes](https://github.com/GradleUp/shadow/releases) - [Commits](https://github.com/GradleUp/shadow/compare/9.2.2...9.3.0) --- updated-dependencies: - dependency-name: com.gradleup.shadow dependency-version: 9.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ce67b49df..8e742ff6de 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -240,6 +240,6 @@ edc-sts = [ [plugins] docker = { id = "com.bmuschko.docker-remote-api", version = "10.0.0" } -shadow = { id = "com.gradleup.shadow", version = "9.2.2" } +shadow = { id = "com.gradleup.shadow", version = "9.3.0" } swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.41" } edc-build = { id = "org.eclipse.edc.edc-build", version.ref = "edc-build" } From ff8b4790dfc300d7751b0602694098f3d8681b4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arno=20Wei=C3=9F?= <86715435+arnoweiss@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:46:53 +0100 Subject: [PATCH 039/112] docs: add did registration DR (#2433) * docs: add did registration DR * fix: incorporate review comments, add flowchart * fix: separate logic and did service client implementation --- .../README.md | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 docs/development/decision-records/2025-11-27-did-service-registration/README.md diff --git a/docs/development/decision-records/2025-11-27-did-service-registration/README.md b/docs/development/decision-records/2025-11-27-did-service-registration/README.md new file mode 100644 index 0000000000..1ed9a9cf23 --- /dev/null +++ b/docs/development/decision-records/2025-11-27-did-service-registration/README.md @@ -0,0 +1,56 @@ +# DID Service Registration + +## Decision + +The controlplane will be enabled to register itself as `DataService` with the participant's did document. There will be +configuration variables to enable the feature, set an id for the DSP endpoint and point to the DID service's write-APIs. +There will not be an additional endpoint on the Management API - this logic is purely internal. + +## Rationale + +Standard CX-0001 describes the predominant method for discovering DSP endpoints as of CX release "Jupiter". It is a +centralized service that is assumed to be a singleton. +Since the "Saturn" release, [CX-0018 section 2.6](https://catenax-ev.github.io/docs/next/standards/CX-0018-DataspaceConnectivity#26-participant-agent-management) +mandates that DID documents are used for discovery of DSP-endpoints based on DIDs. How that can be achieved is +described in [DSP section 4](https://eclipse-dataspace-protocol-base.github.io/DataspaceProtocol/2025-1-err1/#discovery-of-service-endpoints) + +Managing these `service` entries for DSP endpoints can become a chore: hosts may change, deployments may be +deprovisioned. That's why there should be a solution that is extensible for each wallet implementation and smart enough +to avoid creating duplicate `service` entries and manage itself. + +## Approach + +1. Introduce configuration options in application and helm chart. +2. Create a new SPI including an interface that represents the feature in an abstract manner. +3. An extension that implements the SPI's interface as client for [SAP DIV's write endpoint to the did document](https://api.sap.com/api/DIV/path/CompanyIdentityV2HttpController_updateCompanyIdentity_v2.0.0). +4. Another extension that will implement the lifecycle management logic. + +The lifecycle management logic shall look like: + +```mermaid +flowchart TD + A[Connector
starts up] -->|id| B{id already
exists?} + B --> |true| C{same url?} + B --> |false| F[add to did doc] + C -->|true| D[do nothing] + C -->|false| E[update existing entry with URL] + D -->|shutdown| G[remove did doc entry] + E -->|shutdown| G[remove did doc entry] + F -->|shutdown| G[remove did doc entry] +``` + +The SPI will look something like + +```java + +public interface DidServiceClient { + + void createService(String id, String urlOfWellKnown); + + void updateService(String id, String urlOfWellKnown); + + void deleteService(String id); + +} + +``` \ No newline at end of file From 04ca50e6c58018c852d9a320091ac1a9da7406de Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Mon, 8 Dec 2025 17:25:54 +0100 Subject: [PATCH 040/112] Adapt codeowners file Signed-off-by: Lars Geyer-Blaumeiser --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ed24e050fe..53dac483bb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # Identify the committers responsible for the repository as standard reviewers -* @rafaelmag110 @lgblaumeiser @bmg13 @AndrYurk +* @lgblaumeiser @bmg13 @AndrYurk From fce811f91629a5a4b32e5995bc953edf9f9d2b7f Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Wed, 10 Dec 2025 17:44:17 +0100 Subject: [PATCH 041/112] Fix issue with wrong policy left operands (#2458) * Adapt policy tests to also test jsonld expansion and refactor test Signed-off-by: Lars Geyer-Blaumeiser * Fix warranty left operand policy issue Signed-off-by: Lars Geyer-Blaumeiser * Fix wrongly allowed BusinessPartnerNumber use left operand Signed-off-by: Lars Geyer-Blaumeiser * Fix checkstyle issues Signed-off-by: Lars Geyer-Blaumeiser * Fix remaining refactoring issue Signed-off-by: Lars Geyer-Blaumeiser --------- Signed-off-by: Lars Geyer-Blaumeiser --- .../resources/document/cx-policy-v1.jsonld | 7 +- .../validator/PolicyValidationConstants.java | 5 +- .../tests/helpers/PolicyHelperFunctions.java | 92 ++------ .../edc/tests/catalog/CatalogTest.java | 2 +- .../policy/PolicyDefinitionEndToEndTest.java | 204 ++++++++++++------ 5 files changed, 168 insertions(+), 142 deletions(-) diff --git a/core/json-ld-core/src/main/resources/document/cx-policy-v1.jsonld b/core/json-ld-core/src/main/resources/document/cx-policy-v1.jsonld index 5d62c39044..fb91929128 100644 --- a/core/json-ld-core/src/main/resources/document/cx-policy-v1.jsonld +++ b/core/json-ld-core/src/main/resources/document/cx-policy-v1.jsonld @@ -83,8 +83,11 @@ "Warranty": { "@id": "cx-policy:Warranty" }, - "WarrantyDuration": { - "@id": "cx-policy:WarrantyDuration" + "WarrantyDurationMonths": { + "@id": "cx-policy:WarrantyDurationMonths" + }, + "WarrantyDefinition": { + "@id": "cx-policy:WarrantyDefinition" } } } diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/PolicyValidationConstants.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/PolicyValidationConstants.java index 070fea5182..09cb14b3cb 100644 --- a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/PolicyValidationConstants.java +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/PolicyValidationConstants.java @@ -113,10 +113,7 @@ private PolicyValidationConstants() { PRECEDENCE_LITERAL, DATA_USAGE_END_DURATION_LITERAL, DATA_USAGE_END_DATE_LITERAL, - DATA_USAGE_END_DEFINITION_LITERAL, - INFORCE_POLICY_LITERAL, - BUSINESS_PARTNER_GROUP, - BUSINESS_PARTNER_NUMBER + DATA_USAGE_END_DEFINITION_LITERAL ); public static final Set USAGE_PROHIBITION_POLICY_ALLOWED_LEFT_OPERANDS = Set.of( AFFILIATES_REGION_LITERAL, diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/PolicyHelperFunctions.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/PolicyHelperFunctions.java index c3614dc281..2219ffdb07 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/PolicyHelperFunctions.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/helpers/PolicyHelperFunctions.java @@ -31,10 +31,9 @@ import org.eclipse.edc.policy.model.AtomicConstraint; import org.eclipse.edc.policy.model.Operator; -import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -49,14 +48,12 @@ import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_2025_09_NS; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_NS; +import static org.eclipse.tractusx.edc.jsonld.JsonLdExtension.CX_ODRL_CONTEXT; public class PolicyHelperFunctions { - private static final String ODRL_JSONLD = "https://w3id.org/catenax/2025/9/policy/odrl.jsonld"; private static final String BUSINESS_PARTNER_EVALUATION_KEY = "BusinessPartnerNumber"; - public static final String BUSINESS_PARTNER_LEGACY_EVALUATION_KEY = CX_POLICY_NS + BUSINESS_PARTNER_EVALUATION_KEY; - private static final String BUSINESS_PARTNER_CONSTRAINT_KEY = CX_POLICY_2025_09_NS + "BusinessPartnerGroup"; public static final String FRAMEWORK_AGREEMENT_LITERAL = CX_POLICY_2025_09_NS + "FrameworkAgreement"; @@ -85,52 +82,31 @@ public static JsonObject frameworkPolicy(String id, Map permissi public static JsonObject frameworkPolicy(Map permissions, String action) { return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) + .add(CONTEXT, CX_ODRL_CONTEXT) .add(TYPE, "Set") .add("permission", Json.createArrayBuilder() - .add(frameworkPermission(permissions, action))) + .add(frameworkConstraint(new HashMap<>(permissions), action, Operator.EQ, false))) .build(); } public static JsonObject frameworkPolicy(Map permissions, String action, String operator) { - return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) - .add(TYPE, "Set") - .add("permission", Json.createArrayBuilder() - .add(frameworkPermission(permissions, action, operator))) - .build(); + return frameworkPolicy(permissions, action, Operator.valueOf(operator)); } - - public static JsonObject emptyPolicy() { + public static JsonObject frameworkPolicy(Map permissions, String action, Operator operator) { return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) + .add(CONTEXT, CX_ODRL_CONTEXT) .add(TYPE, "Set") + .add("permission", Json.createArrayBuilder() + .add(frameworkConstraint(new HashMap<>(permissions), action, operator, false))) .build(); } - public static JsonObject policyWithEmptyRule(String action) { - var rule = Json.createObjectBuilder() - .add("action", action) - .build(); - var rulesArrayBuilder = Json.createArrayBuilder(); - rulesArrayBuilder.add(rule); - return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) - .add(TYPE, "Set") - .add("permission", rulesArrayBuilder) - .build(); - } - public static JsonObject policyFromRules(String ruleType, JsonObject... rules) { - var rulesArrayBuilder = Json.createArrayBuilder(); - for (JsonObject rule : rules) { - rulesArrayBuilder.add(rule); - } + public static JsonObject emptyPolicy() { return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) + .add(CONTEXT, CX_ODRL_CONTEXT) .add(TYPE, "Set") - .add(ruleType, rulesArrayBuilder) .build(); } @@ -161,7 +137,7 @@ public static JsonObject frameworkPolicy(String leftOperand, Operator operator, .build(); return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) + .add(CONTEXT, CX_ODRL_CONTEXT) .add(TYPE, "Set") .add("permission", Json.createArrayBuilder().add(permission)) .build(); @@ -202,7 +178,7 @@ public static JsonObject legacyFrameworkPolicy() { .build(); return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) + .add(CONTEXT, CX_ODRL_CONTEXT) .add(TYPE, "Set") .add("permission", Json.createArrayBuilder().add(permission)) .build(); @@ -225,19 +201,6 @@ public static JsonObject inForceDateUsagePolicy(String operatorStart, Object sta .build())); } - /** - * Creates a {@link PolicyDefinition} using the given ID, that contains equality constraints for each of the given BusinessPartnerNumbers: - * each BPN is converted into an {@link AtomicConstraint} {@code BusinessPartnerNumber EQ [BPN]}. - */ - public static JsonObject frameworkTemplatePolicy(String id, String frameworkKind) { - var template = fetchFrameworkTemplate().replace("${POLICY_ID}", id).replace("${FRAMEWORK_CREDENTIAL}", frameworkKind); - try { - return MAPPER.readValue(template, JsonObject.class); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - public static JsonObjectBuilder policyDefinitionBuilder() { return Json.createObjectBuilder() .add(TYPE, EDC_NAMESPACE + "PolicyDefinitionDto"); @@ -250,7 +213,7 @@ public static JsonObjectBuilder policyDefinitionBuilder(JsonObject policy) { public static JsonObject bpnPolicy(String... bpns) { return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) + .add(CONTEXT, CX_ODRL_CONTEXT) .add(TYPE, "Set") .add("permission", Json.createArrayBuilder() .add(permission(bpns))) @@ -276,7 +239,7 @@ public static JsonObject bpnPolicy(Operator operator, String... bpns) { .build()) .build(); return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) + .add(CONTEXT, CX_ODRL_CONTEXT) .add(TYPE, "Set") .add("permission", Json.createArrayBuilder() .add(permission)) @@ -296,21 +259,12 @@ private static JsonObject bpnGroupPolicy(String operator, boolean rightOperandAs .build(); return Json.createObjectBuilder() - .add(CONTEXT, ODRL_JSONLD) + .add(CONTEXT, CX_ODRL_CONTEXT) .add(TYPE, "Set") .add("permission", permission) .build(); } - private static String fetchFrameworkTemplate() { - try (var stream = PolicyHelperFunctions.class.getClassLoader().getResourceAsStream("framework-policy.json")) { - return new String(stream.readAllBytes(), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - private static JsonObject permission(String... bpns) { var bpnConstraints = Stream.of(bpns) @@ -326,20 +280,16 @@ private static JsonObject permission(String... bpns) { .build(); } - public static JsonObject frameworkPermission(Map permissions, String action) { - return frameworkPermission(permissions, action, "eq"); - } - - public static JsonObject frameworkPermission(Map permissions, String action, String operator) { - var constraints = permissions.entrySet().stream() - .map(permission -> atomicConstraint(permission.getKey(), operator, permission.getValue(), false)) + public static JsonObject frameworkConstraint(Map operandMappings, String action, Operator operator, boolean createRightOperandsAsArray) { + var constraints = operandMappings.entrySet().stream() + .map(constraint -> atomicConstraint(constraint.getKey(), operator.getOdrlRepresentation(), constraint.getValue(), createRightOperandsAsArray)) .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add); if (action.contains("use")) { - if (!permissions.containsKey(FRAMEWORK_AGREEMENT_LITERAL)) { + if (!operandMappings.containsKey(FRAMEWORK_AGREEMENT_LITERAL)) { constraints.add(frameworkAgreementConstraint()); } - if (!permissions.containsKey(USAGE_PURPOSE_LITERAL)) { + if (!operandMappings.containsKey(USAGE_PURPOSE_LITERAL)) { constraints.add(usagePurposeConstraint()); } } diff --git a/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/CatalogTest.java b/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/CatalogTest.java index 36718cacb0..f04c0b5a8c 100644 --- a/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/CatalogTest.java +++ b/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/CatalogTest.java @@ -153,7 +153,7 @@ void requestCatalog_filteredByBpnLegacy_WithNamespace_shouldReject() { var onlyDiogenesPolicy = frameworkPolicy( Map.of(CX_POLICY_2025_09_NS + "BusinessPartnerNumber", "BPNLAAAAAAAAAAAB"), CX_POLICY_2025_09_NS + "access", - "isAnyOf"); + Operator.IS_ANY_OF); var onlyConsumerId = PROVIDER.createPolicyDefinition(onlyConsumerPolicy); var onlyDiogenesId = PROVIDER.createPolicyDefinition(onlyDiogenesPolicy); diff --git a/edc-tests/e2e/policy-tests/src/test/java/org/eclipse/tractusx/edc/tests/policy/PolicyDefinitionEndToEndTest.java b/edc-tests/e2e/policy-tests/src/test/java/org/eclipse/tractusx/edc/tests/policy/PolicyDefinitionEndToEndTest.java index 1f0ddf2d04..6e52c9aa27 100644 --- a/edc-tests/e2e/policy-tests/src/test/java/org/eclipse/tractusx/edc/tests/policy/PolicyDefinitionEndToEndTest.java +++ b/edc-tests/e2e/policy-tests/src/test/java/org/eclipse/tractusx/edc/tests/policy/PolicyDefinitionEndToEndTest.java @@ -1,6 +1,7 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * Copyright (c) 2025 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * Copyright (c) 2025 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -20,6 +21,9 @@ package org.eclipse.tractusx.edc.tests.policy; +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import jakarta.json.Json; import jakarta.json.JsonObject; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.RuntimeExtension; @@ -39,8 +43,11 @@ import java.util.Map; import java.util.stream.Stream; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_2025_09_NS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.tractusx.edc.jsonld.JsonLdExtension.CX_ODRL_CONTEXT; +import static org.eclipse.tractusx.edc.jsonld.JsonLdExtension.CX_POLICY_2025_09_CONTEXT; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_BPN; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_DID; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; @@ -48,16 +55,12 @@ import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_DID; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.PROVIDER_NAME; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.emptyPolicy; -import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkPermission; -import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkPolicy; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkConstraint; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.inForceDateUsagePolicy; -import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.policyFromRules; -import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.policyWithEmptyRule; import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; @EndToEndTest public class PolicyDefinitionEndToEndTest { - private static final TransferParticipant CONSUMER = TransferParticipant.Builder.newInstance() .name(CONSUMER_NAME) .id(CONSUMER_DID) @@ -88,113 +91,186 @@ void shouldAcceptValidPolicyDefinitions(JsonObject policy, String description) { PROVIDER.createPolicyDefinition(policy); } - @DisplayName("Policy is accepted") + @DisplayName("Policy is not accepted due to missing context") @ParameterizedTest(name = "{1}") @ArgumentsSource(InValidNamespaceContractPolicyProvider.class) void shouldNotAcceptInvalidNamespacePolicyDefinitions(JsonObject policy, String description) { - assertThatThrownBy(() -> PROVIDER.createPolicyDefinition(policy)); + checkForValidationFailure(policy); } - @DisplayName("Policy is not accepted") + @DisplayName("Policy is not accepted because definition is not correct") @ParameterizedTest(name = "{1}") @ArgumentsSource(InValidContractPolicyProvider.class) void shouldNotAcceptInvalidPolicyDefinitions(JsonObject policy, String description) { - assertThatThrownBy(() -> PROVIDER.createPolicyDefinition(policy)); + checkForValidationFailure(policy); + } + + private void checkForValidationFailure(JsonObject policy) { + var response = createPolicyDefinition(policy); + assertThat(response.statusCode()).isEqualTo(400); + assertThat(response.body().jsonPath().getString("[0].type")).isEqualTo("ValidationFailure"); } - private abstract static class BaseContractPolicyProvider implements ArgumentsProvider { + private abstract static class BasePolicyProvider implements ArgumentsProvider { protected final String namespace; - private BaseContractPolicyProvider(String namespace) { + private BasePolicyProvider(String namespace) { this.namespace = namespace; } @Override public Stream provideArguments(ExtensionContext extensionContext) { return Stream.of( - Arguments.of(frameworkPolicy(Map.of(namespace + "Membership", "active"), CX_POLICY_2025_09_NS + "access"), "MembershipCredential"), - Arguments.of(frameworkPolicy(Map.of(namespace + "FrameworkAgreement", "DataExchangeGovernance:1.0"), "use"), "DataExchangeGovernance use case"), - Arguments.of(frameworkPolicy(namespace + "AffiliatesRegion", Operator.IS_ANY_OF, List.of("cx.region.all:1", "cx.region.europe:1", "cx.region.northAmerica:1"), "use", true), "Affiliates Region"), - Arguments.of(frameworkPolicy(namespace + "AffiliatesRegion", Operator.IS_ANY_OF, List.of("cx.region.europe:1"), "use", true), "Affiliates Region (IS_ANY_OF, one element)"), - Arguments.of(frameworkPolicy(namespace + "AffiliatesBpnl", Operator.IS_ANY_OF, "BPNL00000000001A", "use", true), "Affiliates BPNL"), - Arguments.of(frameworkPolicy(namespace + "DataFrequency", Operator.EQ, "cx.dataFrequency.once:1", "use"), "Data Frequency"), - Arguments.of(frameworkPolicy(namespace + "DataUsageEndDate", Operator.EQ, "2025-06-30T14:30:00Z", "use"), "Data Usage End Date"), - Arguments.of(frameworkPolicy(namespace + "DataUsageEndDefinition", Operator.EQ, "cx.dataUsageEnd.unlimited:1", "use"), "Data Usage End Date Definition"), - Arguments.of(frameworkPolicy(namespace + "DataUsageEndDurationDays", Operator.EQ, 3, "use"), "Data Usage End Duration Days"), - Arguments.of(frameworkPolicy(namespace + "JurisdictionLocation", Operator.EQ, "test location", "use"), "Jurisdiction Location"), - Arguments.of(frameworkPolicy(namespace + "JurisdictionLocationReference", Operator.EQ, "cx.location.dataConsumer:1", "use"), "Jurisdiction Location Reference"), - Arguments.of(frameworkPolicy(namespace + "Liability", Operator.EQ, "cx.grossNegligence:1", "use"), "Liability"), - Arguments.of(frameworkPolicy(namespace + "Liability", Operator.EQ, "cx.slightNegligence:1", "use"), "Liability"), - Arguments.of(frameworkPolicy(namespace + "Precedence", Operator.EQ, "cx.precedence.contractReference:1", "use"), "Precedence"), - Arguments.of(frameworkPolicy(namespace + "UsagePurpose", Operator.IS_ANY_OF, List.of("cx.core.legalRequirementForThirdparty:1", "cx.core.industrycore:1"), "use", true), "Usage Purpose"), - Arguments.of(frameworkPolicy(namespace + "VersionChanges", Operator.EQ, "cx.versionChanges.minor:1", "use"), "Version Changes"), - Arguments.of(frameworkPolicy(namespace + "Warranty", Operator.EQ, "cx.warranty.none:1", "use"), "Warranty"), - Arguments.of(frameworkPolicy(namespace + "WarrantyDefinition", Operator.EQ, "cx.warranty.contractEndDate:1", "use"), "Warranty Definition"), - Arguments.of(frameworkPolicy(namespace + "WarrantyDurationMonths", Operator.EQ, 3, "use"), "Warranty Duration Months"), - Arguments.of(frameworkPolicy(namespace + "ExclusiveUsage", Operator.EQ, "cx.exclusiveUsage.dataConsumer:1", "use"), "Exclusive Usage"), - Arguments.of(frameworkPolicy(namespace + "ContractReference", Operator.IS_ALL_OF, "contractReference", "use"), "Contract reference"), - Arguments.of(frameworkPolicy(namespace + "ContractTermination", Operator.EQ, "cx.data.deletion:1", "use"), "ContractTermination"), - Arguments.of(frameworkPolicy(namespace + "ConfidentialInformationMeasures", Operator.EQ, "cx.confidentiality.measures:1", "use"), "Confidential Information Measures"), - Arguments.of(frameworkPolicy(namespace + "ConfidentialInformationSharing", Operator.IS_ANY_OF, List.of("cx.sharing.affiliates:1"), "use", true), "Confidential Information Sharing"), - Arguments.of(frameworkPolicy(namespace + "BusinessPartnerGroup", Operator.IS_ANY_OF, "Some-group", "use", true), "Business Partner Group"), - Arguments.of(frameworkPolicy(namespace + "BusinessPartnerNumber", Operator.IS_ANY_OF, List.of("BPNL00000000001A"), "use", true), "Business Partner Number") + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("Membership", "active"), "use", Operator.EQ, false)), "MembershipCredential"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("FrameworkAgreement", "DataExchangeGovernance:1.0"), "use", Operator.EQ, false)), "DataExchangeGovernance use case"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("AffiliatesRegion", List.of("cx.region.all:1", "cx.region.europe:1", "cx.region.northAmerica:1")), "use", Operator.IS_ANY_OF, true)), "Affiliates Region"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("AffiliatesRegion", List.of("cx.region.europe:1")), "use", Operator.IS_ANY_OF, true)), "Affiliates Region (IS_ANY_OF, one element)"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("AffiliatesBpnl", "BPNL00000000001A"), "use", Operator.IS_ANY_OF, false)), "Affiliates BPNL"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("DataFrequency", "cx.dataFrequency.once:1"), "use", Operator.EQ, false)), "Data Frequency"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("DataUsageEndDate", "2025-06-30T14:30:00Z"), "use", Operator.EQ, false)), "Data Usage End Date"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("DataUsageEndDefinition", "cx.dataUsageEnd.unlimited:1"), "use", Operator.EQ, false)), "Data Usage End Date Definition"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("DataUsageEndDurationDays", 3), "use", Operator.EQ, false)), "Data Usage End Duration Days"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("JurisdictionLocation", "test location"), "use", Operator.EQ, false)), "Jurisdiction Location"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("JurisdictionLocationReference", "cx.location.dataConsumer:1"), "use", Operator.EQ, false)), "Jurisdiction Location Reference"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("Liability", "cx.grossNegligence:1"), "use", Operator.EQ, false)), "Liability"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("Liability", "cx.slightNegligence:1"), "use", Operator.EQ, false)), "Liability"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("Precedence", "cx.precedence.contractReference:1"), "use", Operator.EQ, false)), "Precedence"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("UsagePurpose", List.of("cx.core.legalRequirementForThirdparty:1", "cx.core.industrycore:1")), "use", Operator.IS_ANY_OF, true)), "Usage Purpose"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("VersionChanges", "cx.versionChanges.minor:1"), "use", Operator.EQ, false)), "Version Changes"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("Warranty", "cx.warranty.none:1"), "use", Operator.EQ, false)), "Warranty"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("WarrantyDefinition", "cx.warranty.contractEndDate:1"), "use", Operator.EQ, false)), "Warranty Definition"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("WarrantyDurationMonths", 3), "use", Operator.EQ, false)), "Warranty Duration Months"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("ExclusiveUsage", "cx.exclusiveUsage.dataConsumer:1"), "use", Operator.EQ, false)), "Exclusive Usage"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("ContractReference", List.of("contractReference")), "use", Operator.IS_ALL_OF, true)), "Contract reference"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("ContractTermination", "cx.data.deletion:1"), "use", Operator.EQ, false)), "ContractTermination"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("ConfidentialInformationMeasures", "cx.confidentiality.measures:1"), "use", Operator.EQ, false)), "Confidential Information Measures"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("ConfidentialInformationSharing", List.of("cx.sharing.affiliates:1")), "use", Operator.IS_ANY_OF, true)), "Confidential Information Sharing"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("BusinessPartnerGroup", List.of("Some-group")), "access", Operator.IS_ANY_OF, true)), "Business Partner Group"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("BusinessPartnerNumber", List.of("BPNL00000000001A")), "access", Operator.IS_ANY_OF, true)), "Business Partner Number") ); } } - private static class ValidContractPolicyProvider extends BaseContractPolicyProvider { + private static class ValidContractPolicyProvider extends BasePolicyProvider { private ValidContractPolicyProvider() { - super(CX_POLICY_2025_09_NS); + super(CX_POLICY_2025_09_CONTEXT); } @Override public Stream provideArguments(ExtensionContext extensionContext) { return Stream.concat(super.provideArguments(extensionContext), Stream.of( Arguments.of(emptyPolicy(), "Empty Policy"), - Arguments.of(policyWithEmptyRule(this.namespace + "access"), "Access policy with empty permission"), + Arguments.of(policyWithEmptyRule("access", this.namespace), "Access policy with empty permission"), Arguments.of(inForceDateUsagePolicy("gteq", "contractAgreement+0s", "lteq", "contractAgreement+10s"), "In force date policy") )); } } - private static class InValidNamespaceContractPolicyProvider extends BaseContractPolicyProvider { + private static class InValidNamespaceContractPolicyProvider extends BasePolicyProvider { private InValidNamespaceContractPolicyProvider() { super(""); } } - private static class InValidContractPolicyProvider extends BaseContractPolicyProvider { + private static class InValidContractPolicyProvider extends BasePolicyProvider { private InValidContractPolicyProvider() { - super(CX_POLICY_2025_09_NS); + super(CX_POLICY_2025_09_CONTEXT); } @Override public Stream provideArguments(ExtensionContext extensionContext) { return Stream.of( - Arguments.of(policyWithEmptyRule("use"), "Usage policy with empty permission"), - Arguments.of(policyFromRules("permission", - frameworkPermission(Map.of(namespace + "Membership", "active"), namespace + "access"), - frameworkPermission(Map.of(namespace + "UsagePurpose", "cx.core.industrycore:1"), "use")), "Policy with different actions types"), - Arguments.of(policyFromRules("permission", - frameworkPermission(Map.of(namespace + "Membership", "active"), "unknown-action")), "Policy with unknown actions types"), - Arguments.of(policyFromRules("prohibition", - frameworkPermission(Map.of(namespace + "Membership", "active"), namespace + "access")), "Access Policy with prohibition rule"), - Arguments.of(policyFromRules("permission", - frameworkPermission(Map.of(namespace + "UsagePurpose", "cx.core.industrycore:1"), namespace + "access")), "Access policy permission with not allowed constraints"), - Arguments.of(policyFromRules("permission", - frameworkPermission(Map.of(namespace + "BusinessPartnerNumber", "BPN0022232"), "use")), "Usage policy permission with not allowed constraints"), - Arguments.of(policyFromRules("prohibition", - frameworkPermission(Map.of(namespace + "AffiliatesRegion", "cx.region.europe:1"), "use")), "Usage policy prohibition with not allowed constraints"), - Arguments.of(policyFromRules("obligation", - frameworkPermission(Map.of(namespace + "UsagePurpose", "cx.core.industrycore:1"), "use")), "Usage policy obligation with not allowed constraints"), - Arguments.of(policyFromRules("permission", - frameworkPolicy(namespace + "WarrantyDurationMonths", Operator.EQ, 3, "use"), - frameworkPolicy(namespace + "WarrantyDefinition", Operator.EQ, "cx.warranty.contractEndDate:1", "use")), "Policy with mutually exclusive constraints") + Arguments.of(policyWithEmptyRule("use", this.namespace), "Usage policy with empty permission"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("Membership", "active"), "access", Operator.EQ, false), + frameworkConstraint(Map.of("UsagePurpose", List.of("cx.core.industrycore:1")), "use", Operator.IS_ANY_OF, true)), "Policy with different actions types"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("Membership", "active"), "unknown-action", Operator.EQ, false)), "Policy with unknown actions types"), + Arguments.of(policyFromRules("prohibition", namespace, + frameworkConstraint(Map.of("Membership", "active"), "access", Operator.EQ, false)), "Access Policy with prohibition rule"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("UsagePurpose", "cx.core.industrycore:1"), "access", Operator.EQ, false)), "Access policy permission with not allowed constraints"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("BusinessPartnerNumber", "BPN0022232"), "use", Operator.EQ, false)), "Usage policy permission with not allowed constraints"), + Arguments.of(policyFromRules("prohibition", namespace, + frameworkConstraint(Map.of("AffiliatesRegion", "cx.region.europe:1"), "use", Operator.EQ, false)), "Usage policy prohibition with not allowed constraints"), + Arguments.of(policyFromRules("obligation", namespace, + frameworkConstraint(Map.of("UsagePurpose", "cx.core.industrycore:1"), "use", Operator.EQ, false)), "Usage policy obligation with not allowed constraints"), + Arguments.of(policyFromRules("permission", namespace, + frameworkConstraint(Map.of("WarrantyDurationMonths", 3), "use", Operator.EQ, false), + frameworkConstraint(Map.of("WarrantyDefinition", "cx.warranty.contractEndDate:1"), "use", Operator.EQ, false)), "Policy with mutually exclusive constraints") ); } } + + private Response createPolicyDefinition(JsonObject policy) { + JsonObject requestBody = Json.createObjectBuilder().add("@context", + Json.createObjectBuilder().add("@vocab", "https://w3id.org/edc/v0.0.1/ns/")).add("@type", "PolicyDefinition").add("policy", policy).build(); + return (Response) PROVIDER.baseManagementRequest().contentType(ContentType.JSON).body(requestBody).when().post("/v3/policydefinitions", new Object[0]).then().extract(); + } + + private static JsonObject policyFromRules(String ruleType, String policyDefinition, JsonObject... rules) { + var rulesArrayBuilder = Json.createArrayBuilder(); + for (JsonObject rule : rules) { + rulesArrayBuilder.add(rule); + } + var contextArrayBuilder = Json.createArrayBuilder(); + contextArrayBuilder.add(CX_ODRL_CONTEXT); + if (!policyDefinition.isBlank()) { + contextArrayBuilder.add(policyDefinition); + } + + return Json.createObjectBuilder() + .add(CONTEXT, contextArrayBuilder) + .add(TYPE, "Set") + .add(ruleType, rulesArrayBuilder) + .build(); + } + + private static JsonObject policyWithEmptyRule(String action, String policyContext) { + var rule = Json.createObjectBuilder() + .add("action", action) + .build(); + var rulesArrayBuilder = Json.createArrayBuilder(); + rulesArrayBuilder.add(rule); + var contextArrayBuilder = Json.createArrayBuilder(); + contextArrayBuilder.add(CX_ODRL_CONTEXT); + contextArrayBuilder.add(policyContext); + + return Json.createObjectBuilder() + .add(CONTEXT, contextArrayBuilder) + .add(TYPE, "Set") + .add("permission", rulesArrayBuilder) + .build(); + } } From f42819e609156e7656496a658f71ab37da706dad Mon Sep 17 00:00:00 2001 From: Benjamin Goetz Date: Wed, 10 Dec 2025 17:58:32 +0100 Subject: [PATCH 042/112] FIX: V0_0_1__Init_ContractNegotiation_Database_Schema.sql not being idempotent --- .../V0_0_1__Init_ContractNegotiation_Database_Schema.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edc-extensions/migrations/control-plane-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_1__Init_ContractNegotiation_Database_Schema.sql b/edc-extensions/migrations/control-plane-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_1__Init_ContractNegotiation_Database_Schema.sql index 9ec1b59d35..68859cffe3 100644 --- a/edc-extensions/migrations/control-plane-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_1__Init_ContractNegotiation_Database_Schema.sql +++ b/edc-extensions/migrations/control-plane-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/contractnegotiation/V0_0_1__Init_ContractNegotiation_Database_Schema.sql @@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS edc_lease COMMENT ON COLUMN edc_lease.leased_at IS 'posix timestamp of lease'; COMMENT ON COLUMN edc_lease.lease_duration IS 'duration of lease in milliseconds'; -CREATE UNIQUE INDEX lease_lease_id_uindex +CREATE UNIQUE INDEX IF NOT EXISTS lease_lease_id_uindex ON edc_lease (lease_id); -- From c3cc05c65293cf169aa7892b2b3e2152c064cd49 Mon Sep 17 00:00:00 2001 From: Ronja Quensel <72978761+ronjaquensel@users.noreply.github.com> Date: Fri, 12 Dec 2025 11:33:18 +0100 Subject: [PATCH 043/112] docs: DR for VP caching (#2464) --- .../README.md | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 docs/development/decision-records/2025-12-11-verifiable-presentation-caching/README.md diff --git a/docs/development/decision-records/2025-12-11-verifiable-presentation-caching/README.md b/docs/development/decision-records/2025-12-11-verifiable-presentation-caching/README.md new file mode 100644 index 0000000000..05032d50f6 --- /dev/null +++ b/docs/development/decision-records/2025-12-11-verifiable-presentation-caching/README.md @@ -0,0 +1,160 @@ +# Verifiable Presentation Caching + +## Decision + +We will implement a caching mechanism for Verifiable Presentations (VPs) within the DCP communication flow. Each +requested VP will be cached and before any new presentation request is made, the cache is checked for a matching VP +first. + +## Rationale + +For each DSP message exchanged, the receiving connector requests a VP of the sending participant. This includes multiple +requests to the wallet (sending participant's STS, receiving participant's STS, sending participant's presentation API). +This causes quite high network traffic, as e.g. during a contract negotiation at least 4 DSP messages are exchanged, +i.e. the whole request sequence will be run at least 4 times during a single contract negotiation. + +As available Verifiable Credentials (VCs) do not frequently change, part of the request sequence can be omitted after +the whole sequence has been executed once by introducing a cache for VPs. The initial call to the sending participant's +STS always needs to be made, as the receiving participant may not have any VPs cached. But after initially requesting a +VP for a participant, the requests to the receiving participant's STS as well as to the sending participant's +presentation API can be skipped for subsequent DSP messages exchanged with the same participant, thus greatly reducing +network traffic. + +## Approach + +### VerifiablePresentationCache + +First, we need to define an interface for the cache. It will provide methods for storing a new entry, retrieving an +entry and removing entries for a participant. As each VP is requested for a specific participant and specific scopes, +both `counterPartyDid` and `scopes` need to be used for storing and retrieving entries. As starting from EDC version +`0.15.0` the `participantContextId` is passed to the `DcpIdentityService` and `PresentationRequestService`, this should +also be included in the cache. + +```java +public interface VerifiablePresentationCache { + + StoreResult store(String participantContextId, String counterPartyDid, List scopes, List presentations); + + StoreResult> query(String participantContextId, String counterPartyDid, List scopes); + + StoreResult remove(String participantContextId, String counterPartyDid); +} +``` + +The interface will be located in a new, dedicated spi module `dcp-spi`. Additionally, a model class +`VerifiablePresentationCacheEntry` will be added to this spi module, which encapsulates all values to be cached as well +as the timestamp at which the cache entry was created. To decouple common cache behavior from the underlying persistence +layer, we will create a second interface `VerifiablePresentationCacheStore`, which provides similar methods to the +cache, but uses the `VerifiablePresentationCacheEntry` as parameter/return value for storing/retrieving, and +additionally provides a second `remove` method to remove a single entry by participant ID and scopes. + +#### VerifiablePresentationCacheImpl + +The `VerifiablePresentationCacheImpl` will wrap the `VerifiablePresentationCacheStore` with common cache behaviour, +like checking entries for expiry before returning them. To ensure that no invalid VPs are returned from the cache, +i.e. no expired or revoked VCs or VCs with invalid issuers, as these would cause a validation failure later on, +the `VerifiablePresentationCacheImpl` will utilize the `VerifiableCredentialValidationService`. This will lead to +duplication of some checks, as they will be run once within the cache implementation and once later on in the +`DcpIdentityService`, but as all checks executed in the `VerifiableCredentialValidationService` are lightweight, this +should not be an issue. + +```java +public class VerifiablePresentationCacheImpl implements VerifiablePresentationCache { + + // ... + + public StoreResult store(String participantContextId, String counterPartyDid, List scopes, + List presentations) { + var entry = new VerifiablePresentationCacheEntry(participantContextId, counterPartyDid, scopes, presentations, Instant.now(clock)); + return store.store(entry); + } + + public StoreResult> query(String participantContextId, String counterPartyDid, List scopes) { + var cacheResult = store.query(participantContextId, counterPartyDid, scopes); + + if (cacheResult.failed()) { + return StoreResult.notFound("No cached entry found for given participant and scopes."); + } + + if (isExpired(cacheResult.getContent()) || !areCredentialsValid(cacheResult.getContent().getPresentations(), participantContextId)) { + store.remove(participantContextId, counterPartyDid, scopes); + return StoreResult.notFound("No cached entry found for given participant and scopes."); + } + + return cacheResult.map(VerifiablePresentationCacheEntry::getPresentations); + } + + @Override + public StoreResult remove(String participantContextId, String counterPartyDid) { + return store.remove(participantContextId, counterPartyDid); + } + + private boolean isExpired(VerifiablePresentationCacheEntry entry) { + // ... + } + + private boolean areCredentialsValid(List presentations, String participantContextId) { + // ... + } +} +``` + +The `VerifiablePresentationCacheImpl` will be added as part of the new module `verifiable-presentation-cache` located +in the `dcp` super-module. + +#### VerifiablePresentationCacheStore Implementations + +The default implementation of the `VerifiablePresentationCacheStore` will be an in-memory implementation. But as +EDCs may be downscaled when no processes are running, different implementations using SQL-based persistence or utilizing +external cache solutions like Redis may be beneficial, to not lose the benefits of caching VPs in scenarios where +EDCs are frequently downscaled. + +### CachePresentationRequestService + +To include the cache in the DCP flow, a custom implementation of `PresentationRequestService` needs to be provided. +This service encapsulates the steps of creating an SI token for the receiving participant and requesting the sending +participant's VP. The `DefaultPresentationRequestService` is available in the `dcp-lib` module and will be extended by +the custom implementation as to not duplicate the existing code. The custom implementation will wrap the existing code +with calls to the cache: + +```java +public class CachePresentationRequestService extends DefaultPresentationRequestService { + + private final VerifiablePresentationCache cache; + + // ... + + @Override + public Result> requestPresentation(String participantContextId, String ownDid, + String counterPartyDid, String counterPartyToken, + List scopes) { + var cacheResult = cache.query(participantContextId, counterPartyDid, scopes); + if (cacheResult.succeeded()) { + return Result.success(cacheResult.getContent()); + } + + var vpResult = super.requestPresentation(participantContextId, ownDid, counterPartyDid, counterPartyToken, scopes); + + if (vpResult.succeeded()) { + cache.store(participantContextId, counterPartyDid, scopes, vpResult.getContent()); + } + + return vpResult; + } +} +``` + +The `CachePresentationRequestService` will also be added in the new module `verifiable-presentation-cache`. + +### Cache Invalidation API + +Even though the VCs are checked also within the cache, there may be situations where an invalid VP is cached, e.g. +when a VC defined in the requested scopes was initially missing and shortly after added to the wallet. To not block +communication in these situations, there needs to be a way to trigger removal of cache entries. For this purpose, +we'll add an API, which will comprise a single endpoint which takes a participant ID as parameter and removes all cache +entries for that participant when called. + +### Configuration + +The cache will be enabled by default and have a default validity period of 24 hours. There will be settings for both +disabling the cache and configuring the validity period. From 73459923bb2ecc3bf49bb4d3b81ced2aac25b4fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:30:01 +0100 Subject: [PATCH 044/112] chore(deps): bump actions/download-artifact (#2475) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/generate-and-publish-allure-report/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/generate-and-publish-allure-report/action.yml b/.github/actions/generate-and-publish-allure-report/action.yml index 722dd42d95..a7a3b0799c 100644 --- a/.github/actions/generate-and-publish-allure-report/action.yml +++ b/.github/actions/generate-and-publish-allure-report/action.yml @@ -35,7 +35,7 @@ runs: using: "composite" steps: - name: Download Allure results - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: pattern: ${{ inputs.allure_results }}-* merge-multiple: true From 8e3ea86890475064d139fd570dda618af98cde03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:30:39 +0100 Subject: [PATCH 045/112] chore(deps): bump actions/download-artifact from 6 to 7 (#2474) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-openapi-ui.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-openapi-ui.yml b/.github/workflows/publish-openapi-ui.yml index b261747b00..2f3a4da2e4 100644 --- a/.github/workflows/publish-openapi-ui.yml +++ b/.github/workflows/publish-openapi-ui.yml @@ -74,7 +74,7 @@ jobs: steps: - uses: actions/checkout@v6 - uses: eclipse-edc/.github/.github/actions/setup-build@main - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: name: openapi-spec path: resources/openapi/yaml @@ -128,7 +128,7 @@ jobs: permissions: contents: write steps: - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: path: openapi pattern: "*-api" From fcc8939ce54b827c00cb88f7332469f2c065477a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:31:07 +0100 Subject: [PATCH 046/112] chore(deps): bump trufflesecurity/trufflehog from 3.91.2 to 3.92.3 (#2473) Bumps [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) from 3.91.2 to 3.92.3. - [Release notes](https://github.com/trufflesecurity/trufflehog/releases) - [Commits](https://github.com/trufflesecurity/trufflehog/compare/821e8b9e5cdf8dc484dd23e06f78941fcf6b9191...05cccb53bc9e13bc6d17997db5a6bcc3df44bf2f) --- updated-dependencies: - dependency-name: trufflesecurity/trufflehog dependency-version: 3.92.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/secrets-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml index 890900bd5e..9d3163f56c 100644 --- a/.github/workflows/secrets-scan.yml +++ b/.github/workflows/secrets-scan.yml @@ -46,7 +46,7 @@ jobs: - name: TruffleHog OSS id: trufflehog - uses: trufflesecurity/trufflehog@821e8b9e5cdf8dc484dd23e06f78941fcf6b9191 + uses: trufflesecurity/trufflehog@05cccb53bc9e13bc6d17997db5a6bcc3df44bf2f continue-on-error: true with: path: ./ # Scan the entire repository From 23c3af995b31bd664d6091424282756c629c4e0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:31:42 +0100 Subject: [PATCH 047/112] chore(deps): bump actions/cache from 4 to 5 (#2472) Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deployment-test.yaml | 2 +- .github/workflows/upgradeability-test.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deployment-test.yaml b/.github/workflows/deployment-test.yaml index b7c3534616..69be0e60ec 100644 --- a/.github/workflows/deployment-test.yaml +++ b/.github/workflows/deployment-test.yaml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache ContainerD Image Layers - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs key: ${{ runner.os }}-io.containerd.snapshotter.v1.overlayfs diff --git a/.github/workflows/upgradeability-test.yaml b/.github/workflows/upgradeability-test.yaml index 29da2b8ebf..e353b0cb22 100644 --- a/.github/workflows/upgradeability-test.yaml +++ b/.github/workflows/upgradeability-test.yaml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Cache ContainerD Image Layers - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs key: ${{ runner.os }}-io.containerd.snapshotter.v1.overlayfs From fe094b5978e095fa1868379728ca751c74220aab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:45:29 +0100 Subject: [PATCH 048/112] chore(deps): bump actions/upload-artifact from 5 to 6 (#2471) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-openapi-ui.yml | 4 ++-- .github/workflows/verify.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-openapi-ui.yml b/.github/workflows/publish-openapi-ui.yml index 2f3a4da2e4..4129dac9e5 100644 --- a/.github/workflows/publish-openapi-ui.yml +++ b/.github/workflows/publish-openapi-ui.yml @@ -57,7 +57,7 @@ jobs: run: ./gradlew resolve env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_API_TOKEN }} - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: openapi-spec path: resources/openapi/yaml @@ -117,7 +117,7 @@ jobs: spec-file: ${{ matrix.apiGroup.name }}.yaml GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: ${{ matrix.apiGroup.name }}-api path: dist diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index 65d443e0dd..b3ad956da6 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -81,7 +81,7 @@ jobs: # uploads the jacoco report as artifact - name: Upload JaCoCo Coverage Report - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: JaCoCo coverage-report path: build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml @@ -108,7 +108,7 @@ jobs: # uploads the coverage-report.md artifact - name: Upload Code Coverage Markdown Report - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: code-coverage-report-markdown path: "*/coverage-results.md" @@ -175,7 +175,7 @@ jobs: echo "ARTIFACT_NAME=${SANITIZED_NAME}" >> $GITHUB_ENV - name: Upload test artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: allure-results-${{ env.ARTIFACT_NAME }} path: edc-tests/e2e/${{ matrix.dir }}/build/allure-results From 1bf3d61a737b1839b73bee27826bdf8f38c104dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:46:03 +0100 Subject: [PATCH 049/112] chore(deps): bump io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations (#2470) Bumps [io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations](https://github.com/open-telemetry/opentelemetry-java-instrumentation) from 2.22.0 to 2.23.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java-instrumentation/compare/v2.22.0...v2.23.0) --- updated-dependencies: - dependency-name: io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations dependency-version: 2.23.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e742ff6de..0ef4a46c8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ junit = "6.0.1" nimbus = "10.6" okhttp = "5.3.2" opentelemetry = "2.22.0" -opentelemetry-instrumentation = "2.22.0" +opentelemetry-instrumentation = "2.23.0" opentelemetry-log4j-appender = "2.22.0-alpha" postgres = "42.7.8" restAssured = "5.5.6" From c5adc8d178768ffc5ff9ee5981b28033f699af5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:47:35 +0100 Subject: [PATCH 050/112] chore(deps): bump io.qameta.allure:allure-junit5 from 2.31.0 to 2.32.0 (#2469) Bumps [io.qameta.allure:allure-junit5](https://github.com/allure-framework/allure-java) from 2.31.0 to 2.32.0. - [Release notes](https://github.com/allure-framework/allure-java/releases) - [Commits](https://github.com/allure-framework/allure-java/compare/2.31.0...2.32.0) --- updated-dependencies: - dependency-name: io.qameta.allure:allure-junit5 dependency-version: 2.32.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ef4a46c8a..b95f237783 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ format.version = "1.1" [versions] edc = "0.15.0" edc-build = "1.1.4" -allure = "2.31.0" +allure = "2.32.0" awaitility = "4.3.0" aws = "2.40.3" azure-storage-blob = "12.32.0" From f2cf15f462981b36bacc2dec73dc9e5c4c04a333 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:51:35 +0100 Subject: [PATCH 051/112] chore(deps): bump org.bouncycastle:bcpkix-jdk18on from 1.82 to 1.83 (#2468) Bumps [org.bouncycastle:bcpkix-jdk18on](https://github.com/bcgit/bc-java) from 1.82 to 1.83. - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.bouncycastle:bcpkix-jdk18on dependency-version: '1.83' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b95f237783..7daf1072f4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ allure = "2.32.0" awaitility = "4.3.0" aws = "2.40.3" azure-storage-blob = "12.32.0" -bouncyCastle-jdk18on = "1.82" +bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-SNAPSHOT" dsp-tck = "1.0.0-RC4" flyway = "11.18.0" From 20fd11f285b996d8ab7481fc000d8dd9cc958eb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:52:01 +0100 Subject: [PATCH 052/112] chore(deps): bump com.github.dasniko:testcontainers-keycloak (#2467) Bumps [com.github.dasniko:testcontainers-keycloak](https://github.com/dasniko/testcontainers-keycloak) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/dasniko/testcontainers-keycloak/releases) - [Commits](https://github.com/dasniko/testcontainers-keycloak/compare/v4.0.0...v4.0.1) --- updated-dependencies: - dependency-name: com.github.dasniko:testcontainers-keycloak dependency-version: 4.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7daf1072f4..92c3f39a78 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ postgres = "42.7.8" restAssured = "5.5.6" rsApi = "4.0.0" testcontainers = "2.0.2" -testcontainers-keycloak = "4.0.0" +testcontainers-keycloak = "4.0.1" titanium = "1.7.0" log4j2 = "2.25.2" wiremock = "3.13.2" From 90f98e170385521b2153b25f789eb9a2aed7db89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:52:35 +0100 Subject: [PATCH 053/112] chore(deps): bump flyway from 11.18.0 to 11.19.0 (#2466) Bumps `flyway` from 11.18.0 to 11.19.0. Updates `org.flywaydb:flyway-core` from 11.18.0 to 11.19.0 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-11.18.0...flyway-11.19.0) Updates `org.flywaydb:flyway-database-postgresql` from 11.18.0 to 11.19.0 --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-version: 11.19.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.flywaydb:flyway-database-postgresql dependency-version: 11.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 92c3f39a78..77d89df135 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-SNAPSHOT" dsp-tck = "1.0.0-RC4" -flyway = "11.18.0" +flyway = "11.19.0" jackson = "2.20.1" jakarta-json = "2.0.1" junit = "6.0.1" From c72d7688f40704326db3c79601f8217fefd28ec1 Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Mon, 15 Dec 2025 18:20:56 +0100 Subject: [PATCH 054/112] Add .env as pattern in gitignore (#2476) Signed-off-by: Lars Geyer-Blaumeiser --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 90994c9ffe..b528c08354 100644 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,5 @@ buildNumber.properties bin/ edc-tests/miw-tests/src/test/resources/docker-environment/postgres_data/ + +.env From 3d215fc6acb2e3e44945cc6cbfad3d91d1a23183 Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Wed, 17 Dec 2025 13:46:25 +0100 Subject: [PATCH 055/112] infra: change stale bot behavior (#2479) * Improve stale bot handling Signed-off-by: Lars Geyer-Blaumeiser * Reduce config according to review discussion Signed-off-by: Lars Geyer-Blaumeiser --------- Signed-off-by: Lars Geyer-Blaumeiser --- .github/workflows/stale-bot.yml | 67 ++------------------------------- 1 file changed, 4 insertions(+), 63 deletions(-) diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index c47c558fb0..2e73531417 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -1,6 +1,7 @@ ################################################################################# # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) # Copyright (c) 2023 Contributors to the Eclipse Foundation +# Copyright (c) 2025 Cofinity-X GmbH # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -29,27 +30,6 @@ on: workflow_dispatch: # allow manual trigger jobs: - close-issues-in-triage: - runs-on: ubuntu-latest - permissions: - issues: write - - steps: - - uses: actions/stale@v10 - with: - operations-per-run: 1000 - days-before-issue-stale: 32 # 4 weeks - days-before-issue-close: 14 - stale-issue-label: "stale" - stale-issue-message: "This issue is stale because it has been open for 4 weeks with no activity." - close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." - close-issue-reason: 'not_planned' - days-before-pr-stale: -1 # ignore PRs (overwrite default days-before-stale) - days-before-pr-close: -1 # ignore PRs (overwrite default days-before-close) - remove-issue-stale-when-updated: true - only-labels: 'triage' - repo-token: ${{ github.token }} - close-issues-with-assignee: runs-on: ubuntu-latest permissions: @@ -59,52 +39,13 @@ jobs: with: operations-per-run: 1000 days-before-issue-stale: 32 - days-before-issue-close: 7 + days-before-issue-close: 10 # 10 days to ensure a preparation for the weekly phase being in the period stale-issue-label: "stale" stale-issue-message: "This issue is stale because it has been open for 4 weeks with no activity." - close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale." - close-issue-reason: 'not_planned' - days-before-pr-stale: -1 # ignore PRs (overwrite default days-before-stale) - days-before-pr-close: -1 # ignore PRs (overwrite default days-before-close) - remove-issue-stale-when-updated: true - exempt-issue-labels: bug # ignore issues labelled as bug - repo-token: ${{ github.token }} - - close-issues-without-assignee: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - uses: actions/stale@v10 - with: - operations-per-run: 1000 - days-before-issue-stale: 14 - days-before-issue-close: 7 - stale-issue-label: "stale" - stale-issue-message: "This issue is stale because it has been open for 2 weeks with no activity." - close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale." + close-issue-message: "This issue was closed because it has been inactive for 10 days since being marked as stale." close-issue-reason: 'not_planned' days-before-pr-stale: -1 # ignore PRs (overwrite default days-before-stale) days-before-pr-close: -1 # ignore PRs (overwrite default days-before-close) remove-issue-stale-when-updated: true - exempt-all-issue-assignees: true # issues with assignees will be ignored - exempt-issue-labels: bug,triage # ignore issues labelled as bug or triage - repo-token: ${{ github.token }} - - close-inactive-pull-requests: - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - uses: actions/stale@v10 - with: - operations-per-run: 1000 - days-before-issue-stale: -1 # ignore issues (overwrite default days-before-stale) - days-before-issue-close: -1 # ignore issues (overwrite default days-before-close) - stale-pr-label: "stale" - stale-pr-message: "This pull request is stale because it has been open for 7 days with no activity." - close-pr-message: "This pull request was closed because it has been inactive for 7 days since being marked as stale." - days-before-pr-stale: 7 - days-before-pr-close: 7 - remove-pr-stale-when-updated: true + exempt-issue-labels: bug,later # ignore issues labelled as bug repo-token: ${{ github.token }} From 0d88f8cbdc044397fbf0cb32649575d9989d7cc8 Mon Sep 17 00:00:00 2001 From: Ronja Quensel <72978761+ronjaquensel@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:51:43 +0100 Subject: [PATCH 056/112] feat: introduce cache for Verifiable Presentations (#2480) * feat: VP cache * test: unit tests * chore: license headers * docs: Javadoc * chore: error logs * chore: checkstyle * feat: validations in cache * test: update dcp e2e tests * chore: clean-up * chore: Sonar remarks * chore: update Helm charts * chore: PR comments --- charts/tractusx-connector-memory/README.md | 2 + .../templates/deployment-runtime.yaml | 4 + charts/tractusx-connector-memory/values.yaml | 6 + charts/tractusx-connector/README.md | 2 + .../templates/deployment-controlplane.yaml | 4 + charts/tractusx-connector/values.yaml | 6 + .../edc-controlplane-base/build.gradle.kts | 1 + .../build.gradle.kts | 38 +++ .../CachePresentationRequestExtension.java | 131 +++++++++ .../dcp/CachePresentationRequestService.java | 74 +++++ ...ablePresentationCacheDefaultExtension.java | 35 +++ .../VerifiablePresentationCacheImpl.java | 180 +++++++++++++ ...emoryVerifiablePresentationCacheStore.java | 104 ++++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 21 ++ .../CachePresentationRequestServiceTest.java | 118 ++++++++ .../VerifiablePresentationCacheImplTest.java | 252 ++++++++++++++++++ ...yVerifiablePresentationCacheStoreTest.java | 202 ++++++++++++++ .../AbstractIatpConsumerPullTest.java | 84 ++++++ gradle/libs.versions.toml | 2 + settings.gradle.kts | 2 + spi/dcp-spi/build.gradle.kts | 28 ++ .../spi/dcp/VerifiablePresentationCache.java | 68 +++++ .../dcp/VerifiablePresentationCacheEntry.java | 68 +++++ .../dcp/VerifiablePresentationCacheStore.java | 72 +++++ 24 files changed, 1504 insertions(+) create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/build.gradle.kts create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestExtension.java create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestService.java create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/VerifiablePresentationCacheDefaultExtension.java create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImpl.java create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/store/InMemoryVerifiablePresentationCacheStore.java create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestServiceTest.java create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImplTest.java create mode 100644 edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cache/store/InMemoryVerifiablePresentationCacheStoreTest.java create mode 100644 spi/dcp-spi/build.gradle.kts create mode 100644 spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCache.java create mode 100644 spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCacheEntry.java create mode 100644 spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCacheStore.java diff --git a/charts/tractusx-connector-memory/README.md b/charts/tractusx-connector-memory/README.md index 6a65ce5f32..8c124fb547 100644 --- a/charts/tractusx-connector-memory/README.md +++ b/charts/tractusx-connector-memory/README.md @@ -57,6 +57,8 @@ helm install my-release tractusx-edc/tractusx-connector-memory --version 0.12.0- | customCaCerts | object | `{}` | Add custom ca certificates to the truststore | | customLabels | object | `{}` | Add some custom labels | | fullnameOverride | string | `""` | | +| iatp.cache.enabled | bool | `true` | Whether the Verifiable Presentation cache is enabled | +| iatp.cache.validity | int | `86400` | Validity of the Verifiable Presentation cache in seconds | | iatp.id | string | `"did:web:changeme"` | Decentralized IDentifier (DID) of the connector | | iatp.sts.dim.url | string | `nil` | URL where connectors can request SI tokens | | iatp.sts.oauth.client.id | string | `nil` | Client ID for requesting OAuth2 access token for DIM access | diff --git a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml index 311cbbc9ec..6c00949726 100644 --- a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml +++ b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml @@ -256,6 +256,10 @@ spec: {{- end }} {{- end }} {{- end }} + - name: "TX_EDC_DCP_CACHE_ENABLED" + value: {{ .Values.iatp.cache.enabled | quote }} + - name: "TX_EDC_DCP_CACHE_VALIDITY_SECONDS" + value: {{ .Values.iatp.cache.validity | quote }} ################# ## BDRS CLIENT ## diff --git a/charts/tractusx-connector-memory/values.yaml b/charts/tractusx-connector-memory/values.yaml index 282adc1ce3..51cf432a81 100644 --- a/charts/tractusx-connector-memory/values.yaml +++ b/charts/tractusx-connector-memory/values.yaml @@ -57,6 +57,12 @@ iatp: id: # -- Alias under which the client secret is stored in the vault for requesting OAuth2 access token for DIM access secret_alias: + # - Configures the Verifiable Presentation Ccache + cache: + # -- Whether the Verifiable Presentation cache is enabled + enabled: true + # -- Validity of the Verifiable Presentation cache in seconds + validity: 86400 # -- Add custom ca certificates to the truststore customCaCerts: {} diff --git a/charts/tractusx-connector/README.md b/charts/tractusx-connector/README.md index d5c4ae5675..b2de0a61ce 100644 --- a/charts/tractusx-connector/README.md +++ b/charts/tractusx-connector/README.md @@ -267,6 +267,8 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.12.0-SNAPSHO | dataplane.volumeMounts | string | `nil` | declare where to mount [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) into the container | | dataplane.volumes | string | `nil` | [volume](https://kubernetes.io/docs/concepts/storage/volumes/) directories | | fullnameOverride | string | `""` | | +| iatp.cache.enabled | bool | `true` | Whether the Verifiable Presentation cache is enabled | +| iatp.cache.validity | int | `86400` | Validity of the Verifiable Presentation cache in seconds | | iatp.id | string | `"did:web:changeme"` | Decentralized IDentifier (DID) of the connector | | iatp.sts.dim.url | string | `nil` | URL where connectors can request SI tokens | | iatp.sts.oauth.client.id | string | `nil` | Client ID for requesting OAuth2 access token for DIM access | diff --git a/charts/tractusx-connector/templates/deployment-controlplane.yaml b/charts/tractusx-connector/templates/deployment-controlplane.yaml index e429eee886..382d948b93 100644 --- a/charts/tractusx-connector/templates/deployment-controlplane.yaml +++ b/charts/tractusx-connector/templates/deployment-controlplane.yaml @@ -254,6 +254,10 @@ spec: {{- end }} {{- end }} {{- end }} + - name: "TX_EDC_DCP_CACHE_ENABLED" + value: {{ .Values.iatp.cache.enabled | quote }} + - name: "TX_EDC_DCP_CACHE_VALIDITY_SECONDS" + value: {{ .Values.iatp.cache.validity | quote }} ################# ## BDRS CLIENT ## diff --git a/charts/tractusx-connector/values.yaml b/charts/tractusx-connector/values.yaml index b9eee6adfc..fde986b409 100644 --- a/charts/tractusx-connector/values.yaml +++ b/charts/tractusx-connector/values.yaml @@ -64,6 +64,12 @@ iatp: id: # -- Alias under which the client secret is stored in the vault for requesting OAuth2 access token for DIM access secret_alias: + # - Configures the Verifiable Presentation cache + cache: + # -- Whether the Verifiable Presentation cache is enabled + enabled: true + # -- Validity of the Verifiable Presentation cache in seconds + validity: 86400 # -- Add custom ca certificates to the truststore customCaCerts: {} diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index 18e8f4631a..a6fdc2b352 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { implementation(project(":edc-extensions:data-flow-properties-provider")) implementation(project(":edc-extensions:dcp:tx-dcp")) implementation(project(":edc-extensions:dcp:tx-dcp-sts-dim")) + implementation(project(":edc-extensions:dcp:verifiable-presentation-cache")) implementation(project(":edc-extensions:edr:edr-api-v2")) implementation(project(":edc-extensions:edr:edr-callback")) implementation(project(":edc-extensions:federated-catalog")) diff --git a/edc-extensions/dcp/verifiable-presentation-cache/build.gradle.kts b/edc-extensions/dcp/verifiable-presentation-cache/build.gradle.kts new file mode 100644 index 0000000000..859b277f66 --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/build.gradle.kts @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` + `maven-publish` + id(libs.plugins.swagger.get().pluginId) +} + +dependencies { + implementation(libs.edc.spi.decentralized.claims) + implementation(libs.edc.spi.participant.context.single) + implementation(libs.edc.lib.dcp) + implementation(libs.edc.verifiable.credentials) + implementation(libs.edc.identity.vc.ldp) + implementation(libs.edc.identity.vc.jwt) + implementation(libs.edc.decentralized.claims.service) + + implementation(project(":spi:dcp-spi")) + + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestExtension.java b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestExtension.java new file mode 100644 index 0000000000..0bca94e6e7 --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestExtension.java @@ -0,0 +1,131 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.iam.dcp; + +import org.eclipse.edc.iam.decentralizedclaims.lib.DefaultPresentationRequestService; +import org.eclipse.edc.iam.decentralizedclaims.service.DidCredentialServiceUrlResolver; +import org.eclipse.edc.iam.decentralizedclaims.service.verification.MultiFormatPresentationVerifier; +import org.eclipse.edc.iam.decentralizedclaims.spi.CredentialServiceClient; +import org.eclipse.edc.iam.decentralizedclaims.spi.PresentationRequestService; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; +import org.eclipse.edc.iam.decentralizedclaims.spi.verification.SignatureSuiteRegistry; +import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; +import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; +import org.eclipse.edc.iam.verifiablecredentials.VerifiableCredentialValidationServiceImpl; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.RevocationServiceRegistry; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.PresentationVerifier; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.TrustedIssuerRegistry; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; +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.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.token.spi.TokenValidationRulesRegistry; +import org.eclipse.edc.token.spi.TokenValidationService; +import org.eclipse.edc.verifiablecredentials.jwt.JwtPresentationVerifier; +import org.eclipse.edc.verifiablecredentials.linkeddata.DidMethodResolver; +import org.eclipse.edc.verifiablecredentials.linkeddata.LdpVerifier; +import org.eclipse.tractusx.edc.iam.dcp.cache.VerifiablePresentationCacheImpl; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCacheStore; + +import java.time.Clock; + +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; +import static org.eclipse.tractusx.edc.iam.dcp.cache.VerifiablePresentationCacheImpl.DEFAULT_VP_CACHE_VALIDITY_SECONDS; + +@Extension("Verifiable Presentation Cache") +public class CachePresentationRequestExtension implements ServiceExtension { + + @Setting(key = "tx.edc.dcp.cache.enabled", defaultValue = "true", description = "Defines whether the Verifiable Presentation Cache is enabled.") + private boolean cacheEnabled; + + @Setting(key = "tx.edc.dcp.cache.validity.seconds", defaultValue = DEFAULT_VP_CACHE_VALIDITY_SECONDS + "", min = 1, + description = "Validity period of the Verifiable Presentation Cache in seconds.") + private long cacheValidity; + + @Inject + private VerifiablePresentationCacheStore store; + @Inject + private Clock clock; + @Inject + private TokenValidationService tokenValidationService; + @Inject + private TokenValidationRulesRegistry rulesRegistry; + @Inject + private DidPublicKeyResolver didPublicKeyResolver; + @Inject + private SignatureSuiteRegistry signatureSuiteRegistry; + @Inject + private JsonLd jsonLd; + @Inject + private DidResolverRegistry didResolverRegistry; + @Inject + private TrustedIssuerRegistry trustedIssuerRegistry; + @Inject + private RevocationServiceRegistry revocationServiceRegistry; + @Inject + private SecureTokenService secureTokenService; + @Inject + private CredentialServiceClient credentialServiceClient; + @Inject + private TypeManager typeManager; + @Inject + private SingleParticipantContextSupplier singleParticipantContextSupplier; + @Inject + private Monitor monitor; + + @Provider + public PresentationRequestService cachePresentationRequestService() { + var credentialServiceUrlResolver = new DidCredentialServiceUrlResolver(didResolverRegistry); + + if (!cacheEnabled) { + monitor.info("Verifiable Presentation Cache is disabled. Will not cache any VPs."); + return new DefaultPresentationRequestService(secureTokenService, credentialServiceUrlResolver, credentialServiceClient); + } + + var validationService = new VerifiableCredentialValidationServiceImpl(presentationVerifier(), trustedIssuerRegistry, revocationServiceRegistry, clock, typeManager.getMapper()); + var cache = new VerifiablePresentationCacheImpl(cacheValidity, clock, store, validationService, pcId -> resolveOwnDid(), revocationServiceRegistry, monitor); + return new CachePresentationRequestService(secureTokenService, credentialServiceUrlResolver, credentialServiceClient, cache, monitor); + } + + private PresentationVerifier presentationVerifier() { + var jwtVerifier = new JwtPresentationVerifier(typeManager, JSON_LD, tokenValidationService, rulesRegistry, didPublicKeyResolver); + var ldpVerifier = LdpVerifier.Builder.newInstance() + .signatureSuites(signatureSuiteRegistry) + .jsonLd(jsonLd) + .typeManager(typeManager) + .typeContext(JSON_LD) + .methodResolver(new DidMethodResolver(didResolverRegistry)) + .build(); + + return new MultiFormatPresentationVerifier(jwtVerifier, ldpVerifier); + } + + private String resolveOwnDid() { + return singleParticipantContextSupplier.get().map(ParticipantContext::getIdentity) + .orElseThrow(f -> new EdcException("Cannot get the participant context: " + f.getFailureDetail())); + } +} diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestService.java b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestService.java new file mode 100644 index 0000000000..c1be9036f4 --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestService.java @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.iam.dcp; + +import org.eclipse.edc.iam.decentralizedclaims.lib.DefaultPresentationRequestService; +import org.eclipse.edc.iam.decentralizedclaims.spi.CredentialServiceClient; +import org.eclipse.edc.iam.decentralizedclaims.spi.CredentialServiceUrlResolver; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCache; + +import java.util.List; + +import static java.lang.String.format; + +/** + * An extension of the {@link DefaultPresentationRequestService} that utilizes a cache. Before + * making a request, the cache is checked for corresponding entries. After a successful request has + * been made, the presentations are stored in the cache. + */ +public class CachePresentationRequestService extends DefaultPresentationRequestService { + + private final VerifiablePresentationCache cache; + private final Monitor monitor; + + public CachePresentationRequestService(SecureTokenService secureTokenService, + CredentialServiceUrlResolver credentialServiceUrlResolver, + CredentialServiceClient credentialServiceClient, + VerifiablePresentationCache cache, Monitor monitor) { + super(secureTokenService, credentialServiceUrlResolver, credentialServiceClient); + this.cache = cache; + this.monitor = monitor; + } + + @Override + public Result> requestPresentation(String participantContextId, String ownDid, + String counterPartyDid, String counterPartyToken, + List scopes) { + var cacheResult = cache.query(participantContextId, counterPartyDid, scopes); + if (cacheResult.succeeded()) { + return Result.success(cacheResult.getContent()); + } + + var vpResult = super.requestPresentation(participantContextId, ownDid, counterPartyDid, counterPartyToken, scopes); + + if (vpResult.succeeded()) { + var storeResult = cache.store(participantContextId, counterPartyDid, scopes, vpResult.getContent()); + if (storeResult.failed()) { + monitor.warning(format("Failed to cache Verifiable Presentation for %s: %s", counterPartyDid, storeResult.getFailureDetail())); + } + } + + return vpResult; + } +} diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/VerifiablePresentationCacheDefaultExtension.java b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/VerifiablePresentationCacheDefaultExtension.java new file mode 100644 index 0000000000..b459e54940 --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/VerifiablePresentationCacheDefaultExtension.java @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.iam.dcp; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.tractusx.edc.iam.dcp.cache.store.InMemoryVerifiablePresentationCacheStore; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCacheStore; + +@Extension("Verifiable Presentation Cache Default Services") +public class VerifiablePresentationCacheDefaultExtension implements ServiceExtension { + + @Provider(isDefault = true) + public VerifiablePresentationCacheStore inMemoryVerifiablePresentationCacheStore() { + return new InMemoryVerifiablePresentationCacheStore(); + } +} diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImpl.java b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImpl.java new file mode 100644 index 0000000000..01de773c34 --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImpl.java @@ -0,0 +1,180 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.iam.dcp.cache; + +import org.eclipse.edc.iam.verifiablecredentials.rules.IsInValidityPeriod; +import org.eclipse.edc.iam.verifiablecredentials.rules.IsNotRevoked; +import org.eclipse.edc.iam.verifiablecredentials.spi.VerifiableCredentialValidationService; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.RevocationServiceRegistry; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentation; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.iam.verifiablecredentials.spi.validation.CredentialValidationRule; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.StoreResult; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCache; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCacheEntry; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCacheStore; + +import java.time.Clock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.UnaryOperator; + +import static java.lang.String.format; +import static org.eclipse.edc.spi.result.Result.success; + +/** + * Implementation of the {@link VerifiablePresentationCache}. Performs common tasks like checking + * VPs and VCs for validity before caching them as well as checking cached entries for expiry or + * revocation before returning them. For the actual storing of cache entries, an implementation of + * {@link VerifiablePresentationCacheStore} is used. + */ +public class VerifiablePresentationCacheImpl implements VerifiablePresentationCache { + + public static final long DEFAULT_VP_CACHE_VALIDITY_SECONDS = 86400; //24h + + private final long cacheValidity; + private final Clock clock; + private final VerifiablePresentationCacheStore store; + private final VerifiableCredentialValidationService credentialValidationService; + private final UnaryOperator didResolver; + private final RevocationServiceRegistry revocationServiceRegistry; + private final Monitor monitor; + + public VerifiablePresentationCacheImpl(long cacheValidity, Clock clock, VerifiablePresentationCacheStore store, + VerifiableCredentialValidationService credentialValidationService, + UnaryOperator didResolver, RevocationServiceRegistry revocationServiceRegistry, + Monitor monitor) { + this.cacheValidity = cacheValidity; + this.clock = clock; + this.store = store; + this.credentialValidationService = credentialValidationService; + this.didResolver = didResolver; + this.revocationServiceRegistry = revocationServiceRegistry; + this.monitor = monitor; + } + + @Override + public StoreResult store(String participantContextId, String counterPartyDid, List scopes, + List presentations) { + if (!areCredentialsValid(presentations, participantContextId, counterPartyDid, scopes)) { + return StoreResult.generalError("VPs/VCs are not valid. Will not cache."); + } + var entry = new VerifiablePresentationCacheEntry(participantContextId, counterPartyDid, scopes, presentations, Instant.now(clock)); + return store.store(entry); + } + + @Override + public StoreResult> query(String participantContextId, String counterPartyDid, List scopes) { + var cacheResult = store.query(participantContextId, counterPartyDid, scopes); + + if (cacheResult.failed()) { + return StoreResult.notFound("No cached entry found for given participant and scopes."); + } + + if (isExpired(cacheResult.getContent()) || expiredOrRevoked(cacheResult.getContent().getPresentations())) { + var removeResult = store.remove(participantContextId, counterPartyDid, scopes); + if (removeResult.failed()) { + monitor.warning(format("Failed to remove expired or invalid entry from cache for %s: %s", counterPartyDid, removeResult.getFailureDetail())); + } + return StoreResult.notFound("No cached entry found for given participant and scopes."); + } + + return cacheResult.map(VerifiablePresentationCacheEntry::getPresentations); + } + + @Override + public StoreResult remove(String participantContextId, String counterPartyDid) { + return store.remove(participantContextId, counterPartyDid); + } + + private boolean areCredentialsValid(List presentations, String participantContextId, String counterPartyDid, List scopes) { + var ownDid = didResolver.apply(participantContextId); + + return validateRequestedCredentials(presentations, scopes) + .compose(ignore -> credentialValidationService.validate(presentations, ownDid, Collections.emptyList())) + .compose(ignore -> verifyPresentationIssuer(counterPartyDid, presentations)) + .succeeded(); + } + + /** + * Validates that requested scopes and received VCs match by checking that every requested VC + * has been received. + */ + private Result validateRequestedCredentials(List presentations, List requestedScopes) { + var allCreds = presentations.stream() + .flatMap(p -> p.presentation().getCredentials().stream()) + .toList(); + if (requestedScopes.size() > allCreds.size()) { + return Result.failure("Number of requested credentials does not match the number of returned credentials"); + } + + var types = allCreds.stream().map(VerifiableCredential::getType) + .flatMap(Collection::stream) + .distinct() + .toList(); + + return requestedScopes.stream().allMatch(scope -> types.stream().anyMatch(scope::contains)) ? + Result.success() : + Result.failure("Not all requested credentials are present in the presentation response"); + } + + /** + * Validates the issuer of received VPs by checking that the issuer of the received SI token + * is also the issuer of all received VPs. + */ + private Result verifyPresentationIssuer(String expectedIssuer, List presentationContainers) { + var issuers = presentationContainers.stream().map(VerifiablePresentationContainer::presentation) + .map(VerifiablePresentation::getHolder) + .toList(); + + if (issuers.stream().allMatch(expectedIssuer::equals)) { + return Result.success(); + } else { + return Result.failure("Returned presentations contains invalid issuer. Expected %s found %s".formatted(expectedIssuer, issuers)); + } + } + + private boolean isExpired(VerifiablePresentationCacheEntry entry) { + return entry.getCachedAt().plus(cacheValidity, ChronoUnit.SECONDS).isBefore(Instant.now(clock)); + } + + private boolean expiredOrRevoked(List presentations) { + var checks = List.of( + new IsInValidityPeriod(clock), + new IsNotRevoked(revocationServiceRegistry)); + + var credentials = presentations.stream() + .flatMap(p -> p.presentation().getCredentials().stream()) + .toList(); + + return credentials + .stream() + .map(credential -> checks.stream() + .reduce(t -> success(), CredentialValidationRule::and) + .apply(credential)) + .anyMatch(Result::failed); + } +} diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/store/InMemoryVerifiablePresentationCacheStore.java b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/store/InMemoryVerifiablePresentationCacheStore.java new file mode 100644 index 0000000000..9002c66789 --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/store/InMemoryVerifiablePresentationCacheStore.java @@ -0,0 +1,104 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.iam.dcp.cache.store; + +import org.eclipse.edc.spi.result.StoreResult; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCacheEntry; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCacheStore; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * In-memory implementation of the {@link VerifiablePresentationCacheStore}. Stores all entries + * in a concurrent map. + */ +public class InMemoryVerifiablePresentationCacheStore implements VerifiablePresentationCacheStore { + + private final Map cache = new ConcurrentHashMap<>(); + + @Override + public StoreResult store(VerifiablePresentationCacheEntry entry) { + var id = new CacheEntryId(entry.getParticipantContextId(), entry.getCounterPartyDid(), entry.getScopes()); + cache.put(id, entry); + return StoreResult.success(); + } + + @Override + public StoreResult query(String participantContextId, String counterPartyDid, List scopes) { + var id = new CacheEntryId(participantContextId, counterPartyDid, scopes); + var entry = cache.get(id); + if (entry == null) { + return StoreResult.notFound("No entry found in cache for given participant and scopes."); + } + + return StoreResult.success(entry); + } + + @Override + public StoreResult remove(String participantContextId, String counterPartyDid, List scopes) { + var id = new CacheEntryId(participantContextId, counterPartyDid, scopes); + cache.remove(id); + return StoreResult.success(); + } + + @Override + public StoreResult remove(String participantContextId, String counterPartyDid) { + cache.keySet().stream() + .filter(id -> id.participantContextId.equals(participantContextId) && id.counterPartyDid.equals(counterPartyDid)) + .forEach(cache::remove); + + return StoreResult.success(); + } + + /** + * Compound ID for cache entries. Comprises participant context ID, participant DID and scopes. + */ + private static class CacheEntryId { + private final String participantContextId; + private final String counterPartyDid; + private final HashSet scopes; + + CacheEntryId(String participantContextId, String counterPartyDid, List scopes) { + this.participantContextId = participantContextId; + this.counterPartyDid = counterPartyDid; + this.scopes = new HashSet<>(scopes); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + var other = (CacheEntryId) o; + return this.participantContextId.equals(other.participantContextId) && + this.counterPartyDid.equals(other.counterPartyDid) && + this.scopes.equals(other.scopes); + } + + @Override + public int hashCode() { + return Objects.hash(participantContextId, counterPartyDid, scopes); + } + } +} diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dcp/verifiable-presentation-cache/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..a06fdc76f8 --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,21 @@ +################################################################################# +# Copyright (c) 2025 Cofinity-X GmbH +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# 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. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################# + +org.eclipse.tractusx.edc.iam.dcp.CachePresentationRequestExtension +org.eclipse.tractusx.edc.iam.dcp.VerifiablePresentationCacheDefaultExtension diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestServiceTest.java b/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestServiceTest.java new file mode 100644 index 0000000000..e3f0cc610f --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/CachePresentationRequestServiceTest.java @@ -0,0 +1,118 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.iam.dcp; + +import org.eclipse.edc.iam.decentralizedclaims.spi.CredentialServiceClient; +import org.eclipse.edc.iam.decentralizedclaims.spi.CredentialServiceUrlResolver; +import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.StoreResult; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCache; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +class CachePresentationRequestServiceTest { + + private final String participantContextId = "participantContextId"; + private final String ownDid = "did:web:me"; + private final String counterPartyDid = "did:web:other"; + private final String counterPartyToken = "abc123"; + private final List scopes = List.of("scope1", "scope2"); + + private final SecureTokenService secureTokenService = mock(); + private final CredentialServiceUrlResolver credentialServiceUrlResolver = mock(); + private final CredentialServiceClient credentialServiceClient = mock(); + private final VerifiablePresentationCache cache = mock(); + private final Monitor monitor = mock(); + + private final CachePresentationRequestService service = new CachePresentationRequestService(secureTokenService, + credentialServiceUrlResolver, credentialServiceClient, cache, monitor); + + @Test + void requestPresentation_cachedEntryPresent_returnFromCache() { + StoreResult> cacheResult = StoreResult.success(); + when(cache.query(participantContextId, counterPartyDid, scopes)).thenReturn(cacheResult); + + var result = service.requestPresentation(participantContextId, ownDid, counterPartyDid, counterPartyToken, scopes); + + assertThat(result).isSucceeded(); + assertThat(result.getContent()).isEqualTo(cacheResult.getContent()); + + verifyNoInteractions(secureTokenService); + verifyNoInteractions(credentialServiceUrlResolver); + verifyNoInteractions(credentialServiceClient); + } + + @Test + void requestPresentation_noCachedEntryPresent_requestAndStoreInCache() { + var credentialServiceUrl = "http://url"; + + when(cache.query(participantContextId, counterPartyDid, scopes)).thenReturn(StoreResult.notFound("404")); + when(cache.store(any(), any(), any(), any())).thenReturn(StoreResult.success()); + when(secureTokenService.createToken(any(), any(), any())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().build())); + when(credentialServiceUrlResolver.resolve(any())).thenReturn(Result.success(credentialServiceUrl)); + when(credentialServiceClient.requestPresentation(any(), any(), isA(List.class))).thenReturn(Result.success(List.of())); + + var result = service.requestPresentation(participantContextId, ownDid, counterPartyDid, counterPartyToken, scopes); + + assertThat(result).isSucceeded(); + + verify(secureTokenService).createToken(eq(participantContextId), any(), eq(null)); + verify(credentialServiceUrlResolver).resolve(counterPartyDid); + verify(credentialServiceClient).requestPresentation(eq(credentialServiceUrl), any(), eq(scopes)); + verify(cache).store(eq(participantContextId), eq(counterPartyDid), eq(scopes), eq(result.getContent())); + } + + @Test + void requestPresentation_storingInCacheFails_logWarning() { + var credentialServiceUrl = "http://url"; + + when(cache.query(participantContextId, counterPartyDid, scopes)).thenReturn(StoreResult.notFound("404")); + when(cache.store(any(), any(), any(), any())).thenReturn(StoreResult.generalError("error")); + when(secureTokenService.createToken(any(), any(), any())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().build())); + when(credentialServiceUrlResolver.resolve(any())).thenReturn(Result.success(credentialServiceUrl)); + when(credentialServiceClient.requestPresentation(any(), any(), isA(List.class))).thenReturn(Result.success(List.of())); + + var result = service.requestPresentation(participantContextId, ownDid, counterPartyDid, counterPartyToken, scopes); + + assertThat(result).isSucceeded(); + + verify(secureTokenService).createToken(eq(participantContextId), any(), eq(null)); + verify(credentialServiceUrlResolver).resolve(counterPartyDid); + verify(credentialServiceClient).requestPresentation(eq(credentialServiceUrl), any(), eq(scopes)); + verify(cache).store(eq(participantContextId), eq(counterPartyDid), eq(scopes), eq(result.getContent())); + verify(monitor).warning(anyString()); + } +} diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImplTest.java b/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImplTest.java new file mode 100644 index 0000000000..7fb61c00ab --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImplTest.java @@ -0,0 +1,252 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.iam.dcp.cache; + +import org.eclipse.edc.iam.verifiablecredentials.spi.VerifiableCredentialValidationService; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.RevocationServiceRegistry; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentation; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.StoreResult; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCacheEntry; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCacheStore; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.Clock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.function.UnaryOperator; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.eclipse.edc.spi.result.StoreFailure.Reason.NOT_FOUND; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class VerifiablePresentationCacheImplTest { + + private final String participantContextId = "participantContextId"; + private final String ownDid = "did:web:me"; + private final String counterPartyDid = "did:web:other"; + private final String credentialType = "SomeCredential"; + private final List scopes = List.of(credentialType + ":read"); + private final VerifiablePresentationContainer vpContainer = mock(); + private final VerifiablePresentation vp = mock(); + private final VerifiableCredential vc = mock(); + private final Instant cachedAt = mock(); + private final Instant expiresAt = mock(); + private final Instant vcIssuedAt = mock(); + private final Instant vcExpiresAt = mock(); + + private final int cacheValidity = 123; + private final Clock clock = mock(); + private final VerifiablePresentationCacheStore store = mock(); + private final VerifiableCredentialValidationService validationService = mock(); + private final UnaryOperator didResolver = pcId -> ownDid; + private final RevocationServiceRegistry revocationServiceRegistry = mock(); + + private final VerifiablePresentationCacheImpl cache = new VerifiablePresentationCacheImpl(cacheValidity, + clock, store, validationService, didResolver, revocationServiceRegistry, mock()); + + @BeforeEach + void setUp() { + when(vpContainer.presentation()).thenReturn(vp); + when(vp.getHolder()).thenReturn(counterPartyDid); + when(vp.getCredentials()).thenReturn(List.of(vc)); + when(vc.getType()).thenReturn(List.of(credentialType)); + when(vc.getIssuanceDate()).thenReturn(vcIssuedAt); + when(vc.getExpirationDate()).thenReturn(vcExpiresAt); + + when(cachedAt.plus(anyLong(), eq(ChronoUnit.SECONDS))).thenReturn(expiresAt); + when(expiresAt.isBefore(any())).thenReturn(false); + when(vcIssuedAt.isAfter(any())).thenReturn(false); + when(vcExpiresAt.isBefore(any())).thenReturn(false); + + when(store.remove(participantContextId, counterPartyDid, scopes)).thenReturn(StoreResult.success()); + when(validationService.validate(eq(List.of(vpContainer)), eq(ownDid), eq(emptyList()))).thenReturn(Result.success()); + when(revocationServiceRegistry.checkValidity(vc)).thenReturn(Result.success()); + } + + @Nested + public class Store { + @Test + void store_validPresentations_returnSuccess() { + when(store.store(any())).thenReturn(StoreResult.success()); + + var result = cache.store(participantContextId, counterPartyDid, scopes, List.of(vpContainer)); + + assertThat(result).isSucceeded(); + } + + @Test + void store_credentialsDoNotMatchScopes_returnFailure() { + when(store.store(any())).thenReturn(StoreResult.success()); + when(vc.getType()).thenReturn(List.of("SomeOtherCredential")); + + var result = cache.store(participantContextId, counterPartyDid, scopes, List.of(vpContainer)); + + assertThat(result).isFailed(); + } + + @Test + void store_presentationIssuerInvalid_returnFailure() { + when(store.store(any())).thenReturn(StoreResult.success()); + when(vp.getHolder()).thenReturn("did:web:notTheSame"); + + var result = cache.store(participantContextId, counterPartyDid, scopes, List.of(vpContainer)); + + assertThat(result).isFailed(); + } + + @Test + void store_credentialValidationFails_returnFailure() { + when(store.store(any())).thenReturn(StoreResult.success()); + when(validationService.validate(eq(List.of(vpContainer)), eq(ownDid), eq(emptyList()))).thenReturn(Result.failure("error")); + + var result = cache.store(participantContextId, counterPartyDid, scopes, List.of(vpContainer)); + + assertThat(result).isFailed(); + } + + @Test + void store_storingFails_returnFailure() { + when(store.store(any())).thenReturn(StoreResult.generalError("error")); + + var result = cache.store(participantContextId, counterPartyDid, scopes, List.of(vpContainer)); + + assertThat(result).isFailed(); + } + } + + @Nested + public class Query { + @Test + void query_validCachedEntry_returnFromStore() { + var entry = cacheEntry(); + var storeResult = StoreResult.success(entry); + when(store.query(participantContextId, counterPartyDid, scopes)).thenReturn(storeResult); + + var result = cache.query(participantContextId, counterPartyDid, scopes); + + assertThat(result).isSucceeded(); + assertThat(result.getContent()) + .hasSize(1) + .isEqualTo(entry.getPresentations()); + } + + @Test + void query_noCachedEntry_returnNotFound() { + StoreResult storeResult = StoreResult.notFound("404"); + when(store.query(participantContextId, counterPartyDid, scopes)).thenReturn(storeResult); + + var result = cache.query(participantContextId, counterPartyDid, scopes); + + assertThat(result).isFailed(); + assertThat(result.getFailure().getReason()).isEqualTo(NOT_FOUND); + } + + @Test + void query_errorQueryingCacheStore_returnNotFound() { + StoreResult storeResult = StoreResult.generalError("error"); + when(store.query(participantContextId, counterPartyDid, scopes)).thenReturn(storeResult); + + var result = cache.query(participantContextId, counterPartyDid, scopes); + + assertThat(result).isFailed(); + assertThat(result.getFailure().getReason()).isEqualTo(NOT_FOUND); + } + + @Test + void query_cachedEntryExpired_returnNotFound() { + var storeResult = StoreResult.success(cacheEntry()); + when(store.query(participantContextId, counterPartyDid, scopes)).thenReturn(storeResult); + when(expiresAt.isBefore(any())).thenReturn(true); + + var result = cache.query(participantContextId, counterPartyDid, scopes); + + assertThat(result).isFailed(); + assertThat(result.getFailure().getReason()).isEqualTo(NOT_FOUND); + + verify(store).remove(participantContextId, counterPartyDid, scopes); + } + + @Test + void query_cachedCredentialExpired_returnNotFound() { + var storeResult = StoreResult.success(cacheEntry()); + when(store.query(participantContextId, counterPartyDid, scopes)).thenReturn(storeResult); + when(vcExpiresAt.isBefore(any())).thenReturn(true); + + var result = cache.query(participantContextId, counterPartyDid, scopes); + + assertThat(result).isFailed(); + assertThat(result.getFailure().getReason()).isEqualTo(NOT_FOUND); + + verify(store).remove(participantContextId, counterPartyDid, scopes); + } + + @Test + void query_cachedCredentialRevoked_returnNotFound() { + var storeResult = StoreResult.success(cacheEntry()); + when(store.query(participantContextId, counterPartyDid, scopes)).thenReturn(storeResult); + when(revocationServiceRegistry.checkValidity(vc)).thenReturn(Result.failure("revoked")); + + var result = cache.query(participantContextId, counterPartyDid, scopes); + + assertThat(result).isFailed(); + assertThat(result.getFailure().getReason()).isEqualTo(NOT_FOUND); + + verify(store).remove(participantContextId, counterPartyDid, scopes); + } + } + + @Nested + public class Remove { + @Test + void remove_removingFromStoreSuccessful_returnSuccess() { + when(store.remove(any(), any())).thenReturn(StoreResult.success()); + + var result = cache.remove(participantContextId, counterPartyDid); + + assertThat(result).isSucceeded(); + } + + @Test + void remove_removingFromStoreFails_returnSuccess() { + when(store.remove(any(), any())).thenReturn(StoreResult.generalError("error")); + + var result = cache.remove(participantContextId, counterPartyDid); + + assertThat(result).isFailed(); + } + } + + private VerifiablePresentationCacheEntry cacheEntry() { + return new VerifiablePresentationCacheEntry(participantContextId, counterPartyDid, scopes, List.of(vpContainer), cachedAt); + } +} diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cache/store/InMemoryVerifiablePresentationCacheStoreTest.java b/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cache/store/InMemoryVerifiablePresentationCacheStoreTest.java new file mode 100644 index 0000000000..83f1ae9e03 --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cache/store/InMemoryVerifiablePresentationCacheStoreTest.java @@ -0,0 +1,202 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.iam.dcp.cache.store; + +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.tractusx.edc.spi.dcp.VerifiablePresentationCacheEntry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import static java.util.Collections.reverse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.eclipse.edc.spi.result.StoreFailure.Reason.NOT_FOUND; +import static org.mockito.Mockito.mock; + +class InMemoryVerifiablePresentationCacheStoreTest { + + private final String participantContextId = "participantContextId"; + private final String counterPartyDid = "did:web:other"; + private final List scopes = List.of("scope1", "scope2"); + private final VerifiablePresentationContainer vp = mock(); + private final Instant cachedAt = mock(); + + private InMemoryVerifiablePresentationCacheStore store; + + @BeforeEach + void setUp() { + store = new InMemoryVerifiablePresentationCacheStore(); + } + + @Nested + public class Store { + @Test + void shouldStoreEntry() { + var entry = cacheEntry(); + + var storeResult = store.store(entry); + assertThat(storeResult).isSucceeded(); + + var queryResult = store.query(participantContextId, counterPartyDid, scopes); + assertThat(queryResult).isSucceeded(); + assertThat(queryResult.getContent()).isEqualTo(entry); + } + + @Test + void shouldOverrideEntry_whenIdAlreadyExists() { + var entry1 = cacheEntry(); + store.store(entry1); + + var queryResult = store.query(participantContextId, counterPartyDid, scopes); + assertThat(queryResult).isSucceeded(); + assertThat(queryResult.getContent()).isEqualTo(entry1); + + var entry2 = cacheEntry(List.of()); + store.store(entry2); + + queryResult = store.query(participantContextId, counterPartyDid, scopes); + assertThat(queryResult).isSucceeded(); + assertThat(queryResult.getContent()) + .isEqualTo(entry2) + .isNotEqualTo(entry1); + } + } + + @Nested + public class Query { + @Test + void shouldReturnEntry() { + var entry = cacheEntry(); + store.store(entry); + + var queryResult = store.query(participantContextId, counterPartyDid, scopes); + + assertThat(queryResult).isSucceeded(); + assertThat(queryResult.getContent()).isEqualTo(entry); + } + + @Test + void shouldReturnEntry_whenScopesInDifferentOrder() { + var scopeList = new ArrayList(); + scopeList.add("scope1"); + scopeList.add("scope2"); + var entry = new VerifiablePresentationCacheEntry(participantContextId, counterPartyDid, scopeList, List.of(vp), cachedAt); + store.store(entry); + + reverse(scopeList); + var queryResult = store.query(participantContextId, counterPartyDid, scopeList); + + assertThat(queryResult).isSucceeded(); + assertThat(queryResult.getContent()).isEqualTo(entry); + } + + @Test + void shouldReturnNotFound_whenEntryNotFound() { + var queryResult = store.query(participantContextId, counterPartyDid, scopes); + + assertThat(queryResult).isFailed(); + assertThat(queryResult.getFailure().getReason()).isEqualTo(NOT_FOUND); + } + } + + @Nested + public class Remove { + @Test + void shouldRemoveSingleEntry() { + store.store(cacheEntry()); + + store.remove(participantContextId, counterPartyDid, scopes); + + var queryResult = store.query(participantContextId, counterPartyDid, scopes); + assertThat(queryResult).isFailed(); + assertThat(queryResult.getFailure().getReason()).isEqualTo(NOT_FOUND); + } + + @Test + void shouldRemoveAllEntriesForParticipant() { + var otherScope = "another-scope"; + var entry1 = cacheEntry(); + var entry2 = cacheEntry(otherScope); + store.store(entry1); + store.store(entry2); + + store.remove(participantContextId, counterPartyDid); + + var queryResult1 = store.query(participantContextId, counterPartyDid, scopes); + assertThat(queryResult1).isFailed(); + assertThat(queryResult1.getFailure().getReason()).isEqualTo(NOT_FOUND); + + var queryResult2 = store.query(participantContextId, counterPartyDid, List.of(otherScope)); + assertThat(queryResult2).isFailed(); + assertThat(queryResult2.getFailure().getReason()).isEqualTo(NOT_FOUND); + } + + @Test + void shouldRemoveEntriesOnlyForParticipant() { + var otherScope = "another-scope"; + store.store(cacheEntry()); + store.store(cacheEntry(otherScope)); + + var otherParticipant = "did:web:different"; + var otherParticipantEntry = new VerifiablePresentationCacheEntry(participantContextId, otherParticipant, scopes, List.of(vp), cachedAt); + store.store(otherParticipantEntry); + + store.remove(participantContextId, counterPartyDid); + + var queryResult = store.query(participantContextId, otherParticipant, scopes); + assertThat(queryResult).isSucceeded(); + assertThat(queryResult.getContent()).isEqualTo(otherParticipantEntry); + } + + @Test + void shouldRemoveEntriesOnlyForSameParticpantContext() { + var otherScope = "another-scope"; + store.store(cacheEntry()); + store.store(cacheEntry(otherScope)); + + var otherParticipantContext = "other-participant-context"; + var otherParticipantContextEntry = new VerifiablePresentationCacheEntry(otherParticipantContext, counterPartyDid, scopes, List.of(vp), cachedAt); + store.store(otherParticipantContextEntry); + + store.remove(participantContextId, counterPartyDid); + + var queryResult = store.query(otherParticipantContext, counterPartyDid, scopes); + assertThat(queryResult).isSucceeded(); + assertThat(queryResult.getContent()).isEqualTo(otherParticipantContextEntry); + } + } + + private VerifiablePresentationCacheEntry cacheEntry() { + return new VerifiablePresentationCacheEntry(participantContextId, counterPartyDid, scopes, List.of(vp), cachedAt); + } + + private VerifiablePresentationCacheEntry cacheEntry(String scope) { + return new VerifiablePresentationCacheEntry(participantContextId, counterPartyDid, List.of(scope), List.of(vp), cachedAt); + } + + private VerifiablePresentationCacheEntry cacheEntry(List presentations) { + return new VerifiablePresentationCacheEntry(participantContextId, counterPartyDid, scopes, presentations, cachedAt); + } +} diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java index e9ba3241fe..a659dbb60f 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java +++ b/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java @@ -1,6 +1,7 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * Copyright (c) 2025 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * Copyright (c) 2025 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -26,6 +27,7 @@ import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialFormat; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialStatus; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredentialContainer; import org.eclipse.edc.identityhub.spi.verifiablecredentials.model.VerifiableCredentialResource; @@ -37,7 +39,10 @@ import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.StatusList2021; import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.StsParticipant; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -46,6 +51,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; @@ -67,6 +73,7 @@ import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkPolicy; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public abstract class AbstractIatpConsumerPullTest extends ConsumerPullBaseTest { protected static final StsParticipant STS = StsParticipant.Builder.newInstance() @@ -77,6 +84,7 @@ public abstract class AbstractIatpConsumerPullTest extends ConsumerPullBaseTest @DisplayName("Contract policy is fulfilled") @ParameterizedTest(name = "{1}") @ArgumentsSource(ValidContractPolicyProvider.class) + @Order(5) void transferData_whenContractPolicyFulfilled(JsonObject contractPolicy, String description) { var assetId = "api-asset-1"; @@ -131,6 +139,7 @@ void transferData_whenContractPolicyFulfilled(JsonObject contractPolicy, String @DisplayName("Expect the Catalog request to fail if a credential is expired") @Test + @Order(1) void catalogRequest_whenCredentialExpired() { //update the membership credential to an expirationDate that is in the past var store = credentialStoreRuntime().getService(CredentialStore.class); @@ -180,6 +189,7 @@ void catalogRequest_whenCredentialExpired() { @DisplayName("Expect the Catalog request to fail if a credential is revoked") @Test + @Order(2) void catalogRequest_whenCredentialRevoked() { //update the membership credential to contain a `credentialStatus` with a revocation var store = credentialStoreRuntime().getService(CredentialStore.class); @@ -250,6 +260,80 @@ void catalogRequest_whenCredentialRevoked() { } } + @DisplayName("Expect the Catalog request to fail if a credential has an invalid credential subject id") + @Test + @Order(3) + void catalogRequest_whenCredentialSubjectIdIsInvalid() { + //update the membership credential to an expirationDate that is in the past + var store = credentialStoreRuntime().getService(CredentialStore.class); + + var existingCred = store.query(QuerySpec.Builder.newInstance() + .filter(new Criterion("verifiableCredential.credential.type", "contains", "MembershipCredential")).build()) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())) + .stream().filter(it -> it.getHolderId().equals(consumer().getBpn())) + .findFirst().orElseThrow(RuntimeException::new); + + var credentialSubject = CredentialSubject.Builder.newInstance() + .id("did:web:invalid") + .claim("holderIdentifier", "invalid") + .build(); + var newCred = VerifiableCredential.Builder.newInstance() + .id(existingCred.getVerifiableCredential().credential().getId()) + .types(existingCred.getVerifiableCredential().credential().getType()) + .credentialSubjects(List.of(credentialSubject)) + .issuer(existingCred.getVerifiableCredential().credential().getIssuer()) + .issuanceDate(existingCred.getVerifiableCredential().credential().getIssuanceDate()) + .build(); + + var did = consumer().getDid(); + var bpn = consumer().getBpn(); + var newRawVc = dataspaceIssuer().membershipRawVc(did, bpn).build(); + + var newVcString = dataspaceIssuer().createJwtVc(newRawVc, did); + + store.update(VerifiableCredentialResource.Builder.newInstance() + .id(existingCred.getId()) + .issuerId(dataspaceIssuer().didUrl()) + .holderId(bpn) + .credential(new VerifiableCredentialContainer(newVcString, CredentialFormat.VC1_0_JWT, newCred)) + .build()) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); + + try { + consumer().getCatalog(provider()) + .log().ifError() + .statusCode(502); + } finally { + // restore the original credential + store.update(existingCred); + } + } + + @DisplayName("Expect the Catalog request to fail if a requested credential is missing") + @Test + @Order(4) + void catalogRequest_whenRequestedCredentialMissing() { + //update the membership credential to an expirationDate that is in the past + var store = credentialStoreRuntime().getService(CredentialStore.class); + + var existingCred = store.query(QuerySpec.Builder.newInstance() + .filter(new Criterion("verifiableCredential.credential.type", "contains", "MembershipCredential")).build()) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())) + .stream().filter(it -> it.getHolderId().equals(consumer().getBpn())) + .findFirst().orElseThrow(RuntimeException::new); + + store.deleteById(existingCred.getId()); + + try { + consumer().getCatalog(provider()) + .log().ifError() + .statusCode(502); + } finally { + // restore the original credential + store.create(existingCred); + } + } + @Override protected JsonObject createContractPolicy(String bpn) { return frameworkPolicy(Map.of(CX_POLICY_2025_09_NS + "Membership", "active"), CX_POLICY_2025_09_NS + "access"); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 77d89df135..b4055577b0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -97,6 +97,7 @@ edc-ext-http = { module = "org.eclipse.edc:http", version.ref = "edc" } edc-ext-jsonld = { module = "org.eclipse.edc:json-ld", version.ref = "edc" } edc-validator-data-address-http-data = { module = "org.eclipse.edc:validator-data-address-http-data", version.ref = "edc" } edc-runtime-metamodel = { module = "org.eclipse.edc:runtime-metamodel", version.ref = "edc" } +edc-verifiable-credentials = { module = "org.eclipse.edc:verifiable-credentials", version.ref = "edc" } # EDC lib dependencies edc-lib-api = { module = "org.eclipse.edc:api-lib", version.ref = "edc" } @@ -115,6 +116,7 @@ edc-lib-sql = { module = "org.eclipse.edc:sql-lib", version.ref = "edc" } edc-lib-dsp-catalog-http-api= { module = "org.eclipse.edc:dsp-catalog-http-api-lib", version.ref = "edc" } edc-lib-dsp-negotiation-http-api= { module = "org.eclipse.edc:dsp-negotiation-http-api-lib", version.ref = "edc" } edc-lib-dsp-transfer-http-api= { module = "org.eclipse.edc:dsp-transfer-process-http-api-lib", version.ref = "edc" } +edc-lib-dcp = { module = "org.eclipse.edc:decentralized-claims-lib", version.ref = "edc" } # implementations edc-sql-assetindex = { module = "org.eclipse.edc:asset-index-sql", version.ref = "edc" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2fa6510fd9..40622a9aac 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,6 +37,7 @@ include(":spi:core-spi") include(":spi:tokenrefresh-spi") include(":spi:bdrs-client-spi") include(":spi:dataflow-spi") +include(":spi:dcp-spi") // core modules @@ -66,6 +67,7 @@ include(":edc-extensions:cx-policy") include(":edc-extensions:cx-policy-legacy") include(":edc-extensions:dcp:tx-dcp") include(":edc-extensions:dcp:tx-dcp-sts-dim") +include(":edc-extensions:dcp:verifiable-presentation-cache") include(":edc-extensions:data-flow-properties-provider") include(":edc-extensions:validators:empty-asset-selector") include(":edc-extensions:log4j2-monitor") diff --git a/spi/dcp-spi/build.gradle.kts b/spi/dcp-spi/build.gradle.kts new file mode 100644 index 0000000000..2019d96338 --- /dev/null +++ b/spi/dcp-spi/build.gradle.kts @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` + `maven-publish` +} + +dependencies { + api(libs.edc.spi.core) + api(libs.edc.spi.decentralized.claims) +} diff --git a/spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCache.java b/spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCache.java new file mode 100644 index 0000000000..ff8283a345 --- /dev/null +++ b/spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCache.java @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.spi.dcp; + +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; +import org.eclipse.edc.spi.result.StoreResult; + +import java.util.List; + +/** + * A cache for Verifiable Presentations (VP), so that they can be reused during the DCP presentation + * flow after initial request. As a VP is always requested for a specific participant and a + * specific set of scopes, both of these need to be used for caching. Additionally, the ID of the + * participant context is passed through to the cache for cases where multiple participant contexts + * may be used within a connector. + */ +public interface VerifiablePresentationCache { + + /** + * Stores a new entry in the cache. This method should also make sure that received VPs and VCs + * are valid before caching them to ensure that no invalid entries are cached. + * + * @param participantContextId ID of the participant context + * @param counterPartyDid DID of the participant the VPs were requested for + * @param scopes scopes used for the presentation request + * @param presentations the VPs to cache + * @return successful result, if the VPs were stored in the cache; failed result otherwise + */ + StoreResult store(String participantContextId, String counterPartyDid, List scopes, List presentations); + + /** + * Queries the cache for an existing entry for a given participant context, participant and set + * of scopes. This method should also make sure to check any VCs for expiry or revocation + * before returning them to ensure that no invalid entries are returned from the cache. + * + * @param participantContextId ID of the participant context + * @param counterPartyDid DID of the participant to request the VPs for + * @param scopes scopes to request + * @return successful result containing the cached entry, if present; failed result otherwise + */ + StoreResult> query(String participantContextId, String counterPartyDid, List scopes); + + /** + * Removes all cached entries for a given participant context and participant. + * + * @param participantContextId ID of the participant context + * @param counterPartyDid DID of the participant for which the cached entries should be deleted + * @return successful result, if all entries were deleted; failed result otherwise + */ + StoreResult remove(String participantContextId, String counterPartyDid); +} diff --git a/spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCacheEntry.java b/spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCacheEntry.java new file mode 100644 index 0000000000..262e42bceb --- /dev/null +++ b/spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCacheEntry.java @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.spi.dcp; + +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiablePresentationContainer; + +import java.time.Instant; +import java.util.List; + +/** + * Model class for an entry in the {@link VerifiablePresentationCacheStore}. Comprises all + * information to be cached including an instant when the cache entry was created. + */ +public class VerifiablePresentationCacheEntry { + + private final String participantContextId; + private final String counterPartyDid; + private final List scopes; + private final List presentations; + private final Instant cachedAt; + + public VerifiablePresentationCacheEntry(String participantContextId, String counterPartyDid, List scopes, + List presentations, Instant cachedAt) { + this.participantContextId = participantContextId; + this.counterPartyDid = counterPartyDid; + this.scopes = scopes; + this.presentations = presentations; + this.cachedAt = cachedAt; + } + + public String getParticipantContextId() { + return participantContextId; + } + + public String getCounterPartyDid() { + return counterPartyDid; + } + + public List getScopes() { + return scopes; + } + + public Instant getCachedAt() { + return cachedAt; + } + + public List getPresentations() { + return presentations; + } + +} diff --git a/spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCacheStore.java b/spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCacheStore.java new file mode 100644 index 0000000000..bf68848b1d --- /dev/null +++ b/spi/dcp-spi/src/main/java/org/eclipse/tractusx/edc/spi/dcp/VerifiablePresentationCacheStore.java @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2025 Cofinity-X GmbH + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.spi.dcp; + +import org.eclipse.edc.spi.result.StoreResult; + +import java.util.List; + +/** + * Store for the {@link VerifiablePresentationCache} to decouple the persistence layer from common + * cache behaviour, to allow e.g. utilizing external cache solutions. The ID used for storing + * entries needs to be a compound ID comprising participant context ID, participant DID and scopes + * from the {@link VerifiablePresentationCacheEntry}. + */ +public interface VerifiablePresentationCacheStore { + + /** + * Stores a new entry in the cache. If an entry already exists for the given participant context + * ID, participant DID and scopes, the entry is overridden. + * + * @param entry the entry to cache + * @return successful result, if the entry was stored; failed result otherwise + */ + StoreResult store(VerifiablePresentationCacheEntry entry); + + /** + * Queries the store for an existing entry. + * + * @param participantContextId ID of the participant context + * @param counterPartyDid DID of the participant + * @param scopes scopes + * @return successful result containing the found entry, if present; failed result otherwise + */ + StoreResult query(String participantContextId, String counterPartyDid, List scopes); + + /** + * Removes a single entry for the given participant context ID, participant DID and scopes. + * + * @param participantContextId ID of the participant context + * @param counterPartyDid DID of the participant + * @param scopes scopes + * @return successful result, if the entry was deleted; failed result otherwise + */ + StoreResult remove(String participantContextId, String counterPartyDid, List scopes); + + /** + * Removes all entries for a given participant context ID and participant DID. + * + * @param participantContextId ID of the participant context + * @param counterPartyDid DID of the participant + * @return successful result, if all entries were deleted; failed result otherwise + */ + StoreResult remove(String participantContextId, String counterPartyDid); + +} From 8b41624f388d626b18c05452346623406e94f956 Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Fri, 19 Dec 2025 17:37:47 +0100 Subject: [PATCH 057/112] Add all relevnt versions of openapi specs to api-hub (#2484) Signed-off-by: Lars Geyer-Blaumeiser --- .tractusx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.tractusx b/.tractusx index c037264fa1..c8b568d9ba 100644 --- a/.tractusx +++ b/.tractusx @@ -2,5 +2,11 @@ product: "Tractus-X EDC" leadingRepository: "https://github.com/eclipse-tractusx/tractusx-edc" repositories: [] openApiSpecs: -- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/control-plane-api/control-plane.yaml" -- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/data-plane-api/data-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/control-plane-api/0.11.2/control-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/data-plane-api/0.11.2/data-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/control-plane-api/0.10.2/control-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/data-plane-api/0.10.2/data-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/control-plane-api/0.9.0/control-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/data-plane-api/0.9.0/data-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/control-plane-api/0.8.1/control-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/data-plane-api/0.8.1/data-plane.yaml" From c7d71f0b871c78e34dc2effcc0f1e8cd5feb2704 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:19:13 +0100 Subject: [PATCH 058/112] chore(deps): bump mikefarah/yq (#2492) Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.49.2 to 4.50.1. - [Release notes](https://github.com/mikefarah/yq/releases) - [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt) - [Commits](https://github.com/mikefarah/yq/compare/v4.49.2...v4.50.1) --- updated-dependencies: - dependency-name: mikefarah/yq dependency-version: 4.50.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/update-version-and-charts/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/update-version-and-charts/action.yml b/.github/actions/update-version-and-charts/action.yml index aa7385dccb..05ef2ecb90 100644 --- a/.github/actions/update-version-and-charts/action.yml +++ b/.github/actions/update-version-and-charts/action.yml @@ -42,7 +42,7 @@ runs: fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Bump version in /charts - uses: mikefarah/yq@v4.49.2 + uses: mikefarah/yq@v4.50.1 with: cmd: | find charts -name Chart.yaml -maxdepth 3 | xargs -n1 yq -i '.appVersion = "${{ steps.resolver.outputs.version }}" From 5563059a6a542bac6fb785ae611f1d1ae1cdc85c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:19:43 +0100 Subject: [PATCH 059/112] chore(deps): bump trufflesecurity/trufflehog from 3.92.3 to 3.92.4 (#2491) Bumps [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) from 3.92.3 to 3.92.4. - [Release notes](https://github.com/trufflesecurity/trufflehog/releases) - [Commits](https://github.com/trufflesecurity/trufflehog/compare/05cccb53bc9e13bc6d17997db5a6bcc3df44bf2f...ef6e76c3c4023279497fab4721ffa071a722fd05) --- updated-dependencies: - dependency-name: trufflesecurity/trufflehog dependency-version: 3.92.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/secrets-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml index 9d3163f56c..3ca75ce772 100644 --- a/.github/workflows/secrets-scan.yml +++ b/.github/workflows/secrets-scan.yml @@ -46,7 +46,7 @@ jobs: - name: TruffleHog OSS id: trufflehog - uses: trufflesecurity/trufflehog@05cccb53bc9e13bc6d17997db5a6bcc3df44bf2f + uses: trufflesecurity/trufflehog@ef6e76c3c4023279497fab4721ffa071a722fd05 continue-on-error: true with: path: ./ # Scan the entire repository From a94239e228f63c94bd2faf38826b5dda9792cc03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:20:11 +0100 Subject: [PATCH 060/112] chore(deps): bump flyway from 11.19.0 to 11.19.1 (#2489) Bumps `flyway` from 11.19.0 to 11.19.1. Updates `org.flywaydb:flyway-core` from 11.19.0 to 11.19.1 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-11.19.0...flyway-11.19.1) Updates `org.flywaydb:flyway-database-postgresql` from 11.19.0 to 11.19.1 --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-version: 11.19.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.flywaydb:flyway-database-postgresql dependency-version: 11.19.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b4055577b0..fa1a6d0889 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-SNAPSHOT" dsp-tck = "1.0.0-RC4" -flyway = "11.19.0" +flyway = "11.19.1" jackson = "2.20.1" jakarta-json = "2.0.1" junit = "6.0.1" From 1b39092fc90e9ccd7402986dd9f1fb3117036e71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:20:51 +0100 Subject: [PATCH 061/112] chore(deps): bump checkmarx/kics-github-action from 2.1.17 to 2.1.18 (#2490) Bumps [checkmarx/kics-github-action](https://github.com/checkmarx/kics-github-action) from 2.1.17 to 2.1.18. - [Release notes](https://github.com/checkmarx/kics-github-action/releases) - [Commits](https://github.com/checkmarx/kics-github-action/compare/v2.1.17...v2.1.18) --- updated-dependencies: - dependency-name: checkmarx/kics-github-action dependency-version: 2.1.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/kics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/kics.yml b/.github/workflows/kics.yml index 66c01de2bc..1ae2c219e1 100644 --- a/.github/workflows/kics.yml +++ b/.github/workflows/kics.yml @@ -44,7 +44,7 @@ jobs: - uses: actions/checkout@v6 - name: KICS scan - uses: checkmarx/kics-github-action@v2.1.17 + uses: checkmarx/kics-github-action@v2.1.18 with: path: "." fail_on: high From d7da6ab4599f0b6b17541eb07952aaab9bb5bb02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:21:29 +0100 Subject: [PATCH 062/112] chore(deps): bump io.netty:netty-codec-http2 (#2488) Bumps [io.netty:netty-codec-http2](https://github.com/netty/netty) from 4.2.7.Final to 4.2.9.Final. - [Commits](https://github.com/netty/netty/compare/netty-4.2.7.Final...netty-4.2.9.Final) --- updated-dependencies: - dependency-name: io.netty:netty-codec-http2 dependency-version: 4.2.9.Final dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5c90a1808f..52b08ac1eb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -76,7 +76,7 @@ allprojects { implementation("com.azure:azure-core-http-netty:1.16.2") { because("Version 1.15.12 depends on netty libs that have two vulnerabilities: https://mvnrepository.com/artifact/com.azure/azure-core-http-netty/1.15.12") } - implementation("io.netty:netty-codec-http2:4.2.7.Final") { + implementation("io.netty:netty-codec-http2:4.2.9.Final") { because("Version 4.1.123.Final vulnerability: https://www.cve.org/CVERecord?id=CVE-2025-8916") } } From b89be7b42a27fc0cd1030404d386796987195385 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:21:55 +0100 Subject: [PATCH 063/112] chore(deps): bump io.rest-assured:rest-assured from 5.5.6 to 6.0.0 (#2487) Bumps [io.rest-assured:rest-assured](https://github.com/rest-assured/rest-assured) from 5.5.6 to 6.0.0. - [Changelog](https://github.com/rest-assured/rest-assured/blob/master/changelog.txt) - [Commits](https://github.com/rest-assured/rest-assured/compare/rest-assured-5.5.6...rest-assured-6.0.0) --- updated-dependencies: - dependency-name: io.rest-assured:rest-assured dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fa1a6d0889..a0748fca07 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ opentelemetry = "2.22.0" opentelemetry-instrumentation = "2.23.0" opentelemetry-log4j-appender = "2.22.0-alpha" postgres = "42.7.8" -restAssured = "5.5.6" +restAssured = "6.0.0" rsApi = "4.0.0" testcontainers = "2.0.2" testcontainers-keycloak = "4.0.1" From c095157b70561d9487211826e5f1594466ae3f45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:22:26 +0100 Subject: [PATCH 064/112] chore(deps): bump testcontainers from 2.0.2 to 2.0.3 (#2486) Bumps `testcontainers` from 2.0.2 to 2.0.3. Updates `org.testcontainers:testcontainers-junit-jupiter` from 2.0.2 to 2.0.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/2.0.2...2.0.3) Updates `org.testcontainers:testcontainers-minio` from 2.0.2 to 2.0.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/2.0.2...2.0.3) Updates `org.testcontainers:testcontainers-localstack` from 2.0.2 to 2.0.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/2.0.2...2.0.3) Updates `org.testcontainers:testcontainers-postgresql` from 2.0.2 to 2.0.3 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/2.0.2...2.0.3) --- updated-dependencies: - dependency-name: org.testcontainers:testcontainers-junit-jupiter dependency-version: 2.0.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:testcontainers-minio dependency-version: 2.0.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:testcontainers-localstack dependency-version: 2.0.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.testcontainers:testcontainers-postgresql dependency-version: 2.0.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a0748fca07..f971cb6361 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ opentelemetry-log4j-appender = "2.22.0-alpha" postgres = "42.7.8" restAssured = "6.0.0" rsApi = "4.0.0" -testcontainers = "2.0.2" +testcontainers = "2.0.3" testcontainers-keycloak = "4.0.1" titanium = "1.7.0" log4j2 = "2.25.2" From b2c33cb1a0b834c00aca06f5f1b0773922ebe910 Mon Sep 17 00:00:00 2001 From: pratapipatelbcone Date: Mon, 22 Dec 2025 13:53:22 +0530 Subject: [PATCH 065/112] feat: Use Prefix Monitor to inject class name in logs (#2482) * feat: Use Prefix Monitor to inject class name in logs * fix: Fix failing component and API tests * fix: Remove unused import --- .../edc/edr/core/service/EdrServiceImpl.java | 2 +- .../edr/core/service/EdrServiceImplTest.java | 6 +++++- .../EventContractNegotiationSubscriber.java | 2 +- .../EventContractNegotiationSubscriberTest.java | 2 ++ .../v3/AgreementsRetirementApiV3Controller.java | 2 +- .../edc/identity/mapper/BdrsClientImpl.java | 2 +- .../mapper/BdrsClientImplExtensionTest.java | 2 ++ .../edc/identity/mapper/BdrsClientImplTest.java | 3 ++- .../v1/BusinessPartnerGroupApiV1Controller.java | 2 +- ...BusinessPartnerGroupApiV1ControllerTest.java | 7 ++++++- .../BusinessPartnerGroupLegacyFunction.java | 2 +- .../api/v4alpha/DataFlowApiController.java | 2 +- .../api/v4alpha/DataFlowApiControllerTest.java | 2 ++ .../pipeline/ProxyHttpDataSourceFactory.java | 2 +- .../ProxyHttpDataSourceFactoryTest.java | 2 ++ .../api/TokenBasedAuthenticationService.java | 2 +- .../asset/ConsumerAssetRequestController.java | 2 +- .../core/DataPlaneTokenRefreshServiceImpl.java | 2 +- ...aneTokenRefreshServiceImplComponentTest.java | 6 +++++- .../DataPlaneTokenRefreshServiceImplTest.java | 17 +++++++++++++---- .../iam/dcp/sts/dim/DimSecureTokenService.java | 2 +- .../dcp/sts/dim/oauth/DimOauthClientImpl.java | 2 +- .../sts/dim/oauth/DimOauthClientImplTest.java | 7 +++++++ .../iatp/scope/CredentialScopeExtractor.java | 2 +- .../scope/CredentialScopeExtractorTest.java | 3 +++ .../edc/api/edr/BaseEdrCacheApiController.java | 2 +- .../api/edr/v2/EdrCacheApiV2ControllerTest.java | 7 ++++++- .../api/edr/v3/EdrCacheApiV3ControllerTest.java | 7 ++++++- .../callback/ContractNegotiationCallback.java | 2 +- .../ContractNegotiationCallbackTest.java | 2 ++ .../eventsubscriber/EventLoggingSubscriber.java | 2 +- .../EventLoggingSubscriberTest.java | 1 + .../FileBasedTargetNodeDirectory.java | 2 +- .../FileBasedTargetNodeDirectoryTest.java | 3 +++ .../NonFiniteCapablePipelineService.java | 2 +- ...teCapablePipelineServiceIntegrationTest.java | 11 ++++++++++- .../NonFiniteCapablePipelineServiceTest.java | 5 ++++- .../InMemorySingleParticipantVault.java | 2 +- .../tokenrefresh/TokenRefreshHandlerImpl.java | 2 +- .../TokenRefreshHandlerImplTest.java | 5 ++++- .../tractusx/edc/mock/ResponseQueue.java | 2 +- 41 files changed, 107 insertions(+), 35 deletions(-) diff --git a/core/edr-core/src/main/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImpl.java b/core/edr-core/src/main/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImpl.java index d9a175de55..8acf107f46 100644 --- a/core/edr-core/src/main/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImpl.java +++ b/core/edr-core/src/main/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImpl.java @@ -45,7 +45,7 @@ public EdrServiceImpl(EndpointDataReferenceStore edrStore, TokenRefreshHandler t this.edrStore = edrStore; this.tokenRefreshHandler = tokenRefreshHandler; this.transactionContext = transactionContext; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.edrLock = edrLock; } diff --git a/core/edr-core/src/test/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImplTest.java b/core/edr-core/src/test/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImplTest.java index 1368ea4f8e..1f801ed050 100644 --- a/core/edr-core/src/test/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImplTest.java +++ b/core/edr-core/src/test/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImplTest.java @@ -22,6 +22,7 @@ import org.assertj.core.api.Assertions; import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore; import org.eclipse.edc.edr.spi.types.EndpointDataReferenceEntry; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.result.StoreResult; @@ -43,6 +44,7 @@ import static org.eclipse.tractusx.edc.edr.spi.types.RefreshMode.FORCE_REFRESH; import static org.eclipse.tractusx.edc.edr.spi.types.RefreshMode.NO_REFRESH; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -55,12 +57,14 @@ class EdrServiceImplTest { private final TokenRefreshHandler tokenRefreshHandler = mock(); private final EndpointDataReferenceStore edrStore = mock(); + private final Monitor monitor = mock(); private final EndpointDataReferenceLock edrLock = mock(); private EdrServiceImpl edrService; @BeforeEach void setup() { - edrService = new EdrServiceImpl(edrStore, tokenRefreshHandler, new NoopTransactionContext(), mock(), edrLock); + when(monitor.withPrefix(anyString())).thenReturn(monitor); + edrService = new EdrServiceImpl(edrStore, tokenRefreshHandler, new NoopTransactionContext(), monitor, edrLock); } @Test diff --git a/edc-extensions/agreements-bpns/bpns-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/EventContractNegotiationSubscriber.java b/edc-extensions/agreements-bpns/bpns-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/EventContractNegotiationSubscriber.java index f57b580432..c580be3232 100644 --- a/edc-extensions/agreements-bpns/bpns-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/EventContractNegotiationSubscriber.java +++ b/edc-extensions/agreements-bpns/bpns-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/EventContractNegotiationSubscriber.java @@ -36,7 +36,7 @@ public class EventContractNegotiationSubscriber implements EventSubscriber { public EventContractNegotiationSubscriber(AgreementsBpnsStore store, Monitor monitor, BdrsClient bdrsClient) { this.store = store; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.bdrsClient = bdrsClient; } diff --git a/edc-extensions/agreements-bpns/bpns-evaluation-core/src/test/java/org/eclipse/tractusx/edc/agreements/bpns/EventContractNegotiationSubscriberTest.java b/edc-extensions/agreements-bpns/bpns-evaluation-core/src/test/java/org/eclipse/tractusx/edc/agreements/bpns/EventContractNegotiationSubscriberTest.java index 6be63b2900..98caf2f427 100644 --- a/edc-extensions/agreements-bpns/bpns-evaluation-core/src/test/java/org/eclipse/tractusx/edc/agreements/bpns/EventContractNegotiationSubscriberTest.java +++ b/edc-extensions/agreements-bpns/bpns-evaluation-core/src/test/java/org/eclipse/tractusx/edc/agreements/bpns/EventContractNegotiationSubscriberTest.java @@ -39,6 +39,7 @@ import java.util.UUID; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -53,6 +54,7 @@ class EventContractNegotiationSubscriberTest { @BeforeEach void setup() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); subscriber = new EventContractNegotiationSubscriber(store, monitor, bdrsClient); } diff --git a/edc-extensions/agreements/retirement-evaluation-api/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/api/v3/AgreementsRetirementApiV3Controller.java b/edc-extensions/agreements/retirement-evaluation-api/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/api/v3/AgreementsRetirementApiV3Controller.java index 977fe33314..840fa122e3 100644 --- a/edc-extensions/agreements/retirement-evaluation-api/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/api/v3/AgreementsRetirementApiV3Controller.java +++ b/edc-extensions/agreements/retirement-evaluation-api/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/api/v3/AgreementsRetirementApiV3Controller.java @@ -59,7 +59,7 @@ public AgreementsRetirementApiV3Controller(AgreementsRetirementService service, this.service = service; this.transformerRegistry = transformerRegistry; this.validator = validator; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); } @POST diff --git a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java index 7527f11492..e8e5e11378 100644 --- a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java +++ b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java @@ -85,7 +85,7 @@ class BdrsClientImpl implements BdrsClient { this.serverUrl = baseUrl; this.cacheValidity = cacheValidity; this.httpClient = httpClient; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.mapper = mapper; this.secureTokenService = secureTokenService; this.ownDid = ownDid; diff --git a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplExtensionTest.java b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplExtensionTest.java index e5496587c7..9bf4a10844 100644 --- a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplExtensionTest.java +++ b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplExtensionTest.java @@ -83,6 +83,7 @@ void createClient_whenNoCredentialServiceUrl_shouldInvokeResolver(ServiceExtensi extension.getBdrsClient(context); + verify(monitor).withPrefix(anyString()); verify(monitor).warning("No config value found for 'tx.edc.iam.iatp.credentialservice.url'. As a fallback, the credentialService URL from this connector's DID document will be resolved."); verifyNoMoreInteractions(monitor); } @@ -98,6 +99,7 @@ void createClient_whenResolverFails_expectLogError(ServiceExtensionContext conte var client = extension.getBdrsClient(context); + verify(monitor).withPrefix(anyString()); verify(monitor).warning("No config value found for 'tx.edc.iam.iatp.credentialservice.url'. As a fallback, the credentialService URL from this connector's DID document will be resolved."); // the DID url resolver is only invoked on-demand, so no eager-loading of the DID document diff --git a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java index 02ed849db0..a074b6e486 100644 --- a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java +++ b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplTest.java @@ -94,6 +94,7 @@ void setup() { .withStatus(200))); var participantContext = ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build(); when(participantContextSupplier.get()).thenReturn(ServiceResult.success(participantContext)); + when(monitor.withPrefix(anyString())).thenCallRealMethod(); client = new BdrsClientImpl("http://localhost:%d/api".formatted(bdrsServer.getPort()), 1, "did:web:self", () -> "http://credential.service", @@ -188,7 +189,7 @@ void getData_whenPresentationQueryReturnsTooManyVps() { assertThatNoException().isThrownBy(() -> client.resolveDid("bpn1")); verifyBdrsRequest(1); - verify(monitor).warning("Expected exactly 1 VP, but found 2."); + verify(monitor).warning("[BdrsClientImpl] Expected exactly 1 VP, but found 2."); } @Test diff --git a/edc-extensions/bpn-validation/bpn-validation-api/src/main/java/org/eclipse/tractusx/edc/api/bpn/v1/BusinessPartnerGroupApiV1Controller.java b/edc-extensions/bpn-validation/bpn-validation-api/src/main/java/org/eclipse/tractusx/edc/api/bpn/v1/BusinessPartnerGroupApiV1Controller.java index fc3f8d91b6..105aaf6247 100644 --- a/edc-extensions/bpn-validation/bpn-validation-api/src/main/java/org/eclipse/tractusx/edc/api/bpn/v1/BusinessPartnerGroupApiV1Controller.java +++ b/edc-extensions/bpn-validation/bpn-validation-api/src/main/java/org/eclipse/tractusx/edc/api/bpn/v1/BusinessPartnerGroupApiV1Controller.java @@ -49,7 +49,7 @@ public class BusinessPartnerGroupApiV1Controller extends BaseBusinessPartnerGrou public BusinessPartnerGroupApiV1Controller(BusinessPartnerStore businessPartnerService, BusinessPartnerObservable businessPartnerObservable, Monitor monitor) { super(businessPartnerService, businessPartnerObservable); - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); } @GET diff --git a/edc-extensions/bpn-validation/bpn-validation-api/src/test/java/org/eclipse/tractusx/edc/api/bpn/v1/BusinessPartnerGroupApiV1ControllerTest.java b/edc-extensions/bpn-validation/bpn-validation-api/src/test/java/org/eclipse/tractusx/edc/api/bpn/v1/BusinessPartnerGroupApiV1ControllerTest.java index 3eb0c5e069..51500832d3 100644 --- a/edc-extensions/bpn-validation/bpn-validation-api/src/test/java/org/eclipse/tractusx/edc/api/bpn/v1/BusinessPartnerGroupApiV1ControllerTest.java +++ b/edc-extensions/bpn-validation/bpn-validation-api/src/test/java/org/eclipse/tractusx/edc/api/bpn/v1/BusinessPartnerGroupApiV1ControllerTest.java @@ -21,17 +21,22 @@ import io.restassured.specification.RequestSpecification; import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.tractusx.edc.api.bpn.BaseBusinessPartnerGroupApiControllerTest; import static io.restassured.RestAssured.given; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @ApiTest class BusinessPartnerGroupApiV1ControllerTest extends BaseBusinessPartnerGroupApiControllerTest { @Override protected Object controller() { - return new BusinessPartnerGroupApiV1Controller(businessPartnerStore, mock(), mock()); + var monitor = mock(Monitor.class); + when(monitor.withPrefix(anyString())).thenReturn(monitor); + return new BusinessPartnerGroupApiV1Controller(businessPartnerStore, mock(), monitor); } @Override diff --git a/edc-extensions/bpn-validation/bpn-validation-core/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerGroupLegacyFunction.java b/edc-extensions/bpn-validation/bpn-validation-core/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerGroupLegacyFunction.java index 9738ce9e5d..4b2edffff8 100644 --- a/edc-extensions/bpn-validation/bpn-validation-core/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerGroupLegacyFunction.java +++ b/edc-extensions/bpn-validation/bpn-validation-core/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/functions/BusinessPartnerGroupLegacyFunction.java @@ -96,7 +96,7 @@ public class BusinessPartnerGroupLegacyFunction privateKeyAlias, - mock(), + monitor, TEST_REFRESH_ENDPOINT, PROVIDER_DID, 1, diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java index e92b47491d..856992a18c 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java @@ -29,6 +29,7 @@ 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.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.result.StoreResult; @@ -37,6 +38,7 @@ import org.eclipse.edc.token.spi.TokenGenerationService; import org.eclipse.edc.token.spi.TokenValidationRule; import org.eclipse.edc.token.spi.TokenValidationService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.time.Clock; @@ -67,6 +69,7 @@ class DataPlaneTokenRefreshServiceImplTest { private static final Pattern UUID_PATTERN = Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); private final AccessTokenDataStore accessTokenDataStore = mock(); private final TokenGenerationService tokenGenService = mock(); + private final Monitor monitor = mock(); private final TokenValidationService tokenValidationService = mock(); private final DidPublicKeyResolver didPublicKeyResolver = mock(); private final LocalPublicKeyService localPublicKeyService = mock(); @@ -74,10 +77,16 @@ class DataPlaneTokenRefreshServiceImplTest { ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build() ); - private final DataPlaneTokenRefreshServiceImpl accessTokenService = new DataPlaneTokenRefreshServiceImpl(Clock.systemUTC(), - tokenValidationService, didPublicKeyResolver, localPublicKeyService, accessTokenDataStore, tokenGenService, mock(), mock(), - "https://example.com", "did:web:provider", 1, 300L, - () -> "keyid", mock(), new ObjectMapper(), participantContextSupplier); + private DataPlaneTokenRefreshServiceImpl accessTokenService; + + @BeforeEach + void setUp() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); + accessTokenService = new DataPlaneTokenRefreshServiceImpl(Clock.systemUTC(), + tokenValidationService, didPublicKeyResolver, localPublicKeyService, accessTokenDataStore, tokenGenService, mock(), monitor, + "https://example.com", "did:web:provider", 1, 300L, + () -> "keyid", mock(), new ObjectMapper(), participantContextSupplier); + } @Test void obtainToken() { diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java index 4b3693a863..09337396c4 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java @@ -91,7 +91,7 @@ public DimSecureTokenService(EdcHttpClient httpClient, String dimUrl, DimOauth2C this.dimUrl = dimUrl; this.dimOauth2Client = dimOauth2Client; this.mapper = mapper; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); } @Override diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java index 7361b9142a..479b5b15ad 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java @@ -54,7 +54,7 @@ public DimOauthClientImpl(Oauth2Client oauth2Client, Vault vault, StsRemoteClien this.oauth2Client = oauth2Client; this.vault = vault; this.clock = clock; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.participantContextSupplier = participantContextSupplier; } diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImplTest.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImplTest.java index eac5efe362..7184ee0500 100644 --- a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImplTest.java +++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImplTest.java @@ -29,6 +29,7 @@ import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.security.Vault; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -36,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -49,6 +51,11 @@ public class DimOauthClientImplTest { private final ParticipantContextSupplier participantContextSupplier = () -> ServiceResult.success( ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build()); + @BeforeEach + void setup() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); + } + @Test void obtainRequestToken_withNoExpiration() { var config = new StsRemoteClientConfiguration("http://localhost:8081/token", "clientId", "client_secret_alias"); diff --git a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java index 28ce59ca8f..881ab81eb6 100644 --- a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java +++ b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractor.java @@ -62,7 +62,7 @@ public class CredentialScopeExtractor implements ScopeExtractor { private final Monitor monitor; public CredentialScopeExtractor(Monitor monitor) { - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); } @Override diff --git a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractorTest.java b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractorTest.java index 21244a4451..5d51806b84 100644 --- a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractorTest.java +++ b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/CredentialScopeExtractorTest.java @@ -54,7 +54,9 @@ import static org.eclipse.tractusx.edc.policy.cx.legacy.common.AbstractDynamicCredentialConstraintFunction.ACTIVE; import static org.eclipse.tractusx.edc.policy.cx.legacy.dismantler.DismantlerCredentialConstraintFunction.DISMANTLER_LITERAL; import static org.eclipse.tractusx.edc.policy.cx.legacy.membership.MembershipCredentialConstraintFunction.MEMBERSHIP_LITERAL; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class CredentialScopeExtractorTest { @@ -66,6 +68,7 @@ public class CredentialScopeExtractorTest { @BeforeEach void setup() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); extractor = new CredentialScopeExtractor(monitor); } diff --git a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiController.java b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiController.java index 01e2061313..8548d5d280 100644 --- a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiController.java +++ b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/BaseEdrCacheApiController.java @@ -78,7 +78,7 @@ public BaseEdrCacheApiController(EndpointDataReferenceStore edrStore, this.edrStore = edrStore; this.transformerRegistry = transformerRegistry; this.validator = validator; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.edrService = edrService; this.participantContextSupplier = participantContextSupplier; this.contractNegotiationService = contractNegotiationService; diff --git a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2ControllerTest.java b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2ControllerTest.java index 48362a50e0..9e220725cd 100644 --- a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2ControllerTest.java +++ b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v2/EdrCacheApiV2ControllerTest.java @@ -21,17 +21,22 @@ import io.restassured.specification.RequestSpecification; import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.tractusx.edc.api.edr.BaseEdrCacheApiControllerTest; import static io.restassured.RestAssured.given; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @ApiTest public class EdrCacheApiV2ControllerTest extends BaseEdrCacheApiControllerTest { @Override protected Object controller() { - return new EdrCacheApiV2Controller(edrStore, transformerRegistry, validator, mock(), edrService, contractNegotiationService, participantContextSupplier); + var monitor = mock(Monitor.class); + when(monitor.withPrefix(anyString())).thenReturn(monitor); + return new EdrCacheApiV2Controller(edrStore, transformerRegistry, validator, monitor, edrService, contractNegotiationService, participantContextSupplier); } protected RequestSpecification baseRequest() { diff --git a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3ControllerTest.java b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3ControllerTest.java index 996cf77db3..caba8dae65 100644 --- a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3ControllerTest.java +++ b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/v3/EdrCacheApiV3ControllerTest.java @@ -21,17 +21,22 @@ import io.restassured.specification.RequestSpecification; import org.eclipse.edc.junit.annotations.ApiTest; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.tractusx.edc.api.edr.BaseEdrCacheApiControllerTest; import static io.restassured.RestAssured.given; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @ApiTest public class EdrCacheApiV3ControllerTest extends BaseEdrCacheApiControllerTest { @Override protected Object controller() { - return new EdrCacheApiV3Controller(edrStore, transformerRegistry, validator, mock(), edrService, contractNegotiationService, participantContextSupplier); + var monitor = mock(Monitor.class); + when(monitor.withPrefix(anyString())).thenReturn(monitor); + return new EdrCacheApiV3Controller(edrStore, transformerRegistry, validator, monitor, edrService, contractNegotiationService, participantContextSupplier); } protected RequestSpecification baseRequest() { diff --git a/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallback.java b/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallback.java index 9562fbb24b..2861499e6a 100644 --- a/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallback.java +++ b/edc-extensions/edr/edr-callback/src/main/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallback.java @@ -46,7 +46,7 @@ public class ContractNegotiationCallback implements InProcessCallback { public ContractNegotiationCallback(TransferProcessService transferProcessService, Monitor monitor, ParticipantContextSupplier participantContextSupplier) { this.transferProcessService = transferProcessService; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.participantContextSupplier = participantContextSupplier; } diff --git a/edc-extensions/edr/edr-callback/src/test/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallbackTest.java b/edc-extensions/edr/edr-callback/src/test/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallbackTest.java index 4d60d068be..85c6c062c0 100644 --- a/edc-extensions/edr/edr-callback/src/test/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallbackTest.java +++ b/edc-extensions/edr/edr-callback/src/test/java/org/eclipse/tractusx/edc/callback/ContractNegotiationCallbackTest.java @@ -56,6 +56,7 @@ import static org.eclipse.tractusx.edc.callback.TestFunctions.getNegotiationFinalizedEvent; import static org.eclipse.tractusx.edc.callback.TestFunctions.remoteMessage; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -85,6 +86,7 @@ void setup() { ParticipantContextSupplier participantContextSupplier = mock(); var participantContext = ParticipantContext.Builder.newInstance().identity("any").participantContextId("any").build(); when(participantContextSupplier.get()).thenReturn(ServiceResult.success(participantContext)); + when(monitor.withPrefix(anyString())).thenReturn(monitor); callback = new ContractNegotiationCallback(transferProcessService, monitor, participantContextSupplier); } diff --git a/edc-extensions/event-subscriber/src/main/java/org/eclipse/tractusx/edc/eventsubscriber/EventLoggingSubscriber.java b/edc-extensions/event-subscriber/src/main/java/org/eclipse/tractusx/edc/eventsubscriber/EventLoggingSubscriber.java index 18d84e34d7..25a23342d3 100644 --- a/edc-extensions/event-subscriber/src/main/java/org/eclipse/tractusx/edc/eventsubscriber/EventLoggingSubscriber.java +++ b/edc-extensions/event-subscriber/src/main/java/org/eclipse/tractusx/edc/eventsubscriber/EventLoggingSubscriber.java @@ -57,7 +57,7 @@ public class EventLoggingSubscriber implements EventSubscriber { public EventLoggingSubscriber(TypeManager typeManager, Monitor monitor, EdcHttpClient httpClient, String otelLogsEndpoint, String serviceName) { this.typeManager = typeManager; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.httpClient = httpClient; this.otelLogsEndpoint = otelLogsEndpoint; this.serviceName = serviceName; diff --git a/edc-extensions/event-subscriber/src/test/java/org/eclipse/tractusx/edc/eventsubscriber/EventLoggingSubscriberTest.java b/edc-extensions/event-subscriber/src/test/java/org/eclipse/tractusx/edc/eventsubscriber/EventLoggingSubscriberTest.java index e1daa6bdb6..cd7470c823 100644 --- a/edc-extensions/event-subscriber/src/test/java/org/eclipse/tractusx/edc/eventsubscriber/EventLoggingSubscriberTest.java +++ b/edc-extensions/event-subscriber/src/test/java/org/eclipse/tractusx/edc/eventsubscriber/EventLoggingSubscriberTest.java @@ -67,6 +67,7 @@ class EventLoggingSubscriberTest { @BeforeEach void setup() { + when(mockedMonitor.withPrefix(anyString())).thenReturn(mockedMonitor); eventLoggingSubscriber = new EventLoggingSubscriber(typeManager, mockedMonitor, mockedHttpClient, "http://uri.com", "unknown_service"); } diff --git a/edc-extensions/federated-catalog/src/main/java/org/eclipse/tractusx/edc/federatedcatalog/FileBasedTargetNodeDirectory.java b/edc-extensions/federated-catalog/src/main/java/org/eclipse/tractusx/edc/federatedcatalog/FileBasedTargetNodeDirectory.java index 4ac7154d81..adc6bfbd46 100644 --- a/edc-extensions/federated-catalog/src/main/java/org/eclipse/tractusx/edc/federatedcatalog/FileBasedTargetNodeDirectory.java +++ b/edc-extensions/federated-catalog/src/main/java/org/eclipse/tractusx/edc/federatedcatalog/FileBasedTargetNodeDirectory.java @@ -47,7 +47,7 @@ class FileBasedTargetNodeDirectory implements TargetNodeDirectory { FileBasedTargetNodeDirectory(File nodeFile, Monitor monitor, ObjectMapper objectMapper) { this.nodeFile = nodeFile; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.objectMapper = objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); } diff --git a/edc-extensions/federated-catalog/src/test/java/org/eclipse/tractusx/edc/federatedcatalog/FileBasedTargetNodeDirectoryTest.java b/edc-extensions/federated-catalog/src/test/java/org/eclipse/tractusx/edc/federatedcatalog/FileBasedTargetNodeDirectoryTest.java index 3e491de8c9..ec8492f9e7 100644 --- a/edc-extensions/federated-catalog/src/test/java/org/eclipse/tractusx/edc/federatedcatalog/FileBasedTargetNodeDirectoryTest.java +++ b/edc-extensions/federated-catalog/src/test/java/org/eclipse/tractusx/edc/federatedcatalog/FileBasedTargetNodeDirectoryTest.java @@ -33,8 +33,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class FileBasedTargetNodeDirectoryTest { @@ -55,6 +57,7 @@ void getAll_fileNotExist() { @Test void insert() { var monitor = mock(Monitor.class); + when(monitor.withPrefix(anyString())).thenReturn(monitor); var nodeDir = new FileBasedTargetNodeDirectory(new File("not-exist.json"), monitor, new ObjectMapper()); assertThatNoException().isThrownBy(() -> nodeDir.insert(new TargetNode("foo", "bar", "https://foobar.com", List.of()))); diff --git a/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/main/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineService.java b/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/main/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineService.java index 98ed3e802d..a8bde0d215 100644 --- a/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/main/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineService.java +++ b/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/main/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineService.java @@ -60,7 +60,7 @@ public class NonFiniteCapablePipelineService implements PipelineService { private final FinitenessEvaluator finitenessEvaluator; public NonFiniteCapablePipelineService(Monitor monitor, FinitenessEvaluator finitenessEvaluator) { - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.finitenessEvaluator = finitenessEvaluator; } diff --git a/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/test/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineServiceIntegrationTest.java b/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/test/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineServiceIntegrationTest.java index 36fc0c46ae..87da8b397d 100644 --- a/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/test/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineServiceIntegrationTest.java +++ b/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/test/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineServiceIntegrationTest.java @@ -31,6 +31,7 @@ import org.eclipse.edc.spi.types.domain.transfer.DataFlowStartMessage; import org.eclipse.edc.tractusx.non.finite.provider.push.spi.FinitenessEvaluator; import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; @@ -48,13 +49,15 @@ import static org.eclipse.edc.connector.dataplane.spi.pipeline.StreamResult.success; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.edc.util.async.AsyncUtils.asyncAllOf; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class NonFiniteCapablePipelineServiceIntegrationTest { private final Monitor monitor = mock(); private final FinitenessEvaluator finitenessEvaluator = mock(); - private final NonFiniteCapablePipelineService pipelineService = new NonFiniteCapablePipelineService(monitor, finitenessEvaluator); + private NonFiniteCapablePipelineService pipelineService; private static class InputStreamDataSourceFactory implements DataSourceFactory { @@ -139,6 +142,12 @@ private DataFlowStartMessage createRequest() { .build(); } + @BeforeEach + void setUp() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); + pipelineService = new NonFiniteCapablePipelineService(monitor, finitenessEvaluator); + } + @Test void transferData() { pipelineService.registerFactory(new InputStreamDataSourceFactory()); diff --git a/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/test/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineServiceTest.java b/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/test/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineServiceTest.java index 05d44dbb32..53292a2338 100644 --- a/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/test/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineServiceTest.java +++ b/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/test/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineServiceTest.java @@ -52,6 +52,7 @@ import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -71,10 +72,12 @@ class NonFiniteCapablePipelineServiceTest { private final Monitor monitor = mock(); private final FinitenessEvaluator finitenessEvaluator = mock(); - private final NonFiniteCapablePipelineService service = new NonFiniteCapablePipelineService(monitor, finitenessEvaluator); + private NonFiniteCapablePipelineService service; @BeforeEach void setUp() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); + service = new NonFiniteCapablePipelineService(monitor, finitenessEvaluator); service.registerFactory(sourceFactory); service.registerFactory(sinkFactory); } diff --git a/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVault.java b/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVault.java index c641ce7aa8..7628faa43c 100644 --- a/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVault.java +++ b/edc-extensions/single-participant-vault/src/main/java/org/eclipse/tractusx/edc/connector/InMemorySingleParticipantVault.java @@ -36,7 +36,7 @@ public class InMemorySingleParticipantVault implements Vault { private final Monitor monitor; public InMemorySingleParticipantVault(Monitor monitor) { - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); } @Override diff --git a/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java b/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java index 5977188b6c..41f1eef8d0 100644 --- a/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java +++ b/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java @@ -80,7 +80,7 @@ public TokenRefreshHandlerImpl(EndpointDataReferenceCache edrCache, this.edrCache = edrCache; this.httpClient = httpClient; this.ownDid = ownDid; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.secureTokenService = secureTokenService; this.objectMapper = objectMapper; this.participantContextSupplier = participantContextSupplier; diff --git a/edc-extensions/tokenrefresh-handler/src/test/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImplTest.java b/edc-extensions/tokenrefresh-handler/src/test/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImplTest.java index 9b08e08d97..610494beb3 100644 --- a/edc-extensions/tokenrefresh-handler/src/test/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImplTest.java +++ b/edc-extensions/tokenrefresh-handler/src/test/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImplTest.java @@ -41,6 +41,7 @@ import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.result.StoreResult; @@ -83,6 +84,7 @@ class TokenRefreshHandlerImplTest { private static final String PROVIDER_DID = "did:web:alice"; private final EndpointDataReferenceCache edrCache = mock(); private final EdcHttpClient mockedHttpClient = mock(); + private final Monitor monitor = mock(); private final SecureTokenService mockedTokenService = mock(); private final ParticipantContextSupplier participantContextSupplier = mock(); private TokenRefreshHandlerImpl tokenRefreshHandler; @@ -104,7 +106,8 @@ void setup() { var participantContext = ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build(); when(participantContextSupplier.get()).thenReturn(ServiceResult.success(participantContext)); objectMapper = new ObjectMapper(); - tokenRefreshHandler = new TokenRefreshHandlerImpl(edrCache, mockedHttpClient, CONSUMER_DID, mock(), + when(monitor.withPrefix(anyString())).thenReturn(monitor); + tokenRefreshHandler = new TokenRefreshHandlerImpl(edrCache, mockedHttpClient, CONSUMER_DID, monitor, mockedTokenService, objectMapper, participantContextSupplier); } diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ResponseQueue.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ResponseQueue.java index 2aa7cc405c..6e4db2b674 100644 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ResponseQueue.java +++ b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ResponseQueue.java @@ -43,7 +43,7 @@ public class ResponseQueue { public ResponseQueue(Queue> recordedRequests, Monitor monitor) { this.recordedRequests = recordedRequests; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); } public ServiceResult getNext(Class outputClass, String errorMessageTemplate) { From a1196d065600629a3ee9a5278d3c1f969ac486d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 15:14:19 +0100 Subject: [PATCH 066/112] chore(deps): bump edc from 0.15.0 to 0.15.1 (#2485) * chore(deps): bump edc from 0.15.0 to 0.15.1 Bumps `edc` from 0.15.0 to 0.15.1. Updates `org.eclipse.edc:controlplane-base-bom` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:controlplane-dcp-bom` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:controlplane-feature-sql-bom` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:dataplane-base-bom` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:dataplane-feature-sql-bom` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:federatedcatalog-base-bom` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/FederatedCatalog/releases) - [Commits](https://github.com/eclipse-edc/FederatedCatalog/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:federatedcatalog-dcp-bom` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/FederatedCatalog/releases) - [Commits](https://github.com/eclipse-edc/FederatedCatalog/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:federatedcatalog-feature-sql-bom` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/FederatedCatalog/releases) - [Commits](https://github.com/eclipse-edc/FederatedCatalog/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:auth-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:web-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:catalog-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:contract-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:control-plane-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:core-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:decentralized-claims-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:edr-store-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:http-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:identity-did-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:json-ld-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:jwt-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:jwt-signer-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:keypair-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:keys-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:participant-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:participant-context-single-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:policy-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:policy-engine-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:protocol-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:request-policy-context-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:token-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:transaction-datasource-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:transaction-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:transfer-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:transform-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:verifiable-credentials-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:boot` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:vault-hashicorp` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:connector-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:control-plane-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:edr-store-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:jersey-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:participant-context-config-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:participant-context-single-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:policy-monitor-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:runtime-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:token-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:callback-static-endpoint` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:junit` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:api-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:management-api-configuration` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:management-api` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:management-api-test-fixtures` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:iam-mock` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:auth-tokenbased` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:auth-configuration` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:auth-delegated` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:oauth2-client` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:transaction-local` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:http` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:json-ld` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:validator-data-address-http-data` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:runtime-metamodel` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Runtime-Metamodel/releases) - [Commits](https://github.com/eclipse-edc/Runtime-Metamodel/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:verifiable-credentials` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:api-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:boot-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:crypto-common-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:http-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:jersey-providers-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:jws2020-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:query-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:store-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:token-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:transform-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:util-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:validator-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:sql-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:dsp-catalog-http-api-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:dsp-negotiation-http-api-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:dsp-transfer-process-http-api-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:decentralized-claims-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:asset-index-sql` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:contract-negotiation-store-sql` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:edr-index-sql` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:sql-test-fixtures` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc.azure:data-plane-azure-storage` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Technology-Azure/releases) - [Commits](https://github.com/eclipse-edc/Technology-Azure/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc.azure:provision-blob` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Technology-Azure/releases) - [Commits](https://github.com/eclipse-edc/Technology-Azure/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc.aws:aws-s3-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Technology-Aws/releases) - [Commits](https://github.com/eclipse-edc/Technology-Aws/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc.aws:data-plane-aws-s3` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Technology-Aws/releases) - [Commits](https://github.com/eclipse-edc/Technology-Aws/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc.aws:validator-data-address-s3` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Technology-Aws/releases) - [Commits](https://github.com/eclipse-edc/Technology-Aws/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc.aws:provision-aws-s3` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Technology-Aws/releases) - [Commits](https://github.com/eclipse-edc/Technology-Aws/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:identity-did-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:identity-did-web` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:ldp-verifiable-credentials` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:jwt-verifiable-credentials` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:decentralized-claims-service` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:decentralized-claims-transform` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:decentralized-claims-sts-remote-lib` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:sts-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:sts-api` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:sts-account-provisioner` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:tck-extension` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:dsp-http-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:dsp-spi-08` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:dsp-spi-2024` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:dsp-spi-2025` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:crawler-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/FederatedCatalog/releases) - [Commits](https://github.com/eclipse-edc/FederatedCatalog/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:presentation-api` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:identity-hub-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:participant-context-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:verifiable-credential-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:identity-hub-keypairs` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:identity-hub-participants` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:identity-hub-did` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:identity-hub-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:common-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:data-plane-selector-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:data-plane-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:data-plane-http-spi` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:data-plane-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:data-plane-http` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:micrometer-core` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:jersey-micrometer` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) Updates `org.eclipse.edc:jetty-micrometer` from 0.15.0 to 0.15.1 - [Release notes](https://github.com/eclipse-edc/Connector/releases) - [Commits](https://github.com/eclipse-edc/Connector/compare/v0.15.0...v0.15.1) --- updated-dependencies: - dependency-name: org.eclipse.edc:controlplane-base-bom dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:controlplane-dcp-bom dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:controlplane-feature-sql-bom dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:dataplane-base-bom dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:dataplane-feature-sql-bom dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:federatedcatalog-base-bom dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:federatedcatalog-dcp-bom dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:federatedcatalog-feature-sql-bom dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:auth-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:web-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:catalog-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:contract-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:control-plane-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:core-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:decentralized-claims-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:edr-store-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:http-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:identity-did-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:json-ld-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:jwt-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:jwt-signer-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:keypair-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:keys-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:participant-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:participant-context-single-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:policy-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:policy-engine-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:protocol-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:request-policy-context-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:token-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:transaction-datasource-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:transaction-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:transfer-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:transform-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:verifiable-credentials-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:boot dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:vault-hashicorp dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:connector-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:control-plane-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:edr-store-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:jersey-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:participant-context-config-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:participant-context-single-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:policy-monitor-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:runtime-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:token-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:callback-static-endpoint dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:junit dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:api-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:management-api-configuration dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:management-api dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:management-api-test-fixtures dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:iam-mock dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:auth-tokenbased dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:auth-configuration dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:auth-delegated dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:oauth2-client dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:transaction-local dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:http dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:json-ld dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:validator-data-address-http-data dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:runtime-metamodel dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:verifiable-credentials dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:api-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:boot-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:crypto-common-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:http-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:jersey-providers-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:jws2020-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:query-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:store-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:token-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:transform-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:util-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:validator-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:sql-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:dsp-catalog-http-api-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:dsp-negotiation-http-api-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:dsp-transfer-process-http-api-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:decentralized-claims-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:asset-index-sql dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:contract-negotiation-store-sql dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:edr-index-sql dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:sql-test-fixtures dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc.azure:data-plane-azure-storage dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc.azure:provision-blob dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc.aws:aws-s3-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc.aws:data-plane-aws-s3 dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc.aws:validator-data-address-s3 dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc.aws:provision-aws-s3 dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:identity-did-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:identity-did-web dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:ldp-verifiable-credentials dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:jwt-verifiable-credentials dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:decentralized-claims-service dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:decentralized-claims-transform dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:decentralized-claims-sts-remote-lib dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:sts-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:sts-api dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:sts-account-provisioner dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:tck-extension dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:dsp-http-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:dsp-spi-08 dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:dsp-spi-2024 dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:dsp-spi-2025 dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:crawler-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:presentation-api dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:identity-hub-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:participant-context-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:verifiable-credential-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:identity-hub-keypairs dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:identity-hub-participants dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:identity-hub-did dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:identity-hub-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:common-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:data-plane-selector-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:data-plane-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:data-plane-http-spi dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:data-plane-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:data-plane-http dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:micrometer-core dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:jersey-micrometer dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.eclipse.edc:jetty-micrometer dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore(deps): adapt to edc 0.15.1 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: AndrYurk --- .../tractusx/edc/vault/memory/VaultSeedExtensionTest.java | 2 +- .../core/DataPlaneTokenRefreshServiceImplComponentTest.java | 2 +- gradle/libs.versions.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtensionTest.java b/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtensionTest.java index a1ffc30169..3649ad5832 100644 --- a/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtensionTest.java +++ b/edc-controlplane/edc-runtime-memory/src/test/java/org/eclipse/tractusx/edc/vault/memory/VaultSeedExtensionTest.java @@ -51,7 +51,7 @@ class VaultSeedExtensionTest { @BeforeEach void setup(ServiceExtensionContext context) { context.registerService(Monitor.class, monitor); - context.registerService(Vault.class, new InMemoryVault(monitor)); + context.registerService(Vault.class, new InMemoryVault(monitor, null)); context.registerService(SingleParticipantContextSupplier.class, participantContextSupplier); } diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java index e6cc49e07d..23b1af5e3f 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java @@ -89,7 +89,7 @@ class DataPlaneTokenRefreshServiceImplComponentTest { private DataPlaneTokenRefreshServiceImpl tokenRefreshService; private final InMemoryAccessTokenDataStore tokenDataStore = new InMemoryAccessTokenDataStore(CriterionOperatorRegistryImpl.ofDefaults()); private final Monitor monitor = mock(); - private final InMemoryVault vault = new InMemoryVault(mock()); + private final InMemoryVault vault = new InMemoryVault(mock(), null); private final ObjectMapper objectMapper = new ObjectMapper(); private ECKey consumerKey; private ECKey providerKey; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f971cb6361..c952117840 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ format.version = "1.1" [versions] -edc = "0.15.0" +edc = "0.15.1" edc-build = "1.1.4" allure = "2.32.0" awaitility = "4.3.0" From ea67bb66a8a8c71bb4344863892b34af81481f65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 09:25:43 +0100 Subject: [PATCH 067/112] chore(deps): bump log4j2 from 2.25.2 to 2.25.3 (#2496) Bumps `log4j2` from 2.25.2 to 2.25.3. Updates `org.apache.logging.log4j:log4j-api` from 2.25.2 to 2.25.3 Updates `org.apache.logging.log4j:log4j-core` from 2.25.2 to 2.25.3 Updates `org.apache.logging.log4j:log4j-core-test` from 2.25.2 to 2.25.3 Updates `org.apache.logging.log4j:log4j-layout-template-json` from 2.25.2 to 2.25.3 --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-api dependency-version: 2.25.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-core-test dependency-version: 2.25.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-layout-template-json dependency-version: 2.25.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c952117840..5ba0c6a20b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ rsApi = "4.0.0" testcontainers = "2.0.3" testcontainers-keycloak = "4.0.1" titanium = "1.7.0" -log4j2 = "2.25.2" +log4j2 = "2.25.3" wiremock = "3.13.2" From 1e96cb6766e8e631a9f52425385dd0734d8377b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 09:26:36 +0100 Subject: [PATCH 068/112] chore(deps): bump org.eclipse.edc:identityhub-bom from 0.15.0 to 0.15.1 (#2497) Bumps [org.eclipse.edc:identityhub-bom](https://github.com/eclipse-edc/IdentityHub) from 0.15.0 to 0.15.1. - [Release notes](https://github.com/eclipse-edc/IdentityHub/releases) - [Commits](https://github.com/eclipse-edc/IdentityHub/compare/v0.15.0...v0.15.1) --- updated-dependencies: - dependency-name: org.eclipse.edc:identityhub-bom dependency-version: 0.15.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts b/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts index 82df91b908..edfe39c8a1 100644 --- a/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts +++ b/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts @@ -25,7 +25,7 @@ plugins { dependencies { // use basic (all in-mem) control plane // implementation(project(":edc-controlplane:edc-controlplane-base")) - implementation("org.eclipse.edc:identityhub-bom:0.15.0") // TODO: put in version catalog + implementation("org.eclipse.edc:identityhub-bom:0.15.1") // TODO: put in version catalog implementation(project(":edc-extensions:single-participant-vault")) implementation(project(":core:json-ld-core")) implementation(project(":edc-tests:runtime:iatp:iatp-extensions")) From 78afbafcd8e3ecd3770b03458d2151b6f9c1c0bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 09:27:04 +0100 Subject: [PATCH 069/112] chore(deps): bump org.eclipse.edc.edc-build from 1.1.4 to 1.1.5 (#2495) Bumps org.eclipse.edc.edc-build from 1.1.4 to 1.1.5. --- updated-dependencies: - dependency-name: org.eclipse.edc.edc-build dependency-version: 1.1.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5ba0c6a20b..e5406bee7f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ format.version = "1.1" [versions] edc = "0.15.1" -edc-build = "1.1.4" +edc-build = "1.1.5" allure = "2.32.0" awaitility = "4.3.0" aws = "2.40.3" From d95f87f4d3466bf36f56d40b74928b20eb651873 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 09:27:30 +0100 Subject: [PATCH 070/112] chore(deps): bump flyway from 11.19.1 to 11.20.0 (#2494) Bumps `flyway` from 11.19.1 to 11.20.0. Updates `org.flywaydb:flyway-core` from 11.19.1 to 11.20.0 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-11.19.1...flyway-11.20.0) Updates `org.flywaydb:flyway-database-postgresql` from 11.19.1 to 11.20.0 --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-version: 11.20.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.flywaydb:flyway-database-postgresql dependency-version: 11.20.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e5406bee7f..1729d1920e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-SNAPSHOT" dsp-tck = "1.0.0-RC4" -flyway = "11.19.1" +flyway = "11.20.0" jackson = "2.20.1" jakarta-json = "2.0.1" junit = "6.0.1" From 97185fe95b51b03be453606984f8b2aa6715fe51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Jan 2026 09:27:55 +0100 Subject: [PATCH 071/112] chore(deps): bump io.opentelemetry.javaagent:opentelemetry-javaagent (#2493) Bumps [io.opentelemetry.javaagent:opentelemetry-javaagent](https://github.com/open-telemetry/opentelemetry-java-instrumentation) from 2.22.0 to 2.23.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java-instrumentation/compare/v2.22.0...v2.23.0) --- updated-dependencies: - dependency-name: io.opentelemetry.javaagent:opentelemetry-javaagent dependency-version: 2.23.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1729d1920e..d01480bd59 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ jakarta-json = "2.0.1" junit = "6.0.1" nimbus = "10.6" okhttp = "5.3.2" -opentelemetry = "2.22.0" +opentelemetry = "2.23.0" opentelemetry-instrumentation = "2.23.0" opentelemetry-log4j-appender = "2.22.0-alpha" postgres = "42.7.8" From a0bf4ae1a10879a57eb0f61fca7c784a869f1ad2 Mon Sep 17 00:00:00 2001 From: pratapipatelbcone Date: Fri, 2 Jan 2026 14:37:32 +0530 Subject: [PATCH 072/112] feat: Did Document Service SPI and Self Registration Extension (#2481) * feat: Did Document Service SPI and Self Registration Extension * fix: Review Comments 1. Remove unused methods from SPI 2. Validate service id 3. Update helm templates * fix: Do not throw exception when service id invalid --- charts/tractusx-connector-memory/README.md | 2 + .../templates/deployment-runtime.yaml | 6 + charts/tractusx-connector-memory/values.yaml | 7 + charts/tractusx-connector/README.md | 2 + .../templates/deployment-controlplane.yaml | 6 + charts/tractusx-connector/values.yaml | 6 + .../edc-controlplane-base/build.gradle.kts | 1 + .../build.gradle.kts | 32 +++ ...umentServiceSelfRegistrationExtension.java | 113 ++++++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 20 ++ ...tServiceSelfRegistrationExtensionTest.java | 201 ++++++++++++++++++ settings.gradle.kts | 2 + spi/did-document-service-spi/build.gradle.kts | 28 +++ .../service/DidDocumentServiceClient.java | 73 +++++++ 14 files changed, 499 insertions(+) create mode 100644 edc-extensions/did-document/did-document-service-self-registration/build.gradle.kts create mode 100644 edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java create mode 100644 edc-extensions/did-document/did-document-service-self-registration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java create mode 100644 spi/did-document-service-spi/build.gradle.kts create mode 100644 spi/did-document-service-spi/src/main/java/org/eclipse/tractusx/edc/spi/did/document/service/DidDocumentServiceClient.java diff --git a/charts/tractusx-connector-memory/README.md b/charts/tractusx-connector-memory/README.md index 8c124fb547..5292a33db9 100644 --- a/charts/tractusx-connector-memory/README.md +++ b/charts/tractusx-connector-memory/README.md @@ -59,6 +59,8 @@ helm install my-release tractusx-edc/tractusx-connector-memory --version 0.12.0- | fullnameOverride | string | `""` | | | iatp.cache.enabled | bool | `true` | Whether the Verifiable Presentation cache is enabled | | iatp.cache.validity | int | `86400` | Validity of the Verifiable Presentation cache in seconds | +| iatp.didService.selfRegistration.enabled | bool | `false` | Whether Service Self Registration is enabled | +| iatp.didService.selfRegistration.id | string | `"did:web:changeme"` | Unique id of connector to be used for register / unregister service inside did document (must be valid URI) | | iatp.id | string | `"did:web:changeme"` | Decentralized IDentifier (DID) of the connector | | iatp.sts.dim.url | string | `nil` | URL where connectors can request SI tokens | | iatp.sts.oauth.client.id | string | `nil` | Client ID for requesting OAuth2 access token for DIM access | diff --git a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml index 6c00949726..80ba7c4eb0 100644 --- a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml +++ b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml @@ -256,6 +256,12 @@ spec: {{- end }} {{- end }} {{- end }} + - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED" + value: {{ .Values.iatp.didService.selfRegistration.enabled | quote}} + {{- if .Values.iatp.didService.selfRegistration.enabled }} + - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID" + value: {{ .Values.iatp.didService.selfRegistration.id | required ".Values.iatp.didService.selfRegistration.id is required" | quote }} + {{- end }} - name: "TX_EDC_DCP_CACHE_ENABLED" value: {{ .Values.iatp.cache.enabled | quote }} - name: "TX_EDC_DCP_CACHE_VALIDITY_SECONDS" diff --git a/charts/tractusx-connector-memory/values.yaml b/charts/tractusx-connector-memory/values.yaml index 51cf432a81..1ebe9e3afa 100644 --- a/charts/tractusx-connector-memory/values.yaml +++ b/charts/tractusx-connector-memory/values.yaml @@ -57,6 +57,13 @@ iatp: id: # -- Alias under which the client secret is stored in the vault for requesting OAuth2 access token for DIM access secret_alias: + didService: + selfRegistration: + # -- Whether Service Self Registration is enabled + enabled: false + # -- Unique id of connector to be used for register / unregister service inside did document (must be valid URI) + id: "did:web:changeme" + # - Configures the Verifiable Presentation Ccache cache: # -- Whether the Verifiable Presentation cache is enabled diff --git a/charts/tractusx-connector/README.md b/charts/tractusx-connector/README.md index b2de0a61ce..1dae24f1f3 100644 --- a/charts/tractusx-connector/README.md +++ b/charts/tractusx-connector/README.md @@ -269,6 +269,8 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.12.0-SNAPSHO | fullnameOverride | string | `""` | | | iatp.cache.enabled | bool | `true` | Whether the Verifiable Presentation cache is enabled | | iatp.cache.validity | int | `86400` | Validity of the Verifiable Presentation cache in seconds | +| iatp.didService.selfRegistration.enabled | bool | `false` | Whether Service Self Registration is enabled | +| iatp.didService.selfRegistration.id | string | `"did:web:changeme"` | Unique id of connector to be used for register / unregister service inside did document (must be valid URI) | | iatp.id | string | `"did:web:changeme"` | Decentralized IDentifier (DID) of the connector | | iatp.sts.dim.url | string | `nil` | URL where connectors can request SI tokens | | iatp.sts.oauth.client.id | string | `nil` | Client ID for requesting OAuth2 access token for DIM access | diff --git a/charts/tractusx-connector/templates/deployment-controlplane.yaml b/charts/tractusx-connector/templates/deployment-controlplane.yaml index 382d948b93..2442cbd9fa 100644 --- a/charts/tractusx-connector/templates/deployment-controlplane.yaml +++ b/charts/tractusx-connector/templates/deployment-controlplane.yaml @@ -254,6 +254,12 @@ spec: {{- end }} {{- end }} {{- end }} + - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED" + value: {{ .Values.iatp.didService.selfRegistration.enabled | quote}} + {{- if .Values.iatp.didService.selfRegistration.enabled }} + - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID" + value: {{ .Values.iatp.didService.selfRegistration.id | required ".Values.iatp.didService.selfRegistration.id is required" | quote }} + {{- end }} - name: "TX_EDC_DCP_CACHE_ENABLED" value: {{ .Values.iatp.cache.enabled | quote }} - name: "TX_EDC_DCP_CACHE_VALIDITY_SECONDS" diff --git a/charts/tractusx-connector/values.yaml b/charts/tractusx-connector/values.yaml index fde986b409..b04b524256 100644 --- a/charts/tractusx-connector/values.yaml +++ b/charts/tractusx-connector/values.yaml @@ -64,6 +64,12 @@ iatp: id: # -- Alias under which the client secret is stored in the vault for requesting OAuth2 access token for DIM access secret_alias: + didService: + selfRegistration: + # -- Whether Service Self Registration is enabled + enabled: false + # -- Unique id of connector to be used for register / unregister service inside did document (must be valid URI) + id: "did:web:changeme" # - Configures the Verifiable Presentation cache cache: # -- Whether the Verifiable Presentation cache is enabled diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index a6fdc2b352..93a7799a4e 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -61,6 +61,7 @@ dependencies { implementation(project(":edc-extensions:dataspace-protocol")) implementation(project(":edc-extensions:token-interceptor")) implementation(project(":edc-extensions:event-subscriber")) + implementation(project(":edc-extensions:did-document:did-document-service-self-registration")) runtimeOnly(libs.bundles.edc.monitoring) runtimeOnly(libs.edc.aws.validator.data.address.s3) diff --git a/edc-extensions/did-document/did-document-service-self-registration/build.gradle.kts b/edc-extensions/did-document/did-document-service-self-registration/build.gradle.kts new file mode 100644 index 0000000000..df7f00fb2b --- /dev/null +++ b/edc-extensions/did-document/did-document-service-self-registration/build.gradle.kts @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2025 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` + `maven-publish` +} + +dependencies { + implementation(project(":spi:did-document-service-spi")) + implementation(libs.edc.runtime.metamodel) + implementation(libs.edc.spi.identity.did) + implementation(libs.dsp.spi.http) + + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java b/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java new file mode 100644 index 0000000000..c38e4f3ceb --- /dev/null +++ b/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java @@ -0,0 +1,113 @@ +/******************************************************************************** + * Copyright (c) 2025 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.did.document.service.self.registration; + +import org.eclipse.edc.iam.did.spi.document.Service; +import org.eclipse.edc.protocol.dsp.http.spi.api.DspBaseWebhookAddress; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.tractusx.edc.spi.did.document.service.DidDocumentServiceClient; +import org.jetbrains.annotations.NotNull; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Optional; + +public class DidDocumentServiceSelfRegistrationExtension implements ServiceExtension { + + public static final String TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED = "tx.edc.did.service.self.registration.enabled"; + public static final String TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID = "tx.edc.did.service.self.registration.id"; + + public static final String DATA_SERVICE_TYPE = "DataService"; + private static final String DATA_SERVICE_ID_WITH_TYPE_TEMPLATE = "%s#%s"; + public static final String VERSION_METADATA_ENDPOINT_PATH = "/.well-known/dspace-version"; + + @Inject + private Monitor monitor; + + @Inject + private DspBaseWebhookAddress dspBaseAddress; + + @Inject(required = false) + private DidDocumentServiceClient didDocumentServiceClient; + + @Setting(key = TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED, defaultValue = "false", description = "Enable self-registration of the DID Document Service") + private boolean selfRegistrationEnabled; + + @Setting(key = TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID, required = false, description = "The Id to use for service self-registration (should be valid URI)") + private String serviceId; + + @Override + public void start() { + Optional.ofNullable(didDocumentServiceClient) + .filter(client -> selfRegistrationEnabled) + .ifPresentOrElse(this::selfRegisterDidDocumentService, + () -> monitor.info("Did Document Service Client not available or not enabled, skipping self-registration")); + } + + @Override + public void shutdown() { + Optional.ofNullable(didDocumentServiceClient) + .filter(client -> selfRegistrationEnabled) + .ifPresent(this::selfUnregisterDidDocumentService); + } + + private void selfRegisterDidDocumentService(@NotNull DidDocumentServiceClient client) { + + var wellKnownUrl = String.join("", dspBaseAddress.get(), VERSION_METADATA_ENDPOINT_PATH); + validatedServiceId(serviceId) + .onFailure(failure -> monitor.severe(failure.getFailureDetail())) + .map(id -> DATA_SERVICE_ID_WITH_TYPE_TEMPLATE.formatted(id, DATA_SERVICE_TYPE)) + .map(serviceIdWithType -> new Service(serviceIdWithType, DATA_SERVICE_TYPE, wellKnownUrl)) + .onSuccess(service -> + client.update(service) + .onFailure(failure -> monitor.severe("Failed to self-register DID Document service: %s, reason: %s".formatted(failure.getFailureDetail(), failure.getReason()))) + .onSuccess(result -> monitor.info("Self Registration of DID Document service successful")) + ); + } + + private void selfUnregisterDidDocumentService(@NotNull DidDocumentServiceClient client) { + + validatedServiceId(serviceId) + .map(id -> DATA_SERVICE_ID_WITH_TYPE_TEMPLATE.formatted(id, DATA_SERVICE_TYPE)) + .onSuccess(serviceIdWithType -> + client.deleteById(serviceIdWithType) + .onFailure(failure -> monitor.severe("Failed to unregister DID Document service: %s, reason: %s".formatted(failure.getFailureDetail(), failure.getReason()))) + .onSuccess(result -> monitor.info("Successfully unregistered DID Document service")) + ); + } + + private Result validatedServiceId(String serviceId) { + + if (serviceId == null || serviceId.isBlank()) { + return Result.failure("Service ID for DID Document Service self-registration configured via Property '%s' is missing or blank but self-registration is enabled.".formatted(TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID)); + } + try { + new URI(serviceId); + } catch (URISyntaxException ex) { + return Result.failure("Service ID for DID Document Service self-registration configured via Property '%s' does not contain a valid URI (%s) but self-registration is enabled.'" + .formatted(TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID, ex.getMessage())); + } + return Result.success(serviceId); + } +} diff --git a/edc-extensions/did-document/did-document-service-self-registration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/did-document/did-document-service-self-registration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..00d7864fed --- /dev/null +++ b/edc-extensions/did-document/did-document-service-self-registration/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2025 SAP SE +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# 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. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################# + +org.eclipse.tractusx.edc.did.document.service.self.registration.DidDocumentServiceSelfRegistrationExtension diff --git a/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java b/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java new file mode 100644 index 0000000000..f6622985c4 --- /dev/null +++ b/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java @@ -0,0 +1,201 @@ +/******************************************************************************** + * Copyright (c) 2025 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.did.document.service.self.registration; + +import org.eclipse.edc.boot.system.injection.ObjectFactory; +import org.eclipse.edc.iam.did.spi.document.Service; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.protocol.dsp.http.spi.api.DspBaseWebhookAddress; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.ServiceResult; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.eclipse.tractusx.edc.spi.did.document.service.DidDocumentServiceClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Map; + +import static org.eclipse.tractusx.edc.did.document.service.self.registration.DidDocumentServiceSelfRegistrationExtension.DATA_SERVICE_TYPE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +class DidDocumentServiceSelfRegistrationExtensionTest { + + private static final String SERVICE_ID = "did:web:example.com:connector1"; + private static final String DSP_URL = "https://protocol.edc.com/api/v1/dsp"; + + private final Monitor monitor = mock(Monitor.class); + private final DspBaseWebhookAddress dspBaseAddress = mock(DspBaseWebhookAddress.class); + private final DidDocumentServiceClient didDocumentServiceClient = mock(DidDocumentServiceClient.class); + + @BeforeEach + void setup(ServiceExtensionContext context) { + context.registerService(Monitor.class, monitor); + context.registerService(DspBaseWebhookAddress.class, dspBaseAddress); + when(dspBaseAddress.get()).thenReturn(DSP_URL); + } + + @Test + void start_shouldSelfRegister_whenEnabledAndClientPresent(ServiceExtensionContext context, ObjectFactory objectFactory) { + + var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", + "tx.edc.did.service.self.registration.id", SERVICE_ID); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); + context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); + when(didDocumentServiceClient.update(any(Service.class))).thenReturn(ServiceResult.success()); + when(didDocumentServiceClient.deleteById(anyString())).thenReturn(ServiceResult.success()); + + var extension = objectFactory.constructInstance(DidDocumentServiceSelfRegistrationExtension.class); + extension.start(); + + verify(didDocumentServiceClient).update(argThat(service -> + service.getId().equals(SERVICE_ID + "#" + DATA_SERVICE_TYPE) && + service.getType().equals(DATA_SERVICE_TYPE) && + service.getServiceEndpoint().equals(DSP_URL + "/.well-known/dspace-version"))); + verify(monitor).info("Self Registration of DID Document service successful"); + verify(monitor, never()).info("Did Document Service Client not available or not enabled, skipping self-registration"); + + extension.shutdown(); + verify(didDocumentServiceClient).deleteById(SERVICE_ID + "#" + DATA_SERVICE_TYPE); + verify(monitor).info("Successfully unregistered DID Document service"); + } + + @Test + void start_selfRegister_whenEnabledAndClientReturnsFailure(ServiceExtensionContext context, ObjectFactory objectFactory) { + + var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", + "tx.edc.did.service.self.registration.id", SERVICE_ID); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); + context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); + when(didDocumentServiceClient.update(any(Service.class))).thenReturn(ServiceResult.unexpected()); + when(didDocumentServiceClient.deleteById(anyString())).thenReturn(ServiceResult.unexpected()); + + var extension = objectFactory.constructInstance(DidDocumentServiceSelfRegistrationExtension.class); + extension.start(); + + verify(didDocumentServiceClient).update(argThat(service -> + service.getId().equals(SERVICE_ID + "#" + DATA_SERVICE_TYPE) && + service.getType().equals(DATA_SERVICE_TYPE) && + service.getServiceEndpoint().equals(DSP_URL + "/.well-known/dspace-version"))); + verify(monitor).severe(contains("Failed to self-register DID Document service")); + verify(monitor, never()).info("Did Document Service Client not available or not enabled, skipping self-registration"); + + extension.shutdown(); + verify(didDocumentServiceClient).deleteById(SERVICE_ID + "#" + DATA_SERVICE_TYPE); + verify(monitor).severe(contains("Failed to unregister DID Document service")); + } + + @Test + void start_shouldNotSelfRegister_whenClientNotPresent(ObjectFactory objectFactory) { + + var extension = objectFactory.constructInstance(DidDocumentServiceSelfRegistrationExtension.class); + extension.start(); + + verify(didDocumentServiceClient, never()).update(any(Service.class)); + verify(monitor).info("Did Document Service Client not available or not enabled, skipping self-registration"); + + extension.shutdown(); + verify(didDocumentServiceClient, never()).deleteById(anyString()); + } + + @Test + void start_shouldNotSelfRegister_whenDisabledAndClientPresent(ServiceExtensionContext context, ObjectFactory objectFactory) { + + var settings = Map.of("tx.edc.did.service.self.registration.enabled", "false"); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); + context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); + + var extension = objectFactory.constructInstance(DidDocumentServiceSelfRegistrationExtension.class); + extension.start(); + + verify(didDocumentServiceClient, never()).update(any(Service.class)); + verify(monitor).info("Did Document Service Client not available or not enabled, skipping self-registration"); + + extension.shutdown(); + verify(didDocumentServiceClient, never()).deleteById(anyString()); + } + + @Test + void start_shouldNotSelfRegister_whenEnabledAndServiceIdMissing(ServiceExtensionContext context, ObjectFactory objectFactory) { + + var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true"); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); + context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); + + var extension = objectFactory.constructInstance(DidDocumentServiceSelfRegistrationExtension.class); + extension.start(); + + verify(didDocumentServiceClient, never()).update(any(Service.class)); + verify(monitor).severe(contains("is missing or blank but self-registration is enabled")); + + extension.shutdown(); + verify(didDocumentServiceClient, never()).deleteById(anyString()); + } + + @ParameterizedTest + @EmptySource + @ValueSource(strings = {" "}) + void start_shouldNotSelfRegister_whenEnabledAndServiceIdEmptyOrBlank(String serviceId, ServiceExtensionContext context, ObjectFactory objectFactory) { + + var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", + "tx.edc.did.service.self.registration.id", serviceId); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); + context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); + + var extension = objectFactory.constructInstance(DidDocumentServiceSelfRegistrationExtension.class); + extension.start(); + + verify(didDocumentServiceClient, never()).update(any(Service.class)); + verify(monitor).severe(contains("is missing or blank but self-registration is enabled")); + + extension.shutdown(); + verify(didDocumentServiceClient, never()).deleteById(anyString()); + } + + @Test + void start_shouldNotSelfRegister_whenEnabledAndServiceIdInvalid(ServiceExtensionContext context, ObjectFactory objectFactory) { + + var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", + "tx.edc.did.service.self.registration.id", "invalid uri"); + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); + context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); + + var extension = objectFactory.constructInstance(DidDocumentServiceSelfRegistrationExtension.class); + extension.start(); + + verify(didDocumentServiceClient, never()).update(any(Service.class)); + verify(monitor).severe(contains("does not contain a valid URI")); + + extension.shutdown(); + verify(didDocumentServiceClient, never()).deleteById(anyString()); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 40622a9aac..339de1b944 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,6 +37,7 @@ include(":spi:core-spi") include(":spi:tokenrefresh-spi") include(":spi:bdrs-client-spi") include(":spi:dataflow-spi") +include(":spi:did-document-service-spi") include(":spi:dcp-spi") @@ -73,6 +74,7 @@ include(":edc-extensions:validators:empty-asset-selector") include(":edc-extensions:log4j2-monitor") include("edc-extensions:connector-discovery:connector-discovery-api") include(":edc-extensions:dataspace-protocol") +include(":edc-extensions:did-document:did-document-service-self-registration") include(":edc-extensions:agreements") include(":edc-extensions:agreements:retirement-evaluation-core") diff --git a/spi/did-document-service-spi/build.gradle.kts b/spi/did-document-service-spi/build.gradle.kts new file mode 100644 index 0000000000..9f54b7f08b --- /dev/null +++ b/spi/did-document-service-spi/build.gradle.kts @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2025 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` + `maven-publish` +} + +dependencies { + implementation(libs.edc.runtime.metamodel) + implementation(libs.edc.spi.identity.did) +} diff --git a/spi/did-document-service-spi/src/main/java/org/eclipse/tractusx/edc/spi/did/document/service/DidDocumentServiceClient.java b/spi/did-document-service-spi/src/main/java/org/eclipse/tractusx/edc/spi/did/document/service/DidDocumentServiceClient.java new file mode 100644 index 0000000000..804e054b75 --- /dev/null +++ b/spi/did-document-service-spi/src/main/java/org/eclipse/tractusx/edc/spi/did/document/service/DidDocumentServiceClient.java @@ -0,0 +1,73 @@ +/******************************************************************************** + * Copyright (c) 2025 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.spi.did.document.service; + +import org.eclipse.edc.iam.did.spi.document.Service; +import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; +import org.eclipse.edc.spi.result.ServiceResult; + +/** + * Service Provider Interface (SPI) for managing the dynamic service entries in a DID (Decentralized Identifier Document). + *

+ * A DID document typically contains metadata describing a DID subject, including a list of services that can be advertised or discovered. + * Example structure: + *

+ * {@code
+ * {
+ *     "@context": [],
+ *     "id": "did:web:example.com:edc01",
+ *     "service": [
+ *         {
+ *             "serviceEndpoint": "https://wallet.example.com/api/holder/edc01",
+ *             "type": "CredentialService",
+ *             "id": "did:web:example.com:edc01#CredentialService"
+ *         }
+ *     ],
+ *     "verificationMethod": [],
+ *     "authentication": [],
+ *     "assertionMethod": [],
+ *     "keyAgreement": [],
+ *     "capabilityInvocation": []
+ * }
+ * }
+ * 
+ *

+ * The service list can be extended to advertise additional endpoints, such as DSP endpoints, + * data plane URLs, or other custom services. + *

+ * This SPI allows wallet or DID management solutions to provide implementations for dynamically creating, updating, + * or removing service entries in a DID document. + *

+ * Implementations should ensure that changes to the DID document are performed according to the underlying wallet + * or DID registry's requirements and that updates are properly propagated. + */ +@ExtensionPoint +public interface DidDocumentServiceClient { + + /** + * Creates or updates a service entry in the DID document. + * + * @param service to be updated + * @return a {@link ServiceResult} indicating success or failure of the operation + */ + ServiceResult update(Service service); + + ServiceResult deleteById(String id); +} From 9f772dfe55548abe4299c623ba826b19d3c40a90 Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Fri, 2 Jan 2026 13:27:52 +0100 Subject: [PATCH 073/112] Update jakarta-json to newer version (#2498) * Update jakarta-json to newer version Signed-off-by: Lars Geyer-Blaumeiser * Correct group name Signed-off-by: Lars Geyer-Blaumeiser --------- Signed-off-by: Lars Geyer-Blaumeiser --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d01480bd59..cbf9c6c604 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ dcp-tck = "1.0.0-SNAPSHOT" dsp-tck = "1.0.0-RC4" flyway = "11.20.0" jackson = "2.20.1" -jakarta-json = "2.0.1" +jakarta-json = "2.1.3" junit = "6.0.1" nimbus = "10.6" okhttp = "5.3.2" @@ -211,7 +211,7 @@ flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway" } flyway-database-postgres = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway" } jacksonJsonP = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp", version.ref = "jackson" } jakarta-rsApi = { module = "jakarta.ws.rs:jakarta.ws.rs-api", version.ref = "rsApi" } -jakartaJson = { module = "org.glassfish:jakarta.json", version.ref = "jakarta-json" } +jakartaJson = { module = "jakarta.json:jakarta.json-api", version.ref = "jakarta-json" } nimbus-jwt = { module = "com.nimbusds:nimbus-jose-jwt", version.ref = "nimbus" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } opentelemetry-javaagent = { module = "io.opentelemetry.javaagent:opentelemetry-javaagent", version.ref = "opentelemetry" } From 8b3a1c33d71847eb94bd5ef45c82a77c536ddb13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 09:07:18 +0100 Subject: [PATCH 074/112] chore(deps): bump io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17 (#2502) Bumps [io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17](https://github.com/open-telemetry/opentelemetry-java-instrumentation) from 2.22.0-alpha to 2.23.0-alpha. - [Release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java-instrumentation/commits) --- updated-dependencies: - dependency-name: io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17 dependency-version: 2.23.0-alpha dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cbf9c6c604..502f33f306 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ nimbus = "10.6" okhttp = "5.3.2" opentelemetry = "2.23.0" opentelemetry-instrumentation = "2.23.0" -opentelemetry-log4j-appender = "2.22.0-alpha" +opentelemetry-log4j-appender = "2.23.0-alpha" postgres = "42.7.8" restAssured = "6.0.0" rsApi = "4.0.0" From ff29476fb999129504b3f63236851b0de177bac6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 09:07:48 +0100 Subject: [PATCH 075/112] chore(deps): bump com.gradle.develocity from 4.2.2 to 4.3 (#2501) Bumps com.gradle.develocity from 4.2.2 to 4.3. --- updated-dependencies: - dependency-name: com.gradle.develocity dependency-version: '4.3' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 339de1b944..df90ec96bc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -144,7 +144,7 @@ include(":edc-dataplane:edc-dataplane-hashicorp-vault") include(":samples:testing-with-mocked-connector") plugins { - id("com.gradle.develocity") version "4.2.2" + id("com.gradle.develocity") version "4.3" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.4.0" } From e4593c484b64196ba8623382f67ac41a0ff10238 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 09:08:19 +0100 Subject: [PATCH 076/112] chore(deps): bump aws from 2.40.3 to 2.41.1 (#2500) Bumps `aws` from 2.40.3 to 2.41.1. Updates `software.amazon.awssdk:s3` from 2.40.3 to 2.41.1 Updates `software.amazon.awssdk:s3-transfer-manager` from 2.40.3 to 2.41.1 --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.41.1 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: software.amazon.awssdk:s3-transfer-manager dependency-version: 2.41.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 502f33f306..7cc400a056 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ edc = "0.15.1" edc-build = "1.1.5" allure = "2.32.0" awaitility = "4.3.0" -aws = "2.40.3" +aws = "2.41.1" azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-SNAPSHOT" From 86e7fc91e6004738dc7f3c3c404a63c32a96fbc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 09:09:07 +0100 Subject: [PATCH 077/112] chore(deps): bump com.networknt:json-schema-validator (#2499) Bumps [com.networknt:json-schema-validator](https://github.com/networknt/json-schema-validator) from 2.0.0 to 3.0.0. - [Release notes](https://github.com/networknt/json-schema-validator/releases) - [Changelog](https://github.com/networknt/json-schema-validator/blob/master/CHANGELOG.md) - [Commits](https://github.com/networknt/json-schema-validator/compare/2.0.0...3.0.0) --- updated-dependencies: - dependency-name: com.networknt:json-schema-validator dependency-version: 3.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- edc-tests/e2e/iatp-tests/build.gradle.kts | 2 +- edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts | 2 +- edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/edc-tests/e2e/iatp-tests/build.gradle.kts b/edc-tests/e2e/iatp-tests/build.gradle.kts index 9de9307243..27901d0779 100644 --- a/edc-tests/e2e/iatp-tests/build.gradle.kts +++ b/edc-tests/e2e/iatp-tests/build.gradle.kts @@ -28,7 +28,7 @@ configurations.all { dependencies { constraints { - testImplementation("com.networknt:json-schema-validator:2.0.0") { + testImplementation("com.networknt:json-schema-validator:3.0.0") { because("older versions cause runtime issues") } } diff --git a/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts b/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts index cf871f4de6..ea3f4a7fb4 100644 --- a/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts +++ b/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts @@ -27,7 +27,7 @@ dependencies { implementation(project(":spi:core-spi")) // TODO: test - implementation("com.networknt:json-schema-validator:2.0.0") + implementation("com.networknt:json-schema-validator:3.0.0") } // do not publish diff --git a/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts b/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts index 322718d83b..d04bacc922 100644 --- a/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts +++ b/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts @@ -41,7 +41,7 @@ dependencies { } constraints { - implementation("com.networknt:json-schema-validator:2.0.0") { + implementation("com.networknt:json-schema-validator:3.0.0") { because("older versions cause runtime issues") } } From 53f6c35261973ee001f4ffe9310ae6e347172a0d Mon Sep 17 00:00:00 2001 From: pratapipatelbcone Date: Fri, 9 Jan 2026 16:28:54 +0530 Subject: [PATCH 078/112] feat: Did Document Service Client SPI and DIV Implementation (#2456) * feat: Did Document Service Client SPI and DIV Implementation * Rename DIV to DIM * Update CompanyID Caching * Update create service payload to include id * Review comments * Remove SPI and Update DIM Implementation * fix: fix end to end test * fix: Review comments and readme * fix: Add check for optional dimOauth2Client --- .../edc-controlplane-base/build.gradle.kts | 1 + .../did-document-service-dim/README.md | 112 ++++ .../did-document-service-dim/build.gradle.kts | 34 ++ .../service/DidDocumentServiceDimClient.java | 410 +++++++++++++ .../DidDocumentServiceDimClientExtension.java | 71 +++ ...rg.eclipse.edc.spi.system.ServiceExtension | 20 + .../DidDocumentServiceDimClientTest.java | 565 ++++++++++++++++++ .../README.md | 40 ++ .../tck/dcp/DcpPresentationFlowTest.java | 1 + settings.gradle.kts | 3 +- 10 files changed, 1256 insertions(+), 1 deletion(-) create mode 100644 edc-extensions/did-document/did-document-service-dim/README.md create mode 100644 edc-extensions/did-document/did-document-service-dim/build.gradle.kts create mode 100644 edc-extensions/did-document/did-document-service-dim/src/main/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClient.java create mode 100644 edc-extensions/did-document/did-document-service-dim/src/main/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClientExtension.java create mode 100644 edc-extensions/did-document/did-document-service-dim/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/did-document/did-document-service-dim/src/test/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClientTest.java create mode 100644 edc-extensions/did-document/did-document-service-self-registration/README.md diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index 93a7799a4e..2f2b4339a7 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -62,6 +62,7 @@ dependencies { implementation(project(":edc-extensions:token-interceptor")) implementation(project(":edc-extensions:event-subscriber")) implementation(project(":edc-extensions:did-document:did-document-service-self-registration")) + implementation(project(":edc-extensions:did-document:did-document-service-dim")) runtimeOnly(libs.bundles.edc.monitoring) runtimeOnly(libs.edc.aws.validator.data.address.s3) diff --git a/edc-extensions/did-document/did-document-service-dim/README.md b/edc-extensions/did-document/did-document-service-dim/README.md new file mode 100644 index 0000000000..1df0c80687 --- /dev/null +++ b/edc-extensions/did-document/did-document-service-dim/README.md @@ -0,0 +1,112 @@ +# did-document-service-dim + +## Overview +This extension provides a client for managing DID Document Service entries using the DIM (Decentralized Identity Management) API. +It enables secure and programmatic updates to DID Documents for organizations using the TractusX ecosystem. +The client's purpose is to be injected in the `did-document-service-self-registration` extension by implementing the `DidDocumentServiceClient` SPI. + +## API Details + +### 1. Add DID Document Service +- **Endpoint:** `PATCH {dimUrl}/api/v2.0.0/companyIdentities/{companyIdentityId}` +- **Description:** Adds or replaces a service entry in the DID Document. +- **Sample Request:** + +```json +{ + "didDocUpdates": { + "addServices": [ + { + "id": "did:web:example.com:123#DataService", + "serviceEndpoint": "https://edc.com/edc/.well-known/dspace-version", + "type": "DataService" + } + ] + } +} +``` +- **Sample Response:** +```json +{ + "updateDidRequest": { + "didDocUpdates": { + "addServices": [ + { + "id": "did:web:example.com:123#DataService", + "serviceEndpoint": "https://edc.com/edc/.well-known/dspace-version", + "type": "DataService" + } + ] + }, + "success": true + } +} +``` + +### 2. Delete DID Document Service +- **Endpoint:** `PATCH {dimUrl}/api/v2.0.0/companyIdentities/{companyIdentityId}` +- **Description:** Removes a service entry from the DID Document. +- **Sample Request:** +```json +{ + "didDocUpdates": { + "removeServices": [ + "did:web:example.com:123#DataService" + ] + } +} +``` +- **Sample Response:** +```json +{ + "updateDidRequest": { + "didDocUpdates": { + "removeServices": [ + "did:web:example.com:123#DataService" + ] + }, + "success": true + } +} +``` + +### 3. Update Patch Status +- **Endpoint:** `PATCH {dimUrl}/api/v2.0.0/companyIdentities/{companyIdentityId}/status` +- **Description:** Finalizes the update operation for the DID Document. This API call must be made after adding or deleting services. +- **Sample Response:** +```json +{ + "operation": "update", + "status": "successful" +} +``` + +### 4. Resolve Company Identity +- **Endpoint:** `GET {dimUrl}/api/v2.0.0/companyIdentities?$filter=issuerDID eq "{ownDid}"` +- **Description:** Resolves the company identity ID for the given DID. All DID Document Service operations require the company identity ID. +- **Sample Response:** +```json +{ + "count": 1, + "data": [ + { + "id": "ddfdcbad-44b2-43b5-b49f-6347ec2e586a", + "issuerDID": "did:web:example.com:ABC123", + "isPrivate": false, + "name": "ABC123", + "lastOperationStatus": { + "lastChanged": "2025-12-09T10:28:27.828Z", + "operation": "update", + "status": "successful" + }, + "allOperationStatuses": [], + "downloadURL": "https://div.example.com/did-document/91f6954d-b3c8-474a-ad97-59b52cff1f60/did-web/bf618a73df14b6da49c41215fcd920516ad2dbab6922568dc78c59100ec98d9b", + "application": [ + "provider" + ], + "isSelfHosted": true + } + ] +} +``` +> All APIs require authentication using an authentication token generated via DIM. diff --git a/edc-extensions/did-document/did-document-service-dim/build.gradle.kts b/edc-extensions/did-document/did-document-service-dim/build.gradle.kts new file mode 100644 index 0000000000..b40d6db2b3 --- /dev/null +++ b/edc-extensions/did-document/did-document-service-dim/build.gradle.kts @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2025 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` + `maven-publish` +} + +dependencies { + implementation(project(":spi:did-document-service-spi")) + implementation(project(":edc-extensions:dcp:tx-dcp-sts-dim")) + implementation(project(":core:core-utils")) + implementation(libs.edc.runtime.metamodel) + implementation(libs.edc.spi.identity.did) + implementation(libs.edc.spi.http) + + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/did-document/did-document-service-dim/src/main/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClient.java b/edc-extensions/did-document/did-document-service-dim/src/main/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClient.java new file mode 100644 index 0000000000..984be6eec7 --- /dev/null +++ b/edc-extensions/did-document/did-document-service-dim/src/main/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClient.java @@ -0,0 +1,410 @@ +/******************************************************************************** + * Copyright (c) 2025 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +package org.eclipse.tractusx.edc.did.document.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.eclipse.edc.http.spi.EdcHttpClient; +import org.eclipse.edc.iam.did.spi.document.Service; +import org.eclipse.edc.spi.iam.TokenRepresentation; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.result.ServiceResult; +import org.eclipse.tractusx.edc.iam.dcp.sts.dim.oauth.DimOauth2Client; +import org.eclipse.tractusx.edc.spi.did.document.service.DidDocumentServiceClient; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +import static java.lang.String.format; +import static org.eclipse.edc.http.spi.FallbackFactories.retryWhenStatusIsNotIn; + +/** + * Implementation of {@link DidDocumentServiceClient} that interacts with a DIM (Decentralized Identity + * Verification) Service to manage services in a DID Document. + * + * Did Document is tied to a company identity in DIM, which is resolved using the own DID. Company identity is needed + * to perform any updates to the DID Document. + * + *

+ * Did document update is a two-step process in DIM: + * 1. A PATCH request is sent to add or remove services. + * Sample payload to add a service: + *

+ *  {@code
+ * {
+ *   "didDocUpdates": {
+ *     "addServices": [
+ *       {
+ *         "id": "did:web:example.com:123#DataService",
+ *         "serviceEndpoint": "https://edc.com/edc/.well-known/dspace-version",
+ *         "type": "DataService"
+ *       }
+ *     ]
+ *   }
+ * }
+ * }
+ * 
+ * 2. A subsequent PATCH request is sent to the /status endpoint to finalize the update. + * PATCH {dimUrl}/companyIdentities/{companyIdentityId}/status + */ +public class DidDocumentServiceDimClient implements DidDocumentServiceClient { + + public static final MediaType TYPE_JSON = MediaType.parse("application/json"); + private static final String DID_DOC_API_PATH = "/api/v2.0.0/companyIdentities"; + + private final EdcHttpClient httpClient; + private final DimOauth2Client dimOauth2Client; + private final ObjectMapper mapper; + private final String ownDid; + private final Monitor monitor; + private final String didDocApiUrl; + private final AtomicReference companyIdentity = new AtomicReference<>(); + + public DidDocumentServiceDimClient(EdcHttpClient httpClient, + DimOauth2Client dimOauth2Client, ObjectMapper mapper, String dimUrl, String ownDid, Monitor monitor) { + this.httpClient = httpClient; + this.dimOauth2Client = dimOauth2Client; + this.mapper = mapper; + this.ownDid = ownDid; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); + this.didDocApiUrl = String.join("", dimUrl, DID_DOC_API_PATH); + } + + @Override + public ServiceResult update(Service service) { + return validateService(service) + .compose(v -> deleteServiceEntry(service.getId())) + .compose(v -> updatePatchStatus()) + .compose(v -> createServiceEntry(service)) + .compose(v -> updatePatchStatus()) + .onSuccess(v -> monitor.info("Updated service entry %s in DID Document".formatted(asString(service)))) + .onFailure(f -> monitor.warning("Failed to update service entry %s with failure %s".formatted(asString(service), f.getFailureDetail()))); + } + + private ServiceResult validateService(Service service) { + if (isBlank(service.getServiceEndpoint()) || isBlank(service.getType())) { + return ServiceResult.unexpected("Validation Failure: Service id, type and serviceEndpoint must be provided and non-blank"); + } + return validateServiceId(service.getId()); + } + + private ServiceResult validateServiceId(String serviceId) { + + if (isBlank(serviceId)) { + return ServiceResult.unexpected("Validation Failure: Service ID must be provided and non-blank"); + } + try { + new URI(serviceId); + } catch (URISyntaxException ex) { + return ServiceResult.unexpected("Validation Failure: Service ID must be a valid URI: %s'".formatted(serviceId)); + } + return ServiceResult.success(); + } + + private ServiceResult createServiceEntry(Service service) { + + return createTenantBaseUrl() + .compose(url -> patchRequest(didDocCreateServicePayload(service), url)) + .map(Request.Builder::build) + .compose(request -> this.executeRequest(request, this::handleDidUpdateResponse)) + .flatMap(res -> ServiceResult.success()); + } + + /** + * Constructs the payload for adding a service to the DID Document. + *

+ * The resulting JSON structure is: + *

+     *  {@code
+     * {
+     *   "didDocUpdates": {
+     *     "addServices": [
+     *       {
+     *         "id": "did:web:example.com:123#DataService",
+     *         "serviceEndpoint": "https://edc.com/edc/.well-known/dspace-version",
+     *         "type": "DataService"
+     *       }
+     *     ]
+     *   }
+     * }
+     * }
+     * 
+ * + * @param service the service to add to the DID Document + * @return a map representing the payload for the add service operation + */ + private Map didDocCreateServicePayload(Service service) { + var createPayload = Map.of("id", service.getId(), "type", service.getType(), "serviceEndpoint", service.getServiceEndpoint()); + return didDocUpdatePayload(Map.of("addServices", List.of(createPayload))); + } + + /** + * Handles the response for a DID update request. + * Package Private visibility for testing. + *

+ * The expected successful response structure is: + *

+     *  {@code
+     *  {
+     *   "updateDidRequest": {
+     *     "didDocUpdates": {
+     *       "removeServices": [
+     *         "did:web:example.com:123#DataService"
+     *       ]
+     *     },
+     *     "success": true
+     *   }
+     * }
+     * }
+     * 
+ * + * @param response the HTTP response + * @return a Result containing the response body as a string if successful, or a failure message + */ + Result handleDidUpdateResponse(Response response) { + if (!response.isSuccessful()) { + return Result.failure("DID update request failed with status code: %d, message: %s".formatted(response.code(), response.message())); + } + try { + var body = Objects.requireNonNull(response.body()).string(); + var parsedBody = mapper.readTree(body); + return Optional.ofNullable(parsedBody.get("updateDidRequest")) + .map(updateDidRequest -> updateDidRequest.path("success").asBoolean(false)) + .filter(Boolean.TRUE::equals) + .map(success -> Result.success(body)) + .orElseGet(() -> Result.failure("Failed to Update Did Document, res: %s".formatted(body))); + } catch (IOException e) { + monitor.severe("Failed to parse did update response from DIM", e); + return Result.failure(e.getMessage()); + } + } + + /** + * Deletes a service entry from the DID Document. + * It requires two API calls: one to remove the service and another to update the patch status. + * + * @param id the ID of the service to delete + * @return a ServiceResult indicating success or failure + */ + @Override + public ServiceResult deleteById(String id) { + return validateServiceId(id) + .compose(v -> deleteServiceEntry(id)) + .compose(v -> updatePatchStatus()) + .onSuccess(v -> monitor.info("Deletion of service entry %s in DID Document successful".formatted(id))) + .onFailure(f -> monitor.severe("Failed to delete service entry %s with failure %s".formatted(id, f.getFailureDetail()))); + } + + private ServiceResult deleteServiceEntry(String id) { + return createTenantBaseUrl() + .compose(url -> patchRequest(didDocDeleteServicePayload(id), url)) + .map(Request.Builder::build) + .compose(request -> this.executeRequest(request, this::handleDidUpdateResponse)) + .flatMap(res -> ServiceResult.success()); + } + + /** + * Constructs the payload for removing a service from the DID Document. + *

+ * The resulting JSON structure is: + *

+     *  {@code
+     * {
+     *   "didDocUpdates": {
+     *     "removeServices": [
+     *       "did:web:example.com:123#DataService"
+     *     ]
+     *   }
+     * }
+     * }
+     * 
+ * + * @param id the ID of the service to remove from the DID Document + * @return a map representing the payload for the remove service operation + */ + private Map didDocDeleteServicePayload(String id) { + return didDocUpdatePayload(Map.of("removeServices", List.of(id))); + } + + private ServiceResult updatePatchStatus() { + + return createTenantBaseUrl() + .map("%s/status"::formatted) + .compose(url -> patchRequest(Map.of(), url)) + .map(Request.Builder::build) + .compose(request -> this.executeRequest(request, this::handlePatchStatusResponse)) + .flatMap(res -> ServiceResult.success()); + } + + /** + * Handles the response for a patch status request. + * Package Private visibility for testing. + *

+ * The expected successful response structure is: + *

+     *  {@code
+     *  {
+     *   "operation": "update",
+     *   "status": "successful"
+     * }
+     * }
+     *
+     * @param response the HTTP response
+     * @return a Result containing the response body as a string if successful, or a failure message
+     */
+    Result handlePatchStatusResponse(Response response) {
+        if (!response.isSuccessful()) {
+            return Result.failure("Failed to update patch status request with status code: %d, message %s".formatted(response.code(), response.message()));
+        }
+        try {
+            var body = Objects.requireNonNull(response.body()).string();
+            var parsedBody = mapper.readTree(body);
+            return Optional.ofNullable(parsedBody.path("status").asText(null))
+                    .filter("successful"::equalsIgnoreCase)
+                    .map(success -> Result.success(body))
+                    .orElseGet(() -> Result.failure("Failed to Update Patch Status, res: %s".formatted(body)));
+        } catch (IOException e) {
+            monitor.severe("Failed to parse patch status response from DIM", e);
+            return Result.failure(e.getMessage());
+        }
+    }
+
+    private Result createTenantBaseUrl() {
+        if (companyIdentity.get() == null) {
+            var url = HttpUrl.parse(didDocApiUrl).newBuilder().addQueryParameter("$filter", "issuerDID eq \"%s\"".formatted(ownDid)).build();
+            return getRequest()
+                    .map(builder -> builder.url(url).build())
+                    .compose(request -> this.executeRequest(request, this::handleCompanyIdentityResponse))
+                    .onSuccess(companyIdentity::set)
+                    .compose(companyId -> Result.success(companyIdentityUrl(companyIdentity.get())))
+                    .onFailure(f -> monitor.severe("Failed to resolve company identity for DID %s with failure %s".formatted(ownDid, f.getFailureDetail())));
+        }
+        return Result.success(companyIdentityUrl(companyIdentity.get()));
+    }
+
+    /**
+     * Handles the response for a company identity resolution request.
+     * Package Private visibility for testing.
+     * 

+ * It is expected that DIM returns exactly one company identity for the given DID. + * If none or multiple are returned, it is considered a failure. + *

+ * The expected successful response structure is: + *

+     *  {@code
+     *  {
+     *   "count": 1,
+     *   "data": [
+     *     {
+     *       "id": "ddfdcbad-44b2-43b5-b49f-6347ec2e586a",
+     *       "issuerDID": "did:web:example.com:ABC123",
+     *       "isPrivate": false,
+     *       "name": "ABC123",
+     *       "lastOperationStatus": {
+     *         "lastChanged": "2025-12-09T10:28:27.828Z",
+     *         "operation": "update",
+     *         "status": "successful"
+     *       },
+     *       "allOperationStatuses": [],
+     *       "downloadURL": "https://div.example.com/did-document/91f6954d-b3c8-474a-ad97-59b52cff1f60/did-web/bf618a73df14b6da49c41215fcd920516ad2dbab6922568dc78c59100ec98d9b",
+     *       "application": [
+     *         "provider"
+     *       ],
+     *       "isSelfHosted": true
+     *     }
+     *   ]
+     * }
+     * }
+     *
+     * @param response the HTTP response
+     * @return a Result containing the company identity ID if successful, or a failure message
+     */
+    Result handleCompanyIdentityResponse(Response response) {
+        if (!response.isSuccessful()) {
+            return Result.failure("Company identity resolution request failed with status code: %d, message: %s".formatted(response.code(), response.message()));
+        }
+        try {
+            var body = Objects.requireNonNull(response.body()).string();
+            var parsedBody = mapper.readTree(body);
+            return Optional.of(parsedBody)
+                    .filter(data -> data.path("count").asInt(0) == 1)
+                    .map(data -> data.path("data").path(0).path("id").asText(null))
+                    .map(Result::success)
+                    .orElseGet(() -> Result.failure("Failed to Resolve Company Identity Response, res: %s".formatted(body)));
+        } catch (IOException e) {
+            monitor.severe("Failed to parse company identity response from DIM", e);
+            return Result.failure(e.getMessage());
+        }
+    }
+
+    private Result patchRequest(Map body, String url) {
+        try {
+            var requestBody = RequestBody.create(mapper.writeValueAsString(body), TYPE_JSON);
+            return baseRequestWithToken().map(builder -> builder.patch(requestBody).url(url));
+        } catch (JsonProcessingException e) {
+            return Result.failure(e.getMessage());
+        }
+    }
+
+    private Result getRequest() {
+        return baseRequestWithToken().map(Request.Builder::get);
+    }
+
+    private Result executeRequest(Request request, Function> responseMapping) {
+        return httpClient.execute(request, List.of(retryWhenStatusIsNotIn(200, 201)), responseMapping);
+    }
+
+    private Result baseRequestWithToken() {
+        return dimOauth2Client.obtainRequestToken().map(this::baseRequestWithToken);
+    }
+
+    private Request.Builder baseRequestWithToken(TokenRepresentation tokenRepresentation) {
+        return new Request.Builder().addHeader("Authorization", format("Bearer %s", tokenRepresentation.getToken()));
+    }
+
+    private Map didDocUpdatePayload(Map operationPayload) {
+        return Map.of("didDocUpdates", operationPayload);
+    }
+
+    String asString(Service service) {
+        return "Service [id:%s, type:%s, serviceEndpoint=%s]".formatted(service.getId(), service.getType(), service.getServiceEndpoint());
+    }
+
+    private String companyIdentityUrl(String companyIdentity) {
+        return "%s/%s".formatted(didDocApiUrl, companyIdentity);
+    }
+
+    private boolean isBlank(String str) {
+        return str == null || str.isBlank();
+    }
+}
diff --git a/edc-extensions/did-document/did-document-service-dim/src/main/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClientExtension.java b/edc-extensions/did-document/did-document-service-dim/src/main/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClientExtension.java
new file mode 100644
index 0000000000..03ee87bd12
--- /dev/null
+++ b/edc-extensions/did-document/did-document-service-dim/src/main/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClientExtension.java
@@ -0,0 +1,71 @@
+/********************************************************************************
+ * Copyright (c) 2025 SAP SE
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * 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.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+package org.eclipse.tractusx.edc.did.document.service;
+
+import org.eclipse.edc.http.spi.EdcHttpClient;
+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.monitor.Monitor;
+import org.eclipse.edc.spi.system.ServiceExtension;
+import org.eclipse.edc.spi.system.ServiceExtensionContext;
+import org.eclipse.edc.spi.types.TypeManager;
+import org.eclipse.tractusx.edc.core.utils.PathUtils;
+import org.eclipse.tractusx.edc.iam.dcp.sts.dim.oauth.DimOauth2Client;
+import org.eclipse.tractusx.edc.spi.did.document.service.DidDocumentServiceClient;
+
+@Provides(DidDocumentServiceClient.class)
+public class DidDocumentServiceDimClientExtension implements ServiceExtension {
+
+    @Inject
+    private EdcHttpClient httpClient;
+
+    @Inject(required = false)
+    private DimOauth2Client dimOauth2Client;
+
+    @Inject
+    private TypeManager typeManager;
+
+    @Inject
+    private Monitor monitor;
+
+    @Setting(key = "tx.edc.iam.sts.dim.url", description = "STS Dim endpoint", required = false)
+    private String dimUrl;
+
+    @Setting(key = "edc.participant.id", description = "EDC Participant Id")
+    private String ownDid;
+
+    @Override
+    public void initialize(ServiceExtensionContext context) {
+
+        if (dimUrl == null || dimUrl.isBlank() || dimOauth2Client == null) {
+            monitor.info("DidDocumentServiceDIMClient will not be registered because DIM URL not configured or an implementation of DimOauth2Client is missing");
+        } else {
+            var client = new DidDocumentServiceDimClient(
+                    httpClient,
+                    dimOauth2Client,
+                    typeManager.getMapper(),
+                    PathUtils.removeTrailingSlash(dimUrl),
+                    ownDid,
+                    monitor);
+            context.registerService(DidDocumentServiceClient.class, client);
+        }
+    }
+}
diff --git a/edc-extensions/did-document/did-document-service-dim/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/did-document/did-document-service-dim/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
new file mode 100644
index 0000000000..d79dc65c91
--- /dev/null
+++ b/edc-extensions/did-document/did-document-service-dim/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
@@ -0,0 +1,20 @@
+#################################################################################
+#  Copyright (c) 2025 SAP SE
+#
+#  See the NOTICE file(s) distributed with this work for additional
+#  information regarding copyright ownership.
+#
+#  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.
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#  License for the specific language governing permissions and limitations
+#  under the License.
+#
+#  SPDX-License-Identifier: Apache-2.0
+#################################################################################
+
+org.eclipse.tractusx.edc.did.document.service.DidDocumentServiceDimClientExtension
diff --git a/edc-extensions/did-document/did-document-service-dim/src/test/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClientTest.java b/edc-extensions/did-document/did-document-service-dim/src/test/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClientTest.java
new file mode 100644
index 0000000000..7651824cb9
--- /dev/null
+++ b/edc-extensions/did-document/did-document-service-dim/src/test/java/org/eclipse/tractusx/edc/did/document/service/DidDocumentServiceDimClientTest.java
@@ -0,0 +1,565 @@
+/********************************************************************************
+ * Copyright (c) 2025 SAP SE
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * 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.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+package org.eclipse.tractusx.edc.did.document.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import okhttp3.HttpUrl;
+import okhttp3.MediaType;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import okio.Buffer;
+import org.eclipse.edc.http.spi.EdcHttpClient;
+import org.eclipse.edc.iam.did.spi.document.Service;
+import org.eclipse.edc.spi.iam.TokenRepresentation;
+import org.eclipse.edc.spi.monitor.Monitor;
+import org.eclipse.edc.spi.result.Result;
+import org.eclipse.edc.spi.result.ServiceFailure;
+import org.eclipse.tractusx.edc.iam.dcp.sts.dim.oauth.DimOauth2Client;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
+import org.junit.jupiter.params.provider.ArgumentsSource;
+import org.junit.jupiter.params.provider.EmptySource;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.junit.jupiter.params.support.ParameterDeclarations;
+import org.mockito.ArgumentCaptor;
+
+import java.io.IOException;
+import java.util.stream.Stream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class DidDocumentServiceDimClientTest {
+
+    private static final String COMPANY_ID = "ddfdcbad-44b2-43b5-b49f-6347ec2e586a";
+
+    private static final String DATA_SERVICE_ID = "did:web:example.com:123#DataService";
+    private static final String DATA_SERVICE_TYPE = "DataService";
+    private static final String DATA_SERVICE_ENDPOINT = "https://edc.com/edc/.well-known/dspace-version";
+
+    private final EdcHttpClient httpClient = mock(EdcHttpClient.class);
+    private final DimOauth2Client dimOauth2Client = mock(DimOauth2Client.class);
+    private final ObjectMapper mapper = new ObjectMapper();
+    private final Monitor monitor = mock(Monitor.class);
+    private final String dimUrl = "https://div.example.com";
+    private final String didDocApiUrl = String.join("", dimUrl, "/api/v2.0.0/companyIdentities");
+    private final String tenantBaseUrl = String.join("/", didDocApiUrl, COMPANY_ID);
+    private final String didUpdateStatusUrl = String.join("", tenantBaseUrl + "/status");
+    private final String ownDid = "did:web:example.com:123";
+
+    private DidDocumentServiceDimClient client;
+
+    @BeforeEach
+    void setUp() {
+        when(monitor.withPrefix(anyString())).thenReturn(monitor);
+        client = new DidDocumentServiceDimClient(httpClient, dimOauth2Client, mapper, dimUrl, ownDid, monitor);
+    }
+
+    @Test
+    void update_service_success() {
+
+        when(dimOauth2Client.obtainRequestToken()).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("token").build()));
+        when(httpClient.execute(any(Request.class), anyList(), any()))
+                .thenReturn(Result.success(COMPANY_ID)) // resolve company id
+                .thenReturn(Result.success("")) // delete service
+                .thenReturn(Result.success("")) // // update patch status
+                .thenReturn(Result.success("")) //  create service
+                .thenReturn(Result.success("")); // update patch status
+
+        var dataService = new Service(DATA_SERVICE_ID, DATA_SERVICE_TYPE, DATA_SERVICE_ENDPOINT);
+        var result = client.update(dataService);
+
+        assertThat(result).isSucceeded();
+        var requestCaptor = ArgumentCaptor.forClass(Request.class);
+        verify(httpClient, times(5)).execute(requestCaptor.capture(), anyList(), any());
+
+        var requests = requestCaptor.getAllValues();
+
+        // assert resolve company id
+        var expectedCompanyIdUrl = HttpUrl.parse(didDocApiUrl).newBuilder().addQueryParameter("$filter", "issuerDID eq \"%s\"".formatted(ownDid)).build().toString();
+        assertRequest(requests.get(0), "GET", expectedCompanyIdUrl, null);
+
+        // assert delete service
+        var expectedDeleteServiceBody = """
+                {
+                   "didDocUpdates": {
+                    "removeServices": [
+                      "%s"
+                    ]
+                  }
+                }
+                """.formatted(dataService.getId());
+        assertRequest(requests.get(1), "PATCH", tenantBaseUrl, expectedDeleteServiceBody);
+
+        // assert delete service patch status
+        assertRequest(requests.get(2), "PATCH", didUpdateStatusUrl, "{}");
+
+        // assert create service
+        var expectedCreateServiceBody = """
+                {
+                  "didDocUpdates": {
+                    "addServices": [
+                      {
+                        "id": "%s",
+                        "serviceEndpoint": "%s",
+                        "type": "%s"
+                      }
+                    ]
+                  }
+                }
+                """.formatted(dataService.getId(), dataService.getServiceEndpoint(), dataService.getType());
+        assertRequest(requests.get(3), "PATCH", tenantBaseUrl, expectedCreateServiceBody);
+
+        // assert create service patch status
+        assertRequest(requests.get(4), "PATCH", didUpdateStatusUrl, "{}");
+    }
+
+    @ParameterizedTest
+    @ArgumentsSource(InvalidServiceProvider.class)
+    void update_service_failure(Service service) {
+
+        var result = client.update(service);
+        assertThat(result).isFailed().satisfies(failure -> {
+            assertThat(failure.getReason()).isEqualTo(ServiceFailure.Reason.UNEXPECTED);
+            assertThat(failure.getFailureDetail()).contains("Validation Failure");
+        });
+    }
+
+    private static class InvalidServiceProvider implements ArgumentsProvider {
+        @Override
+        public @NotNull Stream provideArguments(@NotNull ParameterDeclarations parameters, @NotNull ExtensionContext extensionContext) {
+            return Stream.of(
+                    Arguments.of(new Service(null, null, null)),
+                    Arguments.of(new Service(DATA_SERVICE_ID, null, null)),
+                    Arguments.of(new Service(null, DATA_SERVICE_ID, null)),
+                    Arguments.of(new Service(null, null, DATA_SERVICE_ENDPOINT)),
+                    Arguments.of(new Service(DATA_SERVICE_ID, DATA_SERVICE_TYPE, null)),
+                    Arguments.of(new Service(DATA_SERVICE_ID, null, DATA_SERVICE_ENDPOINT)),
+                    Arguments.of(new Service(null, DATA_SERVICE_ID, DATA_SERVICE_ENDPOINT)),
+                    Arguments.of(new Service("invalid uri", DATA_SERVICE_TYPE, DATA_SERVICE_ENDPOINT))
+            );
+        }
+    }
+
+    @Test
+    void deleteById_success() {
+        when(dimOauth2Client.obtainRequestToken()).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("token").build()));
+        when(httpClient.execute(any(Request.class), anyList(), any()))
+                .thenReturn(Result.success(COMPANY_ID)) // resolve company id
+                .thenReturn(Result.success("")) // delete service
+                .thenReturn(Result.success("")); // // update patch status
+
+        var result = client.deleteById(DATA_SERVICE_ID);
+
+        assertThat(result).isSucceeded();
+        var requestCaptor = ArgumentCaptor.forClass(Request.class);
+        verify(httpClient, times(3)).execute(requestCaptor.capture(), anyList(), any());
+
+        var requests = requestCaptor.getAllValues();
+
+        // assert resolve company id
+        var expectedCompanyIdUrl = HttpUrl.parse(didDocApiUrl).newBuilder().addQueryParameter("$filter", "issuerDID eq \"%s\"".formatted(ownDid)).build().toString();
+        assertRequest(requests.get(0), "GET", expectedCompanyIdUrl, null);
+
+        // assert delete service
+        var expectedDeleteServiceBody = """
+                {
+                   "didDocUpdates": {
+                    "removeServices": [
+                      "%s"
+                    ]
+                  }
+                }
+                """.formatted(DATA_SERVICE_ID);
+        assertRequest(requests.get(1), "PATCH", tenantBaseUrl, expectedDeleteServiceBody);
+
+        // assert delete service patch status
+        assertRequest(requests.get(2), "PATCH", didUpdateStatusUrl, "{}");
+    }
+
+    @ParameterizedTest
+    @NullSource
+    @EmptySource
+    @ValueSource(strings = {" ", "invalid uri"})
+    void deleteById_failure(String serviceId) {
+
+        var result = client.deleteById(serviceId);
+        assertThat(result).isFailed().satisfies(failure -> {
+            assertThat(failure.getReason()).isEqualTo(ServiceFailure.Reason.UNEXPECTED);
+            assertThat(failure.getFailureDetail()).contains("Validation Failure");
+        });
+    }
+
+    @Test
+    void handleDidUpdateResponse_addService_success() {
+
+        String didUpdateResponse = """
+                {
+                  "updateDidRequest": {
+                    "didDocUpdates": {
+                      "addServices": [
+                        {
+                         "id": "did:web:example.com:123#DataService",
+                          "serviceEndpoint": "https://edc.com/edc/.well-known/dspace-version",
+                          "type": "DataService"
+                        }
+                      ]
+                    },
+                    "success": true
+                  }
+                }
+                """;
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handleDidUpdateResponse(response);
+        assertThat(result).isSucceeded();
+    }
+
+    @Test
+    void handleDidUpdateResponse_addService_failure() {
+
+        String didUpdateResponse = """
+                {
+                  "updateDidRequest": {
+                    "didDocUpdates": {
+                      "addServices": [
+                        {
+                          "id": "did:web:example.com:123#DataService",
+                          "serviceEndpoint": "https://edc.com/edc/.well-known/dspace-version",
+                          "type": "DataService"
+                        }
+                      ]
+                    },
+                    "success": false
+                  }
+                }
+                """;
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handleDidUpdateResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handleDidUpdateResponse_addService_failureStatusCode() {
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(false);
+
+        var result = client.handleDidUpdateResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handleDidUpdateResponse_addService_resBodyParseFailure() {
+        String didUpdateResponse = "";
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handleDidUpdateResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handleDidUpdateResponse_deleteService_success() {
+
+        String didUpdateResponse = """
+                {
+                   "updateDidRequest": {
+                     "didDocUpdates": {
+                       "removeServices": [
+                          "did:web:example.com:123#DataService"
+                       ]
+                     },
+                     "success": true
+                   }
+                 }
+                """;
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handleDidUpdateResponse(response);
+        assertThat(result).isSucceeded();
+    }
+
+    @Test
+    void handleDidUpdateResponse_deleteService_failure() throws IOException {
+
+        String didUpdateResponse = """
+                {
+                   "updateDidRequest": {
+                     "didDocUpdates": {
+                       "removeServices": [
+                          "did:web:example.com:123#DataService"
+                       ]
+                     },
+                     "success": false
+                   }
+                 }
+                """;
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handleDidUpdateResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handlePatchStatusResponse_success() throws IOException {
+
+        String didUpdateResponse = """
+                {
+                  "operation": "update",
+                  "status": "successful"
+                }
+                """;
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handlePatchStatusResponse(response);
+        assertThat(result).isSucceeded();
+    }
+
+    @Test
+    void handlePatchStatusResponse_failure() throws IOException {
+
+        String didUpdateResponse = """
+                {
+                  "operation": "update",
+                  "status": "failed"
+                }
+                """;
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handlePatchStatusResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handlePatchStatusResponse_failureStatusCode() throws IOException {
+
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(false);
+
+        var result = client.handlePatchStatusResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handlePatchStatusResponse_resBodyParseFailure() {
+        String didUpdateResponse = "";
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handlePatchStatusResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handleCompanyIdentityResponse_success() throws IOException {
+
+        String didUpdateResponse = """
+                {
+                  "count": 1,
+                  "data": [
+                    {
+                      "id": "ddfdcbad-44b2-43b5-b49f-6347ec2e586a",
+                      "issuerDID": "did:web:example.com:ABC123",
+                      "isPrivate": false,
+                      "name": "ABC123",
+                      "lastOperationStatus": {
+                        "lastChanged": "2025-12-09T10:28:27.828Z",
+                        "operation": "update",
+                        "status": "successful"
+                      },
+                      "allOperationStatuses": [],
+                      "downloadURL": "https://div.example.com/did-document/91f6954d-b3c8-474a-ad97-59b52cff1f60/did-web/bf618a73df14b6da49c41215fcd920516ad2dbab6922568dc78c59100ec98d9b",
+                      "application": [
+                        "provider"
+                      ],
+                      "isSelfHosted": true
+                    }
+                  ]
+                }
+                """;
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handleCompanyIdentityResponse(response);
+        assertThat(result).isSucceeded().satisfies(companyId -> assertThat(companyId).isEqualTo(COMPANY_ID));
+    }
+
+    @Test
+    void handleCompanyIdentityResponse_NoData() throws IOException {
+
+        String didUpdateResponse = """
+                {
+                   "count": 0,
+                   "data": []
+                 }
+                """;
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handleCompanyIdentityResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handleCompanyIdentityResponse_failureStatusCode() throws IOException {
+
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(false);
+
+        var result = client.handleCompanyIdentityResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handleCompanyIdentityResponse_MultipleData() throws IOException {
+
+        String didUpdateResponse = """
+                {
+                  "count": 2,
+                  "data": [
+                    {
+                      "id": "ddfdcbad-44b2-43b5-b49f-6347ec2e586a",
+                      "issuerDID": "did:web:example.com:ABC123",
+                      "isPrivate": false,
+                      "name": "ABC123",
+                      "lastOperationStatus": {
+                        "lastChanged": "2025-12-09T10:28:27.828Z",
+                        "operation": "update",
+                        "status": "successful"
+                      },
+                      "allOperationStatuses": [],
+                      "downloadURL": "https://div.example.com/did-document/91f6954d-b3c8-474a-ad97-59b52cff1f60/did-web/bf618a73df14b6da49c41215fcd920516ad2dbab6922568dc78c59100ec98d9b",
+                      "application": [
+                        "provider"
+                      ],
+                      "isSelfHosted": true
+                    },
+                    {
+                      "id": "ddfdcbad-44b2-43b5-b49f-6347ec2e586b",
+                      "issuerDID": "did:web:example.com:DEF456",
+                      "isPrivate": false,
+                      "name": "DEF456",
+                      "lastOperationStatus": {
+                        "lastChanged": "2025-12-09T10:28:27.828Z",
+                        "operation": "update",
+                        "status": "successful"
+                      },
+                      "allOperationStatuses": [],
+                      "downloadURL": "https://div.example.com/did-document/91f6954d-b3c8-474a-ad97-59b52cff1f60/did-web/bf618a73df14b6da49c41215fcd920516ad2dbab6922568dc78c59100ec98d9b",
+                      "application": [
+                        "provider"
+                      ],
+                      "isSelfHosted": true
+                    }
+                  ]
+                }
+                """;
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handleCompanyIdentityResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    @Test
+    void handleCompanyIdentityResponse_resBodyParseFailure() {
+        String didUpdateResponse = "";
+        var responseBody = ResponseBody.create(didUpdateResponse, MediaType.parse("application/json"));
+        var response = mock(Response.class);
+        when(response.isSuccessful()).thenReturn(true);
+        when(response.body()).thenReturn(responseBody);
+
+        var result = client.handleCompanyIdentityResponse(response);
+        assertThat(result).isFailed();
+    }
+
+    private void assertRequest(Request request, String expectedMethod, String expectedUrl, String expectedJsonBody) {
+
+        assertThat(request.url().toString()).isEqualTo(expectedUrl);
+        assertThat(request.method()).isEqualTo(expectedMethod);
+
+        try {
+            JsonNode expectedJsonNode = expectedJsonBody != null ? mapper.readTree(expectedJsonBody) : null;
+            var actualJsonNode = request.body() != null ? mapper.readTree(stringifyRequestBody(request)) : null;
+            assertThat(actualJsonNode).isEqualTo(expectedJsonNode);
+        } catch (IOException e) {
+            throw new RuntimeException("Invalid expectedJsonBody provided", e);
+        }
+    }
+
+    private String stringifyRequestBody(Request request) {
+        try {
+            final Request copy = request.newBuilder().build();
+            final Buffer buffer = new Buffer();
+            if (copy.body() != null) {
+                copy.body().writeTo(buffer);
+            } else {
+                return null;
+            }
+            return buffer.readUtf8();
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/edc-extensions/did-document/did-document-service-self-registration/README.md b/edc-extensions/did-document/did-document-service-self-registration/README.md
new file mode 100644
index 0000000000..24ee9adc53
--- /dev/null
+++ b/edc-extensions/did-document/did-document-service-self-registration/README.md
@@ -0,0 +1,40 @@
+# did-document-service-self-registration
+
+## Overview
+This extension provides automatic self-registration and de-registration of a DID Document service entry at runtime.
+It is designed to simplify service lifecycle management by registering a service entry on application startup and removing it on shutdown, based on configuration properties.
+
+## Self Service Registration
+When enabled, the extension registers a service entry with below details in the DID Document.
+
+| Property        | Value                                                                                             |
+|-----------------|---------------------------------------------------------------------------------------------------|
+| id              | Value defined via config `tx.edc.did.service.self.registration.id`                                |
+| type            | DataService                                                                                       |
+| serviceEndpoint | The EDC's data space version endpoint (e.g., `https:///edc/.well-known/dspace-version`) |
+
+## Features
+- Automatically registers a DID Document service entry on startup.
+- Automatically deletes the service entry on shutdown.
+- Can be enabled or disabled via configuration property.
+- No action is taken if the required implementation extension to update did document is not present.
+
+## Configuration
+The extension is controlled by the following configuration properties:
+
+| Property                                       | Required | Default | Description                                                          |
+|------------------------------------------------|----------|---------|----------------------------------------------------------------------|
+| `tx.edc.did.service.self.registration.enabled` | false    | false   | Enables or disables self-registration extension.                     |
+| `tx.edc.did.service.self.registration.id`      | false    | (none)  | The ID to use for service self-registration (should be a valid URI). |
+
+> `tx.edc.did.service.self.registration.id` is required if self-registration is enabled (`tx.edc.did.service.self.registration.enabled=true`).
+
+## Extension Lifecycle
+- **On Startup:**
+  - If enabled, the extension reads the service entry configuration and automatically registers the service in the DID Document using the configured client.
+- **On Shutdown:**
+  - The extension automatically deletes the registered service entry from the DID Document.
+
+## Disable the Extension
+- Set the property `tx.edc.did.service.self.registration.enabled=false` in your configuration to disable this feature.
+- Do not include any extension which provides the implementation of SPI `DidDocumentServiceClient` to update the DID Document.
diff --git a/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java b/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java
index 8eb98a1fa2..a8905a0b73 100644
--- a/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java
+++ b/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java
@@ -230,6 +230,7 @@ private static Config runtimeConfiguration() {
                 put("web.http.port", String.valueOf(getFreePort()));
                 put("web.http.protocol.path", PROTOCOL_API_PATH);
                 put("web.http.protocol.port", String.valueOf(PROTOCOL_API_PORT));
+                put("edc.participant.id", "id");
                 put("edc.iam.issuer.id", VERIFIER_DID);
                 put("edc.iam.sts.oauth.token.url", "https://example.com/token");
                 put("edc.iam.sts.oauth.client.id", "test-client-id");
diff --git a/settings.gradle.kts b/settings.gradle.kts
index df90ec96bc..0cc4bdcf85 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -37,8 +37,8 @@ include(":spi:core-spi")
 include(":spi:tokenrefresh-spi")
 include(":spi:bdrs-client-spi")
 include(":spi:dataflow-spi")
-include(":spi:did-document-service-spi")
 include(":spi:dcp-spi")
+include(":spi:did-document-service-spi")
 
 
 // core modules
@@ -75,6 +75,7 @@ include(":edc-extensions:log4j2-monitor")
 include("edc-extensions:connector-discovery:connector-discovery-api")
 include(":edc-extensions:dataspace-protocol")
 include(":edc-extensions:did-document:did-document-service-self-registration")
+include(":edc-extensions:did-document:did-document-service-dim")
 
 include(":edc-extensions:agreements")
 include(":edc-extensions:agreements:retirement-evaluation-core")

From 3f6bccfd7ee1908f01a5b02ba7313a199db4ded8 Mon Sep 17 00:00:00 2001
From: Malte Hellmeier 
Date: Fri, 9 Jan 2026 14:22:14 +0100
Subject: [PATCH 079/112] docs: add multi data space support decision record
 (#2506)

* docs: add multi data space support decision record

* chore: fix linting

* docs: implement reviews for multi data space support decision record
---
 .../README.md                                 | 87 +++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 docs/development/decision-records/2026-01-07_multi-dataspace-support/README.md

diff --git a/docs/development/decision-records/2026-01-07_multi-dataspace-support/README.md b/docs/development/decision-records/2026-01-07_multi-dataspace-support/README.md
new file mode 100644
index 0000000000..2e29e9e3cb
--- /dev/null
+++ b/docs/development/decision-records/2026-01-07_multi-dataspace-support/README.md
@@ -0,0 +1,87 @@
+# Multi Data Space Support
+
+## Decision
+
+The Tractus-X EDC will distinguish between generic and Catena-X specific implementations, to enable future multi data space support. Existing extensions will be refactored so that every extension can clearly be categorized into a `core` (data space agnostic implementation) and data space-specific implementation (e.g., `catena-x`). The decision aims to enable future runtime configurations, such as data space specific flavors (e.g., `catena-x`, `factory-x`, `construct-x`), building upon the new `core` as midstream. The migration will be silent, to have minimal impact on current Catena-X users, while newer extensions and runtime configurations will be developed externally and merged into `tractusx-edc` when reaching production status.
+
+## Rationale
+
+The Tractus-X EDC was one of the first production-ready Connector implementations based on the upstream Eclipse Data Space Components (EDC) project for the Catena-X data space. Since then, various new data spaces have emerged, which also require connector configurations but can't use the Eclipse Tractus-X EDC directly because it includes Catena-X-specific implementations (like the BPN, the CX-Policy, etc.). This has led to further projects, like the [Factory-X EDC](https://github.com/factory-x-contributions/factoryx-edc), which build upon the Eclipse Tractus-X EDC as midstream but in a complex way using several exclusions. While other data spaces like Construct-X and Semiconductor-X also need a Connector without Catena-X specific aspects, this decision record aims to consolidate and bundle all development power into this Tractus-X EDC project to create an agnostic midstream `core` that can be used in other contexts than Catena-X. The rationale is supported by the [new Eclipse Tractus-X strategies to open and support multiple data spaces](https://github.com/eclipse-tractusx/eclipse-tractusx.github.io/pull/1370).
+
+## Approach
+
+This approach and work procedure is detailed in the following.
+
+### Runtimes Concept & Structure
+
+**Create one `core` data space agnostic runtime configuration and enable specific runtime flavors like `catena-x`, `factory-x`, and `construct-x`.**
+
+Currently, there are two runtime configurations available: the [`edc-controlplane-base`](https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-controlplane/edc-controlplane-base) and the [`edc-dataplane-base`](https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-dataplane/edc-dataplane-base). Since these configurations are Catena-X specific, a new structure need to distinguish midstream `core` runtimes from data space specific runtimes.
+
+One possible example of how a future structure might look:
+```
+tractusx-edc/
+├── edc-controlplane
+│   ├── edc-controlplane-core
+│   ├── edc-controlplane-catena-x
+        ├── edc-controlplane-base
+        ├── edc-controlplane-postgresql-hashicorp-vault
+        └── edc-runtime-memory
+│   ├── edc-controlplane-factory-x
+│   ├── edc-controlplane-construct-x+
+│   └── ...
+└── edc-dataplane
+    ├── edc-dataplane-core
+    ├── edc-dataplane-catena-x
+        ├── edc-dataplane-base
+        └── edc-dataplane-hashicorp-vault
+    ├── edc-dataplane-factory-x
+    ├── edc-dataplane-construct-x
+    └── ...
+```
+
+### Extensions
+
+**Keep all extensions in the `edc-extensions` folder in this repo. Refactor some existing extensions that are needed across multiple data spaces (`core`) to be Catena-X-independent.** 
+
+Currently, the `edc-extensions` folder contains different extensions. They can be categorized into three categories:
+
+1. **Generic extensions:** Data space agnostic, like the `agreement` extension
+2. **Data space specific extensions:** Only made for one data space, like the `cx-policy` extension
+3. **Generic, but specific:** Have a generic need and concept, but the implementation is currently data space specific, like the `dcp` extension (should be (1.) generic, but the implementation is currently specialized to (2.), the Catena-X data space)
+
+All extensions from category (3.) will be refactored and split to distinguish between (1.) generic, and (2.) data space specific in the future.
+As an example, the current `dcp` extension could be split into a `dcp-core` extension, belonging to (1.) generic and a `cx-dcp` extension, belonging to (2.), including the Catena-X specific implementation.
+
+The following extensions belong to category (3.) and need to be refactored:
+
+- [`connector-discovery`](https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/connector-discovery) (uses BDRS, a catena-x specific identity model, but is currently under refactoring)
+- [`dataspace-protocol`](https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/dataspace-protocol) (supports multiple versions, incl. BPN, as catena-x specific)
+- [`dcp`](https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/dcp) (interwoven with catena-x)
+- [`migrations`](https://github.com/eclipse-tractusx/tractusx-edc/tree/main/edc-extensions/migrations) (interwoven with catena-x)
+
+Further, the Factory-X project [published their extensions in another repository](https://github.com/factory-x-contributions/factoryx-edc/tree/main/edc-extensions). These extensions could be integrated and merged into this Eclipse Tractus-X EDC repository when reaching production status, providing a single place for all extensions.
+
+### Development inside/outside `tractusx-edc`
+
+**Necessary refactoring is made directly inside the `tractusx-edc` repository, while new developments are conducted externally.**
+
+To enable multi data space support, the refactoring of the previously described extensions have to be made directly inside this `tractusx-edc`. The development of new runtimes and extensions is first developed externally, and potentially merged into `tractusx-edc` when reaching production status. _External_ could refer to closed-source or open-source development work in other (GitHub) organizations as well as development directly inside Eclipse Tractus-X, but in a new repository.
+
+
+### Releases, Backward Compatibility
+
+**Create a silent transition with minimal impact for existing Catena-X users.**
+
+Since this repository is only used by the Catena-X data space participants, the overall goal is to implement the suggested changes with minimal impact, so that existing users will not notice them or only notice them slightly. Possible breaking changes should be reduced to a minimum. Such breaking changes may occur due to new folder structures and (re)namings of runtimes and extensions. Nevertheless, the naming of existing Docker images, Helm charts, and Maven artifacts should stay as long as possible and reasonable.
+
+Regarding the releases, future Tractus-X releases should include the Catena-X flavor connector only in the first run. Potentially new connector flavors of the other data spaces should not be included in the release in the first step. This helps create a fast transition, without large alignment between the Tractus-X planning and release cycles and the cycles and procedures of the other data spaces. Future adjustments to also publish the other flavors in the Eclipse Tractus-X release are possible, but not part of this decision record.
+
+## NOTICE
+
+This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0).
+
+- SPDX-License-Identifier: Apache-2.0
+- SPDX-FileCopyrightText: 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST)
+- SPDX-FileCopyrightText: 2026 Contributors to the Eclipse Foundation
+- Source URL: 

From a67d8829abb08a62db19eb94c5c3c664e711b171 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 12 Jan 2026 08:03:27 +0100
Subject: [PATCH 080/112] chore(deps): bump checkmarx/kics-github-action from
 2.1.18 to 2.1.19 (#2517)

Bumps [checkmarx/kics-github-action](https://github.com/checkmarx/kics-github-action) from 2.1.18 to 2.1.19.
- [Release notes](https://github.com/checkmarx/kics-github-action/releases)
- [Commits](https://github.com/checkmarx/kics-github-action/compare/v2.1.18...v2.1.19)

---
updated-dependencies:
- dependency-name: checkmarx/kics-github-action
  dependency-version: 2.1.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/kics.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/kics.yml b/.github/workflows/kics.yml
index 1ae2c219e1..c79bc03f17 100644
--- a/.github/workflows/kics.yml
+++ b/.github/workflows/kics.yml
@@ -44,7 +44,7 @@ jobs:
       - uses: actions/checkout@v6
 
       - name: KICS scan
-        uses: checkmarx/kics-github-action@v2.1.18
+        uses: checkmarx/kics-github-action@v2.1.19
         with:
           path: "."
           fail_on: high

From 6a4c786a554d68eebd811032cd6ec1d62a6668c5 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 12 Jan 2026 08:03:56 +0100
Subject: [PATCH 081/112] chore(deps): bump com.nimbusds:nimbus-jose-jwt from
 10.6 to 10.7 (#2516)

Bumps [com.nimbusds:nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 10.6 to 10.7.
- [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/10.7..10.6)

---
updated-dependencies:
- dependency-name: com.nimbusds:nimbus-jose-jwt
  dependency-version: '10.7'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 gradle/libs.versions.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7cc400a056..351f585765 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -15,7 +15,7 @@ flyway = "11.20.0"
 jackson = "2.20.1"
 jakarta-json = "2.1.3"
 junit = "6.0.1"
-nimbus = "10.6"
+nimbus = "10.7"
 okhttp = "5.3.2"
 opentelemetry = "2.23.0"
 opentelemetry-instrumentation = "2.23.0"

From f3d85f75d3cbea17456d4403e0e888da5af00895 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 12 Jan 2026 08:04:23 +0100
Subject: [PATCH 082/112] chore(deps): bump
 com.github.dasniko:testcontainers-keycloak (#2514)

Bumps [com.github.dasniko:testcontainers-keycloak](https://github.com/dasniko/testcontainers-keycloak) from 4.0.1 to 4.1.0.
- [Release notes](https://github.com/dasniko/testcontainers-keycloak/releases)
- [Commits](https://github.com/dasniko/testcontainers-keycloak/compare/v4.0.1...v4.1.0)

---
updated-dependencies:
- dependency-name: com.github.dasniko:testcontainers-keycloak
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 gradle/libs.versions.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 351f585765..af1cdcd460 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -24,7 +24,7 @@ postgres = "42.7.8"
 restAssured = "6.0.0"
 rsApi = "4.0.0"
 testcontainers = "2.0.3"
-testcontainers-keycloak = "4.0.1"
+testcontainers-keycloak = "4.1.0"
 titanium = "1.7.0"
 log4j2 = "2.25.3"
 wiremock = "3.13.2"

From 883885971df285dea35464f1d758a8418cf9add0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 12 Jan 2026 08:04:52 +0100
Subject: [PATCH 083/112] chore(deps): bump aws from 2.41.1 to 2.41.5 (#2515)

Bumps `aws` from 2.41.1 to 2.41.5.

Updates `software.amazon.awssdk:s3` from 2.41.1 to 2.41.5

Updates `software.amazon.awssdk:s3-transfer-manager` from 2.41.1 to 2.41.5

---
updated-dependencies:
- dependency-name: software.amazon.awssdk:s3
  dependency-version: 2.41.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: software.amazon.awssdk:s3-transfer-manager
  dependency-version: 2.41.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 gradle/libs.versions.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index af1cdcd460..a352fa40a6 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -6,7 +6,7 @@ edc = "0.15.1"
 edc-build = "1.1.5"
 allure = "2.32.0"
 awaitility = "4.3.0"
-aws = "2.41.1"
+aws = "2.41.5"
 azure-storage-blob = "12.32.0"
 bouncyCastle-jdk18on = "1.83"
 dcp-tck = "1.0.0-SNAPSHOT"

From c81b42eda08663cb255703e6c21c16cf2fa50bd7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 12 Jan 2026 08:05:16 +0100
Subject: [PATCH 084/112] chore(deps): bump flyway from 11.20.0 to 11.20.1
 (#2513)

Bumps `flyway` from 11.20.0 to 11.20.1.

Updates `org.flywaydb:flyway-core` from 11.20.0 to 11.20.1
- [Release notes](https://github.com/flyway/flyway/releases)
- [Commits](https://github.com/flyway/flyway/compare/flyway-11.20.0...flyway-11.20.1)

Updates `org.flywaydb:flyway-database-postgresql` from 11.20.0 to 11.20.1

---
updated-dependencies:
- dependency-name: org.flywaydb:flyway-core
  dependency-version: 11.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.flywaydb:flyway-database-postgresql
  dependency-version: 11.20.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 gradle/libs.versions.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index a352fa40a6..7a8625f83a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,7 +11,7 @@ azure-storage-blob = "12.32.0"
 bouncyCastle-jdk18on = "1.83"
 dcp-tck = "1.0.0-SNAPSHOT"
 dsp-tck = "1.0.0-RC4"
-flyway = "11.20.0"
+flyway = "11.20.1"
 jackson = "2.20.1"
 jakarta-json = "2.1.3"
 junit = "6.0.1"

From 614c5af872a7e1d24207447710bdfb79c493e8ee Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 12 Jan 2026 08:06:01 +0100
Subject: [PATCH 085/112] chore(deps): bump com.gradleup.shadow from 9.3.0 to
 9.3.1 (#2512)

Bumps [com.gradleup.shadow](https://github.com/GradleUp/shadow) from 9.3.0 to 9.3.1.
- [Release notes](https://github.com/GradleUp/shadow/releases)
- [Commits](https://github.com/GradleUp/shadow/compare/9.3.0...9.3.1)

---
updated-dependencies:
- dependency-name: com.gradleup.shadow
  dependency-version: 9.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] 
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 gradle/libs.versions.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 7a8625f83a..b27a892abf 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -242,6 +242,6 @@ edc-sts = [
 
 [plugins]
 docker = { id = "com.bmuschko.docker-remote-api", version = "10.0.0" }
-shadow = { id = "com.gradleup.shadow", version = "9.3.0" }
+shadow = { id = "com.gradleup.shadow", version = "9.3.1" }
 swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.41" }
 edc-build = { id = "org.eclipse.edc.edc-build", version.ref = "edc-build" }

From 813e36af38e74ddc921ffe41f4bc6af5e44d5e1b Mon Sep 17 00:00:00 2001
From: Andrii Yurkevych 
Date: Tue, 13 Jan 2026 10:31:08 +0100
Subject: [PATCH 086/112] feat: Log failure messages (#2510)

* feat: Log failure messages

* feat: fix tests

* feat: remove redundant

* feat: fix api tests

* feat: review comments

* feat: review comments
---
 .../edc/edr/core/service/EdrServiceImpl.java  |  6 +-
 .../AgreementRetirementServiceExtension.java  |  7 +-
 .../AgreementsRetirementServiceImpl.java      |  9 ++-
 .../AgreementsRetirementServiceImplTest.java  |  2 +-
 .../mapper/BdrsClientAudienceMapper.java      | 13 +++-
 .../edc/identity/mapper/BdrsClientImpl.java   |  6 +-
 .../mapper/BdrsClientMapperExtension.java     |  6 +-
 .../mapper/BdrsClientAudienceMapperTest.java  | 16 ++++-
 .../mapper/BdrsClientImplComponentTest.java   |  1 +
 .../http/pipeline/ProxyHttpDataSource.java    |  3 +-
 .../api/DataPlanePublicApiV2Extension.java    |  2 +-
 .../DataPlanePublicApiV2Controller.java       |  9 ++-
 .../DataPlanePublicApiV2ControllerTest.java   |  5 +-
 ...DataPlaneTokenRefreshServiceExtension.java |  2 +-
 .../DataPlaneTokenRefreshServiceImpl.java     | 66 ++++++++++++++-----
 ...eTokenRefreshServiceImplComponentTest.java |  1 -
 .../DataPlaneTokenRefreshServiceImplTest.java |  2 +-
 .../protocol/DataspaceProtocolExtension.java  |  9 ++-
 .../identifier/BpnExtractionFunction.java     |  7 +-
 .../identifier/DidExtractionFunction.java     |  7 +-
 ...bershipCredentialIdExtractionFunction.java | 26 ++++++--
 .../identifier/BpnExtractionFunctionTest.java | 11 +++-
 .../identifier/DidExtractionFunctionTest.java | 11 +++-
 .../dcp/sts/dim/DimSecureTokenService.java    | 12 +++-
 .../dcp/sts/dim/oauth/DimOauthClientImpl.java |  8 ++-
 .../sts/dim/DimSecureTokenServiceTest.java    |  2 +
 .../VerifiablePresentationCacheImpl.java      | 35 +++++++---
 .../VerifiablePresentationCacheImplTest.java  | 10 ++-
 .../NonFiniteCapablePipelineService.java      |  4 +-
 .../tokenrefresh/TokenRefreshHandlerImpl.java | 37 ++++++++---
 .../runtimes/ParticipantRuntimeExtension.java |  2 +-
 31 files changed, 258 insertions(+), 79 deletions(-)

diff --git a/core/edr-core/src/main/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImpl.java b/core/edr-core/src/main/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImpl.java
index 8acf107f46..166f2b626c 100644
--- a/core/edr-core/src/main/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImpl.java
+++ b/core/edr-core/src/main/java/org/eclipse/tractusx/edc/edr/core/service/EdrServiceImpl.java
@@ -72,13 +72,15 @@ private ServiceResult handleRefresh(String id, DataAddress edr, Ref
     private ServiceResult autoRefresh(String id, DataAddress edr, RefreshMode mode) {
         var edrEntry = edrStore.findById(id);
         if (edrEntry == null) {
-            return ServiceResult.notFound("An EndpointDataReferenceEntry with ID '%s' does not exist".formatted(id));
+            var msg = "An EndpointDataReferenceEntry with ID '%s' does not exist".formatted(id);
+            monitor.debug(msg);
+            return ServiceResult.notFound(msg);
         }
         if (edrLock.isExpired(edr, edrEntry) || mode.equals(RefreshMode.FORCE_REFRESH)) {
             var result = ServiceResult.from(edrLock.acquireLock(id, edr))
                     .compose(shouldRefresh -> {
                         if (!shouldRefresh && !mode.equals(RefreshMode.FORCE_REFRESH)) {
-                            monitor.debug("Dont need to refresh. Will resolve existing.");
+                            monitor.debug("Don't need to refresh. Will resolve existing.");
                             var refreshedEdr = edrStore.resolveByTransferProcess(id);
                             return ServiceResult.from(refreshedEdr);
                         } else {
diff --git a/edc-extensions/agreements/retirement-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementRetirementServiceExtension.java b/edc-extensions/agreements/retirement-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementRetirementServiceExtension.java
index e7e5b0ecc2..69d838c291 100644
--- a/edc-extensions/agreements/retirement-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementRetirementServiceExtension.java
+++ b/edc-extensions/agreements/retirement-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementRetirementServiceExtension.java
@@ -24,6 +24,7 @@
 import org.eclipse.edc.runtime.metamodel.annotation.Inject;
 import org.eclipse.edc.runtime.metamodel.annotation.Provider;
 import org.eclipse.edc.spi.event.EventRouter;
+import org.eclipse.edc.spi.monitor.Monitor;
 import org.eclipse.edc.spi.system.ServiceExtension;
 import org.eclipse.edc.transaction.spi.TransactionContext;
 import org.eclipse.tractusx.edc.agreements.retirement.spi.service.AgreementsRetirementService;
@@ -48,6 +49,9 @@ public class AgreementRetirementServiceExtension implements ServiceExtension {
     EventRouter eventRouter;
     @Inject
     Clock clock;
+    @Inject
+    private Monitor monitor;
+
 
     @Override
     public String name() {
@@ -56,6 +60,7 @@ public String name() {
 
     @Provider()
     public AgreementsRetirementService createInMemAgreementRetirementService() {
-        return new AgreementsRetirementServiceImpl(store, transactionContext, contractAgreementService, eventRouter, clock);
+        return new AgreementsRetirementServiceImpl(store, transactionContext, contractAgreementService, eventRouter,
+                clock, monitor);
     }
 }
diff --git a/edc-extensions/agreements/retirement-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementsRetirementServiceImpl.java b/edc-extensions/agreements/retirement-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementsRetirementServiceImpl.java
index 9ab94324b6..e3696a9d9f 100644
--- a/edc-extensions/agreements/retirement-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementsRetirementServiceImpl.java
+++ b/edc-extensions/agreements/retirement-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementsRetirementServiceImpl.java
@@ -22,6 +22,7 @@
 import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService;
 import org.eclipse.edc.spi.event.EventEnvelope;
 import org.eclipse.edc.spi.event.EventRouter;
+import org.eclipse.edc.spi.monitor.Monitor;
 import org.eclipse.edc.spi.query.Criterion;
 import org.eclipse.edc.spi.query.QuerySpec;
 import org.eclipse.edc.spi.result.ServiceResult;
@@ -49,15 +50,17 @@ public class AgreementsRetirementServiceImpl implements AgreementsRetirementServ
     private final ContractAgreementService contractAgreementService;
     private final EventRouter eventRouter;
     private final Clock clock;
+    private final Monitor monitor;
 
     public AgreementsRetirementServiceImpl(AgreementsRetirementStore store, TransactionContext transactionContext,
                                            ContractAgreementService contractAgreementService, EventRouter eventRouter,
-                                           Clock clock) {
+                                           Clock clock, Monitor monitor) {
         this.store = store;
         this.transactionContext = transactionContext;
         this.contractAgreementService = contractAgreementService;
         this.eventRouter = eventRouter;
         this.clock = clock;
+        this.monitor = monitor.withPrefix(getClass().getSimpleName());
     }
 
     @Override
@@ -77,7 +80,9 @@ public ServiceResult retireAgreement(AgreementsRetirementEntry entry) {
         return transactionContext.execute(() -> {
             var contractAgreement = contractAgreementService.findById(entry.getAgreementId());
             if (contractAgreement == null) {
-                return ServiceResult.notFound(NOT_FOUND_IN_CONTRACT_AGREEMENT_TEMPLATE.formatted(entry.getAgreementId()));
+                var msg = NOT_FOUND_IN_CONTRACT_AGREEMENT_TEMPLATE.formatted(entry.getAgreementId());
+                monitor.debug(msg);
+                return ServiceResult.notFound(msg);
             }
 
             return store.save(entry)
diff --git a/edc-extensions/agreements/retirement-evaluation-core/src/test/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementsRetirementServiceImplTest.java b/edc-extensions/agreements/retirement-evaluation-core/src/test/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementsRetirementServiceImplTest.java
index 13a9b263cb..9f2385ace4 100644
--- a/edc-extensions/agreements/retirement-evaluation-core/src/test/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementsRetirementServiceImplTest.java
+++ b/edc-extensions/agreements/retirement-evaluation-core/src/test/java/org/eclipse/tractusx/edc/agreements/retirement/service/AgreementsRetirementServiceImplTest.java
@@ -61,7 +61,7 @@ class AgreementsRetirementServiceImplTest {
     private final EventRouter eventRouter = mock();
 
     private final AgreementsRetirementService service = new AgreementsRetirementServiceImpl(store, transactionContext,
-            contractAgreementService, eventRouter, Clock.systemUTC());
+            contractAgreementService, eventRouter, Clock.systemUTC(), mock());
 
     @Test
     void returnsTrue_ifAgreementIsRetired() {
diff --git a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapper.java b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapper.java
index b9c29181cc..f7ba391682 100644
--- a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapper.java
+++ b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapper.java
@@ -20,7 +20,9 @@
 
 package org.eclipse.tractusx.edc.identity.mapper;
 
+import org.eclipse.edc.spi.EdcException;
 import org.eclipse.edc.spi.iam.AudienceResolver;
+import org.eclipse.edc.spi.monitor.Monitor;
 import org.eclipse.edc.spi.result.Result;
 import org.eclipse.edc.spi.types.domain.message.ProtocolRemoteMessage;
 import org.eclipse.edc.spi.types.domain.message.RemoteMessage;
@@ -31,16 +33,18 @@
 import static org.eclipse.tractusx.edc.spi.identity.mapper.BdrsConstants.DID_PREFIX;
 
 /**
- * Extracts the audience from a {@link RemoteMessage} using {@link RemoteMessage#getCounterPartyId()}.
+ * Extracts the audience from a {@link RemoteMessage} using {@link ProtocolRemoteMessage#getCounterPartyId()}.
  * If the counter-party id is a DID, returns it as-is. If it is a BPN, calls {@link BdrsClient#resolveDid(String)}
  * to resolve the corresponding DID.
  */
 class BdrsClientAudienceMapper implements AudienceResolver {
     
     private final BdrsClient client;
+    private final Monitor monitor;
 
-    BdrsClientAudienceMapper(BdrsClient client) {
+    BdrsClientAudienceMapper(BdrsClient client, Monitor monitor) {
         this.client = client;
+        this.monitor = monitor.withPrefix(getClass().getSimpleName());
     }
 
     @Override
@@ -53,8 +57,11 @@ public Result resolve(ProtocolRemoteMessage remoteMessage) {
             
             var resolve = client.resolveDid(counterPartyId);
             return Result.from(Optional.ofNullable(resolve));
-        } catch (Exception e) {
+        } catch (EdcException e) {
             return Result.failure("Failure in DID resolution: " + e.getMessage());
+        } catch (Exception e) {
+            monitor.warning(e.getMessage(), e);
+            return Result.failure(e.getMessage());
         }
     }
 
diff --git a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java
index e8e5e11378..6e33d7b3f4 100644
--- a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java
+++ b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImpl.java
@@ -171,6 +171,7 @@ private Result updateCache() {
                 }
             } else {
                 var msg = "Could not obtain data from BDRS server: code: %d, message: %s".formatted(response.code(), response.message());
+                monitor.warning(msg);
                 return Result.failure(msg);
             }
         } catch (IOException e) {
@@ -194,6 +195,7 @@ private Result createMembershipPresentation() {
                     if (result.succeeded()) {
                         return Result.success(result.getContent());
                     } else {
+                        monitor.severe("Could not get participant context: " + result.getFailureDetail());
                         return Result.failure(result.getFailureDetail());
                     }
                 })
@@ -201,7 +203,9 @@ private Result createMembershipPresentation() {
                 .compose(sit -> credentialServiceClient.requestPresentation(ownCredentialServiceUrl.get(), sit.getToken(), List.of(scope)))
                 .compose(pres -> {
                     if (pres.isEmpty()) {
-                        return Result.failure("Expected exactly 1 VP, but was empty");
+                        var msg = "Expected exactly 1 VP, but was empty";
+                        monitor.warning(msg);
+                        return Result.failure(msg);
                     }
                     if (pres.size() != 1) {
                         monitor.warning("Expected exactly 1 VP, but found %d.".formatted(pres.size()));
diff --git a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientMapperExtension.java b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientMapperExtension.java
index 2f5f5a19cb..e65aef0dd8 100644
--- a/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientMapperExtension.java
+++ b/edc-extensions/bdrs-client/src/main/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientMapperExtension.java
@@ -23,6 +23,7 @@
 import org.eclipse.edc.runtime.metamodel.annotation.Inject;
 import org.eclipse.edc.runtime.metamodel.annotation.Provider;
 import org.eclipse.edc.spi.iam.AudienceResolver;
+import org.eclipse.edc.spi.monitor.Monitor;
 import org.eclipse.edc.spi.system.ServiceExtension;
 import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient;
 
@@ -34,6 +35,9 @@ public class BdrsClientMapperExtension implements ServiceExtension {
     @Inject
     private BdrsClient bdrsClient;
 
+    @Inject
+    private Monitor monitor;
+
     @Override
     public String name() {
         return NAME;
@@ -41,7 +45,7 @@ public String name() {
 
     @Provider
     public AudienceResolver getBdrsAudienceResolver() {
-        return new BdrsClientAudienceMapper(bdrsClient);
+        return new BdrsClientAudienceMapper(bdrsClient, monitor);
     }
 
 }
diff --git a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapperTest.java b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapperTest.java
index 9b4b9a3208..e8b3f6b60e 100644
--- a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapperTest.java
+++ b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientAudienceMapperTest.java
@@ -20,19 +20,29 @@
 
 package org.eclipse.tractusx.edc.identity.mapper;
 
+import org.eclipse.edc.spi.monitor.Monitor;
 import org.eclipse.edc.spi.types.domain.message.ProtocolRemoteMessage;
 import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 class BdrsClientAudienceMapperTest {
 
     private final BdrsClient bdrsClient = mock();
-    private final BdrsClientAudienceMapper clientAudienceMapper = new BdrsClientAudienceMapper(bdrsClient);
-    
+    private BdrsClientAudienceMapper clientAudienceMapper;
+    private final Monitor monitor = mock();
+
+    @BeforeEach
+    void setup() {
+        when(monitor.withPrefix(anyString())).thenReturn(monitor);
+        clientAudienceMapper = new BdrsClientAudienceMapper(bdrsClient, monitor);
+    }
+
     @Test
     void shouldReturnDid_whenCounterPartyIdIsDid() {
         var counterPartyId = "did:web:did1";
@@ -62,7 +72,7 @@ void shouldFail_whenResolutionFails() {
     @Test
     void shouldFail_whenResolutionThrowsException() {
         when(bdrsClient.resolveDid("bpn1")).thenThrow(new RuntimeException("exception"));
-
+        when(monitor.withPrefix("BdrsClientAudienceMapper")).thenReturn(monitor);
         var did = clientAudienceMapper.resolve(new TestMessage("bpn1"));
 
         assertThat(did).isFailed().detail().contains("exception");
diff --git a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplComponentTest.java b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplComponentTest.java
index 2b5b6e57bc..1ede8b8dd1 100644
--- a/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplComponentTest.java
+++ b/edc-extensions/bdrs-client/src/test/java/org/eclipse/tractusx/edc/identity/mapper/BdrsClientImplComponentTest.java
@@ -145,6 +145,7 @@ void setup() throws IOException, ParseException {
         ParticipantContextSupplier participantContextSupplier = mock();
         var participantContext = ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build();
         when(participantContextSupplier.get()).thenReturn(ServiceResult.success(participantContext));
+        when(monitor.withPrefix(anyString())).thenReturn(monitor);
         client = new BdrsClientImpl("http://%s:%d/api/directory".formatted(BDRS_SERVER_CONTAINER.getHost(), directoryPort), 1,
                 "did:web:self",
                 () -> "http://credential.service",
diff --git a/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/main/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSource.java b/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/main/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSource.java
index 10af07a853..206cc1ec52 100644
--- a/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/main/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSource.java
+++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/main/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSource.java
@@ -59,7 +59,7 @@ private ProxyHttpDataSource() {
     @Override
     public StreamResult> openPartStream() {
         var request = requestFactory.toRequest(params);
-        monitor.debug(() -> "Executing HTTP request: " + request.url());
+        monitor.debug(() -> "Executing HTTP request: " + request.url().url());
         try {
             // NB: Do not close the response as the body input stream needs to be read after this method returns. The response closes the body stream.
             var response = httpClient.execute(request);
@@ -69,7 +69,6 @@ public StreamResult> openPartStream() {
         } catch (IOException e) {
             throw new EdcException(e);
         }
-
     }
 
     private StreamResult> handleResponse(Response response, String statusCode) {
diff --git a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/DataPlanePublicApiV2Extension.java b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/DataPlanePublicApiV2Extension.java
index 39106e1b50..565fb17512 100644
--- a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/DataPlanePublicApiV2Extension.java
+++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/DataPlanePublicApiV2Extension.java
@@ -116,7 +116,7 @@ public void initialize(ServiceExtensionContext context) {
             generatorService.addResponseGeneratorFunction(PROXY_HTTP_DATA_TYPE, () -> Endpoint.url(publicApiResponseUrl));
         }
         
-        var publicApiController = new DataPlanePublicApiV2Controller(pipelineService, executorService, authorizationService);
+        var publicApiController = new DataPlanePublicApiV2Controller(context.getMonitor(), pipelineService, executorService, authorizationService);
         webService.registerResource(API_CONTEXT, publicApiController);
     }
 
diff --git a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/DataPlanePublicApiV2Controller.java b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/DataPlanePublicApiV2Controller.java
index d07a214dcb..d56e9f9377 100644
--- a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/DataPlanePublicApiV2Controller.java
+++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/DataPlanePublicApiV2Controller.java
@@ -38,6 +38,7 @@
 import org.eclipse.edc.connector.dataplane.spi.pipeline.PipelineService;
 import org.eclipse.edc.connector.dataplane.spi.response.TransferErrorResponse;
 import org.eclipse.edc.connector.dataplane.util.sink.AsyncStreamingDataSink;
+import org.eclipse.edc.spi.monitor.Monitor;
 import org.eclipse.edc.spi.types.domain.transfer.DataFlowStartMessage;
 
 import java.util.HashMap;
@@ -51,19 +52,23 @@
 import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
 import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED;
 import static jakarta.ws.rs.core.Response.status;
+import static java.lang.String.format;
 
 @Path("{any:.*}")
 @Produces(WILDCARD)
 public class DataPlanePublicApiV2Controller implements DataPlanePublicApiV2 {
 
+    private final Monitor monitor;
     private final PipelineService pipelineService;
     private final DataFlowRequestSupplier requestSupplier;
     private final ExecutorService executorService;
     private final DataPlaneAuthorizationService authorizationService;
 
-    public DataPlanePublicApiV2Controller(PipelineService pipelineService,
+
+    public DataPlanePublicApiV2Controller(Monitor monitor, PipelineService pipelineService,
                                           ExecutorService executorService,
                                           DataPlaneAuthorizationService authorizationService) {
+        this.monitor = monitor.withPrefix(getClass().getSimpleName());
         this.pipelineService = pipelineService;
         this.authorizationService = authorizationService;
         this.requestSupplier = new DataFlowRequestSupplier();
@@ -181,10 +186,12 @@ private void processRequest(DataFlowStartMessage dataFlowStartMessage, AsyncResp
                 .whenComplete((result, throwable) -> {
                     if (throwable == null) {
                         if (result.failed()) {
+                            monitor.severe(format("Failed to read data from source: %s", result.getFailureDetail()));
                             response.resume(error(INTERNAL_SERVER_ERROR, result.getFailureMessages()));
                         }
                     } else {
                         var error = "Unhandled exception occurred during data transfer: " + throwable.getMessage();
+                        monitor.severe(format("Failed to read data from source: %s", error), throwable);
                         response.resume(error(INTERNAL_SERVER_ERROR, List.of(error)));
                     }
                 });
diff --git a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/DataPlanePublicApiV2ControllerTest.java b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/DataPlanePublicApiV2ControllerTest.java
index e562409d0d..2aa757780a 100644
--- a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/DataPlanePublicApiV2ControllerTest.java
+++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/test/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/DataPlanePublicApiV2ControllerTest.java
@@ -30,6 +30,7 @@
 import org.eclipse.edc.connector.dataplane.spi.resolver.DataAddressResolver;
 import org.eclipse.edc.connector.dataplane.util.sink.AsyncStreamingDataSink;
 import org.eclipse.edc.junit.annotations.ApiTest;
+import org.eclipse.edc.spi.monitor.Monitor;
 import org.eclipse.edc.spi.result.Result;
 import org.eclipse.edc.spi.types.domain.DataAddress;
 import org.eclipse.edc.spi.types.domain.transfer.DataFlowStartMessage;
@@ -69,6 +70,7 @@ class DataPlanePublicApiV2ControllerTest extends RestControllerTestBase {
     private final PipelineService pipelineService = mock();
     private final DataAddressResolver dataAddressResolver = mock();
     private final DataPlaneAuthorizationService authorizationService = mock();
+    private final Monitor monitor = mock();
 
     @BeforeEach
     void setup() {
@@ -196,7 +198,8 @@ void shouldStreamSourceToResponse() {
 
     @Override
     protected Object controller() {
-        return new DataPlanePublicApiV2Controller(pipelineService, Executors.newSingleThreadExecutor(), authorizationService);
+        when(monitor.withPrefix(anyString())).thenReturn(monitor);
+        return new DataPlanePublicApiV2Controller(monitor, pipelineService, Executors.newSingleThreadExecutor(), authorizationService);
     }
 
     private RequestSpecification baseRequest() {
diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceExtension.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceExtension.java
index dfb31794a0..9fa4feb770 100644
--- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceExtension.java
+++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceExtension.java
@@ -122,7 +122,7 @@ private DataPlaneTokenRefreshServiceImpl getTokenRefreshService(ServiceExtension
             monitor.debug("Token refresh endpoint: %s".formatted(refreshEndpoint));
             monitor.debug("Token refresh time tolerance: %d s".formatted(expiryTolerance));
             tokenRefreshService = new DataPlaneTokenRefreshServiceImpl(clock, tokenValidationService, didPkResolver, localPublicKeyService, accessTokenDataStore, new JwtGenerationService(jwsSignerProvider),
-                    () -> context.getConfig().getString(TOKEN_SIGNER_PRIVATE_KEY_ALIAS), context.getMonitor(), refreshEndpoint, getOwnDid(context), expiryTolerance, tokenExpiry,
+                    () -> context.getConfig().getString(TOKEN_SIGNER_PRIVATE_KEY_ALIAS), context.getMonitor(), refreshEndpoint, expiryTolerance, tokenExpiry,
                     () -> context.getConfig().getString(TOKEN_VERIFIER_PUBLIC_KEY_ALIAS), vault, typeManager.getMapper(), singleParticipantContextSupplier);
         }
         return tokenRefreshService;
diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImpl.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImpl.java
index d1f6b94329..602fd3737c 100644
--- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImpl.java
+++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImpl.java
@@ -91,7 +91,6 @@ public class DataPlaneTokenRefreshServiceImpl implements DataPlaneTokenRefreshSe
     private final Supplier publicKeyIdSupplier;
     private final Monitor monitor;
     private final String refreshEndpoint;
-    private final String ownDid;
     private final Clock clock;
     private final Vault vault;
     private final ObjectMapper objectMapper;
@@ -106,7 +105,6 @@ public DataPlaneTokenRefreshServiceImpl(Clock clock,
                                             Supplier privateKeyIdSupplier,
                                             Monitor monitor,
                                             String refreshEndpoint,
-                                            String ownDid,
                                             int tokenExpiryToleranceSeconds,
                                             long tokenExpirySeconds,
                                             Supplier publicKeyIdSupplier,
@@ -123,7 +121,6 @@ public DataPlaneTokenRefreshServiceImpl(Clock clock,
         this.refreshEndpoint = refreshEndpoint;
         this.clock = clock;
         this.publicKeyIdSupplier = publicKeyIdSupplier;
-        this.ownDid = ownDid;
         this.vault = vault;
         this.objectMapper = objectMapper;
         this.tokenExpirySeconds = tokenExpirySeconds;
@@ -162,12 +159,16 @@ public Result refreshToken(String refreshToken, String authentica
 
         var authTokenRes = tokenValidationService.validate(authenticationToken, publicKeyResolver, authenticationTokenValidationRules);
         if (authTokenRes.failed()) {
-            return Result.failure("Authentication token validation failed: %s".formatted(authTokenRes.getFailureDetail()));
+            var msg = "Authentication token validation failed: %s".formatted(authTokenRes.getFailureDetail());
+            monitor.debug(msg);
+            return Result.failure(msg);
         }
 
         var participantContextServiceResult = participantContextSupplier.get();
         if (participantContextServiceResult.failed()) {
-            return Result.failure("Cannot retrieve ParticipantContext: " + participantContextServiceResult.getFailureDetail());
+            var msg = "Cannot retrieve ParticipantContext: " + participantContextServiceResult.getFailureDetail();
+            monitor.severe(msg);
+            return Result.failure(msg);
         }
         var participantContext = participantContextServiceResult.getContent();
 
@@ -178,7 +179,9 @@ public Result refreshToken(String refreshToken, String authentica
                 .map(accessTokenClaims -> accessTokenDataStore.getById(accessTokenClaims.getStringClaim(JwtRegisteredClaimNames.JWT_ID)));
 
         if (accessTokenDataResult.failed()) {
-            return Result.failure("Access token validation failed: %s".formatted(accessTokenDataResult.getFailureDetail()));
+            var msg = "Access token validation failed: %s".formatted(accessTokenDataResult.getFailureDetail());
+            monitor.debug(msg);
+            return Result.failure(msg);
         }
 
         var existingAccessTokenData = accessTokenDataResult.getContent();
@@ -192,7 +195,9 @@ public Result refreshToken(String refreshToken, String authentica
         if (newAccessToken.failed() || newRefreshToken.failed()) {
             var errors = new ArrayList<>(newAccessToken.getFailureMessages());
             errors.addAll(newRefreshToken.getFailureMessages());
-            return Result.failure("Failed to regenerate access/refresh token pair: %s".formatted(errors));
+            var msg = "Failed to regenerate access/refresh token pair: %s".formatted(errors);
+            monitor.severe(msg);
+            return Result.failure(msg);
         }
 
         storeRefreshToken(existingAccessTokenData.id(), new RefreshToken(newRefreshToken.getContent(), tokenExpirySeconds, refreshEndpoint), participantContext);
@@ -202,10 +207,12 @@ public Result refreshToken(String refreshToken, String authentica
         var accessTokenData = new AccessTokenData(existingAccessTokenData.id(), claimToken, existingAccessTokenData.dataAddress(), existingAccessTokenData.additionalProperties());
 
         var storeResult = accessTokenDataStore.update(accessTokenData);
-        return storeResult.succeeded() ?
-                Result.success(new TokenResponse(newAccessToken.getContent(),
-                        newRefreshToken.getContent(), tokenExpirySeconds, "bearer")) :
-                Result.failure(storeResult.getFailureMessages());
+
+        if (storeResult.failed()) {
+            monitor.severe("Failed to store refreshed access token data: %s".formatted(storeResult.getFailureDetail()));
+            return Result.failure(storeResult.getFailureMessages());
+        }
+        return Result.success(new TokenResponse(newAccessToken.getContent(), newRefreshToken.getContent(), tokenExpirySeconds, "bearer"));
     }
 
     @Override
@@ -217,12 +224,16 @@ public Result obtainToken(TokenParameters tokenParameters,
         //create a refresh token
         var refreshTokenResult = createToken(TokenParameters.Builder.newInstance().build());
         if (refreshTokenResult.failed()) {
-            return Result.failure("Could not generate refresh token: %s".formatted(refreshTokenResult.getFailureDetail()));
+            var msg = "Could not generate refresh token: %s".formatted(refreshTokenResult.getFailureDetail());
+            monitor.debug(msg);
+            return Result.failure(msg);
         }
 
         var accessTokenResult = createToken(tokenParameters);
         if (accessTokenResult.failed()) {
-            return Result.failure("Could not generate access token: %s".formatted(accessTokenResult.getFailureDetail()));
+            var msg = "Could not generate access token: %s".formatted(accessTokenResult.getFailureDetail());
+            monitor.debug(msg);
+            return Result.failure(msg);
         }
 
         // the edrAdditionalData contains the refresh token, which is NOT supposed to be put in the DB
@@ -237,7 +248,9 @@ public Result obtainToken(TokenParameters tokenParameters,
 
         var participantContextServiceResult = participantContextSupplier.get();
         if (participantContextServiceResult.failed()) {
-            return Result.failure("Cannot retrieve ParticipantContext: " + participantContextServiceResult.getFailureDetail());
+            var msg = "Cannot retrieve ParticipantContext: " + participantContextServiceResult.getFailureDetail();
+            monitor.severe(msg);
+            return Result.failure(msg);
         }
         var participantContext = participantContextServiceResult.getContent();
 
@@ -248,8 +261,11 @@ public Result obtainToken(TokenParameters tokenParameters,
         var audience = additionalDataForStorage.get(AUDIENCE_PROPERTY);
 
         if (audience == null) {
-            return Result.failure("Missing audience in the additional properties");
+            var msg = "Missing audience in the additional properties";
+            monitor.debug(msg);
+            return Result.failure(msg);
         }
+
         var edrAdditionalData = new HashMap<>(additionalTokenData);
         edrAdditionalData.put(EDR_PROPERTY_REFRESH_TOKEN, refreshTokenResult.getContent().tokenRepresentation().getToken());
         edrAdditionalData.put(EDR_PROPERTY_EXPIRES_IN, String.valueOf(tokenExpirySeconds));
@@ -262,8 +278,12 @@ public Result obtainToken(TokenParameters tokenParameters,
                 .expiresIn(tokenExpirySeconds) //todo: needed?
                 .build();
 
+        if (storeResult.failed()) {
+            monitor.severe("Could not store AccessTokenData: %s".formatted(storeResult.getFailureDetail()));
+            return Result.failure(storeResult.getFailureMessages());
+        }
 
-        return storeResult.succeeded() ? Result.success(edrTokenRepresentation) : Result.failure(storeResult.getFailureMessages());
+        return Result.success(edrTokenRepresentation);
     }
 
     @Override
@@ -272,7 +292,13 @@ public Result resolve(String token) {
                 .compose(claimToken -> {
                     var id = claimToken.getStringClaim(JWTClaimNames.JWT_ID);
                     var tokenData = accessTokenDataStore.getById(id);
-                    return tokenData != null ? Result.success(tokenData) : Result.failure("AccessTokenData with ID '%s' does not exist.".formatted(id));
+
+                    if (tokenData == null) {
+                        var msg = "AccessTokenData with ID '%s' does not exist.".formatted(id);
+                        monitor.debug(msg);
+                        return  Result.failure(msg);
+                    }
+                    return Result.success(tokenData);
                 });
     }
 
@@ -286,7 +312,11 @@ public ServiceResult revoke(String transferProcessId, String reason) {
         return tokens.stream().map(this::deleteTokenData)
                 .reduce(Result::merge)
                 .map(ServiceResult::from)
-                .orElseGet(() -> ServiceResult.notFound("AccessTokenData associated to the transfer with ID '%s' does not exist.".formatted(transferProcessId)));
+                .orElseGet(() -> {
+                    var msg = "AccessTokenData associated to the transfer with ID '%s' does not exist.".formatted(transferProcessId);
+                    monitor.debug(msg);
+                    return ServiceResult.notFound(msg);
+                });
     }
 
     private Result deleteTokenData(AccessTokenData tokenData) {
diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java
index 23b1af5e3f..a5a2e3d7a3 100644
--- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java
+++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplComponentTest.java
@@ -110,7 +110,6 @@ void setup() throws JOSEException {
                 () -> privateKeyAlias,
                 monitor,
                 TEST_REFRESH_ENDPOINT,
-                PROVIDER_DID,
                 1,
                 300L,
                 () -> providerKey.getKeyID(),
diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java
index 856992a18c..ce22a6468e 100644
--- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java
+++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/DataPlaneTokenRefreshServiceImplTest.java
@@ -84,7 +84,7 @@ void setUp() {
         when(monitor.withPrefix(anyString())).thenReturn(monitor);
         accessTokenService = new DataPlaneTokenRefreshServiceImpl(Clock.systemUTC(),
                 tokenValidationService, didPublicKeyResolver, localPublicKeyService, accessTokenDataStore, tokenGenService, mock(), monitor,
-                "https://example.com", "did:web:provider", 1, 300L,
+                "https://example.com", 1, 300L,
                 () -> "keyid", mock(), new ObjectMapper(), participantContextSupplier);
     }
 
diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java
index e78d04a380..097b89238b 100644
--- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java
+++ b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java
@@ -27,6 +27,7 @@
 import org.eclipse.edc.runtime.metamodel.annotation.Inject;
 import org.eclipse.edc.runtime.metamodel.annotation.Provider;
 import org.eclipse.edc.runtime.metamodel.annotation.Setting;
+import org.eclipse.edc.spi.monitor.Monitor;
 import org.eclipse.edc.spi.system.ServiceExtension;
 import org.eclipse.edc.spi.system.ServiceExtensionContext;
 import org.eclipse.tractusx.edc.protocol.identifier.BpnExtractionFunction;
@@ -55,14 +56,16 @@ public class DataspaceProtocolExtension implements ServiceExtension {
     private DspBaseWebhookAddress dspWebhookAddress;
     @Inject
     private SingleParticipantContextSupplier singleParticipantContextSupplier;
+    @Inject
+    private Monitor monitor;
 
     @Override
     public void initialize(ServiceExtensionContext context) {
         Stream.of(
-                new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP, V_08, () -> dspWebhookAddress.get(), new BpnExtractionFunction()),
-                new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2025_1, V_2025_1, () -> dspWebhookAddress.get() + V_2025_1_PATH, new DidExtractionFunction()),
+                new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP, V_08, () -> dspWebhookAddress.get(), new BpnExtractionFunction(monitor)),
+                new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2025_1, V_2025_1, () -> dspWebhookAddress.get() + V_2025_1_PATH, new DidExtractionFunction(monitor)),
                 // currently required for DCP TCK tests
-                new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2024_1, V_2024_1, () -> dspWebhookAddress.get() + V_2024_1_PATH, new BpnExtractionFunction())
+                new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2024_1, V_2024_1, () -> dspWebhookAddress.get() + V_2024_1_PATH, new BpnExtractionFunction(monitor))
         ).forEach(contextRegistry::register);
     }
 
diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunction.java b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunction.java
index 7178617b65..0d74c277d8 100644
--- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunction.java
+++ b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunction.java
@@ -20,6 +20,7 @@
 package org.eclipse.tractusx.edc.protocol.identifier;
 
 import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
+import org.eclipse.edc.spi.monitor.Monitor;
 
 import java.util.Map;
 import java.util.Optional;
@@ -31,7 +32,11 @@
 public class BpnExtractionFunction extends MembershipCredentialIdExtractionFunction {
     
     private static final String IDENTITY_PROPERTY = "holderIdentifier";
-    
+
+    public BpnExtractionFunction(Monitor monitor) {
+        super(monitor);
+    }
+
     @Override
     String identityProperty() {
         return IDENTITY_PROPERTY;
diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunction.java b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunction.java
index 5d98b7ddee..2e466adb90 100644
--- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunction.java
+++ b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunction.java
@@ -21,6 +21,7 @@
 
 import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject;
 import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential;
+import org.eclipse.edc.spi.monitor.Monitor;
 
 import java.util.Optional;
 
@@ -31,7 +32,11 @@
 public class DidExtractionFunction extends MembershipCredentialIdExtractionFunction {
     
     private static final String IDENTITY_PROPERTY = "id";
-    
+
+    public DidExtractionFunction(Monitor monitor) {
+        super(monitor);
+    }
+
     @Override
     String identityProperty() {
         return IDENTITY_PROPERTY;
diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunction.java b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunction.java
index 8d456f5dfc..f44cd837c7 100644
--- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunction.java
+++ b/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunction.java
@@ -24,6 +24,7 @@
 import org.eclipse.edc.protocol.spi.ParticipantIdExtractionFunction;
 import org.eclipse.edc.spi.EdcException;
 import org.eclipse.edc.spi.iam.ClaimToken;
+import org.eclipse.edc.spi.monitor.Monitor;
 import org.eclipse.edc.spi.result.Result;
 import org.eclipse.tractusx.edc.core.utils.credentials.CredentialTypePredicate;
 
@@ -41,17 +42,26 @@ public abstract class MembershipCredentialIdExtractionFunction implements Partic
     private static final String IDENTITY_CREDENTIAL = "MembershipCredential";
     
     private final CredentialTypePredicate typePredicate = new CredentialTypePredicate(CX_CREDENTIAL_NS, IDENTITY_CREDENTIAL);
+    private final Monitor monitor;
+
+    public MembershipCredentialIdExtractionFunction(Monitor monitor) {
+        this.monitor = monitor.withPrefix(getClass().getSimpleName());
+    }
     
     @Override
     public String apply(ClaimToken claimToken) {
         var credentials = getCredentialList(claimToken)
                 .orElseThrow(failure -> new EdcException("Failed to fetch credentials from the claim token: %s".formatted(failure.getFailureDetail())));
-        
+
         return credentials.stream()
                 .filter(typePredicate)
                 .findFirst()
                 .flatMap(this::getIdentifier)
-                .orElseThrow(() -> new EdcException("Required credential type '%s' not present in ClaimToken, cannot extract property '%s'".formatted(IDENTITY_CREDENTIAL, identityProperty())));
+                .orElseThrow(() -> {
+                    var msg = "Required credential type '%s' not present in ClaimToken, cannot extract property '%s'".formatted(IDENTITY_CREDENTIAL, identityProperty());
+                    monitor.warning(msg);
+                    return new EdcException(msg);
+                });
     }
     
     @SuppressWarnings("unchecked")
@@ -59,14 +69,20 @@ private Result> getCredentialList(ClaimToken claimTok
         var vcListClaim = claimToken.getClaims().get(VC_CLAIM);
         
         if (vcListClaim == null) {
-            return Result.failure("ClaimToken did not contain a '%s' claim.".formatted(VC_CLAIM));
+            var msg = "ClaimToken did not contain a '%s' claim.".formatted(VC_CLAIM);
+            monitor.warning(msg);
+            return Result.failure(msg);
         }
         if (!(vcListClaim instanceof List)) {
-            return Result.failure("ClaimToken contains a '%s' claim, but the type is incorrect. Expected %s, got %s.".formatted(VC_CLAIM, List.class.getName(), vcListClaim.getClass().getName()));
+            var msg = "ClaimToken contains a '%s' claim, but the type is incorrect. Expected %s, got %s.".formatted(VC_CLAIM, List.class.getName(), vcListClaim.getClass().getName());
+            monitor.warning(msg);
+            return Result.failure(msg);
         }
         var vcList = (List) vcListClaim;
         if (vcList.isEmpty()) {
-            return Result.failure("ClaimToken contains a '%s' claim but it did not contain any VerifiableCredentials.".formatted(VC_CLAIM));
+            var msg = "ClaimToken contains a '%s' claim but it did not contain any VerifiableCredentials.".formatted(VC_CLAIM);
+            monitor.warning(msg);
+            return Result.failure(msg);
         }
         return Result.success(vcList);
     }
diff --git a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java b/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java
index 73c021796f..28c351dd32 100644
--- a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java
+++ b/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java
@@ -19,10 +19,19 @@
 
 package org.eclipse.tractusx.edc.protocol.identifier;
 
+import org.eclipse.edc.spi.monitor.Monitor;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 public class BpnExtractionFunctionTest extends MembershipCredentialIdExtractionFunctionTest {
+    private final Monitor monitor = mock();
+
     @Override
     protected MembershipCredentialIdExtractionFunction extractionFunction() {
-        return new BpnExtractionFunction();
+        when(monitor.withPrefix(anyString())).thenReturn(monitor);
+        return new BpnExtractionFunction(monitor);
     }
     
     @Override
diff --git a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java b/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java
index 2cd571f475..1ee09a2ab9 100644
--- a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java
+++ b/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java
@@ -19,10 +19,19 @@
 
 package org.eclipse.tractusx.edc.protocol.identifier;
 
+import org.eclipse.edc.spi.monitor.Monitor;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 public class DidExtractionFunctionTest extends MembershipCredentialIdExtractionFunctionTest {
+    private final Monitor monitor = mock();
+
     @Override
     protected MembershipCredentialIdExtractionFunction extractionFunction() {
-        return new DidExtractionFunction();
+        when(monitor.withPrefix(anyString())).thenReturn(monitor);
+        return new DidExtractionFunction(monitor);
     }
     
     @Override
diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java
index 09337396c4..a1857b0603 100644
--- a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java
+++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenService.java
@@ -145,7 +145,9 @@ private Result> extractScopes(String bearerAccessScope) {
     private Result extractCredential(String scope, Consumer consumer) {
         var tokens = scope.split(":");
         if (tokens.length != 3) {
-            return Result.failure("Scope string %s has invalid format".formatted(scope));
+            var msg = "Scope string %s has invalid format".formatted(scope);
+            monitor.severe(msg);
+            return Result.failure(msg);
         }
         consumer.accept(tokens[1]);
         return Result.success();
@@ -159,7 +161,10 @@ private Result> signTokenPayload(Map claims)
 
     private Result executeRequest(Request request, String context) {
         return httpClient.execute(request, List.of(retryWhenStatusIsNotIn(200, 201)), this::handleResponse)
-                .recover(failure -> Result.failure("[%s] %s".formatted(context, failure.getFailureDetail())));
+                .recover(failure -> {
+                    monitor.warning("Request to %s failed: [%s] %s".formatted(request.url().url(), context, failure.getFailureDetail()));
+                    return Result.failure("[%s] %s".formatted(context, failure.getFailureDetail()));
+                });
     }
 
     private Result handleResponse(Response response) {
@@ -172,7 +177,7 @@ private Result handleResponse(Response response) {
                     .map(Result::success)
                     .orElseGet(() -> Result.failure("Failed to get jwt field"));
         } catch (IOException e) {
-            monitor.severe("Failed to parse response from DIM");
+            monitor.warning("Failed to parse response from DIM");
             return Result.failure(e.getMessage());
         }
     }
@@ -183,6 +188,7 @@ private Result postRequest(Map body) {
             return baseRequestWithToken()
                     .map(builder -> builder.post(requestBody));
         } catch (JsonProcessingException e) {
+            monitor.severe("Failed to serialize request body: " + e.getMessage(), e);
             return Result.failure(e.getMessage());
         }
 
diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java
index 479b5b15ad..f3da8a3a2f 100644
--- a/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java
+++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/oauth/DimOauthClientImpl.java
@@ -91,7 +91,9 @@ private boolean isExpired() {
     private Result createRequest() {
         var participantContextServiceResult = participantContextSupplier.get();
         if (participantContextServiceResult.failed()) {
-            return Result.failure("Cannot retrieve Participant Context");
+            var msg = "Cannot retrieve Participant Context";
+            monitor.severe(msg + ": " + participantContextServiceResult.getFailureDetail());
+            return Result.failure(msg);
         }
 
         var secret = vault.resolveSecret(participantContextServiceResult.getContent().getParticipantContextId(), configuration.clientSecretAlias());
@@ -104,7 +106,9 @@ private Result createRequest() {
 
             return Result.success(builder.build());
         } else {
-            return Result.failure("Failed to fetch client secret from the vault with alias: %s".formatted(configuration.clientSecretAlias()));
+            var msg = "Failed to fetch client secret from the vault with alias: %s".formatted(configuration.clientSecretAlias());
+            monitor.severe(msg);
+            return Result.failure(msg);
         }
     }
 
diff --git a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenServiceTest.java b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenServiceTest.java
index b7ce1bd6d5..4af86fac40 100644
--- a/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenServiceTest.java
+++ b/edc-extensions/dcp/tx-dcp-sts-dim/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/dim/DimSecureTokenServiceTest.java
@@ -51,6 +51,7 @@
 import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE;
 import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER;
 import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -66,6 +67,7 @@ public class DimSecureTokenServiceTest {
 
     @BeforeEach
     void setup() {
+        when(monitor.withPrefix(anyString())).thenReturn(monitor);
         client = new DimSecureTokenService(testHttpClient(interceptor), DIM_URL, oauth2Client, mapper, monitor);
     }
 
diff --git a/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImpl.java b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImpl.java
index 01de773c34..52e2650bcd 100644
--- a/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImpl.java
+++ b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImpl.java
@@ -73,7 +73,7 @@ public VerifiablePresentationCacheImpl(long cacheValidity, Clock clock, Verifiab
         this.credentialValidationService = credentialValidationService;
         this.didResolver = didResolver;
         this.revocationServiceRegistry = revocationServiceRegistry;
-        this.monitor = monitor;
+        this.monitor = monitor.withPrefix(getClass().getSimpleName());
     }
 
     @Override
@@ -91,7 +91,9 @@ public StoreResult> query(String participa
         var cacheResult = store.query(participantContextId, counterPartyDid, scopes);
 
         if (cacheResult.failed()) {
-            return StoreResult.notFound("No cached entry found for given participant and scopes.");
+            var msg = "No cached entry found for given participant and scopes.";
+            monitor.debug(msg);
+            return StoreResult.notFound(msg);
         }
 
         if (isExpired(cacheResult.getContent()) || expiredOrRevoked(cacheResult.getContent().getPresentations())) {
@@ -99,7 +101,9 @@ public StoreResult> query(String participa
             if (removeResult.failed()) {
                 monitor.warning(format("Failed to remove expired or invalid entry from cache for %s: %s", counterPartyDid, removeResult.getFailureDetail()));
             }
-            return StoreResult.notFound("No cached entry found for given participant and scopes.");
+            var msg = "VPs/VCs are expired or revoked. Removed from cache.";
+            monitor.debug(msg);
+            return StoreResult.notFound(msg);
         }
 
         return cacheResult.map(VerifiablePresentationCacheEntry::getPresentations);
@@ -127,18 +131,27 @@ private Result validateRequestedCredentials(List p.presentation().getCredentials().stream())
                 .toList();
-        if (requestedScopes.size() > allCreds.size()) {
-            return Result.failure("Number of requested credentials does not match the number of returned credentials");
-        }
 
         var types = allCreds.stream().map(VerifiableCredential::getType)
                 .flatMap(Collection::stream)
                 .distinct()
                 .toList();
 
-        return requestedScopes.stream().allMatch(scope -> types.stream().anyMatch(scope::contains)) ?
-                Result.success() :
-                Result.failure("Not all requested credentials are present in the presentation response");
+        if (requestedScopes.size() > allCreds.size()) {
+            var msg = "More credentials are requested than returned";
+            monitor.debug(msg + ": requested { %s }, returned { %s }".formatted(String.join(",", requestedScopes),
+                    String.join(",", types)));
+            return Result.failure(msg);
+        }
+
+        if (!requestedScopes.stream().allMatch(scope -> types.stream().anyMatch(scope::contains))) {
+            var msg = "Not all requested credentials are present in the presentation response";
+            monitor.debug(msg + ": requested { %s }, returned { %s }".formatted(String.join(",", requestedScopes),
+                    String.join(",", types)));
+            return Result.failure(msg);
+        }
+
+        return Result.success();
     }
 
     /**
@@ -153,7 +166,9 @@ private Result verifyPresentationIssuer(String expectedIssuer, List didResolver = pcId -> ownDid;
     private final RevocationServiceRegistry revocationServiceRegistry = mock();
+    private final Monitor monitor = mock();
 
-    private final VerifiablePresentationCacheImpl cache = new VerifiablePresentationCacheImpl(cacheValidity,
-            clock, store, validationService, didResolver, revocationServiceRegistry, mock());
+    private VerifiablePresentationCacheImpl cache;
 
     @BeforeEach
     void setUp() {
@@ -91,6 +93,10 @@ void setUp() {
         when(store.remove(participantContextId, counterPartyDid, scopes)).thenReturn(StoreResult.success());
         when(validationService.validate(eq(List.of(vpContainer)), eq(ownDid), eq(emptyList()))).thenReturn(Result.success());
         when(revocationServiceRegistry.checkValidity(vc)).thenReturn(Result.success());
+        when(monitor.withPrefix(anyString())).thenReturn(monitor);
+
+        cache = new VerifiablePresentationCacheImpl(cacheValidity, clock, store, validationService, didResolver,
+                revocationServiceRegistry, monitor);
     }
 
     @Nested
diff --git a/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/main/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineService.java b/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/main/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineService.java
index a8bde0d215..8c669bdc05 100644
--- a/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/main/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineService.java
+++ b/edc-extensions/non-finite-provider-push/non-finite-provider-push-core/src/main/java/org/eclipse/tractusx/edc/nonfiniteproviderpush/core/pipeline/NonFiniteCapablePipelineService.java
@@ -196,7 +196,9 @@ private StreamResult terminate(String dataFlowId) {
             try {
                 source.close();
             } catch (Exception e) {
-                return StreamResult.error("Cannot terminate DataFlow %s: %s".formatted(dataFlowId, e.getMessage()));
+                var msg = "Cannot terminate DataFlow %s: %s".formatted(dataFlowId, e.getMessage());
+                monitor.severe(msg, e);
+                return StreamResult.error(msg);
             }
         }
         return StreamResult.success();
diff --git a/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java b/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java
index 41f1eef8d0..c203286dd5 100644
--- a/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java
+++ b/edc-extensions/tokenrefresh-handler/src/main/java/org/eclipse/tractusx/edc/common/tokenrefresh/TokenRefreshHandlerImpl.java
@@ -40,6 +40,7 @@
 import java.io.IOException;
 import java.util.Map;
 
+import static java.lang.String.format;
 import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE;
 import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER;
 import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.JWT_ID;
@@ -90,6 +91,8 @@ public TokenRefreshHandlerImpl(EndpointDataReferenceCache edrCache,
     public ServiceResult refreshToken(String tokenId) {
         var edrResult = edrCache.get(tokenId);
         if (edrResult.failed()) {
+            var msg = "Could not find EDR for transfer process ID %s: %s".formatted(tokenId, edrResult.getFailureDetail());
+            monitor.debug(msg);
             return ServiceResult.notFound(edrResult.getFailureDetail());
         }
         var edr = edrResult.getContent();
@@ -104,16 +107,24 @@ public ServiceResult refreshToken(String tokenId, DataAddress edr)
         var refreshAudience = edr.getProperties().get(EDR_PROPERTY_REFRESH_AUDIENCE);
 
         if (isNullOrBlank(accessToken)) {
-            return ServiceResult.badRequest("Cannot perform token refresh: required property 'authorization' not found on EDR.");
+            var msg = "Cannot perform token refresh: required property 'authorization' not found on EDR.";
+            monitor.severe(msg);
+            return ServiceResult.badRequest(msg);
         }
         if (isNullOrBlank(StringUtils.toString(refreshToken))) {
-            return ServiceResult.badRequest("Cannot perform token refresh: required property 'refreshToken' not found on EDR.");
+            var msg = "Cannot perform token refresh: required property 'refreshToken' not found on EDR.";
+            monitor.severe(msg);
+            return ServiceResult.badRequest(msg);
         }
         if (isNullOrBlank(StringUtils.toString(refreshEndpoint))) {
-            return ServiceResult.badRequest("Cannot perform token refresh: required property 'refreshEndpoint' not found on EDR.");
+            var msg = "Cannot perform token refresh: required property 'refreshEndpoint' not found on EDR.";
+            monitor.severe(msg);
+            return ServiceResult.badRequest(msg);
         }
         if (isNullOrBlank(StringUtils.toString(refreshAudience))) {
-            return ServiceResult.badRequest("Cannot perform token refresh: required property 'refreshAudience' not found on EDR.");
+            var msg = "Cannot perform token refresh: required property 'refreshAudience' not found on EDR.";
+            monitor.severe(msg);
+            return ServiceResult.badRequest(msg);
         }
 
         var claims = Map.of(
@@ -129,7 +140,11 @@ public ServiceResult refreshToken(String tokenId, DataAddress edr)
                         .flatMap(ServiceResult::from))
                 .compose(authToken -> createTokenRefreshRequest(refreshEndpoint.toString(), refreshToken.toString(), "Bearer %s".formatted(authToken.getToken()))
                         .flatMap(ServiceResult::from))
-                .recover(f -> ServiceResult.badRequest("Could not execute token refresh: " + f.getFailureDetail()))
+                .recover(f -> {
+                    var msg = "Could not execute token refresh: " + f.getFailureDetail();
+                    monitor.warning(msg);
+                    return ServiceResult.badRequest(msg);
+                })
                 .compose(this::executeRequest)
                 .map(tr -> createNewEdr(edr, tr));
     }
@@ -152,8 +167,13 @@ private ServiceResult executeRequest(Request tokenRefreshRequest)
                     var tokenResponse = objectMapper.readValue(jsonBody, TokenResponse.class);
                     return ServiceResult.success(tokenResponse);
                 }
-                return ServiceResult.badRequest("Token refresh successful, but body was empty.");
+                var msg = "Token refresh successful, but body was empty.";
+                monitor.warning(msg);
+                return ServiceResult.unexpected(msg);
             }
+            var responseBody = StringUtils.toString(response.body());
+            monitor.debug(format("Received error response from %s: %s - %s: \"%s\".", tokenRefreshRequest.url().url(),
+                    response.code(), response.message(), responseBody));
             return switch (response.code()) {
                 case 401 -> ServiceResult.unauthorized(response.message());
                 case 409 -> ServiceResult.conflict(response.message());
@@ -161,8 +181,9 @@ private ServiceResult executeRequest(Request tokenRefreshRequest)
                 default -> ServiceResult.badRequest(response.message());
             };
         } catch (IOException e) {
-            monitor.warning("Error executing token refresh request", e);
-            return ServiceResult.from(StoreResult.generalError("Error executing token refresh request: %s".formatted(e)));
+            var msg = "Error executing token refresh request";
+            monitor.warning(msg, e);
+            return ServiceResult.from(StoreResult.generalError(msg + ": %s".formatted(e)));
         }
     }
 
diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/ParticipantRuntimeExtension.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/ParticipantRuntimeExtension.java
index 39d149e308..3f762fb82f 100644
--- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/ParticipantRuntimeExtension.java
+++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/ParticipantRuntimeExtension.java
@@ -111,7 +111,7 @@ public TokenParameters.Builder decorate(TokenParameters.Builder tokenParameters)
         private static class StaticPrivateKeyResolver implements PrivateKeyResolver {
             private final PrivateKey privateKey;
 
-            public StaticPrivateKeyResolver(PrivateKey privateKey) {
+            StaticPrivateKeyResolver(PrivateKey privateKey) {
                 this.privateKey = privateKey;
             }
 

From e4c2dd6f6c69c8d0c95468fcb0d7b144d0944863 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Arno=20Wei=C3=9F?=
 <86715435+arnoweiss@users.noreply.github.com>
Date: Wed, 14 Jan 2026 05:18:46 +0100
Subject: [PATCH 087/112] fix: add scaling considerations to self registration
 DR (#2511)

* fix: add scaling considerations to self registration DR

* fix: align with implementation of initial PR
---
 .../README.md                                 | 75 ++++++++++++++-----
 1 file changed, 56 insertions(+), 19 deletions(-)

diff --git a/docs/development/decision-records/2025-11-27-did-service-registration/README.md b/docs/development/decision-records/2025-11-27-did-service-registration/README.md
index 1ed9a9cf23..f97e8c2636 100644
--- a/docs/development/decision-records/2025-11-27-did-service-registration/README.md
+++ b/docs/development/decision-records/2025-11-27-did-service-registration/README.md
@@ -22,35 +22,72 @@ to avoid creating duplicate `service` entries and manage itself.
 
 1. Introduce configuration options in application and helm chart.
 2. Create a new SPI including an interface that represents the feature in an abstract manner.
-3. An extension that implements the SPI's interface as client for [SAP DIV's write endpoint to the did document](https://api.sap.com/api/DIV/path/CompanyIdentityV2HttpController_updateCompanyIdentity_v2.0.0).
-4. Another extension that will implement the lifecycle management logic.
+3. Add an extension that will implement the lifecycle management logic.
+4. Another extension implements the SPI's interface as client for [SAP DIV's write endpoint to the did document](https://api.sap.com/api/DIV/path/CompanyIdentityV2HttpController_updateCompanyIdentity_v2.0.0).
 
-The lifecycle management logic shall look like:
+The lifecycle management logic is designed to ensure functional correctness while limiting outbound HTTP traffic on 
+startup. It shall behave acoording to the following diagram:
 
 ```mermaid
 flowchart TD
-    A[Connector
starts up] -->|id| B{id already
exists?} - B --> |true| C{same url?} - B --> |false| F[add to did doc] - C -->|true| D[do nothing] - C -->|false| E[update existing entry with URL] - D -->|shutdown| G[remove did doc entry] - E -->|shutdown| G[remove did doc entry] - F -->|shutdown| G[remove did doc entry] + J@{ shape: stadium, label: "Terminal point" } + A@{ shape: circle, label: "Connector
starts up" } + A --> H{reg-enabled} + H -->|false| G + H -->|true
serves id and url| E[delete and recreate existing entry with URL] + E -->|shutdown| G{dereg-enabled} + G -->|true| K[deregister] + K --> J + G -->|false| J ``` -The SPI will look something like +The SPI will look like ```java -public interface DidServiceClient { +public interface DidDocumentServiceClient { - void createService(String id, String urlOfWellKnown); - - void updateService(String id, String urlOfWellKnown); - - void deleteService(String id); + ServiceResult update(Service service); + ServiceResult deleteById(String id); } +``` + +## Scaling considerations + +As this extension triggers a side-effect on the DID Service, one must consider the case of horizontally scaled runtimes. +When scaling down, the shutdown sequence must not affect the did document service entry if another container is still +running. Containers aren't natively aware of each other and making them would be disproportionate effort. If +deregistration is enabled, this is a very realistic scenario. + +The container image should receive two new environment variables: +- `TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED` (labeled *reg-enabled* in flowchart) +- `TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED` (labeled *dereg-enabled* in flowchart) + +At the same time, requiring an admin to consider this when deploying the helm chart is burdensome. That's why the +values yaml should look like: + +```yaml +controlplane: + didService: + selfRegistration: + # -- Whether Service Self Registration is enabled + enabled: false + # -- Unique id of connector to be used for register / unregister service inside did document (must be valid URI) + id: "did:web:changeme" +``` + +The [deployment-controlplane.yaml](/charts/tractusx-connector/templates/deployment-controlplane.yaml) will infer `TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED` +by inspecting the scaling configuration like: + +```yaml +- name: "TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED" + value: {{ and (eq .Values.controlplane.replicacount 1) (not .Values.controlplane.autoscaling.enabled) }} +``` + +Disabling deregistration in the non-scaled case (`!controlplane.autoscaling.enabled` and `controlplane.replicacount==1`) +they can set `TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED=false` in the map `controlplane.env`. -``` \ No newline at end of file +This approach may result in dangling references from the did document to dead endpoints. Cleanup of those lies outside +tractusx-edc responsibility and should be done on the DID service directly. This state is more desirable than having +available but undiscoverable endpoints as consequence of deletion from every container that shuts down. From 8f5489f49fce9dcaf3d73239b0c8f318334d4a54 Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Wed, 14 Jan 2026 11:46:28 +0100 Subject: [PATCH 088/112] docs: Add decision record for DID document based connector discovery (#2503) * Add dr for connector discovery Signed-off-by: Lars Geyer-Blaumeiser * Simplify response structure to reflect used practise to return plain arrays * Add legal notice * Rename endpoint name to remove duplication * Point out knowns parameter as optional * Change parameter name and improve description * Update the knownConnectors parameter name * Include review comments --------- Signed-off-by: Lars Geyer-Blaumeiser --- .../README.md | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/development/decision-records/2026-01-06_service_retrieval_from_did_document/README.md diff --git a/docs/development/decision-records/2026-01-06_service_retrieval_from_did_document/README.md b/docs/development/decision-records/2026-01-06_service_retrieval_from_did_document/README.md new file mode 100644 index 0000000000..e174e88076 --- /dev/null +++ b/docs/development/decision-records/2026-01-06_service_retrieval_from_did_document/README.md @@ -0,0 +1,87 @@ +# Connector endpoint retrieval from DID Document service section + +## Decision + +We will implement an additional endpoint in the connector discovery endpoint family. The endpoint will, based on a +DID, extract the connector endpoints from a DID document and determine the right dsp version parameters for each +found connector. These parameters are returned in a list. There will be a general support for other identifiers, +using BPNLs as a second supported identifier type. + +## Rationale + +The current Tractus-X connector supports multiple versions of the DSP protocol the ones supported published +in the `.well-known/dspace-version` +[endpoint](https://eclipse-dataspace-protocol-base.github.io/DataspaceProtocol/2025-1/#exposure-of-dataspace-protocol-versions). +In addition, the DSP spec suggests to use the +[DID document](https://eclipse-dataspace-protocol-base.github.io/DataspaceProtocol/2025-1/#discovery-of-service-endpoints) +to publish connector endpoints. With this feature, the Tractus-X connector is about to support this retrieval of +connector endpoints together with the detection of the right version parameters used in the management api to +initiate DSP calls in the right version. + +As for multi version connectors, only the option to use a `DataService` reference in the DID document makes sense, +the discovery of endpoints is limited to this type of connector references. + +## Approach + +There is already a +[connector discovery extension](https://github.com/eclipse-tractusx/tractusx-edc/blob/eaa7084e83912e6dae42c13c948607a68d85ffa7/edc-extensions/connector-discovery/connector-discovery-api) +that implements the retrieval of the `.well-known/dspace-version` endpoint and to create the proper dsp version +parameters for a single connector endpoint provided as parameter. There is a second extension there which provides +a default implementation of the defined api. + +As the intended api is related to this functionality, the approach is, to add another management api endpoint +called `/connectors` in this management api section that takes the following input parameters: + +```json +{ + "counterPartyId": "did:web:", + "knownConnectors": [ + "https://first.provider-domain.com/somepath/dsp/v1/api", + "https://first.provider-domain.com/otherpath/dsp/v1/api", + "https://second.provider-domain.com/dsp/v1/api" + ] +} +``` + +The `counterPartyId` field is type-neutral, in order to support different identifier types. The service will interpret +the identifier based on properties of the identifier, for now, DIDs and BPNLs will be supported. The mechanism +will be implemented in an extensible fashion, so that a general mapping from any identifier to a DID can be added. +The default implementation will detect DIDs and map them to themselves. A second extension will allow to handle +BPNLs and map them to the DID using the BDRS client. + +The second input parameter `knownConnectors` is optional and allows to add additional known connector endpoints. This is a +convenience addition that allows to use one service call to retrieve version information for all relevant +connectors. The idea is, that this method can be used for any provider and it returns a complete list of connector +endpoints. + +Consequently, the response has a body like this: + +```json +[ + { + "counterPartyAddress": "https://provider-domain/somepath/dsp/v1/api/2025-1", + "counterPartyId": "did:web:...", + "protocol": "dataspace-protocol-http:2025-1" + }, + { + "counterPartyAddress": "https://other-provider-domain/otherpath/dsp/v1/api", + "counterPartyId": "BPNL...", + "protocol": "dataspace-protocol-http" + } +] +``` + +So it returns a list of parameter sets for the listed connectors in the DID document. It adds the known connectors +given as input and also requests the central discovery for now and provides the list of connectors from there. + +The algorithm will make use of the existing features to download the DID document as well as the `dspace-version` +endpoint and processes the information. In case of a BPNL provided, it makes use of the BDRS client to translate +the BPNL to the DID. + +## NOTICE + +This work is licensed under the [CC-BY-4.0](https://creativecommons.org/licenses/by/4.0/legalcode). + +- SPDX-License-Identifier: CC-BY-4.0 +- SPDX-FileCopyrightText: 2026 Cofinity-X GmbH +- Source URL: [https://github.com/eclipse-tractusx/tractusx-edc](https://github.com/eclipse-tractusx/tractusx-edc) From db71dbb013b64ca010f0c37fff7e4e9d55f05e77 Mon Sep 17 00:00:00 2001 From: pratapipatelbcone Date: Thu, 15 Jan 2026 21:14:11 +0530 Subject: [PATCH 089/112] fix: Add Did Document Self Service De Registration Config (#2519) * fix: Add Did Document Self Service De Registration Config * fix: Cast replica count as int * fix: add quote to TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED var * fix: Trigger PR Build via empty commit --- .../templates/deployment-runtime.yaml | 6 +++--- .../templates/deployment-controlplane.yaml | 6 +++--- .../DidDocumentServiceSelfRegistrationExtension.java | 8 ++++++-- ...DocumentServiceSelfRegistrationExtensionTest.java | 12 ++++++++---- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml index 80ba7c4eb0..541977ab81 100644 --- a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml +++ b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml @@ -258,10 +258,10 @@ spec: {{- end }} - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED" value: {{ .Values.iatp.didService.selfRegistration.enabled | quote}} - {{- if .Values.iatp.didService.selfRegistration.enabled }} + - name: "TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED" + value: "false" - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID" - value: {{ .Values.iatp.didService.selfRegistration.id | required ".Values.iatp.didService.selfRegistration.id is required" | quote }} - {{- end }} + value: {{ .Values.iatp.didService.selfRegistration.id | quote }} - name: "TX_EDC_DCP_CACHE_ENABLED" value: {{ .Values.iatp.cache.enabled | quote }} - name: "TX_EDC_DCP_CACHE_VALIDITY_SECONDS" diff --git a/charts/tractusx-connector/templates/deployment-controlplane.yaml b/charts/tractusx-connector/templates/deployment-controlplane.yaml index 2442cbd9fa..6ec20bc985 100644 --- a/charts/tractusx-connector/templates/deployment-controlplane.yaml +++ b/charts/tractusx-connector/templates/deployment-controlplane.yaml @@ -256,10 +256,10 @@ spec: {{- end }} - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED" value: {{ .Values.iatp.didService.selfRegistration.enabled | quote}} - {{- if .Values.iatp.didService.selfRegistration.enabled }} + - name: "TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED" + value: {{ and (eq (int .Values.controlplane.replicaCount) 1) (not .Values.controlplane.autoscaling.enabled) | quote }} - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID" - value: {{ .Values.iatp.didService.selfRegistration.id | required ".Values.iatp.didService.selfRegistration.id is required" | quote }} - {{- end }} + value: {{ .Values.iatp.didService.selfRegistration.id | quote }} - name: "TX_EDC_DCP_CACHE_ENABLED" value: {{ .Values.iatp.cache.enabled | quote }} - name: "TX_EDC_DCP_CACHE_VALIDITY_SECONDS" diff --git a/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java b/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java index c38e4f3ceb..256a4a8264 100644 --- a/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java +++ b/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java @@ -36,6 +36,7 @@ public class DidDocumentServiceSelfRegistrationExtension implements ServiceExtension { public static final String TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED = "tx.edc.did.service.self.registration.enabled"; + public static final String TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED = "tx.edc.did.service.self.deregistration.enabled"; public static final String TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID = "tx.edc.did.service.self.registration.id"; public static final String DATA_SERVICE_TYPE = "DataService"; @@ -54,6 +55,9 @@ public class DidDocumentServiceSelfRegistrationExtension implements ServiceExten @Setting(key = TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED, defaultValue = "false", description = "Enable self-registration of the DID Document Service") private boolean selfRegistrationEnabled; + @Setting(key = TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED, defaultValue = "false", description = "Enable self-deregistration of the DID Document Service") + private boolean selfDeregistrationEnabled; + @Setting(key = TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID, required = false, description = "The Id to use for service self-registration (should be valid URI)") private String serviceId; @@ -68,7 +72,7 @@ public void start() { @Override public void shutdown() { Optional.ofNullable(didDocumentServiceClient) - .filter(client -> selfRegistrationEnabled) + .filter(client -> selfDeregistrationEnabled) .ifPresent(this::selfUnregisterDidDocumentService); } @@ -100,7 +104,7 @@ private void selfUnregisterDidDocumentService(@NotNull DidDocumentServiceClient private Result validatedServiceId(String serviceId) { if (serviceId == null || serviceId.isBlank()) { - return Result.failure("Service ID for DID Document Service self-registration configured via Property '%s' is missing or blank but self-registration is enabled.".formatted(TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID)); + return Result.failure("Service ID for DID Document Service configured via Property '%s' is missing or blank but self-registration / de-registration is enabled.".formatted(TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID)); } try { new URI(serviceId); diff --git a/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java b/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java index f6622985c4..71da8f4031 100644 --- a/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java +++ b/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java @@ -68,6 +68,7 @@ void setup(ServiceExtensionContext context) { void start_shouldSelfRegister_whenEnabledAndClientPresent(ServiceExtensionContext context, ObjectFactory objectFactory) { var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", + "tx.edc.did.service.self.deregistration.enabled", "true", "tx.edc.did.service.self.registration.id", SERVICE_ID); when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); @@ -93,6 +94,7 @@ void start_shouldSelfRegister_whenEnabledAndClientPresent(ServiceExtensionContex void start_selfRegister_whenEnabledAndClientReturnsFailure(ServiceExtensionContext context, ObjectFactory objectFactory) { var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", + "tx.edc.did.service.self.deregistration.enabled", "true", "tx.edc.did.service.self.registration.id", SERVICE_ID); when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); @@ -130,7 +132,7 @@ void start_shouldNotSelfRegister_whenClientNotPresent(ObjectFactory objectFactor @Test void start_shouldNotSelfRegister_whenDisabledAndClientPresent(ServiceExtensionContext context, ObjectFactory objectFactory) { - var settings = Map.of("tx.edc.did.service.self.registration.enabled", "false"); + var settings = Map.of("tx.edc.did.service.self.registration.enabled", "false", "tx.edc.did.service.self.deregistration.enabled", "false"); when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); @@ -147,7 +149,7 @@ void start_shouldNotSelfRegister_whenDisabledAndClientPresent(ServiceExtensionCo @Test void start_shouldNotSelfRegister_whenEnabledAndServiceIdMissing(ServiceExtensionContext context, ObjectFactory objectFactory) { - var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true"); + var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", "tx.edc.did.service.self.deregistration.enabled", "true"); when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); @@ -155,7 +157,7 @@ void start_shouldNotSelfRegister_whenEnabledAndServiceIdMissing(ServiceExtension extension.start(); verify(didDocumentServiceClient, never()).update(any(Service.class)); - verify(monitor).severe(contains("is missing or blank but self-registration is enabled")); + verify(monitor).severe(contains("is missing or blank but self-registration / de-registration is enabled")); extension.shutdown(); verify(didDocumentServiceClient, never()).deleteById(anyString()); @@ -167,6 +169,7 @@ void start_shouldNotSelfRegister_whenEnabledAndServiceIdMissing(ServiceExtension void start_shouldNotSelfRegister_whenEnabledAndServiceIdEmptyOrBlank(String serviceId, ServiceExtensionContext context, ObjectFactory objectFactory) { var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", + "tx.edc.did.service.self.dregistration.enabled", "true", "tx.edc.did.service.self.registration.id", serviceId); when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); @@ -175,7 +178,7 @@ void start_shouldNotSelfRegister_whenEnabledAndServiceIdEmptyOrBlank(String serv extension.start(); verify(didDocumentServiceClient, never()).update(any(Service.class)); - verify(monitor).severe(contains("is missing or blank but self-registration is enabled")); + verify(monitor).severe(contains("is missing or blank but self-registration / de-registration is enabled")); extension.shutdown(); verify(didDocumentServiceClient, never()).deleteById(anyString()); @@ -185,6 +188,7 @@ void start_shouldNotSelfRegister_whenEnabledAndServiceIdEmptyOrBlank(String serv void start_shouldNotSelfRegister_whenEnabledAndServiceIdInvalid(ServiceExtensionContext context, ObjectFactory objectFactory) { var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", + "tx.edc.did.service.self.deregistration.enabled", "true", "tx.edc.did.service.self.registration.id", "invalid uri"); when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); From 8afeb8fbb6c6f8fd708585a985aec555c34d455e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:11:23 +0100 Subject: [PATCH 090/112] chore(deps): bump flyway from 11.20.1 to 11.20.2 (#2525) Bumps `flyway` from 11.20.1 to 11.20.2. Updates `org.flywaydb:flyway-core` from 11.20.1 to 11.20.2 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-11.20.1...flyway-11.20.2) Updates `org.flywaydb:flyway-database-postgresql` from 11.20.1 to 11.20.2 --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-version: 11.20.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.flywaydb:flyway-database-postgresql dependency-version: 11.20.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b27a892abf..ff97c43e9b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-SNAPSHOT" dsp-tck = "1.0.0-RC4" -flyway = "11.20.1" +flyway = "11.20.2" jackson = "2.20.1" jakarta-json = "2.1.3" junit = "6.0.1" From 3c8c8f3caefc607ef329999e567632078b3974f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:11:57 +0100 Subject: [PATCH 091/112] chore(deps): bump trufflesecurity/trufflehog from 3.92.4 to 3.92.5 (#2528) Bumps [trufflesecurity/trufflehog](https://github.com/trufflesecurity/trufflehog) from 3.92.4 to 3.92.5. - [Release notes](https://github.com/trufflesecurity/trufflehog/releases) - [Commits](https://github.com/trufflesecurity/trufflehog/compare/ef6e76c3c4023279497fab4721ffa071a722fd05...116e7171542d2f1dad8810f00dcfacbe0b809183) --- updated-dependencies: - dependency-name: trufflesecurity/trufflehog dependency-version: 3.92.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/secrets-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml index 3ca75ce772..aeafab0d7c 100644 --- a/.github/workflows/secrets-scan.yml +++ b/.github/workflows/secrets-scan.yml @@ -46,7 +46,7 @@ jobs: - name: TruffleHog OSS id: trufflehog - uses: trufflesecurity/trufflehog@ef6e76c3c4023279497fab4721ffa071a722fd05 + uses: trufflesecurity/trufflehog@116e7171542d2f1dad8810f00dcfacbe0b809183 continue-on-error: true with: path: ./ # Scan the entire repository From 9905c0b0ffc3bb6368b7d0763264779710b76472 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:12:36 +0100 Subject: [PATCH 092/112] chore(deps): bump io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17 (#2527) Bumps [io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17](https://github.com/open-telemetry/opentelemetry-java-instrumentation) from 2.23.0-alpha to 2.24.0-alpha. - [Release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java-instrumentation/commits) --- updated-dependencies: - dependency-name: io.opentelemetry.instrumentation:opentelemetry-log4j-appender-2.17 dependency-version: 2.24.0-alpha dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff97c43e9b..76b1568266 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ nimbus = "10.7" okhttp = "5.3.2" opentelemetry = "2.23.0" opentelemetry-instrumentation = "2.23.0" -opentelemetry-log4j-appender = "2.23.0-alpha" +opentelemetry-log4j-appender = "2.24.0-alpha" postgres = "42.7.8" restAssured = "6.0.0" rsApi = "4.0.0" From acfb504b03995d1527af1468e1d4d1becb150d1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:13:03 +0100 Subject: [PATCH 093/112] chore(deps): bump aws from 2.41.5 to 2.41.10 (#2524) Bumps `aws` from 2.41.5 to 2.41.10. Updates `software.amazon.awssdk:s3` from 2.41.5 to 2.41.10 Updates `software.amazon.awssdk:s3-transfer-manager` from 2.41.5 to 2.41.10 --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.41.10 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: software.amazon.awssdk:s3-transfer-manager dependency-version: 2.41.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 76b1568266..743325f1c1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ edc = "0.15.1" edc-build = "1.1.5" allure = "2.32.0" awaitility = "4.3.0" -aws = "2.41.5" +aws = "2.41.10" azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-SNAPSHOT" From 8d6b04d1812193d14759ca78b01d7847ea968da0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:13:46 +0100 Subject: [PATCH 094/112] chore(deps): bump com.github.dasniko:testcontainers-keycloak (#2523) Bumps [com.github.dasniko:testcontainers-keycloak](https://github.com/dasniko/testcontainers-keycloak) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/dasniko/testcontainers-keycloak/releases) - [Commits](https://github.com/dasniko/testcontainers-keycloak/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: com.github.dasniko:testcontainers-keycloak dependency-version: 4.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 743325f1c1..83dd34153e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ postgres = "42.7.8" restAssured = "6.0.0" rsApi = "4.0.0" testcontainers = "2.0.3" -testcontainers-keycloak = "4.1.0" +testcontainers-keycloak = "4.1.1" titanium = "1.7.0" log4j2 = "2.25.3" wiremock = "3.13.2" From bd9aba7e4d7ab0f92d102ffe18cbc5f6d4c593a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:21:00 +0100 Subject: [PATCH 095/112] chore(deps): bump com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp (#2526) Bumps [com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp](https://github.com/FasterXML/jackson-datatypes-misc) from 2.20.1 to 2.21.0. - [Commits](https://github.com/FasterXML/jackson-datatypes-misc/compare/jackson-datatypes-misc-parent-2.20.1...jackson-datatypes-misc-parent-2.21.0) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.datatype:jackson-datatype-jakarta-jsonp dependency-version: 2.21.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 83dd34153e..d0e6732406 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-SNAPSHOT" dsp-tck = "1.0.0-RC4" flyway = "11.20.2" -jackson = "2.20.1" +jackson = "2.21.0" jakarta-json = "2.1.3" junit = "6.0.1" nimbus = "10.7" From 7b52088862777f55b889feffcf08348fb3072880 Mon Sep 17 00:00:00 2001 From: Andrii Yurkevych Date: Tue, 20 Jan 2026 12:55:50 +0100 Subject: [PATCH 096/112] fix: update DCP/DSP TCKs version (#2530) --- .../tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java | 3 +-- gradle/libs.versions.toml | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java b/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java index a8905a0b73..a89073a745 100644 --- a/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java +++ b/edc-tests/e2e/dcp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dcp/DcpPresentationFlowTest.java @@ -118,8 +118,7 @@ void setUp(TrustedIssuerRegistry trustedIssuerRegistry) throws JOSEException { @Test void runPresentationFlowTests() { var monitor = new ConsoleMonitor(true, true); - //Should be used DSP 2024-1 until it will not be updated in DCP TCK to 2025-1 - var triggerPath = PROTOCOL_API_PATH + "/2024/1/catalog/request"; + var triggerPath = PROTOCOL_API_PATH + "/2025-1/catalog/request"; var holderDid = formatDid(CALLBACK_PORT, "holder"); var thirdPartyDid = formatDid(CALLBACK_PORT, "thirdparty"); var baseCallbackUrl = "http://localhost:%s".formatted(CALLBACK_PORT); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0e6732406..79d1becaea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,8 +9,8 @@ awaitility = "4.3.0" aws = "2.41.10" azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.83" -dcp-tck = "1.0.0-SNAPSHOT" -dsp-tck = "1.0.0-RC4" +dcp-tck = "1.0.0-RC6" +dsp-tck = "1.0.0-RC6" flyway = "11.20.2" jackson = "2.21.0" jakarta-json = "2.1.3" From b2866355e9fb99ccf88b20b5c7d2befe643d4fb7 Mon Sep 17 00:00:00 2001 From: andrea bertagnolli Date: Wed, 21 Jan 2026 18:44:16 +0100 Subject: [PATCH 097/112] fix: clear lease table before applying lease migrations (#2531) --- .../main/resources/migrations/connector/V1_1_0__Lease_Fix.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_1_0__Lease_Fix.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_1_0__Lease_Fix.sql index 372f3f9f1b..cbf34c2071 100644 --- a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_1_0__Lease_Fix.sql +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_1_0__Lease_Fix.sql @@ -4,7 +4,7 @@ ALTER TABLE edc_data_plane_instance DROP CONSTRAINT data_plane_instance_lease_id ALTER TABLE edc_policy_monitor DROP CONSTRAINT policy_monitor_lease_lease_id_fk; ALTER TABLE edc_transfer_process DROP CONSTRAINT transfer_process_lease_lease_id_fk; -DELETE FROM edc_lease WHERE lease_id IS NULL; +DELETE FROM edc_lease; ALTER TABLE edc_lease ADD COLUMN resource_id varchar NOT NULL, From 2e0dc1b074524081ad20d00323204196ce6aa339 Mon Sep 17 00:00:00 2001 From: Moritz72 Date: Fri, 23 Jan 2026 13:56:19 +0100 Subject: [PATCH 098/112] Fix PULL transfer body processing (#2532) Co-authored-by: moritz eckert --- .../api/controller/ContainerRequestContextApiImpl.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/ContainerRequestContextApiImpl.java b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/ContainerRequestContextApiImpl.java index 975fa5e0e0..9b10eb6072 100644 --- a/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/ContainerRequestContextApiImpl.java +++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-public-api-v2/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/api/controller/ContainerRequestContextApiImpl.java @@ -24,9 +24,7 @@ import jakarta.ws.rs.core.MediaType; import org.eclipse.edc.spi.EdcException; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -63,8 +61,8 @@ public String queryParams() { @Override public String body() { - try (BufferedReader br = new BufferedReader(new InputStreamReader(context.getEntityStream()))) { - return br.lines().collect(Collectors.joining("\n")); + try { + return new String(context.getEntityStream().readAllBytes()); } catch (IOException e) { throw new EdcException("Failed to read request body: " + e.getMessage()); } From 55c3595c72a40d45b8b0d4a03c042da048176458 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:02:50 +0100 Subject: [PATCH 099/112] chore(deps): bump org.junit.platform:junit-platform-launcher (#2535) Bumps [org.junit.platform:junit-platform-launcher](https://github.com/junit-team/junit-framework) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.1...r6.0.2) --- updated-dependencies: - dependency-name: org.junit.platform:junit-platform-launcher dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 79d1becaea..e62d8297f0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ dsp-tck = "1.0.0-RC6" flyway = "11.20.2" jackson = "2.21.0" jakarta-json = "2.1.3" -junit = "6.0.1" +junit = "6.0.2" nimbus = "10.7" okhttp = "5.3.2" opentelemetry = "2.23.0" From 72952264267efbd9d00176e60e14284f3a78c70c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:03:18 +0100 Subject: [PATCH 100/112] chore(deps): bump actions/setup-java in /.github/actions/setup-java (#2540) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.1.0 to 5.2.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v5.1.0...v5.2.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: 5.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/setup-java/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-java/action.yml b/.github/actions/setup-java/action.yml index 00ad411c72..cf2d2a9f89 100644 --- a/.github/actions/setup-java/action.yml +++ b/.github/actions/setup-java/action.yml @@ -26,7 +26,7 @@ runs: using: "composite" steps: - name: Setup JDK 21 - uses: actions/setup-java@v5.1.0 + uses: actions/setup-java@v5.2.0 with: java-version: '21' distribution: 'temurin' From dca67e90e6a8d7052ade60f3a84dfdd5a3198621 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:03:50 +0100 Subject: [PATCH 101/112] chore(deps): bump gradle-wrapper from 9.2.1 to 9.3.0 (#2539) Bumps gradle-wrapper from 9.2.1 to 9.3.0. --- updated-dependencies: - dependency-name: gradle-wrapper dependency-version: 9.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 +---- gradlew.bat | 3 +-- 4 files changed, 3 insertions(+), 7 deletions(-) mode change 100644 => 100755 gradlew.bat diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 37988 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 xt3G?g z4&W%tBqXPbR`Dfgu>2G0TRV&4bOw6_Oz;EyI?i0{oODJ_4RFbbY2RF|&r|=*#sMY0 z^C#;!#u8O8tq#gKuO^NKL!6Empu_>FK3#2qJJ>Gg2Xel_lYOijF0X4d*)|59)BO|T zbURajp;K0G60wrdr5zug$aA=!F{BTlUpz*+?~5|qnzM^2-)J~`dY|bTblUUB2E0zZ zTVU9xOlwKgGs`C~SzmLe70MsFd5R%^#gG5Fe3Ew+Y@7c|KK8HV)<8aT1VdaD@53hs zK3VM$cX*5nGAbswuZ9B6S5JK1%{5s~{ABm!0TL{o--m-y_e#*5;Gks<%U4#kR=7EL zfdL<|duXWGB1Xo2BtemLawd&4`z5*w2gbEEKX_nZG}cRNIdHbz1`hQ=nDsa0xI&=n84=S@gGA2DHTxQF0jjouQnFsD*`p#;*Xg)EAP?cklFQb>(sA} zqNk`Su_t$9u&Lr6nlrh_xaFqd&PVrLKB?HbeLk9Nmy1i)h$6BN$+5&R?!ns!q3|_` zIU3m-`iV13Iwu;dUqV*uj0CvF&z5e_ryG5NqxHN$QUepAY&@Sf0-)FUqLKGPE?Tuybs0 zflp2+u!wi|uhII!bB}KtElo6FEaxecon!{PqX04^gX-k2woe$JhG1$>RKotzO>vVX zf~U5Pf+n937U@14ClFNVgE3AL>-48h;3<)wwUITF_%(1WH zt`@dMMY1U6RpwZhRTz1f@oPD?J~KglvhXauZl_U5!YyO@N!e(v>YEx#ue9$_$}Klc zs*8wd-HSapLJ!kDq;u0txcz@oOi2^~qFdeV`n?vl9v&LL=}oqoP8OqVAI@`b-wuJV z#+?@iA-7*ULLx$N1cjJ#h|QcqZoFJLn_I{uu?x*pMmvmx_kgMF0znV&_ztnBr@!8p zUC?2~#v)1@;PrS~$vt15qCoU8teD&L%Pq%N$EZFxAGBC8hc`FVXvTO(Jo_M1oy+eA z^_7k=J!_a^M?dmq@x)*>Kzt@6Y;3xou%6JGW&y8B> z%%tcqpf79fPUvikJUbYLNldGFu*~+sGnDd&gPW10*$Bj5mAH|82V>xVA~7jHbf_8) zkrU#%C=m-jZC@4f5pIxXQAfE2o*puTv=@LPB{+ngnBZA~wL)Sn@ezgnuokGZkd2Xp+j2&bAixl>UdhtWT=nGFR8N_Zz(q8aI!vtcZ;N^| z_PMlgO?9N@79w`#!HdJ_Bwwt`%JX-w>Wr?i5{#LOhtl6{jm^1C9Oaw(Ts4^QQJGmt z?>sWdDdptyT>&Adp*tjGx)@ko6sv(%+W1Jaia(KUfv12MZ&K9|@NE-I_&1qmt`**Am7|)DZk7<}0lEb(bp|OCIpv*7ETM(?Lz-@t>PU0~)D1BEIs_YYB&Z7Oh6Mb&2M1vC4_z5qw(S9VpM z(nw04WrEHJBmi=6!AM5Kz2u>-^?$<^=|x*Z=SbD^Quu;#a7yW-gd-TEj7IIvbGYQY zQ8Vby5K2EmJ!6U^gGiQ|bZ3v_9_J&t&?p*X@#E{>6R_(qR}F=yq_Ior>zBGeUfW8H~Sw?Z6`>8E+J9iP_v1=!S{< z7cL7;5UE+f;AR)G+fPuRb7yyAU!d8}<3ABR2{@VN{c~_q!3x1*kqkaMK6X6r?3z1<@E1h5xo+)q%j zQeUw}SQ(M@cv>Ykax~-it9ui1yu#%7$Qpo;opj*sy1)IXM>x}9{$ZmYTBW#%;qVqi zhZibvl2%4P>LkNfDwL&iLfuZ3WSr5XiNO嵍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 delta 35498 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 Date: Mon, 26 Jan 2026 11:04:12 +0100 Subject: [PATCH 102/112] chore(deps): bump aws from 2.41.10 to 2.41.14 (#2538) Bumps `aws` from 2.41.10 to 2.41.14. Updates `software.amazon.awssdk:s3` from 2.41.10 to 2.41.14 Updates `software.amazon.awssdk:s3-transfer-manager` from 2.41.10 to 2.41.14 --- updated-dependencies: - dependency-name: software.amazon.awssdk:s3 dependency-version: 2.41.14 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: software.amazon.awssdk:s3-transfer-manager dependency-version: 2.41.14 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e62d8297f0..9090c4393b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ edc = "0.15.1" edc-build = "1.1.5" allure = "2.32.0" awaitility = "4.3.0" -aws = "2.41.10" +aws = "2.41.14" azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-RC6" From 39fee62a1b405d4ea823098aea7838b3976aacd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:05:01 +0100 Subject: [PATCH 103/112] chore(deps): bump io.swagger.core.v3.swagger-gradle-plugin (#2536) Bumps io.swagger.core.v3.swagger-gradle-plugin from 2.2.41 to 2.2.42. --- updated-dependencies: - dependency-name: io.swagger.core.v3.swagger-gradle-plugin dependency-version: 2.2.42 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9090c4393b..616787ac59 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -243,5 +243,5 @@ edc-sts = [ [plugins] docker = { id = "com.bmuschko.docker-remote-api", version = "10.0.0" } shadow = { id = "com.gradleup.shadow", version = "9.3.1" } -swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.41" } +swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.42" } edc-build = { id = "org.eclipse.edc.edc-build", version.ref = "edc-build" } From 7f3eb636c55f0548eced9f2e3ba602e059601996 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:06:30 +0100 Subject: [PATCH 104/112] chore(deps): bump io.opentelemetry.javaagent:opentelemetry-javaagent (#2537) Bumps [io.opentelemetry.javaagent:opentelemetry-javaagent](https://github.com/open-telemetry/opentelemetry-java-instrumentation) from 2.23.0 to 2.24.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java-instrumentation/compare/v2.23.0...v2.24.0) --- updated-dependencies: - dependency-name: io.opentelemetry.javaagent:opentelemetry-javaagent dependency-version: 2.24.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 616787ac59..5a54c3fb01 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ jakarta-json = "2.1.3" junit = "6.0.2" nimbus = "10.7" okhttp = "5.3.2" -opentelemetry = "2.23.0" +opentelemetry = "2.24.0" opentelemetry-instrumentation = "2.23.0" opentelemetry-log4j-appender = "2.24.0-alpha" postgres = "42.7.8" From 5a85b9c71c74ff2a1dab4833f826871a96c20957 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:17:10 +0100 Subject: [PATCH 105/112] chore(deps): bump io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations (#2548) Bumps [io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations](https://github.com/open-telemetry/opentelemetry-java-instrumentation) from 2.23.0 to 2.24.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java-instrumentation/compare/v2.23.0...v2.24.0) --- updated-dependencies: - dependency-name: io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations dependency-version: 2.24.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5a54c3fb01..90a6a0912e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ junit = "6.0.2" nimbus = "10.7" okhttp = "5.3.2" opentelemetry = "2.24.0" -opentelemetry-instrumentation = "2.23.0" +opentelemetry-instrumentation = "2.24.0" opentelemetry-log4j-appender = "2.24.0-alpha" postgres = "42.7.8" restAssured = "6.0.0" From 183440e2949b56009cee0dcf883f5fffee6b1f00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:18:18 +0100 Subject: [PATCH 106/112] chore(deps): bump com.gradle.develocity from 4.3 to 4.3.2 (#2546) Bumps com.gradle.develocity from 4.3 to 4.3.2. --- updated-dependencies: - dependency-name: com.gradle.develocity dependency-version: 4.3.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 0cc4bdcf85..dba2687b29 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -145,7 +145,7 @@ include(":edc-dataplane:edc-dataplane-hashicorp-vault") include(":samples:testing-with-mocked-connector") plugins { - id("com.gradle.develocity") version "4.3" + id("com.gradle.develocity") version "4.3.2" id("com.gradle.common-custom-user-data-gradle-plugin") version "2.4.0" } From 93f7dee488a7d842a0cc46b7704e08fff9401959 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:19:04 +0100 Subject: [PATCH 107/112] chore(deps): bump org.postgresql:postgresql from 42.7.8 to 42.7.9 (#2545) Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.7.8 to 42.7.9. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.8...REL42.7.9) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-version: 42.7.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 90a6a0912e..86515b2b25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ okhttp = "5.3.2" opentelemetry = "2.24.0" opentelemetry-instrumentation = "2.24.0" opentelemetry-log4j-appender = "2.24.0-alpha" -postgres = "42.7.8" +postgres = "42.7.9" restAssured = "6.0.0" rsApi = "4.0.0" testcontainers = "2.0.3" From 09b1905a3d554b35ed9a0bf32d2b95899b208aad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:19:47 +0100 Subject: [PATCH 108/112] chore(deps): bump mikefarah/yq (#2550) Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.50.1 to 4.52.2. - [Release notes](https://github.com/mikefarah/yq/releases) - [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt) - [Commits](https://github.com/mikefarah/yq/compare/v4.50.1...v4.52.2) --- updated-dependencies: - dependency-name: mikefarah/yq dependency-version: 4.52.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/update-version-and-charts/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/update-version-and-charts/action.yml b/.github/actions/update-version-and-charts/action.yml index 05ef2ecb90..fecb4b1e17 100644 --- a/.github/actions/update-version-and-charts/action.yml +++ b/.github/actions/update-version-and-charts/action.yml @@ -42,7 +42,7 @@ runs: fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Bump version in /charts - uses: mikefarah/yq@v4.50.1 + uses: mikefarah/yq@v4.52.2 with: cmd: | find charts -name Chart.yaml -maxdepth 3 | xargs -n1 yq -i '.appVersion = "${{ steps.resolver.outputs.version }}" From d5416bfcc7cebf7c149749a0c025ffac05ef16a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:20:21 +0100 Subject: [PATCH 109/112] chore(deps): bump flyway from 11.20.2 to 12.0.0 (#2549) Bumps `flyway` from 11.20.2 to 12.0.0. Updates `org.flywaydb:flyway-core` from 11.20.2 to 12.0.0 - [Release notes](https://github.com/flyway/flyway/releases) - [Commits](https://github.com/flyway/flyway/compare/flyway-11.20.2...flyway-12.0.0) Updates `org.flywaydb:flyway-database-postgresql` from 11.20.2 to 12.0.0 --- updated-dependencies: - dependency-name: org.flywaydb:flyway-core dependency-version: 12.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: org.flywaydb:flyway-database-postgresql dependency-version: 12.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 86515b2b25..0f5f9bee25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ azure-storage-blob = "12.32.0" bouncyCastle-jdk18on = "1.83" dcp-tck = "1.0.0-RC6" dsp-tck = "1.0.0-RC6" -flyway = "11.20.2" +flyway = "12.0.0" jackson = "2.21.0" jakarta-json = "2.1.3" junit = "6.0.2" From 53eec902522829f435d24d899b09cd8b7e212c1b Mon Sep 17 00:00:00 2001 From: pratapipatelbcone Date: Tue, 3 Feb 2026 14:02:23 +0530 Subject: [PATCH 110/112] feat: Split Dataspace Protocol Extension (#2533) * feat: Split Dataspace Protocol Extension * feat: Merge dataspace-protocol-lib into dataspace-protocol-core * feat: Remove unused dependency from libs.version.toml * feat: nit change * feat: add copyright * feat: move related classes into identifier package * fix: Review comments: Remove DefaultParticipantIdentityResolver --- .../dataspace-protocol/build.gradle.kts | 23 +----- .../cx-dataspace-protocol/build.gradle.kts | 48 ++++++++++++ .../cx/CxDataspaceProtocolExtension.java} | 14 ++-- .../cx}/identifier/BpnExtractionFunction.java | 8 +- .../CatenaxParticipantIdentityResolver.java | 3 +- ...rg.eclipse.edc.spi.system.ServiceExtension | 3 +- .../cx/CxDataspaceProtocolExtensionTest.java} | 19 ++--- .../identifier/BpnExtractionFunctionTest.java | 78 +++++++++++++++++++ .../dataspace-protocol-core/build.gradle.kts | 48 ++++++++++++ .../core/CoreDataspaceProtocolExtension.java | 52 +++++++++++++ .../identifier/DidExtractionFunction.java | 7 +- ...bershipCredentialIdExtractionFunction.java | 7 +- ...rg.eclipse.edc.spi.system.ServiceExtension | 21 +++++ .../CoreDataspaceProtocolExtensionTest.java | 69 ++++++++++++++++ .../identifier/DidExtractionFunctionTest.java | 74 ++++++++++++++++++ ...hipCredentialIdExtractionFunctionTest.java | 25 +----- .../identifier/BpnExtractionFunctionTest.java | 41 ---------- .../identifier/DidExtractionFunctionTest.java | 41 ---------- settings.gradle.kts | 2 + 19 files changed, 426 insertions(+), 157 deletions(-) create mode 100644 edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts rename edc-extensions/dataspace-protocol/{src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java => cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtension.java} (79%) rename edc-extensions/dataspace-protocol/{src/main/java/org/eclipse/tractusx/edc/protocol => cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx}/identifier/BpnExtractionFunction.java (86%) rename edc-extensions/dataspace-protocol/{src/main/java/org/eclipse/tractusx/edc/protocol => cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx}/identifier/CatenaxParticipantIdentityResolver.java (96%) rename edc-extensions/dataspace-protocol/{ => cx-dataspace-protocol}/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension (90%) rename edc-extensions/dataspace-protocol/{src/test/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtensionTest.java => cx-dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtensionTest.java} (73%) create mode 100644 edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunctionTest.java create mode 100644 edc-extensions/dataspace-protocol/dataspace-protocol-core/build.gradle.kts create mode 100644 edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtension.java rename edc-extensions/dataspace-protocol/{src/main/java/org/eclipse/tractusx/edc/protocol => dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core}/identifier/DidExtractionFunction.java (89%) rename edc-extensions/dataspace-protocol/{src/main/java/org/eclipse/tractusx/edc/protocol => dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core}/identifier/MembershipCredentialIdExtractionFunction.java (94%) create mode 100644 edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/dataspace-protocol/dataspace-protocol-core/src/test/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtensionTest.java create mode 100644 edc-extensions/dataspace-protocol/dataspace-protocol-core/src/test/java/org/eclipse/tractusx/edc/protocol/core/identifier/DidExtractionFunctionTest.java rename edc-extensions/dataspace-protocol/{src/test/java/org/eclipse/tractusx/edc/protocol => dataspace-protocol-core/src/testFixtures/java/org/eclipse/tractusx/edc/protocol/core}/identifier/MembershipCredentialIdExtractionFunctionTest.java (79%) delete mode 100644 edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java delete mode 100644 edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java diff --git a/edc-extensions/dataspace-protocol/build.gradle.kts b/edc-extensions/dataspace-protocol/build.gradle.kts index 54234e86d4..e576de5c88 100644 --- a/edc-extensions/dataspace-protocol/build.gradle.kts +++ b/edc-extensions/dataspace-protocol/build.gradle.kts @@ -1,5 +1,6 @@ /* * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -23,24 +24,6 @@ plugins { } dependencies { - implementation(libs.edc.runtime.metamodel) - - implementation(libs.edc.spi.boot) - implementation(libs.edc.spi.core) - implementation(libs.edc.spi.decentralized.claims) - implementation(libs.edc.spi.participant.context.single) - implementation(libs.edc.ih.spi.credentials) - - implementation(libs.dsp.spi.http) - implementation(libs.dsp.spi.v08) - implementation(libs.dsp.spi.v2024) - implementation(libs.dsp.spi.v2025) - - implementation(libs.edc.spi.participant) - implementation(libs.edc.spi.protocol) - - implementation(project(":spi:core-spi")) - implementation(project(":core:core-utils")) - - testImplementation(libs.edc.junit) + api(project(":edc-extensions:dataspace-protocol:dataspace-protocol-core")) + api(project(":edc-extensions:dataspace-protocol:cx-dataspace-protocol")) } diff --git a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts new file mode 100644 index 0000000000..fa43ae1a51 --- /dev/null +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + `maven-publish` + `java-library` +} + +dependencies { + implementation(libs.edc.runtime.metamodel) + + implementation(libs.edc.spi.boot) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.decentralized.claims) + implementation(libs.edc.spi.participant.context.single) + implementation(libs.edc.ih.spi.credentials) + + implementation(libs.dsp.spi.http) + implementation(libs.dsp.spi.v08) + implementation(libs.dsp.spi.v2024) + + implementation(libs.edc.spi.participant) + implementation(libs.edc.spi.protocol) + + implementation(project(":spi:core-spi")) + implementation(project(":core:core-utils")) + implementation(project(":edc-extensions:dataspace-protocol:dataspace-protocol-core")) + + testImplementation(libs.edc.junit) + testImplementation(testFixtures(project(":edc-extensions:dataspace-protocol:dataspace-protocol-core"))) +} diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtension.java similarity index 79% rename from edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java rename to edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtension.java index 097b89238b..806b60275b 100644 --- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtension.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -17,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.protocol; +package org.eclipse.tractusx.edc.protocol.cx; import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; import org.eclipse.edc.participantcontext.spi.identity.ParticipantIdentityResolver; @@ -30,9 +31,8 @@ import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.tractusx.edc.protocol.identifier.BpnExtractionFunction; -import org.eclipse.tractusx.edc.protocol.identifier.CatenaxParticipantIdentityResolver; -import org.eclipse.tractusx.edc.protocol.identifier.DidExtractionFunction; +import org.eclipse.tractusx.edc.protocol.cx.identifier.BpnExtractionFunction; +import org.eclipse.tractusx.edc.protocol.cx.identifier.CatenaxParticipantIdentityResolver; import java.util.stream.Stream; @@ -41,11 +41,8 @@ import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2024Constants.DATASPACE_PROTOCOL_HTTP_V_2024_1; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2024Constants.V_2024_1; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2024Constants.V_2024_1_PATH; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DATASPACE_PROTOCOL_HTTP_V_2025_1; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1_PATH; -public class DataspaceProtocolExtension implements ServiceExtension { +public class CxDataspaceProtocolExtension implements ServiceExtension { @Setting(description = "the BPN of the participant", key = "tractusx.edc.participant.bpn") private String bpn; @@ -63,7 +60,6 @@ public class DataspaceProtocolExtension implements ServiceExtension { public void initialize(ServiceExtensionContext context) { Stream.of( new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP, V_08, () -> dspWebhookAddress.get(), new BpnExtractionFunction(monitor)), - new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2025_1, V_2025_1, () -> dspWebhookAddress.get() + V_2025_1_PATH, new DidExtractionFunction(monitor)), // currently required for DCP TCK tests new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2024_1, V_2024_1, () -> dspWebhookAddress.get() + V_2024_1_PATH, new BpnExtractionFunction(monitor)) ).forEach(contextRegistry::register); diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunction.java b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunction.java similarity index 86% rename from edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunction.java rename to edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunction.java index 0d74c277d8..3146bb4de6 100644 --- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunction.java +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunction.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -17,10 +18,11 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.protocol.identifier; +package org.eclipse.tractusx.edc.protocol.cx.identifier; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.tractusx.edc.protocol.core.identifier.MembershipCredentialIdExtractionFunction; import java.util.Map; import java.util.Optional; @@ -38,12 +40,12 @@ public BpnExtractionFunction(Monitor monitor) { } @Override - String identityProperty() { + public String identityProperty() { return IDENTITY_PROPERTY; } @Override - protected Optional getIdentifier(VerifiableCredential vc) { + public Optional getIdentifier(VerifiableCredential vc) { return vc.getCredentialSubject().stream() .flatMap(credentialSubject -> credentialSubject.getClaims().entrySet().stream()) .filter(entry -> entry.getKey().endsWith(IDENTITY_PROPERTY)) diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/CatenaxParticipantIdentityResolver.java b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/CatenaxParticipantIdentityResolver.java similarity index 96% rename from edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/CatenaxParticipantIdentityResolver.java rename to edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/CatenaxParticipantIdentityResolver.java index 4d2e31646a..c30b8a7574 100644 --- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/CatenaxParticipantIdentityResolver.java +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/CatenaxParticipantIdentityResolver.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2025 Think-it GmbH + * Copyright (c) 2026 SAP SE * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -17,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.protocol.identifier; +package org.eclipse.tractusx.edc.protocol.cx.identifier; import org.eclipse.edc.participantcontext.spi.identity.ParticipantIdentityResolver; import org.eclipse.edc.participantcontext.spi.service.ParticipantContextSupplier; diff --git a/edc-extensions/dataspace-protocol/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension similarity index 90% rename from edc-extensions/dataspace-protocol/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension rename to edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension index d67984c6eb..467d4207e5 100644 --- a/edc-extensions/dataspace-protocol/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -1,5 +1,6 @@ ################################################################################# # Copyright (c) 2025 Cofinity-X GmbH +# Copyright (c) 2026 SAP SE # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -17,4 +18,4 @@ # SPDX-License-Identifier: Apache-2.0 ################################################################################# -org.eclipse.tractusx.edc.protocol.DataspaceProtocolExtension \ No newline at end of file +org.eclipse.tractusx.edc.protocol.cx.CxDataspaceProtocolExtension diff --git a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtensionTest.java b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtensionTest.java similarity index 73% rename from edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtensionTest.java rename to edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtensionTest.java index bb638af0f1..5ad256f9d8 100644 --- a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtensionTest.java +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtensionTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -17,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.protocol; +package org.eclipse.tractusx.edc.protocol.cx; import org.eclipse.edc.boot.system.injection.ObjectFactory; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; @@ -25,8 +26,7 @@ import org.eclipse.edc.protocol.spi.DataspaceProfileContextRegistry; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.configuration.ConfigFactory; -import org.eclipse.tractusx.edc.protocol.identifier.BpnExtractionFunction; -import org.eclipse.tractusx.edc.protocol.identifier.DidExtractionFunction; +import org.eclipse.tractusx.edc.protocol.cx.identifier.BpnExtractionFunction; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -35,16 +35,13 @@ import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.V_08; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DATASPACE_PROTOCOL_HTTP_V_2025_1; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1_PATH; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(DependencyInjectionExtension.class) -class DataspaceProtocolExtensionTest { +class CxDataspaceProtocolExtensionTest { private final String webhook = "https://webhook"; private final String bpn = "bpn"; @@ -64,18 +61,12 @@ void setup(ServiceExtensionContext context) { void initialize_shouldRegisterProfileContexts(ObjectFactory factory, ServiceExtensionContext context) { when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of("tractusx.edc.participant.bpn", bpn))); - factory.constructInstance(DataspaceProtocolExtension.class).initialize(context); + factory.constructInstance(CxDataspaceProtocolExtension.class).initialize(context); verify(dataspaceProfileContextRegistry).register(argThat( dataspaceProfileContext -> dataspaceProfileContext.name().equals(DATASPACE_PROTOCOL_HTTP) && dataspaceProfileContext.protocolVersion().equals(V_08) && dataspaceProfileContext.webhook().url().equals(webhook) && dataspaceProfileContext.idExtractionFunction() instanceof BpnExtractionFunction)); - verify(dataspaceProfileContextRegistry).register(argThat( - dataspaceProfileContext -> dataspaceProfileContext.name().equals(DATASPACE_PROTOCOL_HTTP_V_2025_1) && - dataspaceProfileContext.protocolVersion().equals(V_2025_1) && - dataspaceProfileContext.webhook().url().equals(webhook + V_2025_1_PATH) && - dataspaceProfileContext.idExtractionFunction() instanceof DidExtractionFunction - )); } } diff --git a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunctionTest.java b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunctionTest.java new file mode 100644 index 0000000000..a32b58b252 --- /dev/null +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunctionTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.protocol.cx.identifier; + +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.tractusx.edc.protocol.core.identifier.MembershipCredentialIdExtractionFunction; +import org.eclipse.tractusx.edc.protocol.core.identifier.MembershipCredentialIdExtractionFunctionTest; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BpnExtractionFunctionTest extends MembershipCredentialIdExtractionFunctionTest { + + public static final String BPN = "bpn"; + public static final String ID_PROPERTY = "holderIdentifier"; + + private final Monitor monitor = mock(); + + @Override + protected MembershipCredentialIdExtractionFunction extractionFunction() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); + return new BpnExtractionFunction(monitor); + } + + @Override + protected String expectedId() { + return BPN; + } + + @ParameterizedTest + @ArgumentsSource(VerifiableCredentialArgumentProvider.class) + void apply(VerifiableCredential credential) { + var id = extractionFunction().apply(ClaimToken.Builder.newInstance().claim("vc", List.of(credential)).build()); + assertThat(id).isEqualTo(expectedId()); + } + + private static class VerifiableCredentialArgumentProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of(vc("MembershipCredential", Map.of("id", DID, ID_PROPERTY, BPN))), + Arguments.of(vc(CX_CREDENTIAL_NS + "MembershipCredential", Map.of("id", DID, ID_PROPERTY, BPN))), + Arguments.of(vc(CX_CREDENTIAL_NS + "MembershipCredential", Map.of("id", DID, CX_CREDENTIAL_NS + ID_PROPERTY, BPN)))); + } + } +} diff --git a/edc-extensions/dataspace-protocol/dataspace-protocol-core/build.gradle.kts b/edc-extensions/dataspace-protocol/dataspace-protocol-core/build.gradle.kts new file mode 100644 index 0000000000..cf7583a642 --- /dev/null +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/build.gradle.kts @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + `maven-publish` + `java-library` +} + +dependencies { + implementation(libs.edc.runtime.metamodel) + + implementation(libs.edc.spi.boot) + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.decentralized.claims) + implementation(libs.edc.spi.participant.context.single) + implementation(libs.edc.ih.spi.credentials) + + implementation(libs.dsp.spi.http) + implementation(libs.dsp.spi.v2025) + + implementation(libs.edc.spi.participant) + implementation(libs.edc.spi.protocol) + + implementation(project(":spi:core-spi")) + implementation(project(":core:core-utils")) + + testImplementation(libs.edc.junit) + testFixturesApi(libs.edc.ih.spi.credentials) + testFixturesApi(libs.edc.spi.protocol) + testFixturesApi(project(":spi:core-spi")) +} diff --git a/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtension.java b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtension.java new file mode 100644 index 0000000000..a980cd96de --- /dev/null +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtension.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.protocol.core; + +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.protocol.dsp.http.spi.api.DspBaseWebhookAddress; +import org.eclipse.edc.protocol.spi.DataspaceProfileContext; +import org.eclipse.edc.protocol.spi.DataspaceProfileContextRegistry; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.protocol.core.identifier.DidExtractionFunction; + +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DATASPACE_PROTOCOL_HTTP_V_2025_1; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1_PATH; + +public class CoreDataspaceProtocolExtension implements ServiceExtension { + + @Inject + private DataspaceProfileContextRegistry contextRegistry; + @Inject + private DspBaseWebhookAddress dspWebhookAddress; + @Inject + private SingleParticipantContextSupplier singleParticipantContextSupplier; + @Inject + private Monitor monitor; + + @Override + public void initialize(ServiceExtensionContext context) { + contextRegistry.register(new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2025_1, V_2025_1, () -> dspWebhookAddress.get() + V_2025_1_PATH, new DidExtractionFunction(monitor))); + } +} diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunction.java b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/identifier/DidExtractionFunction.java similarity index 89% rename from edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunction.java rename to edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/identifier/DidExtractionFunction.java index 2e466adb90..9ab86c5ce7 100644 --- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunction.java +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/identifier/DidExtractionFunction.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -17,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.protocol.identifier; +package org.eclipse.tractusx.edc.protocol.core.identifier; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; @@ -38,12 +39,12 @@ public DidExtractionFunction(Monitor monitor) { } @Override - String identityProperty() { + public String identityProperty() { return IDENTITY_PROPERTY; } @Override - protected Optional getIdentifier(VerifiableCredential vc) { + public Optional getIdentifier(VerifiableCredential vc) { return vc.getCredentialSubject().stream() .map(CredentialSubject::getId) .findFirst(); diff --git a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunction.java b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunction.java similarity index 94% rename from edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunction.java rename to edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunction.java index f44cd837c7..8321f1806b 100644 --- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunction.java +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunction.java @@ -1,6 +1,7 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -18,7 +19,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.protocol.identifier; +package org.eclipse.tractusx.edc.protocol.core.identifier; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.protocol.spi.ParticipantIdExtractionFunction; @@ -87,8 +88,8 @@ private Result> getCredentialList(ClaimToken claimTok return Result.success(vcList); } - abstract String identityProperty(); + public abstract String identityProperty(); - protected abstract Optional getIdentifier(VerifiableCredential vc); + public abstract Optional getIdentifier(VerifiableCredential vc); } diff --git a/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..585a79e366 --- /dev/null +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,21 @@ +################################################################################# +# Copyright (c) 2025 Cofinity-X GmbH +# Copyright (c) 2026 SAP SE +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# 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. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################# + +org.eclipse.tractusx.edc.protocol.core.CoreDataspaceProtocolExtension diff --git a/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/test/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtensionTest.java b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/test/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtensionTest.java new file mode 100644 index 0000000000..2540882798 --- /dev/null +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/test/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtensionTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.protocol.core; + +import org.eclipse.edc.boot.system.injection.ObjectFactory; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.protocol.dsp.http.spi.api.DspBaseWebhookAddress; +import org.eclipse.edc.protocol.spi.DataspaceProfileContextRegistry; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.protocol.core.identifier.DidExtractionFunction; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DATASPACE_PROTOCOL_HTTP_V_2025_1; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1_PATH; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +class CoreDataspaceProtocolExtensionTest { + + private final String webhook = "https://webhook"; + + private final DataspaceProfileContextRegistry dataspaceProfileContextRegistry = mock(); + private final DspBaseWebhookAddress dspBaseWebhookAddress = mock(); + + @BeforeEach + void setup(ServiceExtensionContext context) { + context.registerService(DataspaceProfileContextRegistry.class, dataspaceProfileContextRegistry); + context.registerService(DspBaseWebhookAddress.class, dspBaseWebhookAddress); + + when(dspBaseWebhookAddress.get()).thenReturn(webhook); + } + + @Test + void initialize_shouldRegisterProfileContexts(ObjectFactory factory, ServiceExtensionContext context) { + + factory.constructInstance(CoreDataspaceProtocolExtension.class).initialize(context); + + verify(dataspaceProfileContextRegistry).register(argThat( + dataspaceProfileContext -> dataspaceProfileContext.name().equals(DATASPACE_PROTOCOL_HTTP_V_2025_1) && + dataspaceProfileContext.protocolVersion().equals(V_2025_1) && + dataspaceProfileContext.webhook().url().equals(webhook + V_2025_1_PATH) && + dataspaceProfileContext.idExtractionFunction() instanceof DidExtractionFunction + )); + } +} diff --git a/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/test/java/org/eclipse/tractusx/edc/protocol/core/identifier/DidExtractionFunctionTest.java b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/test/java/org/eclipse/tractusx/edc/protocol/core/identifier/DidExtractionFunctionTest.java new file mode 100644 index 0000000000..c60e899c12 --- /dev/null +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/test/java/org/eclipse/tractusx/edc/protocol/core/identifier/DidExtractionFunctionTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.tractusx.edc.protocol.core.identifier; + +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.spi.iam.ClaimToken; +import org.eclipse.edc.spi.monitor.Monitor; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DidExtractionFunctionTest extends MembershipCredentialIdExtractionFunctionTest { + + public static final String ID_PROPERTY = "id"; + private final Monitor monitor = mock(); + + @Override + protected MembershipCredentialIdExtractionFunction extractionFunction() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); + return new DidExtractionFunction(monitor); + } + + @Override + protected String expectedId() { + return DID; + } + + @ParameterizedTest + @ArgumentsSource(VerifiableCredentialArgumentProvider.class) + void apply(VerifiableCredential credential) { + var id = extractionFunction().apply(ClaimToken.Builder.newInstance().claim("vc", List.of(credential)).build()); + assertThat(id).isEqualTo(expectedId()); + } + + private static class VerifiableCredentialArgumentProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + Arguments.of(vc("MembershipCredential", Map.of(ID_PROPERTY, DID))), + Arguments.of(vc(CX_CREDENTIAL_NS + "MembershipCredential", Map.of(ID_PROPERTY, DID))), + Arguments.of(vc(CX_CREDENTIAL_NS + "MembershipCredential", Map.of(ID_PROPERTY, DID, CX_CREDENTIAL_NS + ID_PROPERTY, DID)))); + } + } +} diff --git a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunctionTest.java b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/testFixtures/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunctionTest.java similarity index 79% rename from edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunctionTest.java rename to edc-extensions/dataspace-protocol/dataspace-protocol-core/src/testFixtures/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunctionTest.java index 592d201b5e..b81c641287 100644 --- a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/MembershipCredentialIdExtractionFunctionTest.java +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/testFixtures/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunctionTest.java @@ -1,6 +1,7 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 SAP SE * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -18,7 +19,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.protocol.identifier; +package org.eclipse.tractusx.edc.protocol.core.identifier; import org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject; import org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer; @@ -43,15 +44,7 @@ public abstract class MembershipCredentialIdExtractionFunctionTest { - protected static final String BPN = "bpn"; - protected static final String DID = "did:web:example"; - - @ParameterizedTest - @ArgumentsSource(VerifiableCredentialArgumentProvider.class) - void apply(VerifiableCredential credential) { - var id = extractionFunction().apply(ClaimToken.Builder.newInstance().claim("vc", List.of(credential)).build()); - assertThat(id).isEqualTo(expectedId()); - } + public static final String DID = "did:web:example"; @Test void apply_fails_WhenCredentialNotFound() { @@ -90,17 +83,7 @@ void apply_fails_WhenVcClaimsIsEmptyList() { .hasMessageContaining("Failed to fetch credentials from the claim token: ClaimToken contains a 'vc' claim but it did not contain any VerifiableCredentials."); } - private static class VerifiableCredentialArgumentProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of( - Arguments.of(vc("MembershipCredential", Map.of("id", DID, "holderIdentifier", BPN))), - Arguments.of(vc(CX_CREDENTIAL_NS + "MembershipCredential", Map.of("id", DID, "holderIdentifier", BPN))), - Arguments.of(vc(CX_CREDENTIAL_NS + "MembershipCredential", Map.of("id", DID, CX_CREDENTIAL_NS + "holderIdentifier", BPN)))); - } - } - - private static VerifiableCredential vc(String type, Map claims) { + public static VerifiableCredential vc(String type, Map claims) { return VerifiableCredential.Builder.newInstance().type(type) .issuanceDate(Instant.now()) .issuer(new Issuer("issuer", Map.of())) diff --git a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java b/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java deleted file mode 100644 index 28c351dd32..0000000000 --- a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2025 Cofinity-X GmbH - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * 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. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.protocol.identifier; - -import org.eclipse.edc.spi.monitor.Monitor; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class BpnExtractionFunctionTest extends MembershipCredentialIdExtractionFunctionTest { - private final Monitor monitor = mock(); - - @Override - protected MembershipCredentialIdExtractionFunction extractionFunction() { - when(monitor.withPrefix(anyString())).thenReturn(monitor); - return new BpnExtractionFunction(monitor); - } - - @Override - protected String expectedId() { - return BPN; - } -} diff --git a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java b/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java deleted file mode 100644 index 1ee09a2ab9..0000000000 --- a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2025 Cofinity-X GmbH - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * 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. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.eclipse.tractusx.edc.protocol.identifier; - -import org.eclipse.edc.spi.monitor.Monitor; - -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class DidExtractionFunctionTest extends MembershipCredentialIdExtractionFunctionTest { - private final Monitor monitor = mock(); - - @Override - protected MembershipCredentialIdExtractionFunction extractionFunction() { - when(monitor.withPrefix(anyString())).thenReturn(monitor); - return new DidExtractionFunction(monitor); - } - - @Override - protected String expectedId() { - return DID; - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index dba2687b29..92eba56924 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -74,6 +74,8 @@ include(":edc-extensions:validators:empty-asset-selector") include(":edc-extensions:log4j2-monitor") include("edc-extensions:connector-discovery:connector-discovery-api") include(":edc-extensions:dataspace-protocol") +include(":edc-extensions:dataspace-protocol:cx-dataspace-protocol") +include(":edc-extensions:dataspace-protocol:dataspace-protocol-core") include(":edc-extensions:did-document:did-document-service-self-registration") include(":edc-extensions:did-document:did-document-service-dim") From 95c69f81dbdc2104e3ca58717f88a03dfab8a758 Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Tue, 3 Feb 2026 14:24:23 +0100 Subject: [PATCH 111/112] Remove DSP version 2024/1 from Eclipse Tractus-X connector implementation (#2554) * Remove 2024/1 version from runtime Signed-off-by: Lars Geyer-Blaumeiser * Remove 2024-1 from repository Signed-off-by: Lars Geyer-Blaumeiser --------- Signed-off-by: Lars Geyer-Blaumeiser --- .../src/main/resources/document/dspace.jsonld | 62 ------------------- .../edc-controlplane-base/build.gradle.kts | 4 +- .../cx-dataspace-protocol/build.gradle.kts | 1 - .../cx/CxDataspaceProtocolExtension.java | 7 +-- .../CatenaxParticipantIdentityResolver.java | 3 +- ...hipCredentialIdExtractionFunctionTest.java | 8 --- gradle/libs.versions.toml | 1 - 7 files changed, 5 insertions(+), 81 deletions(-) delete mode 100644 core/json-ld-core/src/main/resources/document/dspace.jsonld diff --git a/core/json-ld-core/src/main/resources/document/dspace.jsonld b/core/json-ld-core/src/main/resources/document/dspace.jsonld deleted file mode 100644 index f26eaff028..0000000000 --- a/core/json-ld-core/src/main/resources/document/dspace.jsonld +++ /dev/null @@ -1,62 +0,0 @@ -{ - "@context": { - "odrl": "http://www.w3.org/ns/odrl/2/", - "xsd": "http://www.w3.org/2001/XMLSchema#", - "cred": "https://www.w3.org/2018/credentials#", - "sec": "https://w3id.org/security#", - "foaf": "http://xmlns.com/foaf/0.1/", - "cc": "http://creativecommons.org/ns#", - "dct": "http://purl.org/dc/terms/", - "dcat": "http://www.w3.org/ns/dcat#", - "dspace": "https://w3id.org/dspace/2024/1/", - - "dct:title": { "@language": "en" }, - "dct:creator": { "@type": "@id" }, - "dct:description": { "@container": "@set" }, - "dct:issued": { "@type": "xsd:dateTime" }, - "dct:modified": { "@type": "xsd:dateTime" }, - - "dcat:byteSize": { "@type": "xsd:decimal" }, - "dcat:distribution": { "@container": "@set" }, - "dcat:theme": { "@type": "@id" }, - "dcat:conformsTo": { "@type": "@id" }, - "dcat:dataset": { "@container": "@set" }, - "dcat:endpointURL": { "@type": "xsd:anyURI" }, - "dcat:endpointDescription": { "@type": "xsd:anyURI" }, - "dcat:keyword": { "@container": "@set" }, - "dcat:servesDataset": {"@container": "@set" }, - "dcat:service": { "@container": "@set" }, - "dcat:accessService": { "@container": "@set" }, - - "dspace:agreementId": { "@type": "@id" }, - "dspace:dataset": { "@type": "@id" }, - "dspace:transportType": { "@type": "@id" }, - "dspace:state": { "@type": "@id" }, - "dspace:providerId": { "@type": "@id" }, - "dspace:consumerId": { "@type": "@id" }, - "dspace:participantId": { "@type": "@id" }, - "dspace:reason": { "@container": "@set" }, - "dspace:catalog": { "@container": "@set" }, - "dspace:filter": { "@container": "@set" }, - "dspace:timestamp": { "@type": "xsd:dateTime" }, - "dspace:callbackAddress": { "@type": "xsd:anyURI" }, - "dspace:endpointProperties": { "@container": "@set" }, - - "foaf:homepage": { "@type": "xsd:anyURI" }, - - "odrl:hasPolicy": { "@container": "@set" }, - "odrl:permission": { "@container": "@set" }, - "odrl:prohibition": { "@container": "@set" }, - "odrl:obligation": { "@container": "@set" }, - "odrl:duty": { "@container": "@set" }, - "odrl:constraint": { "@container": "@set" }, - "odrl:action": { "@type": "@id" }, - "odrl:target": { "@type": "@id" }, - "odrl:leftOperand": { "@type": "@id" }, - "odrl:operator": { "@type": "@id" }, - "odrl:rightOperandReference": { "@type": "@id" }, - "odrl:profile": { "@container": "@set" }, - "odrl:assigner": { "@type": "@id" }, - "odrl:assignee": { "@type": "@id" } - } -} diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index 2f2b4339a7..3903d76173 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -32,7 +32,9 @@ configurations.all { } dependencies { - runtimeOnly(libs.edc.bom.controlplane.base) + runtimeOnly(libs.edc.bom.controlplane.base) { + exclude(module = "dsp-2024") + } runtimeOnly(libs.edc.bom.controlplane.dcp) runtimeOnly(libs.edc.bom.federatedcatalog.base) diff --git a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts index fa43ae1a51..c8e1e9d1fe 100644 --- a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts @@ -34,7 +34,6 @@ dependencies { implementation(libs.dsp.spi.http) implementation(libs.dsp.spi.v08) - implementation(libs.dsp.spi.v2024) implementation(libs.edc.spi.participant) implementation(libs.edc.spi.protocol) diff --git a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtension.java b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtension.java index 806b60275b..6226c6e7c1 100644 --- a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtension.java +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtension.java @@ -38,9 +38,6 @@ import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.V_08; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2024Constants.DATASPACE_PROTOCOL_HTTP_V_2024_1; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2024Constants.V_2024_1; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2024Constants.V_2024_1_PATH; public class CxDataspaceProtocolExtension implements ServiceExtension { @@ -59,9 +56,7 @@ public class CxDataspaceProtocolExtension implements ServiceExtension { @Override public void initialize(ServiceExtensionContext context) { Stream.of( - new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP, V_08, () -> dspWebhookAddress.get(), new BpnExtractionFunction(monitor)), - // currently required for DCP TCK tests - new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP_V_2024_1, V_2024_1, () -> dspWebhookAddress.get() + V_2024_1_PATH, new BpnExtractionFunction(monitor)) + new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP, V_08, () -> dspWebhookAddress.get(), new BpnExtractionFunction(monitor)) ).forEach(contextRegistry::register); } diff --git a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/CatenaxParticipantIdentityResolver.java b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/CatenaxParticipantIdentityResolver.java index c30b8a7574..583497e48e 100644 --- a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/CatenaxParticipantIdentityResolver.java +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/CatenaxParticipantIdentityResolver.java @@ -27,7 +27,6 @@ import org.jetbrains.annotations.Nullable; import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; -import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2024Constants.DATASPACE_PROTOCOL_HTTP_V_2024_1; public class CatenaxParticipantIdentityResolver implements ParticipantIdentityResolver { private final String bpn; @@ -41,7 +40,7 @@ public CatenaxParticipantIdentityResolver(String bpn, ParticipantContextSupplier @Nullable @Override public String getParticipantId(String participantContextId, String protocol) { - if (DATASPACE_PROTOCOL_HTTP.equals(protocol) || DATASPACE_PROTOCOL_HTTP_V_2024_1.equals(protocol)) { + if (DATASPACE_PROTOCOL_HTTP.equals(protocol)) { return bpn; } return participantContextSupplier.get().map(ParticipantContext::getIdentity) diff --git a/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/testFixtures/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunctionTest.java b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/testFixtures/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunctionTest.java index b81c641287..6433c0d3c2 100644 --- a/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/testFixtures/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunctionTest.java +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/testFixtures/java/org/eclipse/tractusx/edc/protocol/core/identifier/MembershipCredentialIdExtractionFunctionTest.java @@ -27,20 +27,12 @@ import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.iam.ClaimToken; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; import java.time.Instant; import java.util.List; import java.util.Map; -import java.util.stream.Stream; -import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS; public abstract class MembershipCredentialIdExtractionFunctionTest { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0f5f9bee25..eac199233a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -169,7 +169,6 @@ tck-extension = { module = "org.eclipse.edc:tck-extension", version.ref = "edc" # DSP libraries dsp-spi-http = { module = "org.eclipse.edc:dsp-http-spi", version.ref = "edc" } dsp-spi-v08 = { module = "org.eclipse.edc:dsp-spi-08", version.ref = "edc" } -dsp-spi-v2024 = { module = "org.eclipse.edc:dsp-spi-2024", version.ref = "edc" } dsp-spi-v2025 = { module = "org.eclipse.edc:dsp-spi-2025", version.ref = "edc" } # Federated Catalog modules From 5962e1580ebecbd2dd6f5ad658b897413a56212f Mon Sep 17 00:00:00 2001 From: Lars Geyer-Blaumeiser Date: Tue, 3 Feb 2026 16:28:25 +0100 Subject: [PATCH 112/112] Participant id migration (#2543) * Add migration to set participant context id Signed-off-by: Lars Geyer-Blaumeiser * Add new identifier requirement to helm charts Signed-off-by: Lars Geyer-Blaumeiser * Update helmdocs Signed-off-by: Lars Geyer-Blaumeiser * Fix issues in checkstyle and testing Signed-off-by: Lars Geyer-Blaumeiser * Add participant context id to e2e test participants Signed-off-by: Lars Geyer-Blaumeiser * Add participant context id to deployment test helm value file Signed-off-by: Lars Geyer-Blaumeiser * Fix participant context id for dsp tck test Signed-off-by: Lars Geyer-Blaumeiser * Add participant context id to dataplane as well Signed-off-by: Lars Geyer-Blaumeiser * Add newer TCK support methods for DSP TCK testing Signed-off-by: Lars Geyer-Blaumeiser --------- Signed-off-by: Lars Geyer-Blaumeiser --- charts/tractusx-connector-memory/README.md | 1 + .../templates/deployment-runtime.yaml | 2 ++ charts/tractusx-connector-memory/values.yaml | 2 ++ charts/tractusx-connector/README.md | 1 + .../templates/deployment-controlplane.yaml | 2 ++ .../templates/deployment-dataplane.yaml | 2 ++ charts/tractusx-connector/values.yaml | 2 ++ .../ConnectorPostgresqlMigration.java | 18 ++++++++++++++++++ .../V1_5_0_SetParticipantContextId.sql | 12 ++++++++++++ .../ConnectorPostgresqlMigrationTest.java | 4 +++- .../DatabaseMigrationConfiguration.java | 10 +++++++++- .../helm/tractusx-connector-memory-test.yaml | 1 + .../helm/tractusx-connector-test.yaml | 1 + .../participant/TractusxParticipantBase.java | 1 + .../tck/dsp/EdcCompatibilityPostgresTest.java | 1 + edc-tests/runtime/runtime-dsp/build.gradle.kts | 1 + gradle/libs.versions.toml | 3 ++- 17 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_5_0_SetParticipantContextId.sql diff --git a/charts/tractusx-connector-memory/README.md b/charts/tractusx-connector-memory/README.md index 5292a33db9..beb99d1858 100644 --- a/charts/tractusx-connector-memory/README.md +++ b/charts/tractusx-connector-memory/README.md @@ -71,6 +71,7 @@ helm install my-release tractusx-edc/tractusx-connector-memory --version 0.12.0- | log4j2.config | string | `"Appenders:\n Console:\n name: CONSOLE\n JsonTemplateLayout:\n eventTemplate: |-\n {\n \"timestamp\": {\n \"$resolver\": \"timestamp\",\n \"pattern\": {\n \"format\": \"yyyy-MM-dd'T'HH:mm:ss.SSSSSSS\",\n \"timeZone\": \"UTC\"\n }\n },\n \"level\": {\n \"$resolver\": \"level\",\n \"field\": \"severity\",\n \"severity\": {\n \"field\": \"keyword\"\n }\n },\n \"message\": {\n \"$resolver\": \"message\"\n }\n }\nLoggers:\n Root:\n level: \"OFF\"\n Logger:\n name: org.eclipse.edc.monitor.logger\n level: DEBUG\n AppenderRef:\n ref: CONSOLE"` | Log4j2 configuration for json log formatting. | | log4j2.enableJsonLogs | bool | `true` | Whether to enable the json log config in log4j2.config | | nameOverride | string | `""` | | +| participant.contextId | string | `"UUID CHANGEME"` | Participant Context Id - Newly introduced id for a connector instance (needed for multitenancy) | | participant.id | string | `"BPNLCHANGEME"` | BPN Number | | runtime.affinity | object | `{}` | [affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) to configure which nodes the pods can be scheduled on | | runtime.autoscaling.enabled | bool | `false` | Enables [horizontal pod autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) | diff --git a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml index 541977ab81..527df379f2 100644 --- a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml +++ b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml @@ -149,6 +149,8 @@ spec: value: {{ .Values.iatp.id | required ".Values.iatp.id is required" | quote }} - name: "EDC_IAM_ISSUER_ID" value: {{ .Values.iatp.id | required ".Values.iatp.id is required" | quote }} + - name: "EDC_PARTICIPANT_CONTEXT_ID" + value: {{ .Values.participant.contextId | required ".Values.participant.contextId is required" | quote }} - name: "TRACTUSX_EDC_PARTICIPANT_BPN" value: {{ .Values.participant.id | required ".Values.participant.id is required" | quote }} diff --git a/charts/tractusx-connector-memory/values.yaml b/charts/tractusx-connector-memory/values.yaml index 1ebe9e3afa..1d52cb346b 100644 --- a/charts/tractusx-connector-memory/values.yaml +++ b/charts/tractusx-connector-memory/values.yaml @@ -35,6 +35,8 @@ customLabels: {} participant: # -- BPN Number id: "BPNLCHANGEME" + # -- Participant Context Id - Newly introduced id for a connector instance (needed for multitenancy) + contextId: "UUID CHANGEME" iatp: # -- Decentralized IDentifier (DID) of the connector diff --git a/charts/tractusx-connector/README.md b/charts/tractusx-connector/README.md index 1dae24f1f3..c7b7b35068 100644 --- a/charts/tractusx-connector/README.md +++ b/charts/tractusx-connector/README.md @@ -288,6 +288,7 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.12.0-SNAPSHO | networkPolicy.dataplane | object | `{"from":[{"namespaceSelector":{}}]}` | Configuration of the dataplane component | | networkPolicy.dataplane.from | list | `[{"namespaceSelector":{}}]` | Specify from rule network policy for dp (defaults to all namespaces) | | networkPolicy.enabled | bool | `false` | If `true` network policy will be created to restrict access to control- and dataplane | +| participant.contextId | string | `"UUID CHANGEME"` | Participant Context Id - Newly introduced id for a connector instance (needed for multitenancy) | | participant.id | string | `"BPNLCHANGEME"` | BPN Number | | postgresql.auth.database | string | `"edc"` | | | postgresql.auth.password | string | `"password"` | | diff --git a/charts/tractusx-connector/templates/deployment-controlplane.yaml b/charts/tractusx-connector/templates/deployment-controlplane.yaml index 6ec20bc985..e2fe4a3c85 100644 --- a/charts/tractusx-connector/templates/deployment-controlplane.yaml +++ b/charts/tractusx-connector/templates/deployment-controlplane.yaml @@ -150,6 +150,8 @@ spec: value: {{ .Values.iatp.id | required ".Values.iatp.id is required" | quote }} - name: "EDC_IAM_ISSUER_ID" value: {{ .Values.iatp.id | required ".Values.iatp.id is required" | quote }} + - name: "EDC_PARTICIPANT_CONTEXT_ID" + value: {{ .Values.participant.contextId | required ".Values.participant.contextId is required" | quote }} - name: "TRACTUSX_EDC_PARTICIPANT_BPN" value: {{ .Values.participant.id | required ".Values.participant.id is required" | quote }} diff --git a/charts/tractusx-connector/templates/deployment-dataplane.yaml b/charts/tractusx-connector/templates/deployment-dataplane.yaml index 8af2fb3644..b784c17f51 100644 --- a/charts/tractusx-connector/templates/deployment-dataplane.yaml +++ b/charts/tractusx-connector/templates/deployment-dataplane.yaml @@ -146,6 +146,8 @@ spec: ######################## - name: EDC_PARTICIPANT_ID value: {{ .Values.participant.id | required ".Values.participant.id is required" | quote }} + - name: EDC_PARTICIPANT_CONTEXT_ID + value: {{ .Values.participant.contextId | required ".Values.participant.contextId is required" | quote}} - name: "EDC_IAM_ISSUER_ID" value: {{ .Values.iatp.id | required ".Values.iatp.id is required" | quote}} diff --git a/charts/tractusx-connector/values.yaml b/charts/tractusx-connector/values.yaml index b04b524256..886bab0b00 100644 --- a/charts/tractusx-connector/values.yaml +++ b/charts/tractusx-connector/values.yaml @@ -42,6 +42,8 @@ customLabels: {} participant: # -- BPN Number id: "BPNLCHANGEME" + # -- Participant Context Id - Newly introduced id for a connector instance (needed for multitenancy) + contextId: "UUID CHANGEME" iatp: # -- Decentralized IDentifier (DID) of the connector diff --git a/edc-extensions/migrations/connector-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigration.java b/edc-extensions/migrations/connector-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigration.java index 9da25d04ea..1936643ec5 100644 --- a/edc-extensions/migrations/connector-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigration.java +++ b/edc-extensions/migrations/connector-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigration.java @@ -22,12 +22,17 @@ import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.persistence.EdcPersistenceException; import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.tractusx.edc.postgresql.migration.DatabaseMigrationConfiguration; import org.flywaydb.core.Flyway; +import java.util.Map; +import java.util.UUID; + import static org.eclipse.tractusx.edc.postgresql.migration.connector.ConnectorPostgresqlMigration.NAME; import static org.flywaydb.core.api.MigrationVersion.LATEST; @@ -47,6 +52,18 @@ public String name() { return NAME; } + @Override + public void initialize(ServiceExtensionContext context) { + if (configuration.enabled() && configuration.participantContextId() == null) { + throw new EdcException("The participant context id has not been set, it is a mandatory setting now. You can " + + "use this UUID generated randomly for you: %s, or you can generate one by yourself. Please note that" + .formatted(UUID.randomUUID().toString()) + + " once set, it must never change. Depending on how you are configuring the Connector, set it on the " + + "`edc.participant.context.id` setting/system property or `EDC_PARTICIPANT_CONTEXT_ID` environment " + + "variable, then restart the Connector"); + } + } + @Override public void prepare() { if (!configuration.enabled()) { @@ -64,6 +81,7 @@ public void prepare() { .table("flyway_schema_history") .locations("classpath:migrations/connector") .defaultSchema(configuration.schema()) + .placeholders(Map.of("ParticipantContextId", configuration.participantContextId())) .target(LATEST) .load(); diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_5_0_SetParticipantContextId.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_5_0_SetParticipantContextId.sql new file mode 100644 index 0000000000..4046b2a771 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_5_0_SetParticipantContextId.sql @@ -0,0 +1,12 @@ +UPDATE edc_asset SET participant_context_id = '${ParticipantContextId}'; +UPDATE edc_contract_agreement SET agr_participant_context_id = '${ParticipantContextId}'; +UPDATE edc_contract_definitions SET participant_context_id = '${ParticipantContextId}'; +UPDATE edc_contract_negotiation SET participant_context_id = '${ParticipantContextId}'; +UPDATE edc_edr_entry SET participant_context_id = '${ParticipantContextId}'; +UPDATE edc_policy_monitor SET participant_context_id = '${ParticipantContextId}'; +UPDATE edc_policydefinitions SET participant_context_id = '${ParticipantContextId}'; +UPDATE edc_transfer_process SET participant_context_id = '${ParticipantContextId}'; +UPDATE edc_data_plane SET participant_context_id = '${ParticipantContextId}'; + +-- UPDATE edc_data_plane_instance SET data = data || '{"participantContextId": "${ParticipantContextId}"}'::json; +UPDATE edc_data_plane_instance SET data = (data::jsonb || '{"participantContextId": "${ParticipantContextId}"}'::jsonb)::json; \ No newline at end of file diff --git a/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java b/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java index d00e26529b..0f42c25ff7 100644 --- a/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java +++ b/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java @@ -51,6 +51,7 @@ import java.sql.SQLException; import java.util.List; import java.util.Map; +import java.util.UUID; import javax.sql.DataSource; import static org.assertj.core.api.Assertions.assertThat; @@ -71,7 +72,8 @@ void setUp(ServiceExtensionContext context) { when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of( "edc.datasource.default.url", postgresql.getJdbcUrl(), "edc.datasource.default.user", postgresql.getUsername(), - "edc.datasource.default.password", postgresql.getPassword() + "edc.datasource.default.password", postgresql.getPassword(), + "edc.participant.context.id", UUID.randomUUID().toString() ))); } diff --git a/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DatabaseMigrationConfiguration.java b/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DatabaseMigrationConfiguration.java index cd983f42d9..58a84d5db8 100644 --- a/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DatabaseMigrationConfiguration.java +++ b/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DatabaseMigrationConfiguration.java @@ -59,7 +59,15 @@ public record DatabaseMigrationConfiguration( key = "edc.datasource.default.password", description = "DataSource JDBC password" ) - String password + String password, + + @Setting( + key = "edc.participant.context.id", + required = false + ) + String participantContextId + + ) { private static final String DEFAULT_MIGRATION_ENABLED = "true"; private static final String DEFAULT_MIGRATION_SCHEMA = "public"; diff --git a/edc-tests/deployment/src/main/resources/helm/tractusx-connector-memory-test.yaml b/edc-tests/deployment/src/main/resources/helm/tractusx-connector-memory-test.yaml index 2953caf297..fc9689a263 100644 --- a/edc-tests/deployment/src/main/resources/helm/tractusx-connector-memory-test.yaml +++ b/edc-tests/deployment/src/main/resources/helm/tractusx-connector-memory-test.yaml @@ -23,6 +23,7 @@ fullnameOverride: tx-inmem participant: id: "test-participant" + contextId: "test-participant-context" iatp: # Decentralized IDentifier id: "did:web:changeme" diff --git a/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml b/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml index e6d5c472a4..582c801099 100644 --- a/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml +++ b/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml @@ -24,6 +24,7 @@ fullnameOverride: tx-prod ################################ participant: id: "test-participant" + contextId: "test-participant-context" iatp: # Decentralized IDentifier id: "did:web:changeme" diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxParticipantBase.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxParticipantBase.java index 58ea61539e..37a82a7fa6 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxParticipantBase.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxParticipantBase.java @@ -139,6 +139,7 @@ public Config getConfig() { put("edc.catalog.cache.execution.delay.seconds", "2"); put("edc.catalog.cache.execution.period.seconds", "2"); put("edc.policy.validation.enabled", "true"); + put("edc.participant.context.id", "general-test-id"); put("tractusx.edc.participant.bpn", getBpn()); } }; diff --git a/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java b/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java index c90e795b78..78db7c6c5d 100644 --- a/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java +++ b/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java @@ -93,6 +93,7 @@ private static Config runtimeConfiguration() { return ConfigFactory.fromMap(new HashMap<>() { { put("edc.participant.id", CONNECTOR_UNDER_TEST); + put("edc.participant.context.id", CONNECTOR_UNDER_TEST + "_context"); put("web.http.port", "8080"); put("web.http.path", "/api"); put("web.http.control.port", String.valueOf(CONTROL_URL.getPort())); diff --git a/edc-tests/runtime/runtime-dsp/build.gradle.kts b/edc-tests/runtime/runtime-dsp/build.gradle.kts index f5c326313d..f8304cabd0 100644 --- a/edc-tests/runtime/runtime-dsp/build.gradle.kts +++ b/edc-tests/runtime/runtime-dsp/build.gradle.kts @@ -26,6 +26,7 @@ plugins { dependencies { implementation(project(":edc-tests:runtime:runtime-postgresql")) runtimeOnly(libs.tck.extension) + runtimeOnly(libs.tck.lib) } application { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eac199233a..6b733b26f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -164,7 +164,8 @@ dsp-tck-catalog = { module = "org.eclipse.dataspacetck.dsp:dsp-catalog", version dsp-tck-contractnegotiation = { module = "org.eclipse.dataspacetck.dsp:dsp-contract-negotiation", version.ref = "dsp-tck" } dsp-tck-transferprocess = { module = "org.eclipse.dataspacetck.dsp:dsp-transfer-process", version.ref = "dsp-tck" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit" } -tck-extension = { module = "org.eclipse.edc:tck-extension", version.ref = "edc" } +tck-extension = { module = "org.eclipse.edc:tck-extension", version = "0.16.0-SNAPSHOT" } +tck-lib = { module = "org.eclipse.edc:tck-lib", version = "0.16.0-SNAPSHOT" } # DSP libraries dsp-spi-http = { module = "org.eclipse.edc:dsp-http-spi", version.ref = "edc" }