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 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 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 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 diff --git a/.github/actions/setup-java/action.yml b/.github/actions/setup-java/action.yml index 4af08fc008..cf2d2a9f89 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 - uses: actions/setup-java@v5.0.0 + - name: Setup JDK 21 + uses: actions/setup-java@v5.2.0 with: - java-version: '17' + java-version: '21' distribution: 'temurin' - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 diff --git a/.github/actions/update-version-and-charts/action.yml b/.github/actions/update-version-and-charts/action.yml index 429c4d77b3..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.48.2 + uses: mikefarah/yq@v4.52.2 with: cmd: | find charts -name Chart.yaml -maxdepth 3 | xargs -n1 yq -i '.appVersion = "${{ steps.resolver.outputs.version }}" 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..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 @@ -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..1b2260520a 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,16 +106,10 @@ 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 - - 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 3f6969a07e..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 @@ -33,17 +34,51 @@ jobs: check-dependencies: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - 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 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..c79bc03f17 100644 --- a/.github/workflows/kics.yml +++ b/.github/workflows/kics.yml @@ -41,10 +41,10 @@ jobs: security-events: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: KICS scan - uses: checkmarx/kics-github-action@v2.1.15 + uses: checkmarx/kics-github-action@v2.1.19 with: path: "." fail_on: high 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..c58f55582c 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: | @@ -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" @@ -132,7 +133,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..4129dac9e5 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,19 +40,24 @@ 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: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/setup-java - name: Generate openapi spec 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 @@ -62,9 +72,9 @@ 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 + - uses: actions/download-artifact@v7 with: name: openapi-spec path: resources/openapi/yaml @@ -101,13 +111,13 @@ 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 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: ${{ matrix.apiGroup.name }}-api path: dist @@ -118,7 +128,7 @@ jobs: permissions: contents: write steps: - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: path: openapi pattern: "*-api" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ada43ba912..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: @@ -41,7 +47,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 +114,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 +147,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: | @@ -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: @@ -188,7 +195,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 +230,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..aeafab0d7c 100644 --- a/.github/workflows/secrets-scan.yml +++ b/.github/workflows/secrets-scan.yml @@ -40,13 +40,13 @@ 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 - name: TruffleHog OSS id: trufflehog - uses: trufflesecurity/trufflehog@cb6aeefd6e2498240d0418e63f69684d28337e7b + uses: trufflesecurity/trufflehog@116e7171542d2f1dad8810f00dcfacbe0b809183 continue-on-error: true with: path: ./ # Scan the entire repository 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 }} 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..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 @@ -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..b3ad956da6 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 @@ -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" @@ -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 }}) @@ -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 @@ -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 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 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" diff --git a/build.gradle.kts b/build.gradle.kts index c32c2b0790..52b08ac1eb 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) } @@ -79,15 +76,9 @@ 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") } - 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.") - } } } @@ -98,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) @@ -140,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 { @@ -152,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) @@ -174,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 @@ -189,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,28 +207,20 @@ 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); - 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")) } @@ -258,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) { diff --git a/charts/tractusx-connector-memory/README.md b/charts/tractusx-connector-memory/README.md index 6a65ce5f32..beb99d1858 100644 --- a/charts/tractusx-connector-memory/README.md +++ b/charts/tractusx-connector-memory/README.md @@ -57,6 +57,10 @@ 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.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 | @@ -67,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 311cbbc9ec..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 }} @@ -256,6 +258,16 @@ spec: {{- end }} {{- end }} {{- end }} + - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED" + value: {{ .Values.iatp.didService.selfRegistration.enabled | quote}} + - name: "TX_EDC_DID_SERVICE_SELF_DEREGISTRATION_ENABLED" + value: "false" + - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ID" + 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" + 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..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 @@ -57,6 +59,19 @@ 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 + 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..c7b7b35068 100644 --- a/charts/tractusx-connector/README.md +++ b/charts/tractusx-connector/README.md @@ -267,6 +267,10 @@ 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.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 | @@ -284,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 e429eee886..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 }} @@ -254,6 +256,16 @@ spec: {{- end }} {{- end }} {{- end }} + - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED" + value: {{ .Values.iatp.didService.selfRegistration.enabled | quote}} + - 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 | quote }} + - 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/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 b9eee6adfc..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 @@ -64,6 +66,18 @@ 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 + enabled: true + # -- Validity of the Verifiable Presentation cache in seconds + validity: 86400 # -- Add custom ca certificates to the truststore customCaCerts: {} 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/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..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 @@ -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; } @@ -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/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/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/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 f475b23504..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/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..f97e8c2636 --- /dev/null +++ b/docs/development/decision-records/2025-11-27-did-service-registration/README.md @@ -0,0 +1,93 @@ +# 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. 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 is designed to ensure functional correctness while limiting outbound HTTP traffic on +startup. It shall behave acoording to the following diagram: + +```mermaid +flowchart TD + 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 like + +```java + +public interface DidDocumentServiceClient { + + 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`. + +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. 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. 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) 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: 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/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/) 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/" } 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": [ { diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index 866fea5179..3903d76173 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -27,12 +27,14 @@ 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 { - 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) @@ -50,6 +52,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")) @@ -59,7 +62,9 @@ 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")) + 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-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-postgresql-hashicorp-vault/build.gradle.kts index 2ee1dd6919..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,22 +32,23 @@ 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) - 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) } -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-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/build.gradle.kts b/edc-controlplane/edc-runtime-memory/build.gradle.kts index 06ba0752ed..53f6fb28e2 100644 --- a/edc-controlplane/edc-runtime-memory/build.gradle.kts +++ b/edc-controlplane/edc-runtime-memory/build.gradle.kts @@ -24,20 +24,23 @@ 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) } -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/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-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..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 @@ -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(Vault.class, new InMemoryVault(monitor, null)); + 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-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts index d3e8e7200c..c799f9cf1d 100644 --- a/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-hashicorp-vault/build.gradle.kts @@ -24,17 +24,15 @@ 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.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/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-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/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/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/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/build.gradle.kts b/edc-extensions/bdrs-client/build.gradle.kts index 2c88b482fa..a657e4dcb6 100644 --- a/edc-extensions/bdrs-client/build.gradle.kts +++ b/edc-extensions/bdrs-client/build.gradle.kts @@ -29,13 +29,14 @@ 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.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/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..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,8 +20,11 @@ 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; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; @@ -30,20 +33,22 @@ 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 - public Result resolve(RemoteMessage remoteMessage) { + public Result resolve(ProtocolRemoteMessage remoteMessage) { try { var counterPartyId = remoteMessage.getCounterPartyId(); if (counterPartyId.startsWith(DID_PREFIX)) { @@ -52,8 +57,11 @@ public Result resolve(RemoteMessage 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/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..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 @@ -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,16 +81,17 @@ 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; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.mapper = mapper; this.secureTokenService = secureTokenService; 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(); @@ -167,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) { @@ -185,11 +190,22 @@ 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 { + monitor.severe("Could not get participant context: " + result.getFailureDetail()); + 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()) { - 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 5d60c15b8e..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.types.domain.message.RemoteMessage; +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,13 +72,19 @@ 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"); } - 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 +99,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..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 @@ -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,12 @@ 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)); + 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", @@ -146,7 +153,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/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 f990d54d18..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 @@ -21,25 +21,27 @@ 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; -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.junit.jupiter.api.AfterEach; +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; 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 +50,19 @@ 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.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; @@ -61,9 +70,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 { @@ -72,20 +78,23 @@ 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; - 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))); + 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", @@ -93,20 +102,15 @@ 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())))); } - @AfterEach - void teardown() { - bdrsServer.stop(); - } - @Test void getData_whenCacheCold_shouldHitServer() { var did = client.resolveDid("bpn1"); @@ -151,19 +155,18 @@ 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); } @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"); - bdrsServer.verify(request(), never()); + bdrsServer.verify(0, getRequestedFor(anyUrl())); } @Test @@ -173,7 +176,7 @@ void getData_whenPresentationQueryFails() { assertThatThrownBy(() -> client.resolveDid("bpn1")) .isInstanceOf(EdcException.class) .hasMessage("test-failure"); - bdrsServer.verify(request(), never()); + bdrsServer.verify(0, getRequestedFor(anyUrl())); } @Test @@ -186,27 +189,23 @@ 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 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() { @@ -223,4 +222,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/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()); 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/build.gradle.kts b/edc-extensions/cx-policy/build.gradle.kts index e57f82b063..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) @@ -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-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/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/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-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/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(); + } } diff --git a/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiController.java b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiController.java index 911bfe0114..4d82dbc279 100644 --- a/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiController.java +++ b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiController.java @@ -41,7 +41,7 @@ public class DataFlowApiController implements DataFlowApi { private final DataFlowService service; public DataFlowApiController(Monitor monitor, DataFlowService service) { - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.service = service; } diff --git a/edc-extensions/dataplane/dataflow/dataflow-api/src/test/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiControllerTest.java b/edc-extensions/dataplane/dataflow/dataflow-api/src/test/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiControllerTest.java index 85ff17e022..7926222676 100644 --- a/edc-extensions/dataplane/dataflow/dataflow-api/src/test/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiControllerTest.java +++ b/edc-extensions/dataplane/dataflow/dataflow-api/src/test/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiControllerTest.java @@ -29,6 +29,7 @@ import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -41,6 +42,7 @@ class DataFlowApiControllerTest extends RestControllerTestBase { @Override protected Object controller() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); return new DataFlowApiController(monitor, service); } 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-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-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-proxy-http/src/main/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSourceFactory.java b/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/main/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSourceFactory.java index 1a4a47994b..9f54e88449 100644 --- a/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/main/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSourceFactory.java +++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/main/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSourceFactory.java @@ -47,7 +47,7 @@ public class ProxyHttpDataSourceFactory implements DataSourceFactory { public ProxyHttpDataSourceFactory(EdcHttpClient httpClient, HttpRequestParamsProvider requestParamsProvider, Monitor monitor, HttpRequestFactory requestFactory) { this.httpClient = httpClient; this.requestParamsProvider = requestParamsProvider; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.requestFactory = requestFactory; } diff --git a/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/test/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSourceFactoryTest.java b/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/test/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSourceFactoryTest.java index 09de8d6e1a..0106194524 100644 --- a/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/test/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSourceFactoryTest.java +++ b/edc-extensions/dataplane/dataplane-proxy/dataplane-proxy-http/src/test/java/org/eclipse/tractusx/edc/dataplane/http/pipeline/ProxyHttpDataSourceFactoryTest.java @@ -37,6 +37,7 @@ import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -51,6 +52,7 @@ class ProxyHttpDataSourceFactoryTest { @BeforeEach void setUp() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); factory = new ProxyHttpDataSourceFactory(httpClient, provider, monitor, requestFactory); } 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-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..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 @@ -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), @@ -114,15 +116,15 @@ public void initialize(ServiceExtensionContext context) { generatorService.addResponseGeneratorFunction(PROXY_HTTP_DATA_TYPE, () -> Endpoint.url(publicApiResponseUrl)); } - var publicApiController = new DataPlanePublicApiV2Controller(pipelineService, executorService, authorizationService); - webService.registerResource(ApiContext.PUBLIC, publicApiController); + var publicApiController = new DataPlanePublicApiV2Controller(context.getMonitor(), pipelineService, executorService, authorizationService); + 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-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()); } 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-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/TokenBasedAuthenticationService.java b/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/TokenBasedAuthenticationService.java index 14ecbbdb77..a8aeb31fef 100644 --- a/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/TokenBasedAuthenticationService.java +++ b/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/TokenBasedAuthenticationService.java @@ -41,7 +41,7 @@ public class TokenBasedAuthenticationService implements AuthenticationService { private final String hardCodedApiKey; //todo: have a list of API keys? public TokenBasedAuthenticationService(Monitor monitor, String hardCodedApiKey) { - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.hardCodedApiKey = hardCodedApiKey; monitor.warning(TEMPORARY_USE_WARNING); } diff --git a/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java b/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java index 4d6f3bd3be..efd850389f 100644 --- a/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java +++ b/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/asset/ConsumerAssetRequestController.java @@ -89,7 +89,7 @@ public ConsumerAssetRequestController(EdrService edrService, this.edrService = edrService; this.transferService = transferService; this.executorService = executorService; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); } @POST 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..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 @@ -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; @@ -120,8 +122,8 @@ 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_VERIFIER_PUBLIC_KEY_ALIAS), vault, typeManager.getMapper()); + () -> 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 57eb968181..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 @@ -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; @@ -88,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; @@ -103,23 +105,22 @@ public DataPlaneTokenRefreshServiceImpl(Clock clock, Supplier privateKeyIdSupplier, Monitor monitor, String refreshEndpoint, - String ownDid, int tokenExpiryToleranceSeconds, long tokenExpirySeconds, Supplier publicKeyIdSupplier, Vault vault, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + ParticipantContextSupplier participantContextSupplier) { this.tokenValidationService = tokenValidationService; this.publicKeyResolver = publicKeyResolver; this.localPublicKeyService = localPublicKeyService; this.accessTokenDataStore = accessTokenDataStore; this.tokenGenerationService = tokenGenerationService; this.privateKeyIdSupplier = privateKeyIdSupplier; - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); this.refreshEndpoint = refreshEndpoint; this.clock = clock; this.publicKeyIdSupplier = publicKeyIdSupplier; - this.ownDid = ownDid; this.vault = vault; this.objectMapper = objectMapper; this.tokenExpirySeconds = tokenExpirySeconds; @@ -128,6 +129,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), @@ -157,16 +159,29 @@ 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()) { + var msg = "Cannot retrieve ParticipantContext: " + participantContextServiceResult.getFailureDetail(); + monitor.severe(msg); + return Result.failure(msg); + } + 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()) { - 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(); @@ -180,20 +195,24 @@ 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)); + 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(); 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 @@ -205,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 @@ -223,15 +246,26 @@ 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()) { + var msg = "Cannot retrieve ParticipantContext: " + participantContextServiceResult.getFailureDetail(); + monitor.severe(msg); + return Result.failure(msg); + } + 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); 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)); @@ -244,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 @@ -254,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); }); } @@ -268,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) { @@ -289,7 +337,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 +361,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..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 @@ -38,12 +38,16 @@ 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.monitor.Monitor; 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; @@ -63,6 +67,7 @@ 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_ENDPOINT; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.EDR_PROPERTY_REFRESH_TOKEN; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -78,25 +83,24 @@ 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 Monitor monitor = mock(); + private final InMemoryVault vault = new InMemoryVault(mock(), null); + 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()); + when(monitor.withPrefix(anyString())).thenReturn(monitor); tokenRefreshService = new DataPlaneTokenRefreshServiceImpl(Clock.systemUTC(), new TokenValidationServiceImpl(), didPkResolverMock, @@ -104,16 +108,15 @@ void setup() throws JOSEException { tokenDataStore, new JwtGenerationService(new DefaultJwsSignerProvider(privateKeyResolver)), () -> privateKeyAlias, - mock(), + monitor, TEST_REFRESH_ENDPOINT, - PROVIDER_DID, 1, 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 +150,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..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 @@ -24,16 +24,21 @@ 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.monitor.Monitor; 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; 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; @@ -64,23 +69,31 @@ 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(); - - 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()); - + private final ParticipantContextSupplier participantContextSupplier = () -> ServiceResult.success( + ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build() + ); + + 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", 1, 300L, + () -> "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 +102,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 +111,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 +135,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 +150,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 +164,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 +192,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 +249,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 +260,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 +281,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 +293,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 +314,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 +330,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..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,23 +24,6 @@ plugins { } dependencies { - implementation(libs.edc.runtime.metamodel) - - implementation(libs.edc.spi.boot) - implementation(libs.edc.spi.core) - implementation(libs.edc.ih.spi.credentials) - implementation(libs.edc.spi.identitytrust) - - 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..c8e1e9d1fe --- /dev/null +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts @@ -0,0 +1,47 @@ +/* + * 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.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/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 new file mode 100644 index 0000000000..6226c6e7c1 --- /dev/null +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/CxDataspaceProtocolExtension.java @@ -0,0 +1,68 @@ +/* + * 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; + +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.monitor.Monitor; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.protocol.cx.identifier.BpnExtractionFunction; +import org.eclipse.tractusx.edc.protocol.cx.identifier.CatenaxParticipantIdentityResolver; + +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; + +public class CxDataspaceProtocolExtension implements ServiceExtension { + + @Setting(description = "the BPN of the participant", key = "tractusx.edc.participant.bpn") + private String bpn; + + @Inject + private DataspaceProfileContextRegistry contextRegistry; + @Inject + 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(monitor)) + ).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/BpnExtractionFunction.java b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/BpnExtractionFunction.java similarity index 80% 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 7178617b65..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,9 +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; @@ -31,14 +34,18 @@ public class BpnExtractionFunction extends MembershipCredentialIdExtractionFunction { private static final String IDENTITY_PROPERTY = "holderIdentifier"; - + + public BpnExtractionFunction(Monitor monitor) { + super(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/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 new file mode 100644 index 0000000000..583497e48e --- /dev/null +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/cx/identifier/CatenaxParticipantIdentityResolver.java @@ -0,0 +1,49 @@ +/* + * 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. + * + * 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.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; + +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)) { + 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/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 65% 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 fc07b1a5ab..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,23 +35,19 @@ 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"; - 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,22 +59,14 @@ 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); + 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.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)); } } 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/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtension.java similarity index 55% rename from edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java rename to edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtension.java index 3b65157fcf..a980cd96de 100644 --- a/edc-extensions/dataspace-protocol/src/main/java/org/eclipse/tractusx/edc/protocol/DataspaceProtocolExtension.java +++ b/edc-extensions/dataspace-protocol/dataspace-protocol-core/src/main/java/org/eclipse/tractusx/edc/protocol/core/CoreDataspaceProtocolExtension.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,43 +18,35 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.protocol; +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.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; -import org.eclipse.tractusx.edc.protocol.identifier.DidExtractionFunction; +import org.eclipse.tractusx.edc.protocol.core.identifier.DidExtractionFunction; -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; 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 { - - @Setting(description = "the BPN of the participant", key = "tractusx.edc.participant.bpn") - private String bpn; - +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_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())); + 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 82% 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 5d98b7ddee..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,10 +18,11 @@ * 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; +import org.eclipse.edc.spi.monitor.Monitor; import java.util.Optional; @@ -31,14 +33,18 @@ public class DidExtractionFunction extends MembershipCredentialIdExtractionFunction { private static final String IDENTITY_PROPERTY = "id"; - + + public DidExtractionFunction(Monitor monitor) { + super(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 67% 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 8d456f5dfc..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,12 +19,13 @@ * 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; 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 +43,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,20 +70,26 @@ 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); } - 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 71% 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..6433c0d3c2 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; @@ -26,32 +27,16 @@ 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 { - 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 +75,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/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..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 @@ -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; @@ -91,11 +91,11 @@ 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 - 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)); @@ -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 14a0e81679..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 @@ -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.monitor = monitor.withPrefix(getClass().getSimpleName()); + this.participantContextSupplier = participantContextSupplier; } @Override @@ -85,7 +89,14 @@ private boolean isExpired() { @NotNull private Result createRequest() { - var secret = vault.resolveSecret(configuration.clientSecretAlias()); + var participantContextServiceResult = participantContextSupplier.get(); + if (participantContextServiceResult.failed()) { + var msg = "Cannot retrieve Participant Context"; + monitor.severe(msg + ": " + participantContextServiceResult.getFailureDetail()); + return Result.failure(msg); + } + + var secret = vault.resolveSecret(participantContextServiceResult.getContent().getParticipantContextId(), configuration.clientSecretAlias()); if (secret != null) { var builder = SharedSecretOauth2CredentialsRequest.Builder.newInstance() .url(configuration.tokenUrl()) @@ -95,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/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..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 @@ -46,11 +46,12 @@ 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; 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); } @@ -96,7 +98,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 +132,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 +147,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 +162,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 +179,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 +195,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 +211,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 +226,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 +242,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 +263,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..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 @@ -19,13 +19,17 @@ 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.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -33,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; @@ -41,18 +46,23 @@ 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()); + + @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"); 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 +80,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 +106,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 +134,6 @@ void obtainRequestToken_withExpiration_whenExpired() throws InterruptedException assertThat(response).isNotNull().extracting(Result::getContent).isEqualTo(tokenRepresentation); verify(oauth2Client, times(2)).requestToken(any()); - } @Test @@ -134,7 +141,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 00e1098768..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) @@ -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/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 1870d21c99..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 @@ -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. @@ -23,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; @@ -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,23 +55,14 @@ 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" ); private final Monitor monitor; public CredentialScopeExtractor(Monitor monitor) { - this.monitor = monitor; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); } @Override @@ -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/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/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..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 @@ -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,18 +47,28 @@ 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.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; 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; @BeforeEach void setup() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); extractor = new CredentialScopeExtractor(monitor); } @@ -68,9 +79,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 +91,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 +125,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 +155,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-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..52e2650bcd --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImpl.java @@ -0,0 +1,195 @@ +/******************************************************************************** + * 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.withPrefix(getClass().getSimpleName()); + } + + @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()) { + 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())) { + 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())); + } + var msg = "VPs/VCs are expired or revoked. Removed from cache."; + monitor.debug(msg); + return StoreResult.notFound(msg); + } + + 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(); + + var types = allCreds.stream().map(VerifiableCredential::getType) + .flatMap(Collection::stream) + .distinct() + .toList(); + + 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(); + } + + /** + * 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 { + var msg = "Returned presentations contains invalid issuer. Expected %s found %s".formatted(expectedIssuer, issuers); + monitor.warning(msg); + return Result.failure(msg); + } + } + + 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..f032543af0 --- /dev/null +++ b/edc-extensions/dcp/verifiable-presentation-cache/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cache/VerifiablePresentationCacheImplTest.java @@ -0,0 +1,258 @@ +/******************************************************************************** + * 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.monitor.Monitor; +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.anyString; +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 Monitor monitor = mock(); + + private VerifiablePresentationCacheImpl cache; + + @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()); + when(monitor.withPrefix(anyString())).thenReturn(monitor); + + cache = new VerifiablePresentationCacheImpl(cacheValidity, clock, store, validationService, didResolver, + revocationServiceRegistry, monitor); + } + + @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-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-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..256a4a8264
--- /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,117 @@
+/********************************************************************************
+ * 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_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";
+    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_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;
+
+    @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 -> selfDeregistrationEnabled)
+                .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 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);
+        } 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..71da8f4031
--- /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,205 @@
+/********************************************************************************
+ * 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.deregistration.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.deregistration.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", "tx.edc.did.service.self.deregistration.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", "tx.edc.did.service.self.deregistration.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 / de-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.dregistration.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 / de-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.deregistration.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/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..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
@@ -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.monitor = monitor.withPrefix(getClass().getSimpleName());
         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..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);
+        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 ce7d196849..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);
+        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/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..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
@@ -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.monitor = monitor.withPrefix(getClass().getSimpleName());
+        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..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
@@ -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;
@@ -54,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;
@@ -80,7 +83,11 @@ 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/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/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/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..1936643ec5
--- /dev/null
+++ b/edc-extensions/migrations/connector-migration/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigration.java
@@ -0,0 +1,97 @@
+/*
+ * 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.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;
+
+@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 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()) {
+            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())
+                .placeholders(Map.of("ParticipantContextId", configuration.participantContextId()))
+                .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/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..cbf34c2071
--- /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;
+
+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/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
new file mode 100644
index 0000000000..0f42c25ff7
--- /dev/null
+++ b/edc-extensions/migrations/connector-migration/src/test/java/org/eclipse/tractusx/edc/postgresql/migration/connector/ConnectorPostgresqlMigrationTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.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;
+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 java.util.UUID;
+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(),
+                "edc.participant.context.id", UUID.randomUUID().toString()
+        )));
+    }
+
+    @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(DataPlanePostgresqlMigrationExtension.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/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);
 
 --
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/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..58a84d5db8
--- /dev/null
+++ b/edc-extensions/migrations/postgresql-migration-lib/src/main/java/org/eclipse/tractusx/edc/postgresql/migration/DatabaseMigrationConfiguration.java
@@ -0,0 +1,88 @@
+/*
+ * 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,
+
+        @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";
+
+    /**
+     * 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/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..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
@@ -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;
     }
 
@@ -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/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/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java b/edc-extensions/single-participant-vault/build.gradle.kts
similarity index 62%
rename from edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java
rename to edc-extensions/single-participant-vault/build.gradle.kts
index 2cd571f475..9f1bc3c9ad 100644
--- a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/DidExtractionFunctionTest.java
+++ b/edc-extensions/single-participant-vault/build.gradle.kts
@@ -1,5 +1,5 @@
-/*
- * Copyright (c) 2025 Cofinity-X GmbH
+/********************************************************************************
+ * Copyright (c) 2025 Think-it GmbH
  *
  * See the NOTICE file(s) distributed with this work for additional
  * information regarding copyright ownership.
@@ -15,18 +15,13 @@
  * under the License.
  *
  * SPDX-License-Identifier: Apache-2.0
- */
+ ********************************************************************************/
 
-package org.eclipse.tractusx.edc.protocol.identifier;
 
-public class DidExtractionFunctionTest extends MembershipCredentialIdExtractionFunctionTest {
-    @Override
-    protected MembershipCredentialIdExtractionFunction extractionFunction() {
-        return new DidExtractionFunction();
-    }
-    
-    @Override
-    protected String expectedId() {
-        return DID;
-    }
+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..7628faa43c
--- /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.withPrefix(getClass().getSimpleName());
+    }
+
+    @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..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
@@ -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;
@@ -38,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;
@@ -57,34 +60,39 @@ 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.monitor = monitor.withPrefix(getClass().getSimpleName());
         this.secureTokenService = secureTokenService;
         this.objectMapper = objectMapper;
+        this.participantContextSupplier = participantContextSupplier;
     }
 
     @Override
     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();
@@ -99,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(
@@ -119,14 +135,17 @@ 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 -> {
+                    var msg = "Could not execute token refresh: " + f.getFailureDetail();
+                    monitor.warning(msg);
+                    return ServiceResult.badRequest(msg);
+                })
+                .compose(this::executeRequest)
                 .map(tr -> createNewEdr(edr, tr));
     }
 
@@ -143,16 +162,18 @@ 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.");
+                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());
@@ -160,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-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..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
@@ -37,9 +37,13 @@
 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.monitor.Monitor;
 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;
@@ -80,7 +84,9 @@ 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;
     private ObjectMapper objectMapper;
 
@@ -97,15 +103,18 @@ 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);
+        when(monitor.withPrefix(anyString())).thenReturn(monitor);
+        tokenRefreshHandler = new TokenRefreshHandlerImpl(edrCache, mockedHttpClient, CONSUMER_DID, monitor,
+                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 +162,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 +175,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 +187,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 +197,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 +237,4 @@ public Stream provideArguments(ExtensionContext extensionCo
             );
         }
     }
-}
\ No newline at end of file
+}
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/build.gradle.kts b/edc-tests/e2e-fixtures/build.gradle.kts
index 099f6bf509..a3bac351c3 100644
--- a/edc-tests/e2e-fixtures/build.gradle.kts
+++ b/edc-tests/e2e-fixtures/build.gradle.kts
@@ -41,24 +41,26 @@ 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)
     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)
     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/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 8d5ebffc8d..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 org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.mockserver.model.HttpRequest;
 
-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(HttpRequest request) {
-        try {
-            return MAPPER.readValue(request.getBody().getRawBytes(), 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/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-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..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
@@ -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");
@@ -141,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-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..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
@@ -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.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;
-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/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..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
@@ -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;
+
+            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-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-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/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/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/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/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 23210036ed..bbd70097bc 100644
--- a/edc-tests/e2e/dcp-tck-tests/build.gradle.kts
+++ b/edc-tests/e2e/dcp-tck-tests/build.gradle.kts
@@ -22,18 +22,10 @@ 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)
-    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)
@@ -42,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..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
@@ -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;
@@ -34,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;
@@ -50,20 +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.AfterEach;
-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.mockserver.integration.ClientAndServer;
+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;
@@ -74,8 +81,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 {
@@ -89,42 +94,40 @@ 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));
-    private ClientAndServer didServer;
+    @RegisterExtension
+    protected static WireMockExtension didServer = WireMockExtension.newInstance()
+            .options(wireMockConfig().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() {
         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);
+        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,
@@ -140,12 +143,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 {
@@ -154,22 +158,19 @@ 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() {
-        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())
@@ -228,6 +229,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/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/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..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
@@ -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))));
@@ -94,10 +93,9 @@ 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.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 +138,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 +165,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 71b5efaa44..9adf646bd5 100644
--- a/edc-tests/e2e/edr-api-tests/build.gradle.kts
+++ b/edc-tests/e2e/edr-api-tests/build.gradle.kts
@@ -28,12 +28,13 @@ 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)
     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/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..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,6 +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;
@@ -44,17 +47,19 @@
 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.io.IOException;
 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;
@@ -64,11 +69,9 @@
 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;
-import static org.mockserver.model.HttpRequest.request;
 
 @EndToEndTest
 public class NegotiateEdrTest {
@@ -99,11 +102,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 +126,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 +150,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,7 +200,6 @@ 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
@@ -202,4 +207,11 @@ 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 3ff0cc3530..27901d0779 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:3.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.netty.mockserver)
+    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 b475f46d03..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.
@@ -20,11 +21,13 @@
 
 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;
 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;
@@ -36,20 +39,32 @@
 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;
 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;
+import java.util.List;
 import java.util.Map;
 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,10 +72,8 @@
 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;
 
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 public abstract class AbstractIatpConsumerPullTest extends ConsumerPullBaseTest {
 
     protected static final StsParticipant STS = StsParticipant.Builder.newInstance()
@@ -71,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";
 
@@ -106,7 +120,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,25 +132,23 @@ 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
 
     @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 = 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()
@@ -177,15 +189,16 @@ 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 = 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())
@@ -219,17 +232,22 @@ 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())
                 .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 +256,81 @@ void catalogRequest_whenCredentialRevoked() {
         } finally {
             // restore the original credential
             store.update(existingCred);
+            revocationServer.stop();
+        }
+    }
+
+    @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);
         }
     }
 
@@ -246,9 +339,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 bd983ef4f8..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
@@ -19,17 +19,18 @@
 
 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.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;
@@ -39,19 +40,20 @@
 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 +62,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 +97,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)
@@ -119,22 +122,12 @@ static void beforeAll() {
 
         BDRS_SERVER_EXTENSION.addMapping(CONSUMER.getBpn(), CONSUMER.getDid());
         BDRS_SERVER_EXTENSION.addMapping(PROVIDER.getBpn(), PROVIDER.getDid());
-    }
-
-    @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() {
@@ -146,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()));
 
@@ -172,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()));
 
@@ -202,25 +195,28 @@ 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();
 
         var transformerRegistry = MALICIOUS_ACTOR_RUNTIME.getService(TypeTransformerRegistry.class);
         var jsonLd = MALICIOUS_ACTOR_RUNTIME.getService(JsonLd.class);
 
+        JsonObject json =
+                response.apply(sokratesMembershipCredential)
+                        .compose(p -> 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..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
@@ -19,22 +19,26 @@
 
 package org.eclipse.tractusx.edc.tests.transfer;
 
-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 com.github.tomakehurst.wiremock.WireMockServer;
+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;
@@ -44,14 +48,18 @@
 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.List;
 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;
@@ -60,9 +68,9 @@
 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;
-import static org.mockserver.model.HttpRequest.request;
 
 @EndToEndTest
 public class DimConsumerPullTest extends AbstractIatpConsumerPullTest {
@@ -107,11 +115,12 @@ 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() {
+        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());
@@ -123,17 +132,19 @@ 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();
-        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));
     }
@@ -144,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
         );
     }
 
@@ -177,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 902825cd09..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
@@ -19,18 +19,20 @@
 
 package org.eclipse.tractusx.edc.tests.transfer.extension;
 
-import org.eclipse.edc.connector.controlplane.test.system.utils.LazySupplier;
+import com.github.tomakehurst.wiremock.WireMockServer;
 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;
 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..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
@@ -19,20 +19,22 @@
 
 package org.eclipse.tractusx.edc.tests.transfer.iatp.dispatchers;
 
-import org.eclipse.edc.iam.identitytrust.sts.service.EmbeddedSecureTokenService;
+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.decentralizedclaims.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;
 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;
@@ -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/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..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);
@@ -114,12 +125,19 @@ 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")
         );
     }
 
+    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() {
@@ -142,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();
 
@@ -168,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/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/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();
+    }
 }
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..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
@@ -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));
     }
 
@@ -123,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 providerTransferProcess = PROVIDER.getTransferProcesses().stream()
+                .filter(it -> it.asJsonObject().getString("correlationId").equals(transferProcessId)).findFirst().get();
 
-        var response = PROVIDER.retireProviderAgreement(agreementId);
-        response.statusCode(204);
+        await().untilAsserted(() -> {
+            var response = PROVIDER.retireProviderAgreement(providerTransferProcess.asJsonObject().getString("contractId"));
+            response.statusCode(204);
+        });
 
         var event = PROVIDER.waitForEvent("ContractAgreementRetired");
         assertThat(event).isNotNull();
@@ -150,10 +156,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 fa2400117f..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,11 +33,16 @@
 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 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 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;
@@ -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,27 +209,24 @@ 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);
 
-            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");
 
-            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());
+            CONSUMER.data().pullDataRequest(edr, Map.of()).statusCode(Status.INTERNAL_SERVER_ERROR.getStatusCode());
 
-            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,28 +279,25 @@ 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);
 
             // 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");
 
-            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,28 +318,25 @@ 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);
 
             // 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();
 
-            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,25 +356,22 @@ 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);
 
-            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");
 
-            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,16 +391,16 @@ 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);
 
-            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();
 
-            server.verify(requestDefinition, VerificationTimes.exactly(1));
+            server.verify(1, getRequestedFor(urlPathEqualTo(MOCK_BACKEND_PATH)));
         }
     }
 
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/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..ea3f4a7fb4 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:3.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/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"
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..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
@@ -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:3.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..edfe39c8a1 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.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"))
 
     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 797e0310da..ff423b513d 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)
  *
@@ -36,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")
@@ -59,8 +58,9 @@ edcBuild {
     publish.set(false)
 }
 
-tasks.withType {
+tasks.shadowJar {
     mergeServiceFiles()
+    duplicatesStrategy = DuplicatesStrategy.INCLUDE
     archiveFileName.set("${project.name}.jar")
 }
 
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/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/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) {
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..f8304cabd0 100644
--- a/edc-tests/runtime/runtime-dsp/build.gradle.kts
+++ b/edc-tests/runtime/runtime-dsp/build.gradle.kts
@@ -24,8 +24,9 @@ plugins {
 
 
 dependencies {
-    runtimeOnly(project(":edc-tests:runtime:runtime-postgresql"))
+    implementation(project(":edc-tests:runtime:runtime-postgresql"))
     runtimeOnly(libs.tck.extension)
+    runtimeOnly(libs.tck.lib)
 }
 
 application {
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 7357a73dd9..6b733b26f7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,32 +2,32 @@
 format.version = "1.1"
 
 [versions]
-edc = "0.14.1"
-edc-build = "1.1.2"
-allure = "2.30.0"
+edc = "0.15.1"
+edc-build = "1.1.5"
+allure = "2.32.0"
 awaitility = "4.3.0"
-aws = "2.38.7"
+aws = "2.41.14"
 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"
-jackson = "2.20.1"
-jakarta-json = "2.0.1"
-junit = "6.0.1"
-nimbus = "10.6"
-netty-mockserver = "5.15.0"
-okhttp = "5.3.1"
-opentelemetry = "2.21.0"
-opentelemetry-instrumentation = "2.21.0"
-opentelemetry-log4j-appender = "2.21.0-alpha"
-postgres = "42.7.8"
-restAssured = "5.5.6"
+bouncyCastle-jdk18on = "1.83"
+dcp-tck = "1.0.0-RC6"
+dsp-tck = "1.0.0-RC6"
+flyway = "12.0.0"
+jackson = "2.21.0"
+jakarta-json = "2.1.3"
+junit = "6.0.2"
+nimbus = "10.7"
+okhttp = "5.3.2"
+opentelemetry = "2.24.0"
+opentelemetry-instrumentation = "2.24.0"
+opentelemetry-log4j-appender = "2.24.0-alpha"
+postgres = "42.7.9"
+restAssured = "6.0.0"
 rsApi = "4.0.0"
-testcontainers = "1.21.3"
-testcontainers-keycloak = "3.9.0"
+testcontainers = "2.0.3"
+testcontainers-keycloak = "4.1.1"
 titanium = "1.7.0"
-log4j2 = "2.25.2"
+log4j2 = "2.25.3"
+wiremock = "3.13.2"
 
 
 [libraries]
@@ -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" }
@@ -93,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" }
@@ -111,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" }
@@ -130,17 +136,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" }
@@ -159,12 +164,12 @@ 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" }
 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
@@ -206,20 +211,20 @@ 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" }
 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" }
 
 log4j2-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j2" }
 log4j2-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j2" }
@@ -237,7 +242,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" }
-swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.40" }
+shadow = { id = "com.gradleup.shadow", version = "9.3.1" }
+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" }
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index a4b76b9530..61285a659d 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ca025c83a7..19a6bdeb84 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.3.0-bin.zip
 networkTimeout=10000
 validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index f5feea6d6b..adff685a03 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright © 2015-2021 the original authors.
+# Copyright © 2015 the original authors.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -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,6 @@ case "$( uname )" in                #(
   NONSTOP* )        nonstop=true ;;
 esac
 
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
 
 # Determine the Java command to use to start the JVM.
@@ -173,7 +171,6 @@ fi
 # For Cygwin or MSYS, switch paths to Windows format before running java
 if "$cygwin" || "$msys" ; then
     APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
-    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
 
     JAVACMD=$( cygpath --unix "$JAVACMD" )
 
@@ -206,15 +203,14 @@ 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.
 
 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
old mode 100644
new mode 100755
index 9b42019c79..e509b2dd8f
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -70,11 +70,10 @@ goto fail
 :execute
 @rem Setup the command line
 
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 
 
 @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%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
 
 :end
 @rem End local scope for the variables with windows NT shell
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
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)
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 27a0c60755..92eba56924 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -37,6 +37,8 @@ include(":spi:core-spi")
 include(":spi:tokenrefresh-spi")
 include(":spi:bdrs-client-spi")
 include(":spi:dataflow-spi")
+include(":spi:dcp-spi")
+include(":spi:did-document-service-spi")
 
 
 // core modules
@@ -51,6 +53,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")
@@ -65,11 +68,16 @@ 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")
 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")
 
 include(":edc-extensions:agreements")
 include(":edc-extensions:agreements:retirement-evaluation-core")
@@ -95,6 +103,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")
@@ -138,7 +147,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.2"
     id("com.gradle.common-custom-user-data-gradle-plugin") version "2.4.0"
 }
 
diff --git a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java b/spi/dcp-spi/build.gradle.kts
similarity index 65%
rename from edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java
rename to spi/dcp-spi/build.gradle.kts
index 73c021796f..2019d96338 100644
--- a/edc-extensions/dataspace-protocol/src/test/java/org/eclipse/tractusx/edc/protocol/identifier/BpnExtractionFunctionTest.java
+++ b/spi/dcp-spi/build.gradle.kts
@@ -1,4 +1,4 @@
-/*
+/********************************************************************************
  * Copyright (c) 2025 Cofinity-X GmbH
  *
  * See the NOTICE file(s) distributed with this work for additional
@@ -15,18 +15,14 @@
  * under the License.
  *
  * SPDX-License-Identifier: Apache-2.0
- */
+ ********************************************************************************/
 
-package org.eclipse.tractusx.edc.protocol.identifier;
+plugins {
+    `java-library`
+    `maven-publish`
+}
 
-public class BpnExtractionFunctionTest extends MembershipCredentialIdExtractionFunctionTest {
-    @Override
-    protected MembershipCredentialIdExtractionFunction extractionFunction() {
-        return new BpnExtractionFunction();
-    }
-    
-    @Override
-    protected String expectedId() {
-        return BPN;
-    }
+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);
+
+}
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); +}