diff --git a/.github/actions/generate-and-publish-allure-report/action.yml b/.github/actions/generate-and-publish-allure-report/action.yml index 78415514b6..df683b376f 100644 --- a/.github/actions/generate-and-publish-allure-report/action.yml +++ b/.github/actions/generate-and-publish-allure-report/action.yml @@ -35,23 +35,24 @@ runs: using: "composite" steps: - name: Download Allure results - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: ${{ inputs.allure_results }}-* merge-multiple: true path: allure-results - name: Build Allure test report - uses: simple-elf/allure-report-action@v1.13 - with: - gh_pages: gh-pages - allure_results: allure-results - allure_report: ${{ inputs.version }} + shell: bash + env: + VERSION: ${{ inputs.version }} + run: | + npm install -g allure-commandline + allure generate allure-results --clean -o "$VERSION" - name: Publish Allure test report to gh-pages - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ inputs.token }} publish_dir: ./${{ inputs.version }} destination_dir: allure-reports/${{ inputs.version }} - keep_files: false \ No newline at end of file + keep_files: false diff --git a/.github/actions/import-gpg-key/action.yml b/.github/actions/import-gpg-key/action.yml index c76e7024a2..c606262876 100644 --- a/.github/actions/import-gpg-key/action.yml +++ b/.github/actions/import-gpg-key/action.yml @@ -37,10 +37,12 @@ runs: - name: Import GPG Private Key shell: bash + env: + GPG_PRIVATE_KEY: ${{ inputs.gpg-private-key }} run: | echo "use-agent" >> ~/.gnupg/gpg.conf echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf - echo -e "${{ inputs.gpg-private-key }}" | gpg --import --batch + echo -e "$GPG_PRIVATE_KEY" | gpg --import --batch for fpr in $(gpg --list-keys --with-colons | awk -F: '/fpr:/ {print $10}' | sort -u); do echo -e "5\\ny\\n" | gpg --batch --command-fd 0 --expert --edit-key $fpr trust; diff --git a/.github/actions/publish-docker-image/action.yml b/.github/actions/publish-docker-image/action.yml index 3875f423c5..0ea86c26a1 100644 --- a/.github/actions/publish-docker-image/action.yml +++ b/.github/actions/publish-docker-image/action.yml @@ -46,25 +46,27 @@ inputs: runs: using: "composite" steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false ############################################### # Enable emulation for cross-arch builds ############################################### - name: Set up QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 ############################################### # Use Docker Buildx (required for multi-arch) ############################################### - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 ##################### # Login to DockerHub ##################### - name: DockerHub login - uses: docker/login-action@v4 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ inputs.docker_user }} password: ${{ inputs.docker_token }} @@ -75,8 +77,10 @@ runs: - uses: ./.github/actions/setup-java - name: Build Controlplane shell: bash + env: + ROOT_DIR: ${{ inputs.rootDir }} run: |- - ./gradlew -p ${{ inputs.rootDir }} shadowJar + ./gradlew -p "$ROOT_DIR" shadowJar ############################### # Set metadata of docker image @@ -84,7 +88,7 @@ runs: # Create SemVer or ref tags dependent of trigger event - name: Docker meta id: meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: | ${{ inputs.namespace }}/${{ inputs.imagename }} @@ -101,12 +105,15 @@ runs: # Build and push the image ############################### - name: Build and push - uses: docker/build-push-action@v7 + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + env: + ROOT_DIR: ${{ inputs.rootDir }} + IMAGE_NAME: ${{ inputs.imagename }} with: - context: ${{ inputs.rootDir }} - file: ${{ inputs.rootDir }}/build/resources/docker/Dockerfile + context: ${{ env.ROOT_DIR }} + file: ${{ env.ROOT_DIR }}/build/resources/docker/Dockerfile build-args: | - JAR=build/libs/${{ inputs.imagename }}.jar + JAR=build/libs/${{ env.IMAGE_NAME }}.jar OTEL_JAR=build/resources/otel/opentelemetry-javaagent.jar ADDITIONAL_FILES=build/legal/* push: true @@ -119,7 +126,7 @@ runs: # https://github.com/peter-evans/dockerhub-description ############################### - name: Update Docker Hub description - uses: peter-evans/dockerhub-description@v5 + uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0 with: readme-filepath: ${{ inputs.rootDir }}/notice.md username: ${{ inputs.docker_user }} diff --git a/.github/actions/publish-latest-versioned-snapshot/action.yml b/.github/actions/publish-latest-versioned-snapshot/action.yml index 17900cc53c..e22a82864d 100644 --- a/.github/actions/publish-latest-versioned-snapshot/action.yml +++ b/.github/actions/publish-latest-versioned-snapshot/action.yml @@ -29,12 +29,14 @@ runs: steps: - name: Write version to latest-versioned-snapshot file shell: bash + env: + VERSION: ${{ inputs.version }} run: | mkdir -p lvs - echo "${{ inputs.version }}" > lvs/latest-versioned-snapshot.txt + echo "$VERSION" > lvs/latest-versioned-snapshot.txt - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ env.GITHUB_TOKEN }} publish_dir: ./lvs diff --git a/.github/actions/publish-maven-artifacts/action.yml b/.github/actions/publish-maven-artifacts/action.yml new file mode 100644 index 0000000000..ed733cbdd6 --- /dev/null +++ b/.github/actions/publish-maven-artifacts/action.yml @@ -0,0 +1,53 @@ +################################################################################# +# Copyright (c) 2026 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 +################################################################################# + + +name: "Publish Maven Artifacts" +description: "Publish maven artifacts to central repository" + +inputs: + gpg-private-key: + description: the gpg private key used to publish + required: true + gpg-passphrase: + description: the gpg passphrase used to publish + required: true + username: + description: the username + required: true + password: + description: the password + required: true + +runs: + using: "composite" + steps: + + - uses: ./.github/actions/import-gpg-key + with: + gpg-private-key: ${{ inputs.gpg-private-key }} + + - name: Publish to maven central + shell: bash + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ inputs.username }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ inputs.password }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ inputs.gpg-private-key }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ inputs.gpg-passphrase }} + run: ./gradlew publishToMavenCentral -Psigning.gnupg.executable=gpg -Psigning.gnupg.passphrase="$ORG_GRADLE_PROJECT_signingInMemoryKeyPassword" diff --git a/.github/actions/run-deployment-test/action.yml b/.github/actions/run-deployment-test/action.yml index 264e9d3279..06ffacb820 100644 --- a/.github/actions/run-deployment-test/action.yml +++ b/.github/actions/run-deployment-test/action.yml @@ -44,36 +44,48 @@ inputs: k8sversion: required: false description: "Version of Kubernetes to use" - default: "v1.34.3" + default: "v1.36.1" runs: using: "composite" steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java - uses: ./.github/actions/setup-helm - uses: ./.github/actions/setup-kubectl - name: Create k8s Kind Cluster - uses: helm/kind-action@v1.14.0 + uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0 with: + # kind >= v0.32.0 is required for the current node images (containerd config v4); + # the kind-action default (v0.31.0) fails "kind load" with: + # "unknown containerd config version: 4 (supported versions: 2 and 3)". + # Drop this pin once helm/kind-action's default kind version is >= v0.32.0. + version: v0.32.0 node_image: kindest/node:${{ inputs.k8sversion }} - name: Build docker images shell: bash + env: + ROOT_DIR: ${{ inputs.rootDir }} run: |- - modules=(${{ inputs.rootDir }}) + read -ra modules <<< "$ROOT_DIR" for module in "${modules[@]}" do - ./gradlew -p $module dockerize + ./gradlew -p "$module" dockerize done - name: Load images into KinD shell: bash + env: + IMAGE_NAME: ${{ inputs.imagename }} + IMAGE_TAG: ${{ inputs.image_tag }} run: | - images=(${{ inputs.imagename }}) - versioned=( "${images[@]/%/:${{ inputs.image_tag }}}" ) + read -ra images <<< "$IMAGE_NAME" + versioned=( "${images[@]/%/:$IMAGE_TAG}" ) versioned_images=$(IFS=' ' ; echo "${versioned[*]}") kind get clusters | xargs -n1 kind load docker-image $versioned_images --name @@ -83,7 +95,9 @@ runs: - name: Install Runtime shell: bash - run: ${{ inputs.helm_command }} + env: + HELM_COMMAND: ${{ inputs.helm_command }} + run: eval "$HELM_COMMAND" - name: Print logs if: failure() diff --git a/.github/actions/setup-helm/action.yml b/.github/actions/setup-helm/action.yml index 02d868f9f4..a7fafe4b98 100644 --- a/.github/actions/setup-helm/action.yml +++ b/.github/actions/setup-helm/action.yml @@ -24,6 +24,6 @@ description: "Setup Helm" runs: using: "composite" steps: - - uses: azure/setup-helm@v4 + - uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: v3.16.1 diff --git a/.github/actions/setup-java/action.yml b/.github/actions/setup-java/action.yml index cf2d2a9f89..8cffbb18b5 100644 --- a/.github/actions/setup-java/action.yml +++ b/.github/actions/setup-java/action.yml @@ -26,9 +26,9 @@ runs: using: "composite" steps: - name: Setup JDK 21 - uses: actions/setup-java@v5.2.0 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 with: java-version: '21' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 diff --git a/.github/actions/setup-kubectl/action.yml b/.github/actions/setup-kubectl/action.yml index 369400d154..3163749f05 100644 --- a/.github/actions/setup-kubectl/action.yml +++ b/.github/actions/setup-kubectl/action.yml @@ -24,6 +24,6 @@ description: "Setup Kubectl" runs: using: "composite" steps: - - uses: azure/setup-kubectl@v4 + - uses: azure/setup-kubectl@829323503d1be3d00ca8346e5391ca0b07a9ab0d # v5.1.0 with: version: v1.31.1 diff --git a/.github/actions/update-version-and-charts/action.yml b/.github/actions/update-version-and-charts/action.yml index 2f9b0af1eb..bc1536a34f 100644 --- a/.github/actions/update-version-and-charts/action.yml +++ b/.github/actions/update-version-and-charts/action.yml @@ -33,36 +33,46 @@ runs: - name: Resolve version id: resolver shell: bash + env: + BUMP_VERSION: ${{ inputs.bump_version }} + INPUT_VERSION: ${{ inputs.version }} run: | - if [[ "${{ inputs.bump_version }}" == "true" ]] then - IFS=.- read -r MAJOR MINOR PATCH SNAPSHOT<<<"${{ inputs.version }}" + if [[ "$BUMP_VERSION" == "true" ]] then + IFS=.- read -r MAJOR MINOR PATCH SNAPSHOT<<<"$INPUT_VERSION" VERSION="$MAJOR.$((MINOR+1)).0-SNAPSHOT" else - VERSION=${{ inputs.version }} + VERSION="$INPUT_VERSION" fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Bump version in /charts - uses: mikefarah/yq@v4.52.4 + uses: mikefarah/yq@1b9b4ac5187171d2e5e3129be0cfa827c7f9d53d # v4.53.3 + env: + RESOLVED_VERSION: ${{ steps.resolver.outputs.version }} with: cmd: | - find charts -name Chart.yaml -maxdepth 3 | xargs -n1 yq -i '.appVersion = "${{ steps.resolver.outputs.version }}" - | .version = "${{ steps.resolver.outputs.version }}"' + find charts -name Chart.yaml -maxdepth 3 | xargs -n1 yq -i '.appVersion = env(RESOLVED_VERSION) | .version = env(RESOLVED_VERSION)' - name: Update Chart READMEs shell: bash run: | - docker run -v ${{ github.workspace }}/charts:/helm-docs jnorwood/helm-docs helm-docs + docker run -v "${GITHUB_WORKSPACE}/charts:/helm-docs" jnorwood/helm-docs helm-docs - name: Update version in gradle.properties shell: bash + env: + RESOLVED_VERSION: ${{ steps.resolver.outputs.version }} run: |- - sed -i 's/version=.*/version=${{ steps.resolver.outputs.version }}/g' gradle.properties + sed -i 's/version=.*/version='"$RESOLVED_VERSION"'/g' gradle.properties - name: Commit changes id: make-commit shell: bash + env: + BUMP_VERSION: ${{ inputs.bump_version }} + INPUT_VERSION: ${{ inputs.version }} + RESOLVED_VERSION: ${{ steps.resolver.outputs.version }} run: | - if [[ "${{ inputs.bump_version }}" == "true" ]] then - MESSAGE="Bump to version ${{ steps.resolver.outputs.version }}" + if [[ "$BUMP_VERSION" == "true" ]] then + MESSAGE="Bump to version $RESOLVED_VERSION" else - MESSAGE="Prepare release ${{ inputs.version }}" + MESSAGE="Prepare release $INPUT_VERSION" fi git add . diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0979491a74..c7efc523fa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,6 +16,7 @@ # under the License. # # SPDX-License-Identifier: Apache-2.0 +# Assisted-By: Claude-Opus-4.8 ################################################################################# @@ -34,9 +35,55 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 50 + cooldown: + default-days: 7 ignore: - - dependency-name: "org.eclipse.dataspacetck.dsp:*" - - dependency-name: "org.eclipse.dataspacetck.dcp:*" + - dependency-name: "org.eclipse.dataspacetck.*" + groups: + test-dependencies: + patterns: + - "org.junit*" + - "org.mockito*" + - "org.assertj*" + - "org.awaitility*" + - "org.testcontainers*" + - "com.github.dasniko:testcontainers-keycloak" + - "io.rest-assured*" + - "org.wiremock*" + - "io.qameta.allure*" + build-tooling: + patterns: + - "com.bmuschko*" + - "com.gradleup*" + - "io.swagger*" + - "com.diffplug.spotless*" + - "org.sonarqube*" + - "com.github.ben-manes*" + - "org.jreleaser*" + logging: + patterns: + - "org.apache.logging.log4j*" + - "org.slf4j*" + - "ch.qos.logback*" + - "io.opentelemetry*" + serialization-and-crypto: + patterns: + - "com.fasterxml.jackson*" + - "jakarta.json*" + - "jakarta.ws.rs*" + - "com.apicatalog*" + - "com.networknt*" + - "org.bouncycastle*" + - "com.nimbusds*" + cloud-sdks: + patterns: + - "software.amazon.awssdk*" + - "com.azure*" + database: + patterns: + - "org.postgresql*" + - "org.flywaydb*" + - "org.apache.kafka*" # Github Actions - @@ -51,6 +98,12 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 50 + cooldown: + default-days: 7 + groups: + github-actions-all: + patterns: + - "*" # Docker - package-ecosystem: "docker" @@ -65,3 +118,9 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 50 + cooldown: + default-days: 7 + groups: + docker-base-images: + patterns: + - "*" diff --git a/.github/poutine.yml b/.github/poutine.yml new file mode 100644 index 0000000000..550919627e --- /dev/null +++ b/.github/poutine.yml @@ -0,0 +1,29 @@ +################################################################################# +# Copyright (c) 2026 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 +################################################################################# + +skip: + - rule: github_action_from_unverified_creator_used + path: .github/workflows/trivy.yml + - rule: unpinnable_action + path: .github/workflows/trivy.yml + - rule: github_action_from_unverified_creator_used + path: .github/actions/generate-and-check-dependencies/action.yml + - rule: unpinnable_action + path: .github/actions/generate-and-check-dependencies/action.yml + diff --git a/.github/scripts/fix-poutine-sarif.py b/.github/scripts/fix-poutine-sarif.py new file mode 100644 index 0000000000..41f0ac1661 --- /dev/null +++ b/.github/scripts/fix-poutine-sarif.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +################################################################################# +# Copyright (c) 2026 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 +################################################################################# + +""" +Fix and patch the poutine SARIF output: + 1. Restore empty URI locations for composite action findings. + 2. Patch artifact lengths (-1 → real file size) so GitHub Advanced Security + can correctly map findings to source lines. +""" + +import json +import os +import re + +VERIFIED_OWNERS = { + "actions", "github", "docker", "gradle", "step-security", + "aws-actions", "google-github-actions", "azure", "hashicorp", + "slsa-framework", "sigstore", "ossf", +} + +with open("results.sarif") as f: + sarif = json.load(f) + +# ── Fix 1: Restore empty URI locations for composite action findings ────────── +# poutine bug: github_action_from_unverified_creator_used in composite action.yml +# files is emitted with empty artifactLocation.uri. Reconstruct from scanned files. +scanned_files = [] +for run in sarif.get("runs", []): + for artifact in run.get("artifacts", []): + uri = artifact.get("location", {}).get("uri", "") + if uri and os.path.isfile(uri): + scanned_files.append(uri) + +unverified_locations = [] +for filepath in scanned_files: + with open(filepath) as f: + for lineno, line in enumerate(f, 1): + m = re.search(r"uses:\s*([^\s#]+)", line) + if m: + ref = m.group(1) + owner = ref.split("/")[0] if "/" in ref else "" + if owner and owner not in VERIFIED_OWNERS and not ref.startswith("."): + unverified_locations.append((filepath, lineno, ref)) + +fixed_idx = 0 +for run in sarif.get("runs", []): + for result in run.get("results", []): + if result.get("ruleId") != "github_action_from_unverified_creator_used": + continue + uri = ( + result.get("locations", [{}])[0] + .get("physicalLocation", {}) + .get("artifactLocation", {}) + .get("uri", "") + ) + if uri: + continue + if fixed_idx < len(unverified_locations): + filepath, lineno, ref = unverified_locations[fixed_idx] + result["locations"] = [{ + "physicalLocation": { + "artifactLocation": {"uri": filepath}, + "region": {"startLine": lineno, "endLine": lineno}, + } + }] + print(f"Fixed location -> {filepath}:{lineno} ({ref})") + fixed_idx += 1 + +# Drop stale fingerprints after location/region rewrites so GitHub recalculates them. +for run in sarif.get("runs", []): + for result in run.get("results", []): + result.pop("fingerprints", None) + result.pop("partialFingerprints", None) + +# ── Fix 2: Patch artifact lengths (-1 → real size) ─────────────────────────── +# poutine emits length=-1 which causes GitHub Advanced Security to ignore +# startLine and show every finding at line 1 in the Security tab. +for run in sarif.get("runs", []): + for artifact in run.get("artifacts", []): + uri = artifact.get("location", {}).get("uri", "").lstrip("./") + if uri and os.path.isfile(uri): + artifact["length"] = os.path.getsize(uri) + +with open("results-fixed.sarif", "w") as f: + json.dump(sarif, f) + +print(f"Done. Fixed {fixed_idx} location(s). Written to results-fixed.sarif.") diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 8e7aa6d0fa..13fbf0e51e 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -54,12 +54,18 @@ jobs: # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file @@ -78,6 +84,6 @@ jobs: ./gradlew compileJava --no-daemon --no-build-cache - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v4 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: - category: "/language:${{matrix.language}}" \ No newline at end of file + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/copy-labels.yaml b/.github/workflows/copy-labels.yaml new file mode 100644 index 0000000000..129b01d445 --- /dev/null +++ b/.github/workflows/copy-labels.yaml @@ -0,0 +1,101 @@ +################################################################################# +# Copyright (c) 2026 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 +################################################################################# + + +--- +name: Copy labels from closing issue to PR + +on: + pull_request_target: # zizmor: ignore[dangerous-triggers] + types: [opened, edited] + +permissions: + issues: write + pull-requests: write + +jobs: + copy-labels: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - name: Copy labels from linked issue to PR + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const CLOSING_KEYWORDS = /\b(?:closes?|fixes?|resolves?)\s+#(\d+)\b/gi; + + // Only these labels are meaningful to copy to a PR + const ALLOWED_LABELS = new Set([ + "breaking change", "bug", "documentation", "enhancement", + "Feature", "infrastructure", "refactoring" + ]); + + const { owner, repo } = context.repo; + const prNumber = context.payload.pull_request.number; + + const { data: pr } = await github.rest.pulls.get({ + owner, + repo, + pull_number: prNumber, + }); + + const text = [pr.title || "", pr.body || ""].join("\n"); + + const issueNumbers = [...text.matchAll(CLOSING_KEYWORDS)].map(m => Number(m[1])); + + if (!issueNumbers.length) { + core.info("No closing-keyword issue references found."); + return; + } + + const prLabelsRes = await github.rest.issues.listLabelsOnIssue({ + owner, repo, issue_number: prNumber + }); + const existingPrLabels = new Set(prLabelsRes.data.map(l => l.name)); + + for (const issue_number of issueNumbers) { + let issue; + try { + issue = await github.rest.issues.get({ owner, repo, issue_number }); + } catch (error) { + core.warning(`Issue #${issue_number} not found or inaccessible: ${error.message}`); + continue; + } + + const labels = issue.data.labels + .map(label => typeof label === "string" ? label : label.name) + .filter(name => name && ALLOWED_LABELS.has(name) && !existingPrLabels.has(name)); + + if (!labels.length) { + core.info(`Issue #${issue_number} has no new applicable labels to copy.`); + continue; + } + + core.info(`Copying labels from issue #${issue_number} to PR #${prNumber}: ${labels.join(", ")}`); + + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: prNumber, + labels + }); + } diff --git a/.github/workflows/dependabot-on-demand.yaml b/.github/workflows/dependabot-on-demand.yaml new file mode 100644 index 0000000000..1d5acaa339 --- /dev/null +++ b/.github/workflows/dependabot-on-demand.yaml @@ -0,0 +1,58 @@ +################################################################################# +# Copyright (c) 2026 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 +################################################################################# + +--- +name: "Dependabot (on-demand)" +run-name: "Dependabot (gradle/docker ecosystem) on ${{ inputs.branch }}" + +on: + workflow_dispatch: + inputs: + branch: + description: "Branch to check (e.g. release/0.10.0)." + required: true + +permissions: + contents: read + +jobs: + run: + runs-on: ubuntu-latest + name: "Dependabot ${{ matrix.ecosystem }} (${{ matrix.directory }})" + strategy: + fail-fast: false + matrix: + include: + - ecosystem: gradle + directory: / + - ecosystem: docker + directory: resources + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LOCAL_GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ECOSYSTEM: ${{ matrix.ecosystem }} + DIRECTORY: ${{ matrix.directory }} + BRANCH: ${{ inputs.branch }} + REPO: ${{ github.repository }} + steps: + - name: Run Dependabot + run: | + VERSION="$(gh release view --repo dependabot/cli --json tagName -q .tagName)" + curl -sSfL "https://github.com/dependabot/cli/releases/download/${VERSION}/dependabot-${VERSION}-linux-amd64.tar.gz" | tar -xz dependabot + ./dependabot update "$ECOSYSTEM" "$REPO" --branch "$BRANCH" --directory "$DIRECTORY" diff --git a/.github/workflows/deployment-test.yaml b/.github/workflows/deployment-test.yaml index c737fc7a79..6def412538 100644 --- a/.github/workflows/deployment-test.yaml +++ b/.github/workflows/deployment-test.yaml @@ -27,13 +27,20 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: +permissions: + contents: read + jobs: test-prepare: runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - name: Cache ContainerD Image Layers - uses: actions/cache@v5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs key: ${{ runner.os }}-io.containerd.snapshotter.v1.overlayfs @@ -42,7 +49,13 @@ jobs: runs-on: ubuntu-latest needs: test-prepare steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/run-deployment-test name: "Run deployment test using KinD and Helm" with: @@ -66,12 +79,18 @@ jobs: fail-fast: false # this will verify that the official distribution of the Tractus-X EDC Helm chart runs on the last 3 Kubernetes versions matrix: - k8s-version: [ "v1.35.1", - "v1.34.3", - "v1.33.7" ] + k8s-version: [ "v1.36.1", + "v1.35.5", + "v1.34.8" ] steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - 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 1b2260520a..dd33278f89 100644 --- a/.github/workflows/draft-release.yaml +++ b/.github/workflows/draft-release.yaml @@ -31,24 +31,35 @@ on: description: 'The version you want to release.' required: true +permissions: + contents: read + jobs: validate-and-prepare: name: "Validate that tag does not already exist and prepare branch" runs-on: ubuntu-latest - if: ${{ github.ref_name == 'main' || github.ref_type == 'tag' }} + if: ${{ github.ref_name == 'main' || github.ref_type == 'tag' }} outputs: branch_name: ${{ steps.resolve_branch.outputs.branch_name }} is_official_release: ${{ steps.validation.outputs.is_official_release }} steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - id: validation name: "Validations" shell: bash + env: + INPUT_VERSION: ${{ inputs.version }} + REF_TYPE: ${{ github.ref_type }} run: | shopt -s nocasematch - IFS=.- read -r MAJOR_INPUT MINOR_INPUT PATCH_INPUT SNAPSHOT_INPUT<<<"${{ inputs.version }}" + IFS=.- read -r MAJOR_INPUT MINOR_INPUT PATCH_INPUT SNAPSHOT_INPUT<<<"$INPUT_VERSION" VERSION=$(grep "version" gradle.properties | awk -F= '{print $2}') IFS=.- read -r MAJOR MINOR PATCH SNAPSHOT<<<"$VERSION" @@ -62,14 +73,14 @@ jobs: # Bugfix elif [[ -z $SNAPSHOT_INPUT && $PATCH_INPUT != '0' ]] then - if [[ ${{ github.ref_type }} != 'tag' || ! -z $SNAPSHOT ]] then + if [[ $REF_TYPE != 'tag' || ! -z $SNAPSHOT ]] then echo "You want to build a bugfix, but the selected commit is neither a bugfix nor an official release" exit 1 fi # Official Release elif [[ -z $SNAPSHOT_INPUT && $PATCH_INPUT == '0' ]] then - if [[ ${{ github.ref_type }} != 'tag' || $SNAPSHOT != *rc* ]] then + if [[ $REF_TYPE != 'tag' || $SNAPSHOT != *rc* ]] then echo "You want to build an official release, but the selected commit is not a release candidate" exit 1 fi @@ -83,19 +94,22 @@ jobs: - id: check-tag name: "Check if tag exists" + env: + INPUT_VERSION: ${{ inputs.version }} run: |- - - tag=$(git tag -l ${{ inputs.version }}) - + tag=$(git tag -l "$INPUT_VERSION") + if [ ! -z $tag ]; then - echo "Tag ${{ inputs.version }} already exists! Please choose another tag." + echo "Tag $INPUT_VERSION already exists! Please choose another tag." exit 1 fi - id: resolve_branch name: "Resolve branch name" + env: + INPUT_VERSION: ${{ inputs.version }} run: | - echo "branch_name=release/${{ inputs.version }}" >> "$GITHUB_OUTPUT" + echo "branch_name=release/$INPUT_VERSION" >> "$GITHUB_OUTPUT" draft-new-release: name: "Draft a new release" @@ -106,9 +120,14 @@ jobs: packages: write pages: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: true - name: Create Release branch - run: git checkout -b ${{ needs.validate-and-prepare.outputs.branch_name }} + env: + BRANCH_NAME: ${{ needs.validate-and-prepare.outputs.branch_name }} + run: | + git checkout -b "$BRANCH_NAME" - uses: ./.github/actions/setup-java - name: Version and Chart Updates uses: ./.github/actions/update-version-and-charts @@ -116,7 +135,9 @@ jobs: version: ${{ inputs.version }} bump_version: "false" - name: Push new branch - run: git push origin ${{ needs.validate-and-prepare.outputs.branch_name }} + env: + BRANCH_NAME: ${{ needs.validate-and-prepare.outputs.branch_name }} + run: git push origin "$BRANCH_NAME" - name: Check dependencies before release uses: ./.github/actions/generate-and-check-dependencies with: @@ -125,6 +146,8 @@ jobs: run: sed -i "s#\[DEPENDENCIES\]\(.*\)#\[DEPENDENCIES\]\(DEPENDENCIES\)#g" NOTICE.md - name: Commit DEPENDENCIES changes shell: bash + env: + BRANCH_NAME: ${{ needs.validate-and-prepare.outputs.branch_name }} run: | if git diff --quiet -- DEPENDENCIES; then echo "No changes in DEPENDENCIES, skipping commit." @@ -135,5 +158,5 @@ jobs: 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 }} + git push origin "$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 907dcee055..b7d16aac5d 100644 --- a/.github/workflows/generate-and-publish-dependencies.yaml +++ b/.github/workflows/generate-and-publish-dependencies.yaml @@ -34,7 +34,13 @@ jobs: check-dependencies: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: true - uses: ./.github/actions/setup-java - name: Output release type id: release_type @@ -59,7 +65,7 @@ jobs: cp DEPENDENCIES public/ - name: Publish to GitHub Pages if: ${{ github.ref_name == 'main' }} - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: public @@ -70,6 +76,8 @@ jobs: - name: Commit DEPENDENCIES changes if: ${{ startsWith(github.ref_name, 'release/') }} shell: bash + env: + REF_NAME: ${{ github.ref_name }} run: | if git diff --quiet -- DEPENDENCIES; then echo "No changes in DEPENDENCIES, skipping commit." @@ -80,5 +88,5 @@ jobs: 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 }} + git push origin "$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 7cef5f3386..c6f3b45023 100644 --- a/.github/workflows/helm-lint.yaml +++ b/.github/workflows/helm-lint.yaml @@ -38,23 +38,31 @@ on: - '**' - '!charts/**' +permissions: + contents: read + jobs: helm-lint: runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit ############## ### Set-Up ### ############## - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - uses: ./.github/actions/setup-helm - name: python (setup) - uses: actions/setup-python@v6 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: 3.13 - name: chart-testing (setup) - uses: helm/chart-testing-action@v2.8.0 + uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0 ##################### ### Chart Testing ### ##################### diff --git a/.github/workflows/kics.yml b/.github/workflows/kics.yml index 3a02b17da9..c177ef9d85 100644 --- a/.github/workflows/kics.yml +++ b/.github/workflows/kics.yml @@ -31,6 +31,9 @@ on: schedule: - cron: "0 0 * * *" +permissions: + contents: read + jobs: analyze: name: Analyze @@ -41,10 +44,16 @@ jobs: security-events: write steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: KICS scan - uses: checkmarx/kics-github-action@v2.1.20 + uses: checkmarx/kics-github-action@05aa5eb70eede1355220f4ca5238d96b397e30a6 # v2.1.20 with: path: "." fail_on: high @@ -55,6 +64,6 @@ jobs: - name: Upload SARIF file for GitHub Advanced Security Dashboard if: always() - uses: github/codeql-action/upload-sarif@v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: kicsResults/results.sarif diff --git a/.github/workflows/publish-context.yaml b/.github/workflows/publish-context.yaml index e8f84038f4..220073e07d 100644 --- a/.github/workflows/publish-context.yaml +++ b/.github/workflows/publish-context.yaml @@ -27,6 +27,9 @@ on: paths: - 'core/json-ld-core/src/main/resources/document/**' +permissions: + contents: write + jobs: build: runs-on: ubuntu-latest @@ -34,15 +37,21 @@ jobs: contents: write pages: write steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: copy contexts into public folder run: | mkdir -p public/context cp core/json-ld-core/src/main/resources/document/tx-auth-v1.jsonld public/context/ - name: deploy to gh-pages - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./public - keep_files: true \ No newline at end of file + keep_files: true diff --git a/.github/workflows/publish-new-snapshot.yaml b/.github/workflows/publish-new-snapshot.yaml index c58f55582c..7a596f192d 100644 --- a/.github/workflows/publish-new-snapshot.yaml +++ b/.github/workflows/publish-new-snapshot.yaml @@ -26,13 +26,30 @@ name: "Publish new snapshot" run-name: "Publish new snapshot from ${{github.ref_name}}" on: - workflow_run: - workflows: [ "Run-All-Tests" ] - branches: - - main - - release/* - types: - - completed + workflow_call: + inputs: + dated_snapshot: + type: boolean + required: false + default: false + description: 'Dated snapshot version.' + secrets: + DOCKER_HUB_USER: + required: false + DOCKER_HUB_TOKEN: + required: false + ORG_GPG_PRIVATE_KEY: + required: false + ORG_GPG_PASSPHRASE: + required: false + CENTRAL_SONATYPE_TOKEN_USERNAME: + required: false + CENTRAL_SONATYPE_TOKEN_PASSWORD: + required: false + SWAGGERHUB_API_KEY: + required: false + SWAGGERHUB_USER: + required: false workflow_dispatch: inputs: dated_snapshot: @@ -43,10 +60,8 @@ on: # periodic build triggers are defined in run-all-tests.yml, which triggers this workflow -concurrency: - # cancel older running jobs on the same branch - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true +permissions: + contents: read jobs: secret-presence: @@ -56,6 +71,10 @@ jobs: HAS_MVNCRED: ${{ steps.secret-presence.outputs.HAS_MVNCRED }} HAS_SWAGGER: ${{ steps.secret-presence.outputs.HAS_SWAGGER }} steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - name: Check whether secrets exist id: secret-presence run: | @@ -75,14 +94,21 @@ jobs: VERSION: ${{ steps.get-version.outputs.VERSION }} DATED: ${{ steps.get-version.outputs.DATED }} steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: "Get version" id: get-version + env: + DATED_SNAPSHOT: ${{ inputs.dated_snapshot }} run: | - IFS=.- read -r RELEASE_VERSION_MAJOR RELEASE_VERSION_MINOR RELEASE_VERSION_PATCH SNAPSHOT<<<$(grep "version" gradle.properties | awk -F= '{print $2}') - if [[ "${{ github.event.workflow_run.event }}" == "schedule" || "${{ inputs.dated_snapshot }}" == "true" ]]; then + if [[ "$DATED_SNAPSHOT" == "true" ]]; then echo "VERSION=$RELEASE_VERSION_MAJOR.$RELEASE_VERSION_MINOR.$RELEASE_VERSION_PATCH-$(date +"%Y%m%d")-SNAPSHOT" >> "$GITHUB_OUTPUT" echo "DATED=true" >> "$GITHUB_OUTPUT" else @@ -97,7 +123,9 @@ jobs: permissions: contents: write uses: ./.github/workflows/trigger-docker-publish.yaml - secrets: inherit + secrets: + DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }} + DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }} with: docker_tag: ${{ needs.determine-version.outputs.VERSION }} @@ -110,7 +138,11 @@ jobs: if: | needs.secret-presence.outputs.HAS_MVNCRED uses: ./.github/workflows/trigger-maven-publish.yaml - secrets: inherit + secrets: + ORG_GPG_PRIVATE_KEY: ${{ secrets.ORG_GPG_PRIVATE_KEY }} + ORG_GPG_PASSPHRASE: ${{ secrets.ORG_GPG_PASSPHRASE }} + CENTRAL_SONATYPE_TOKEN_USERNAME: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} + CENTRAL_SONATYPE_TOKEN_PASSWORD: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} with: version: ${{ needs.determine-version.outputs.VERSION }} @@ -120,7 +152,6 @@ jobs: contents: write needs: [ secret-presence, determine-version ] uses: ./.github/workflows/publish-openapi-ui.yml - secrets: inherit with: version: ${{ needs.determine-version.outputs.VERSION }} latest: false @@ -133,7 +164,13 @@ jobs: needs: [ determine-version ] if: ${{ needs.determine-version.outputs.DATED == 'true' }} steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - 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 d3b6639499..5bc0527b01 100644 --- a/.github/workflows/publish-openapi-ui.yml +++ b/.github/workflows/publish-openapi-ui.yml @@ -46,16 +46,21 @@ on: type: boolean default: false +permissions: + contents: read + jobs: generate-openapi-spec: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java - name: Generate openapi spec run: ./gradlew resolve - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: openapi-spec path: resources/openapi/yaml @@ -70,48 +75,58 @@ jobs: { name: "data-plane", folder: "edc-dataplane/edc-dataplane-base" } ] steps: - - uses: actions/checkout@v6 - - uses: eclipse-edc/.github/.github/actions/setup-build@main - - uses: actions/download-artifact@v8 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: ./.github/actions/setup-java + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: openapi-spec path: resources/openapi/yaml - name: Set version + env: + INPUT_VERSION: ${{ inputs.version }} run: | - if [ -z ${{ inputs.version }} ]; then - export VERSION=$(grep "version" gradle.properties | awk -F= '{print $2}') + if [ -z "$INPUT_VERSION" ]; then + VERSION=$(grep "version" gradle.properties | awk -F= '{print $2}') else - export VERSION=${{ inputs.version }} + VERSION="$INPUT_VERSION" fi echo "VERSION=$VERSION" >> "$GITHUB_ENV" - name: Download upstream API specs for ${{ matrix.apiGroup.name }} + env: + API_FOLDER: ${{ matrix.apiGroup.folder }} + API_NAME: ${{ matrix.apiGroup.name }} run: | - ./gradlew -p ${{ matrix.apiGroup.folder }} downloadOpenapi - cp ${{ matrix.apiGroup.folder }}/build/docs/openapi/* resources/openapi/yaml/${{ matrix.apiGroup.name }} + ./gradlew -p "$API_FOLDER" downloadOpenapi + cp "$API_FOLDER/build/docs/openapi/"* "resources/openapi/yaml/$API_NAME" - name: Merge API specs + env: + API_NAME: ${{ matrix.apiGroup.name }} + VERSION: ${{ env.VERSION }} run: | - ./gradlew mergeOpenApiSpec --inputDir=${PWD}/resources/openapi/yaml/${{ matrix.apiGroup.name }} --output=${{ matrix.apiGroup.name }}.yaml --infoTitle="Tractus-X EDC ${{ matrix.apiGroup.name }} API" --infoDescription="Tractus-X EDC ${{ matrix.apiGroup.name }} API Documentation" --infoVersion=${{ env.VERSION }} + ./gradlew mergeOpenApiSpec --inputDir="${PWD}/resources/openapi/yaml/$API_NAME" --output="$API_NAME.yaml" --infoTitle="Tractus-X EDC $API_NAME API" --infoDescription="Tractus-X EDC $API_NAME API Documentation" --infoVersion=$VERSION - name: Generate Swagger UI current version - uses: Legion2/swagger-ui-action@v1 + uses: Legion2/swagger-ui-action@eff65dc3f193f0a749872be82f74baa35be0797d # v1.3.0 with: output: dist/${{ env.VERSION }} spec-file: ${{ matrix.apiGroup.name }}.yaml GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate Swagger UI stable version - uses: Legion2/swagger-ui-action@v1 + uses: Legion2/swagger-ui-action@eff65dc3f193f0a749872be82f74baa35be0797d # v1.3.0 if: ${{ inputs.latest }} with: output: dist spec-file: ${{ matrix.apiGroup.name }}.yaml GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/upload-artifact@v7 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ matrix.apiGroup.name }}-api path: dist @@ -122,12 +137,12 @@ jobs: permissions: contents: write steps: - - uses: actions/download-artifact@v8 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: openapi pattern: "*-api" - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: . diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c90f1341af..db9bf83246 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,10 +39,17 @@ on: default: "" +permissions: + contents: read + jobs: run-all-tests: name: "Run All Tests" + permissions: + contents: write uses: ./.github/workflows/run-all-tests.yml + with: + publish: false # Gate validation: name: "Workflow Validation" @@ -52,9 +59,14 @@ 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@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: false - name: Output release version id: release-version run: | @@ -62,8 +74,10 @@ jobs: echo "RELEASE_VERSION=$VERSION" >> $GITHUB_OUTPUT - name: Output update main branch version id: update-main + env: + RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} run: | - IFS=.- read -r MAJOR MINOR PATCH SNAPSHOT<<<"${{ steps.release-version.outputs.RELEASE_VERSION }}" + IFS=.- read -r MAJOR MINOR PATCH SNAPSHOT<<<"$RELEASE_VERSION" if [[ $PATCH == '0' && -z $SNAPSHOT ]]; then echo "update_main_branch_version=true" >> $GITHUB_OUTPUT @@ -71,25 +85,31 @@ jobs: echo "update_main_branch_version=false" >> $GITHUB_OUTPUT fi - name: Validations + env: + REPOSITORY: ${{ github.repository }} + REF_NAME: ${{ github.ref_name }} + RELEASE_VERSION: ${{ steps.release-version.outputs.RELEASE_VERSION }} run: | - if [[ "${{ github.repository }}" != "eclipse-tractusx/tractusx-edc" ]]; then + if [[ "$REPOSITORY" != "eclipse-tractusx/tractusx-edc" ]]; then echo "This workflow can only be run on the eclipse-tractusx/tractusx-edc repository." exit 1 fi - if [[ ! "${{ github.ref_name }}" =~ ^release/ ]]; then + if [[ ! "$REF_NAME" =~ ^release/ ]]; then echo "This workflow can only be run on the branches starting with release/." exit 1 fi - if [[ "${{steps.release-version.outputs.RELEASE_VERSION}}" =~ SNAPSHOT ]]; then + if [[ "$RELEASE_VERSION" =~ SNAPSHOT ]]; then echo "This workflow can not be executed for SNAPSHOT versions." exit 1 fi - name: Validate previous_tag exists + env: + PREVIOUS_TAG: ${{ inputs.previous_tag }} run: | - git show-ref --tags --verify --quiet "refs/tags/${{ inputs.previous_tag }}" \ - || (echo "Tag '${{ inputs.previous_tag }}' not found." && exit 1) + git show-ref --tags --verify --quiet "refs/tags/$PREVIOUS_TAG" \ + || (echo "Tag '$PREVIOUS_TAG' not found." && exit 1) # Release: Maven Artifacts @@ -100,7 +120,11 @@ jobs: contents: read if: needs.validation.outputs.RELEASE_VERSION uses: ./.github/workflows/trigger-maven-publish.yaml - secrets: inherit + secrets: + ORG_GPG_PRIVATE_KEY: ${{ secrets.ORG_GPG_PRIVATE_KEY }} + ORG_GPG_PASSPHRASE: ${{ secrets.ORG_GPG_PASSPHRASE }} + CENTRAL_SONATYPE_TOKEN_USERNAME: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} + CENTRAL_SONATYPE_TOKEN_PASSWORD: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} with: version: ${{ needs.validation.outputs.RELEASE_VERSION }} @@ -110,7 +134,9 @@ jobs: needs: [ validation ] if: needs.validation.outputs.RELEASE_VERSION uses: ./.github/workflows/trigger-docker-publish.yaml - secrets: inherit + secrets: + DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }} + DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }} with: docker_tag: ${{ needs.validation.outputs.RELEASE_VERSION }} @@ -125,11 +151,18 @@ jobs: if: needs.validation.outputs.RELEASE_VERSION steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 + persist-credentials: true - uses: ./.github/actions/setup-helm - name: Package helm, update index.yaml and push to gh-pages + env: + RELEASE_VERSION: ${{ needs.validation.outputs.RELEASE_VERSION }} run: | # Prepare git env git config user.name "eclipse-tractusx-bot" @@ -146,7 +179,7 @@ jobs: # Commit and push to gh-pages git add index.yaml helm-charts - git commit -s -m "Release ${{ needs.validation.outputs.RELEASE_VERSION }}" + git commit -s -m "Release $RELEASE_VERSION" git push origin gh-pages @@ -158,7 +191,13 @@ jobs: contents: write if: needs.validation.outputs.RELEASE_VERSION steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: true - name: Prepare Git Config shell: bash run: | @@ -168,23 +207,32 @@ jobs: - name: Create Release Tag id: create_release_tag shell: bash + env: + RELEASE_VERSION: ${{ needs.validation.outputs.RELEASE_VERSION }} run: | # informative git branch -a git tag # Create & push tag - git tag ${{ needs.validation.outputs.RELEASE_VERSION }} - git push origin ${{ needs.validation.outputs.RELEASE_VERSION }} + git tag "$RELEASE_VERSION" + git push origin "$RELEASE_VERSION" - name: Create GitHub Release - uses: ncipollo/release-action@v1 - with: - generateReleaseNotes: true - generateReleaseNotesPreviousTag: ${{ inputs.previous_tag }} - tag: ${{ needs.validation.outputs.RELEASE_VERSION }} - token: ${{ secrets.GITHUB_TOKEN }} - makeLatest: ${{ inputs.latest }} - removeArtifacts: true + env: + RELEASE_VERSION: ${{ needs.validation.outputs.RELEASE_VERSION }} + PREVIOUS_TAG: ${{ inputs.previous_tag }} + MAKE_LATEST: ${{ inputs.latest }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + LATEST_FLAG="" + if [[ "$MAKE_LATEST" == "true" ]]; then + LATEST_FLAG="--latest" + fi + gh release create "$RELEASE_VERSION" \ + --generate-notes \ + --notes-start-tag "$PREVIOUS_TAG" \ + --verify-tag \ + $LATEST_FLAG # Release: Publish specs to GitHub Pages publish-openapi-to-gh-pages: @@ -193,7 +241,6 @@ jobs: contents: write needs: [ validation ] uses: ./.github/workflows/publish-openapi-ui.yml - secrets: inherit with: version: ${{ needs.validation.outputs.RELEASE_VERSION }} latest: ${{ inputs.latest }} @@ -207,13 +254,22 @@ jobs: contents: write if: needs.validation.outputs.RELEASE_VERSION steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - - uses: actions/github-script@v8 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RELEASE_VERSION: ${{ needs.validation.outputs.RELEASE_VERSION }} with: script: | const { owner, repo } = context.repo; - const version = "${{ needs.validation.outputs.RELEASE_VERSION }}"; + const version = process.env.RELEASE_VERSION; const { data: release } = await github.rest.repos.getReleaseByTag({ owner, @@ -241,11 +297,16 @@ jobs: packages: write pages: write steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - name: Checkout main - uses: actions/checkout@v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 ref: main + persist-credentials: true - name: Version and Chart Updates uses: ./.github/actions/update-version-and-charts with: diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 0ea3c246ee..5d9326432f 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -32,7 +32,19 @@ on: paths-ignore: - docs/** workflow_dispatch: + inputs: + publish: + description: "Publish snapshot" + required: false + type: boolean + default: false workflow_call: + inputs: + publish: + description: "Publish snapshot" + required: false + type: boolean + default: false schedule: - cron: 0 3 * * 1 # run on 03:00 UTC every Monday @@ -42,19 +54,24 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: {} + # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: verify: + permissions: + contents: write uses: ./.github/workflows/verify.yaml - secrets: inherit deployment-test: + permissions: + contents: read uses: ./.github/workflows/deployment-test.yaml - secrets: inherit upgradeability-test: + permissions: + contents: read uses: ./.github/workflows/upgradeability-test.yaml - secrets: inherit # this job really serves no other purpose than waiting for the other two test workflows # in future iterations, this could be used as a choke point to collect test data, etc. @@ -68,3 +85,22 @@ jobs: - name: 'Master test job' run: echo "all test jobs have run by now" + publish-snapshot: + name: "Publish new snapshot" + needs: [ summary ] + permissions: + contents: write + if: ${{ (github.ref_name == 'main' || startsWith(github.ref_name, 'release/')) && (github.event_name == 'push' || github.event_name == 'schedule' || inputs.publish == true) }} + uses: ./.github/workflows/publish-new-snapshot.yaml + with: + dated_snapshot: ${{ github.event_name == 'schedule' }} + secrets: + DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USER }} + DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }} + ORG_GPG_PRIVATE_KEY: ${{ secrets.ORG_GPG_PRIVATE_KEY }} + ORG_GPG_PASSPHRASE: ${{ secrets.ORG_GPG_PASSPHRASE }} + CENTRAL_SONATYPE_TOKEN_USERNAME: ${{ secrets.CENTRAL_SONATYPE_TOKEN_USERNAME }} + CENTRAL_SONATYPE_TOKEN_PASSWORD: ${{ secrets.CENTRAL_SONATYPE_TOKEN_PASSWORD }} + SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} + SWAGGERHUB_USER: ${{ secrets.SWAGGERHUB_USER }} + diff --git a/.github/workflows/secrets-scan.yml b/.github/workflows/secrets-scan.yml index a87e33be6b..a01f926583 100644 --- a/.github/workflows/secrets-scan.yml +++ b/.github/workflows/secrets-scan.yml @@ -28,25 +28,32 @@ on: - cron: "0 0 * * *" # Once a day permissions: - actions: read contents: read - security-events: write - id-token: write - issues: write jobs: ScanSecrets: name: Scan secrets runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + id-token: write + issues: write steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - name: Checkout Repository - uses: actions/checkout@v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 # Ensure full clone for pull request workflows + persist-credentials: false - name: TruffleHog OSS id: trufflehog - uses: trufflesecurity/trufflehog@6c05c4a00b91aa542267d8e32a8254774799d68d + uses: trufflesecurity/trufflehog@bcfcf73aaf4759d4dadc2783177c245a02792318 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 2e73531417..43b58ff5f1 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -29,13 +29,20 @@ on: - cron: "30 1 * * *" # once a day (1:30 UTC) workflow_dispatch: # allow manual trigger +permissions: + contents: read + jobs: close-issues-with-assignee: runs-on: ubuntu-latest permissions: issues: write steps: - - uses: actions/stale@v10 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 with: operations-per-run: 1000 days-before-issue-stale: 32 diff --git a/.github/workflows/triage-issue.yml b/.github/workflows/triage-issue.yml index 72805dc71f..fcc056b2a1 100644 --- a/.github/workflows/triage-issue.yml +++ b/.github/workflows/triage-issue.yml @@ -26,12 +26,19 @@ on: - reopened - opened +permissions: + contents: read + jobs: label-issue: runs-on: ubuntu-latest permissions: issues: write steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - run: gh issue edit "$NUMBER" --add-label "$LABELS" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/trigger-docker-publish.yaml b/.github/workflows/trigger-docker-publish.yaml index 495528df0c..51020d629e 100644 --- a/.github/workflows/trigger-docker-publish.yaml +++ b/.github/workflows/trigger-docker-publish.yaml @@ -45,6 +45,16 @@ on: type: string description: 'Explicitly specify the Docker tag. Note that SHA and latest are added automatically.' required: false + secrets: + DOCKER_HUB_USER: + required: false + description: 'DockerHub username' + DOCKER_HUB_TOKEN: + required: false + description: 'DockerHub token' + +permissions: + contents: read jobs: create-docker-image: @@ -55,15 +65,23 @@ jobs: matrix: variant: [ { dir: edc-controlplane, img: edc-runtime-memory }, { dir: edc-controlplane, img: edc-controlplane-postgresql-hashicorp-vault }, - { dir: edc-dataplane, img: edc-dataplane-hashicorp-vault }, - { dir: edc-tests/runtime, img: mock-connector }] + { dir: edc-dataplane, img: edc-dataplane-hashicorp-vault }] permissions: - contents: write + contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Log inputs + env: + INPUT_DOCKER_TAG: ${{ inputs.docker_tag }} + INPUT_NAMESPACE: ${{ inputs.namespace }} run: | - echo "Input Version: ${{ inputs.docker_tag }}, Input namespace: ${{ inputs.namespace}}" + echo "Input Version: $INPUT_DOCKER_TAG, Input namespace: $INPUT_NAMESPACE" - uses: ./.github/actions/publish-docker-image name: Publish ${{ matrix.variant.img }} with: diff --git a/.github/workflows/trigger-maven-publish.yaml b/.github/workflows/trigger-maven-publish.yaml index 78d5a2fb71..52c40c0db8 100644 --- a/.github/workflows/trigger-maven-publish.yaml +++ b/.github/workflows/trigger-maven-publish.yaml @@ -34,6 +34,22 @@ on: type: string required: false description: 'a semver string denoting the version. Append -SNAPSHOT for snapshots. If omitted, the version is taken from gradle.properties' + secrets: + ORG_GPG_PRIVATE_KEY: + required: false + description: 'GPG private key for signing' + ORG_GPG_PASSPHRASE: + required: false + description: 'GPG passphrase' + CENTRAL_SONATYPE_TOKEN_USERNAME: + required: false + description: 'Sonatype token username' + CENTRAL_SONATYPE_TOKEN_PASSWORD: + required: false + description: 'Sonatype token password' + +permissions: + contents: read jobs: maven-release: @@ -42,22 +58,30 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java - - run: | - echo "Input Version: ${{ inputs.version }}" + - name: Set version + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + echo "Input Version: $INPUT_VERSION" # set the input version in gradle.properties, if passed - if [ ! -z ${{ inputs.version }} ]; + if [ ! -z "$INPUT_VERSION" ]; then - sed -i 's#^version=.*#version='"${{ inputs.version }}"'#g' $(find . -name "gradle.properties") - VERSION=${{ inputs.version }} + sed -i 's#^version=.*#version='"$INPUT_VERSION"'#g' $(find . -name "gradle.properties") fi grep version gradle.properties | (echo -n "Publishing using " && cat) - - uses: eclipse-edc/.github/.github/actions/publish-maven-artifacts@main + - uses: ./.github/actions/publish-maven-artifacts with: gpg-private-key: ${{ secrets.ORG_GPG_PRIVATE_KEY }} gpg-passphrase: ${{ secrets.ORG_GPG_PASSPHRASE }} diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 9a767e5080..e35bb7d7ed 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -25,17 +25,10 @@ on: schedule: - cron: "0 0 * * *" workflow_dispatch: - workflow_run: - workflows: [ "Publish Artefacts" ] - branches: - - main - - releases - - release/* - - hotfix/* - tags: - - '[0-9]+.[0-9]+.[0-9]+' - types: - - completed + workflow_call: + +permissions: + contents: read jobs: git-sha7: @@ -50,66 +43,47 @@ jobs: echo "SHA7=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT trivy-analyze-config: - runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write - steps: - - uses: actions/checkout@v6 - - name: Run Trivy vulnerability scanner in repo mode - uses: aquasecurity/trivy-action@0.35.0 - with: - scan-type: "config" - # ignore-unfixed: true - exit-code: "0" - hide-progress: false - format: "sarif" - output: "trivy-results-config.sarif" - severity: "CRITICAL,HIGH" - - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v4 - if: always() - with: - sarif_file: "trivy-results-config.sarif" + uses: eclipse-tractusx/sig-infra/.github/workflows/reusable-trivy.yaml@main + with: + scan-type: "config" + output-file: "trivy-results-config.sarif" - trivy: + trivy-edc-runtime-memory: needs: [ git-sha7 ] permissions: actions: read contents: read security-events: write - runs-on: ubuntu-latest - strategy: - fail-fast: false # continue scanning other images although if the other has been vulnerable - matrix: - image: - - edc-runtime-memory - - edc-controlplane-postgresql-hashicorp-vault - - edc-dataplane-hashicorp-vault - steps: - - uses: actions/checkout@v6 + uses: eclipse-tractusx/sig-infra/.github/workflows/reusable-trivy.yaml@main + with: + scan-type: "image" + image-ref: "tractusx/edc-runtime-memory:sha-${{ needs.git-sha7.outputs.value }}" + output-file: "trivy-results-edc-runtime-memory.sarif" - ## This step will fail if the docker images is not found - - name: "Check if image exists" - id: imageCheck - run: | - docker buildx imagetools inspect --format '{{ json . }}' tractusx/${{ matrix.image }}:sha-${{ needs.git-sha7.outputs.value }} - continue-on-error: true + trivy-edc-controlplane: + needs: [ git-sha7 ] + permissions: + actions: read + contents: read + security-events: write + uses: eclipse-tractusx/sig-infra/.github/workflows/reusable-trivy.yaml@main + with: + scan-type: "image" + image-ref: "tractusx/edc-controlplane-postgresql-hashicorp-vault:sha-${{ needs.git-sha7.outputs.value }}" + output-file: "trivy-results-edc-controlplane-postgresql-hashicorp-vault.sarif" - ## the next two steps will only execute if the image exists check was successful - - name: Run Trivy vulnerability scanner - if: success() && steps.imageCheck.outcome != 'failure' - uses: aquasecurity/trivy-action@0.35.0 - with: - image-ref: "tractusx/${{ matrix.image }}:sha-${{ needs.git-sha7.outputs.value }}" - format: "sarif" - output: "trivy-results-${{ matrix.image }}.sarif" - exit-code: "0" - severity: "CRITICAL,HIGH" - timeout: "10m0s" - - name: Upload Trivy scan results to GitHub Security tab - if: success() && steps.imageCheck.outcome != 'failure' - uses: github/codeql-action/upload-sarif@v4 - with: - sarif_file: "trivy-results-${{ matrix.image }}.sarif" + trivy-edc-dataplane: + needs: [ git-sha7 ] + permissions: + actions: read + contents: read + security-events: write + uses: eclipse-tractusx/sig-infra/.github/workflows/reusable-trivy.yaml@main + with: + scan-type: "image" + image-ref: "tractusx/edc-dataplane-hashicorp-vault:sha-${{ needs.git-sha7.outputs.value }}" + output-file: "trivy-results-edc-dataplane-hashicorp-vault.sarif" diff --git a/.github/workflows/upgradeability-test.yaml b/.github/workflows/upgradeability-test.yaml index c5d79006a2..945877898c 100644 --- a/.github/workflows/upgradeability-test.yaml +++ b/.github/workflows/upgradeability-test.yaml @@ -27,13 +27,20 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: +permissions: + contents: read + jobs: test-prepare: runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - name: Cache ContainerD Image Layers - uses: actions/cache@v5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs key: ${{ runner.os }}-io.containerd.snapshotter.v1.overlayfs @@ -42,14 +49,20 @@ jobs: runs-on: ubuntu-latest needs: [ test-prepare ] steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-helm - uses: ./.github/actions/setup-kubectl - name: Create k8s Kind Cluster - uses: helm/kind-action@v1.14.0 + uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0 - name: "Update helm repo" run: | @@ -59,7 +72,7 @@ jobs: - name: "Get latest released version" id: get-version run: | - RELEASED_VERSION=$(helm search repo tractusx/tractusx-connector -l -o json | jq -r 'first | .version') + RELEASED_VERSION=$(helm search repo tractusx/tractusx-connector -l --devel -o json | jq -r 'first | .version') echo "Last official release is $RELEASED_VERSION" echo "RELEASE=$RELEASED_VERSION" >> $GITHUB_ENV exit 0 diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index e067eafc3d..79d3b36d45 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -27,14 +27,23 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -jobs: +permissions: {} +jobs: verify-helm-docs: runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - run: | - docker run -v ${{ github.workspace }}/charts:/helm-docs jnorwood/helm-docs helm-docs + docker run -v "${GITHUB_WORKSPACE}/charts:/helm-docs" jnorwood/helm-docs helm-docs if $(git diff --quiet --exit-code); then echo "Helm chart docs up to date" @@ -46,8 +55,16 @@ jobs: verify-formatting: runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java - name: Run Checkstyle @@ -55,18 +72,34 @@ jobs: ./gradlew checkstyleMain checkstyleTest verify-javadoc: + permissions: + contents: read runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java - name: Run Javadoc run: ./gradlew javadoc unit-tests: + permissions: + contents: read runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java @@ -75,7 +108,7 @@ jobs: # uploads the jacoco report as artifact - name: Upload JaCoCo Coverage Report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: JaCoCo coverage-report path: build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml @@ -84,7 +117,7 @@ jobs: # generates coverage-report.md - name: JaCoCo Code Coverage Report id: jacoco_reporter - uses: PavanMudigonda/jacoco-reporter@v5.1 + uses: PavanMudigonda/jacoco-reporter@112997f0c32da82d8bfa4a972b4afe67a15529fe # v5.2.1 with: coverage_results_path: build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml skip_check_run: true @@ -92,17 +125,22 @@ jobs: # Publish Coverage Job Summary - name: Output KPIs from JaCoCo report + env: + COVERAGE_PERCENTAGE: ${{ steps.jacoco_reporter.outputs.coverage_percentage }} + COVERED_LINES: ${{ steps.jacoco_reporter.outputs.covered_lines }} + MISSED_LINES: ${{ steps.jacoco_reporter.outputs.missed_lines }} + TOTAL_LINES: ${{ steps.jacoco_reporter.outputs.total_lines }} run: | echo "| Outcome | Value |" >> $GITHUB_STEP_SUMMARY echo "| --- | --- |" >> $GITHUB_STEP_SUMMARY - echo "| Code Coverage % | ${{ steps.jacoco_reporter.outputs.coverage_percentage }} |" >> $GITHUB_STEP_SUMMARY - echo "| :heavy_check_mark: Number of Lines Covered | ${{ steps.jacoco_reporter.outputs.covered_lines }} |" >> $GITHUB_STEP_SUMMARY - echo "| :x: Number of Lines Missed | ${{ steps.jacoco_reporter.outputs.missed_lines }} |" >> $GITHUB_STEP_SUMMARY - echo "| Total Number of Lines | ${{ steps.jacoco_reporter.outputs.total_lines }} |" >> $GITHUB_STEP_SUMMARY + echo "| Code Coverage % | $COVERAGE_PERCENTAGE |" >> $GITHUB_STEP_SUMMARY + echo "| :heavy_check_mark: Number of Lines Covered | $COVERED_LINES |" >> $GITHUB_STEP_SUMMARY + echo "| :x: Number of Lines Missed | $MISSED_LINES |" >> $GITHUB_STEP_SUMMARY + echo "| Total Number of Lines | $TOTAL_LINES |" >> $GITHUB_STEP_SUMMARY # uploads the coverage-report.md artifact - name: Upload Code Coverage Markdown Report - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: code-coverage-report-markdown path: "*/coverage-results.md" @@ -110,20 +148,35 @@ jobs: integration-tests: runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java - name: Run Integration tests run: | - ./gradlew :edc-tests:runtime:mock-connector:dockerize ./gradlew test -DincludeTags="ComponentTest" api-tests: runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java @@ -134,8 +187,16 @@ jobs: runs-on: ubuntu-latest outputs: matrix: ${{ steps.outputStep.outputs.matrix }} + permissions: + contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: get api groups and create matrix for next job id: outputStep run: | @@ -148,30 +209,50 @@ jobs: strategy: fail-fast: false matrix: ${{ fromJson(needs.prepare-end-to-end-tests.outputs.matrix) }} + permissions: + contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java - name: Run E2E tests (${{ matrix.dir }}) + env: + E2E_DIR: ${{ matrix.dir }} run: | ./gradlew compileJava compileTestJava - ./gradlew -p edc-tests/e2e/${{ matrix.dir }} test -DincludeTags="EndToEndTest" -PverboseTest=true --no-build-cache + ./gradlew -p "edc-tests/e2e/$E2E_DIR" test -DincludeTags="EndToEndTest" -PverboseTest=true --no-build-cache - name: Set sanitized artifact name + env: + E2E_DIR: ${{ matrix.dir }} run: | - SANITIZED_NAME=$(echo "edc-tests/e2e/${{ matrix.dir }}" | tr '/' '-') + SANITIZED_NAME=$(echo "edc-tests/e2e/$E2E_DIR" | tr '/' '-') echo "ARTIFACT_NAME=${SANITIZED_NAME}" >> $GITHUB_ENV - name: Upload test artifacts - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: allure-results-${{ env.ARTIFACT_NAME }} path: edc-tests/e2e/${{ matrix.dir }}/build/allure-results postgres-tests: runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java - name: Run Postgresql E2E tests @@ -179,8 +260,16 @@ jobs: compatibility-tests: runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v6 + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/setup-java - name: Build docker images @@ -193,9 +282,17 @@ jobs: runs-on: ubuntu-latest needs: end-to-end-tests if: ${{ github.ref_name == 'main' || startsWith(github.ref_name, 'release/') }} + permissions: + contents: write steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Output report version id: release-version diff --git a/.github/workflows/workflow-security-lint.yaml b/.github/workflows/workflow-security-lint.yaml new file mode 100644 index 0000000000..b820274d22 --- /dev/null +++ b/.github/workflows/workflow-security-lint.yaml @@ -0,0 +1,97 @@ +################################################################################# +# Copyright (c) 2026 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 +################################################################################# + +--- +name: "Workflow Security Lint" + +on: + push: + branches: [ main ] + paths: + - '.github/workflows/**' + - '.github/actions/**' + pull_request: + paths: + - '.github/workflows/**' + - '.github/actions/**' + schedule: + - cron: "0 0 * * 1" # every Monday at 00:00 UTC + workflow_dispatch: + +permissions: + contents: read + +jobs: + zizmor: + name: "zizmor" + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write # required to upload SARIF to GitHub Advanced Security + steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2 + with: + version: "1.23.1" + advanced-security: "true" + token: ${{ secrets.GITHUB_TOKEN }} + config: .github/zizmor.yml + + poutine: + name: "poutine" + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write # required to upload SARIF to GitHub Advanced Security + steps: + - name: Harden Runner + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run poutine + uses: boostsecurityio/poutine-action@e240ebd3eff8b2db5a8e5f6b28f58739d7db2247 # v1.1.4 + with: + config: .github/poutine.yml + + - name: Fix and patch poutine SARIF + if: always() + run: python3 .github/scripts/fix-poutine-sarif.py + + - name: Upload poutine SARIF to GitHub Advanced Security + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + if: always() + with: + sarif_file: results-fixed.sarif + category: poutine diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 0000000000..fd33965d6f --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,28 @@ +################################################################################# +# Copyright (c) 2026 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 +################################################################################# + +rules: + unpinned-uses: + config: + policies: + # eclipse-tractusx/sig-infra has no release tags and is a first-party + # Eclipse Tractus-X org repo — ref-pin (branch reference) is acceptable. + "eclipse-tractusx/*": ref-pin + # everything else must be fully hash-pinned + "*": hash-pin diff --git a/.tractusx b/.tractusx index 4897df1ed1..d00b3446b7 100644 --- a/.tractusx +++ b/.tractusx @@ -2,12 +2,12 @@ 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/0.12.0/control-plane.yaml" -- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/data-plane-api/0.12.0/data-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/control-plane-api/0.12.1/control-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/data-plane-api/0.12.1/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.10.3/control-plane.yaml" +- "https://eclipse-tractusx.github.io/tractusx-edc/openapi/data-plane-api/0.10.3/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" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9cbd34489..3450c95d2c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,21 +4,11 @@ Thanks for your interest in this project. ## Project description -The companies involved want to increase the automotive industry's -competitiveness, improve efficiency through industry-specific cooperation and -accelerate company processes through standardization and access to information -and data. A special focus is also on SMEs, whose active participation is of -central importance for the network's success. That is why Catena-X has been -conceived from the outset as an open network with solutions ready for SMEs, -where these companies will be able to participate quickly and with little IT -infrastructure investment. Tractus-X is meant to be the PoC project of the -Catena-X alliance focusing on parts traceability. - -* +Here you can find the latest project description: ## Project licenses -The Tractus-X project uses the following licenses: +The Eclipse Tractus-X project uses the following licenses: * Apache-2.0 for code * CC-BY-4.0 for non-code diff --git a/DEPENDENCIES b/DEPENDENCIES index e69de29bb2..1ec6e531c6 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -0,0 +1,483 @@ +maven/mavencentral/com.apicatalog/carbon-did/0.3.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.apicatalog/copper-multibase/0.5.0, Apache-2.0, approved, #14501 +maven/mavencentral/com.apicatalog/copper-multicodec/0.1.1, Apache-2.0, approved, #14500 +maven/mavencentral/com.apicatalog/iron-ed25519-cryptosuite-2020/0.14.0, Apache-2.0, approved, #14503 +maven/mavencentral/com.apicatalog/iron-verifiable-credentials/0.14.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.apicatalog/titanium-jcs/1.1.1, Apache-2.0, approved, #25093 +maven/mavencentral/com.apicatalog/titanium-json-ld/1.0.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.apicatalog/titanium-json-ld/1.4.0, Apache-2.0, approved, #15200 +maven/mavencentral/com.apicatalog/titanium-json-ld/1.7.0, Apache-2.0, approved, #25089 +maven/mavencentral/com.apicatalog/titanium-rdf-api/1.0.0, Apache-2.0, approved, #20245 +maven/mavencentral/com.apicatalog/titanium-rdf-n-quads/1.0.2, Apache-2.0, approved, #20243 +maven/mavencentral/com.azure/azure-core-http-netty/1.15.11, MIT AND Apache-2.0, approved, #16697 +maven/mavencentral/com.azure/azure-core-http-netty/1.16.3, MIT AND Apache-2.0, approved, #29562 +maven/mavencentral/com.azure/azure-core/1.55.3, MIT, approved, clearlydefined +maven/mavencentral/com.azure/azure-core/1.57.1, MIT, approved, clearlydefined +maven/mavencentral/com.azure/azure-identity/1.18.2, MIT, approved, #29561 +maven/mavencentral/com.azure/azure-json/1.4.0, MIT, approved, clearlydefined +maven/mavencentral/com.azure/azure-json/1.5.1, MIT AND Apache-2.0, approved, #26444 +maven/mavencentral/com.azure/azure-storage-blob/12.30.0, MIT, approved, #20441 +maven/mavencentral/com.azure/azure-storage-common/12.29.0, MIT, approved, #20440 +maven/mavencentral/com.azure/azure-storage-internal-avro/12.15.0, MIT, approved, clearlydefined +maven/mavencentral/com.azure/azure-xml/1.2.0, Apache-2.0, approved, #26447 +maven/mavencentral/com.azure/azure-xml/1.2.1, Apache-2.0, approved, #26447 +maven/mavencentral/com.ethlo.time/itu/1.14.0, Apache-2.0, approved, #19505 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.18.4, Apache-2.0, approved, #16364 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.19.1, Apache-2.0, approved, #21911 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.19.2, Apache-2.0, approved, #21911 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.21, Apache-2.0, approved, #25587 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.18.4.1, Apache-2.0 AND MIT, approved, #16371 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.21.4, Apache-2.0 AND MIT, approved, #25590 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.18.3, Apache-2.0, approved, #16372 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.18.4, Apache-2.0, approved, #16372 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.19.1, Apache-2.0, approved, #21909 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.19.2, Apache-2.0, approved, #21909 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.21.2, Apache-2.0, approved, #25591 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.21.4, Apache-2.0, approved, #25591 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.18.3, Apache-2.0, approved, #16370 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.19.2, Apache-2.0, approved, #21912 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.21.4, Apache-2.0, approved, #25589 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jakarta-jsonp/2.21.2, Apache-2.0, approved, #26448 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jakarta-jsonp/2.21.4, Apache-2.0, approved, #26448 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.18.4, Apache-2.0, approved, #16625 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.19.2, Apache-2.0, approved, #23299 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.21.2, Apache-2.0, approved, #26441 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.21.4, Apache-2.0, approved, #26441 +maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-base/2.21.4, Apache-2.0, approved, #26442 +maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.19.2, Apache-2.0, approved, #20839 +maven/mavencentral/com.fasterxml.jackson.jakarta.rs/jackson-jakarta-rs-json-provider/2.21.4, Apache-2.0, approved, #26443 +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.19.1, Apache-2.0, approved, #21910 +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jakarta-xmlbind-annotations/2.21.4, Apache-2.0, approved, #25588 +maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.21.4, Apache-2.0, approved, #26451 +maven/mavencentral/com.google.code.findbugs/jsr305/3.0.2, Apache-2.0 and CC-BY-2.5, approved, #15220 +maven/mavencentral/com.google.code.gson/gson/2.13.2, Apache-2.0, approved, #20656 +maven/mavencentral/com.google.crypto.tink/tink/1.20.0, Apache-2.0, approved, #28384 +maven/mavencentral/com.google.errorprone/error_prone_annotations/2.41.0, Apache-2.0, approved, #22631 +maven/mavencentral/com.google.protobuf/protobuf-java/4.33.0, BSD-3-Clause, approved, #29831 +maven/mavencentral/com.microsoft.azure/msal4j-persistence-extension/1.3.0, MIT, approved, #14411 +maven/mavencentral/com.microsoft.azure/msal4j/1.15.0, MIT, approved, clearlydefined +maven/mavencentral/com.microsoft.azure/msal4j/1.23.1, MIT, approved, clearlydefined +maven/mavencentral/com.networknt/json-schema-validator/2.0.0, Apache-2.0 AND Unicode-TOU, approved, #25988 +maven/mavencentral/com.nimbusds/nimbus-jose-jwt/10.8, Apache-2.0, approved, #26455 +maven/mavencentral/com.nimbusds/nimbus-jose-jwt/10.9.1, Apache-2.0, approved, #29000 +maven/mavencentral/com.squareup.okhttp3/okhttp-dnsoverhttps/5.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okhttp3/okhttp-jvm/5.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okhttp3/okhttp-jvm/5.4.0, Apache-2.0, approved, #29005 +maven/mavencentral/com.squareup.okhttp3/okhttp/4.9.3, Apache-2.0 AND MPL-2.0, approved, #3225 +maven/mavencentral/com.squareup.okhttp3/okhttp/5.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okhttp3/okhttp/5.4.0, Apache-2.0, approved, #29015 +maven/mavencentral/com.squareup.okio/okio-jvm/3.16.4, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okio/okio-jvm/3.17.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okio/okio/3.16.4, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.squareup.okio/okio/3.17.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/commons-codec/commons-codec/1.11, Apache-2.0 AND BSD-3-Clause, approved, CQ15971 +maven/mavencentral/commons-codec/commons-codec/1.17.1, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #14583 +maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ10162 +maven/mavencentral/dev.failsafe/failsafe-okhttp/3.3.2, Apache-2.0, approved, #15208 +maven/mavencentral/dev.failsafe/failsafe/3.3.2, Apache-2.0, approved, #9268 +maven/mavencentral/io.github.classgraph/classgraph/4.8.184, MIT, approved, CQ22530 +maven/mavencentral/io.micrometer/micrometer-commons/1.16.4, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #24726 +maven/mavencentral/io.micrometer/micrometer-core/1.16.4, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #24722 +maven/mavencentral/io.micrometer/micrometer-observation/1.16.4, Apache-2.0, approved, #24713 +maven/mavencentral/io.netty/netty-bom/4.1.137.Final, Apache-2.0, approved, #30531 +maven/mavencentral/io.netty/netty-buffer/4.1.130.Final, Apache-2.0, approved, CQ21842 +maven/mavencentral/io.netty/netty-buffer/4.1.132.Final, Apache-2.0, approved, CQ21842 +maven/mavencentral/io.netty/netty-buffer/4.1.137.Final, Apache-2.0, approved, CQ21842 +maven/mavencentral/io.netty/netty-codec-dns/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http/4.1.128.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http/4.1.130.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http/4.1.132.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http2/4.1.128.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http2/4.1.130.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http2/4.1.132.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-http2/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec-socks/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec/4.1.130.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec/4.1.132.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-codec/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-common/4.1.130.Final, Apache-2.0 AND MIT AND CC0-1.0, approved, CQ21843 +maven/mavencentral/io.netty/netty-common/4.1.132.Final, Apache-2.0 AND MIT AND CC0-1.0, approved, CQ21843 +maven/mavencentral/io.netty/netty-common/4.1.137.Final, Apache-2.0 AND MIT AND CC0-1.0, approved, CQ21843 +maven/mavencentral/io.netty/netty-handler-proxy/4.1.128.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-handler-proxy/4.1.130.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-handler-proxy/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-handler/4.1.128.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-handler/4.1.130.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-handler/4.1.132.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-handler/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-resolver-dns-classes-macos/4.1.137.Final, Apache-2.0, approved, #6367 +maven/mavencentral/io.netty/netty-resolver-dns-native-macos/4.1.128.Final, Apache-2.0, approved, #7004 +maven/mavencentral/io.netty/netty-resolver-dns-native-macos/4.1.137.Final, Apache-2.0, approved, #7004 +maven/mavencentral/io.netty/netty-resolver-dns/4.1.128.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-resolver-dns/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-resolver/4.1.132.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-resolver/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-tcnative-boringssl-static/2.0.74.Final, Apache-2.0 OR LicenseRef-Public-Domain OR BSD-2-Clause OR MIT, approved, CQ15280 +maven/mavencentral/io.netty/netty-tcnative-boringssl-static/2.0.81.Final, Apache-2.0 OR LicenseRef-Public-Domain OR BSD-2-Clause OR MIT, approved, CQ15280 +maven/mavencentral/io.netty/netty-tcnative-classes/2.0.81.Final, Apache-2.0, approved, #23398 +maven/mavencentral/io.netty/netty-transport-classes-epoll/4.1.132.Final, Apache-2.0, approved, #6366 +maven/mavencentral/io.netty/netty-transport-classes-epoll/4.1.137.Final, Apache-2.0, approved, #6366 +maven/mavencentral/io.netty/netty-transport-classes-kqueue/4.1.137.Final, Apache-2.0, approved, #4107 +maven/mavencentral/io.netty/netty-transport-native-epoll/4.1.128.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport-native-epoll/4.1.130.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport-native-epoll/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport-native-kqueue/4.1.130.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport-native-kqueue/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport-native-unix-common/4.1.130.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport-native-unix-common/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport/4.1.132.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.netty/netty-transport/4.1.137.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926 +maven/mavencentral/io.opentelemetry.instrumentation/opentelemetry-instrumentation-annotations/1.32.0, Apache-2.0, approved, #11684 +maven/mavencentral/io.opentelemetry.instrumentation/opentelemetry-instrumentation-annotations/2.30.0, Apache-2.0, approved, #30125 +maven/mavencentral/io.opentelemetry.instrumentation/opentelemetry-instrumentation-api-incubator/2.30.0-alpha, Apache-2.0, approved, #30532 +maven/mavencentral/io.opentelemetry.instrumentation/opentelemetry-instrumentation-api/2.30.0, Apache-2.0, approved, #30533 +maven/mavencentral/io.opentelemetry.instrumentation/opentelemetry-log4j-appender-2.17/2.30.0-alpha, Apache-2.0, approved, #30534 +maven/mavencentral/io.opentelemetry.instrumentation/opentelemetry-log4j-context-data-2.17-autoconfigure/2.30.0-alpha, Apache-2.0, approved, #30535 +maven/mavencentral/io.opentelemetry.semconv/opentelemetry-semconv/1.43.0, Apache-2.0, approved, #30446 +maven/mavencentral/io.opentelemetry/opentelemetry-api-incubator/1.64.0-alpha, Apache-2.0, approved, #30536 +maven/mavencentral/io.opentelemetry/opentelemetry-api/1.32.0, Apache-2.0, approved, #11682 +maven/mavencentral/io.opentelemetry/opentelemetry-api/1.61.0, Apache-2.0, approved, #29641 +maven/mavencentral/io.opentelemetry/opentelemetry-api/1.64.0, Apache-2.0, approved, #29872 +maven/mavencentral/io.opentelemetry/opentelemetry-common/1.64.0, Apache-2.0, approved, #29870 +maven/mavencentral/io.opentelemetry/opentelemetry-context/1.64.0, Apache-2.0, approved, #29871 +maven/mavencentral/io.projectreactor.netty/reactor-netty-core/1.2.13, Apache-2.0, approved, #23393 +maven/mavencentral/io.projectreactor.netty/reactor-netty-http/1.2.13, Apache-2.0, approved, #23394 +maven/mavencentral/io.projectreactor/reactor-core/3.7.14, Apache-2.0, approved, #17529 +maven/mavencentral/io.setl/rdf-urdna/1.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.swagger.core.v3/swagger-annotations-jakarta/2.2.42, Apache-2.0, approved, #5947 +maven/mavencentral/io.swagger.core.v3/swagger-core-jakarta/2.2.42, Apache-2.0, approved, #5929 +maven/mavencentral/io.swagger.core.v3/swagger-integration-jakarta/2.2.42, Apache-2.0, approved, #11475 +maven/mavencentral/io.swagger.core.v3/swagger-jaxrs2-jakarta/2.2.42, Apache-2.0, approved, #11477 +maven/mavencentral/io.swagger.core.v3/swagger-models-jakarta/2.2.42, Apache-2.0, approved, #5919 +maven/mavencentral/jakarta.activation/jakarta.activation-api/2.1.3, EPL-2.0 OR BSD-3-Clause OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jaf +maven/mavencentral/jakarta.annotation/jakarta.annotation-api/3.0.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.ca +maven/mavencentral/jakarta.inject/jakarta.inject-api/2.0.1, Apache-2.0, approved, ee4j.cdi +maven/mavencentral/jakarta.json/jakarta.json-api/2.1.3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jsonp +maven/mavencentral/jakarta.servlet/jakarta.servlet-api/6.0.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.servlet +maven/mavencentral/jakarta.servlet/jakarta.servlet-api/6.1.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.servlet +maven/mavencentral/jakarta.validation/jakarta.validation-api/3.0.2, Apache-2.0, approved, ee4j.validation +maven/mavencentral/jakarta.validation/jakarta.validation-api/3.1.0, Apache-2.0, approved, ee4j.validation +maven/mavencentral/jakarta.ws.rs/jakarta.ws.rs-api/4.0.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.rest +maven/mavencentral/jakarta.xml.bind/jakarta.xml.bind-api/3.0.1, BSD-3-Clause, approved, ee4j.jaxb +maven/mavencentral/jakarta.xml.bind/jakarta.xml.bind-api/4.0.2, BSD-3-Clause, approved, ee4j.jaxb +maven/mavencentral/net.java.dev.jna/jna-platform/5.13.0, Apache-2.0 OR LGPL-2.1-or-later, approved, #6707 +maven/mavencentral/net.java.dev.jna/jna-platform/5.17.0, Apache-2.0 OR LGPL-2.1-or-later, approved, #20116 +maven/mavencentral/net.java.dev.jna/jna/5.13.0, Apache-2.0 AND LGPL-2.1-or-later, approved, #15196 +maven/mavencentral/net.java.dev.jna/jna/5.17.0, Apache-2.0 AND LGPL-2.1-or-later, approved, #20112 +maven/mavencentral/org.apache.commons/commons-lang3/3.18.0, Apache-2.0, approved, #22470 +maven/mavencentral/org.apache.commons/commons-pool2/2.13.1, Apache-2.0 AND CC-PDDC, approved, #25941 +maven/mavencentral/org.apache.httpcomponents/httpclient/4.5.13, Apache-2.0, approved, #15248 +maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.13, Apache-2.0, approved, CQ23528 +maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.16, Apache-2.0, approved, CQ23528 +maven/mavencentral/org.apache.logging.log4j/log4j-api/2.26.1, Apache-2.0, approved, #27998 +maven/mavencentral/org.apache.logging.log4j/log4j-core/2.26.1, Apache-2.0 AND (Apache-2.0 AND LGPL-2.0-or-later), approved, #28001 +maven/mavencentral/org.apache.logging.log4j/log4j-layout-template-json/2.26.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.bouncycastle/bcpkix-jdk18on/1.84, MIT, approved, #27142 +maven/mavencentral/org.bouncycastle/bcprov-jdk18on/1.84, MIT AND CC0-1.0, approved, #27143 +maven/mavencentral/org.bouncycastle/bcutil-jdk18on/1.84, MIT, approved, #27144 +maven/mavencentral/org.checkerframework/checker-qual/3.55.1, MIT, approved, #29939 +maven/mavencentral/org.eclipse.edc.aws/aws-s3-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc.aws/aws-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc.aws/data-plane-aws-s3/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc.aws/data-plane-provision-aws-s3/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc.aws/validator-data-address-s3/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc.azure/azure-blob-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc.azure/data-plane-azure-storage/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc.azure/data-plane-provision-blob/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/accesstokendata-store-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/api-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/api-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/api-observability/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/asset-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/asset-index-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/asset-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/auth-configuration/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/auth-delegated/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/auth-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/auth-tokenbased/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/boot-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/boot-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/boot/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/callback-event-dispatcher/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/callback-http-dispatcher/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/callback-static-endpoint/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/catalog-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/catalog-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/cel-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/configuration-filesystem/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/connector-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/connector-participant-context-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/console-monitor/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-agreement-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-definition-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-definition-store-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-negotiation-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-negotiation-store-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/contract-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-api-configuration/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-aggregate-services/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-api-client/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-catalog/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-contract-manager/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-contract/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-policies-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-transfer-manager/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-transfer-provision-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-transfer/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/control-plane-transform/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/controlplane-base-bom/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/controlplane-dcp-bom/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/controlplane-feature-sql-bom/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/core-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/crawler-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/crypto-common-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-address-http-data-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-http-oauth2-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-http-oauth2/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-http-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-http/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-iam/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-instance-store-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-selector-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-selector-client/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-selector-control-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-selector-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-selector-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-self-registration/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-signaling-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-signaling-client/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-signaling-transform/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/data-plane-store-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dataplane-base-bom/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dataplane-feature-sql-bom/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/decentralized-claims-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/decentralized-claims-issuers-configuration/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/decentralized-claims-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/decentralized-claims-service/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/decentralized-claims-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/decentralized-claims-sts-remote-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/decentralized-claims-transform/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/decentralized-identity/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-catalog-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-catalog-http-api-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-catalog-http-api-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-catalog-http-dispatcher/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-catalog-transform-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-catalog-transform-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-catalog-validation-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-http-api-base-configuration/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-http-api-configuration-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-http-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-http-dispatcher-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-http-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-negotiation-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-negotiation-http-api-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-negotiation-http-api-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-negotiation-http-dispatcher/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-negotiation-transform-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-negotiation-transform-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-negotiation-validation-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-spi-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-transfer-process-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-transfer-process-http-api-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-transfer-process-http-api-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-transfer-process-http-dispatcher/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-transfer-process-transform-2025/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-transfer-process-transform-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-transfer-process-validation-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-version-http-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-version-transform-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp-version/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/dsp/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/edr-index-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/edr-store-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/edr-store-receiver/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/edr-store-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/encryption-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/encryption-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/federated-catalog-cache-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/federated-catalog-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/holder-credential-request-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/http-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/http-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/http/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/identity-did-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/identity-did-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/identity-did-web/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jersey-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jersey-micrometer/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jersey-providers-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jetty-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jetty-micrometer/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/json-ld-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/json-ld-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/json-ld/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/json-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jti-validation-store-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jws2020-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jwt-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/jwt-verifiable-credentials/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/keys-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/keys-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/ldp-verifiable-credentials/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/management-api-configuration/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/management-api-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/management-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/micrometer-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/oauth2-client/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/oauth2-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/participant-context-config-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/participant-context-config-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/participant-context-connector-classic-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/participant-context-connector-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/participant-context-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/participant-context-single-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/participant-context-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/participant-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-definition-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-definition-store-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-engine-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-engine-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-evaluator-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-model/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-monitor-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-monitor-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-monitor-store-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/policy-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/protocol-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/query-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/request-policy-context-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/runtime-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/runtime-metamodel/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/secrets-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-bootstrapper/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-lease-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-lease-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-lease/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/sql-pool-apache-commons/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/state-machine-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/store-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/token-core/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/token-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/token-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transaction-datasource-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transaction-local/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transaction-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-data-plane-signaling/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-process-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-process-store-sql/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transfer-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transform-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/transform-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/util-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/validator-data-address-http-data/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/validator-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/validator-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/vault-hashicorp-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/vault-hashicorp/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/verifiable-credential-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/verifiable-credentials-lib/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/verifiable-credentials-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/verifiable-credentials/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/version-api/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.edc/web-spi/0.17.0, Apache-2.0, approved, technology.edc +maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-bom/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-servlet/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-servlet/12.1.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket/12.1.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-bom/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-http/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-http/12.1.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-io/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-security/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-server/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-server/12.1.7, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-session/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-util/12.1.10, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.parsson/parsson/1.1.7, EPL-2.0, approved, ee4j.parsson +maven/mavencentral/org.flywaydb/flyway-core/13.3.0, Apache-2.0, approved, #30537 +maven/mavencentral/org.flywaydb/flyway-database-cockroachdb/13.3.0, Apache-2.0, approved, #30538 +maven/mavencentral/org.flywaydb/flyway-database-postgresql/13.3.0, Apache-2.0, approved, #30539 +maven/mavencentral/org.glassfish.hk2.external/aopalliance-repackaged/4.0.0-M3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish +maven/mavencentral/org.glassfish.hk2/hk2-api/4.0.0-M3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish +maven/mavencentral/org.glassfish.hk2/hk2-locator/4.0.0-M3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish +maven/mavencentral/org.glassfish.hk2/hk2-utils/4.0.0-M3, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish +maven/mavencentral/org.glassfish.hk2/osgi-resource-locator/3.0.0, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.glassfish +maven/mavencentral/org.glassfish.jersey.containers/jersey-container-servlet/4.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.core/jersey-client/4.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.core/jersey-common/4.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.core/jersey-server/4.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.ext/jersey-entity-filtering/4.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.inject/jersey-hk2/4.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.media/jersey-media-json-jackson/4.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish.jersey.media/jersey-media-multipart/4.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jersey +maven/mavencentral/org.glassfish/jakarta.json/2.0.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jsonp +maven/mavencentral/org.hdrhistogram/HdrHistogram/2.2.2, BSD-2-Clause AND CC0-1.0 AND CC0-1.0, approved, #14828 +maven/mavencentral/org.javassist/javassist/3.30.2-GA, Apache-2.0 AND LGPL-2.1-or-later AND MPL-1.1, approved, #12108 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib/2.1.21, Apache-2.0, approved, #17634 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib/2.2.20, Apache-2.0, approved, #22410 +maven/mavencentral/org.jetbrains.kotlin/kotlin-stdlib/2.2.21, Apache-2.0, approved, #22410 +maven/mavencentral/org.jetbrains/annotations/13.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jetbrains/annotations/26.1.0, Apache-2.0, approved, #26477 +maven/mavencentral/org.jspecify/jspecify/1.0.0, Apache-2.0, approved, #21897 +maven/mavencentral/org.jvnet.mimepull/mimepull/1.9.15, CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, CQ21484 +maven/mavencentral/org.latencyutils/LatencyUtils/2.0.3, CC0-1.0, approved, #15280 +maven/mavencentral/org.postgresql/postgresql/42.7.10, BSD-2-Clause AND Apache-2.0, approved, #11681 +maven/mavencentral/org.postgresql/postgresql/42.7.13, BSD-2-Clause AND Apache-2.0, approved, #11681 +maven/mavencentral/org.reactivestreams/reactive-streams/1.0.4, CC0-1.0, approved, CQ16332 +maven/mavencentral/org.slf4j/slf4j-api/1.7.36, MIT, approved, CQ13368 +maven/mavencentral/org.slf4j/slf4j-api/1.7.7, MIT, approved, CQ9827 +maven/mavencentral/org.slf4j/slf4j-api/2.0.17, MIT, approved, #5915 +maven/mavencentral/org.slf4j/slf4j-api/2.0.18, MIT, approved, #5915 +maven/mavencentral/org.slf4j/slf4j-api/2.0.9, MIT, approved, #5915 +maven/mavencentral/org.yaml/snakeyaml/2.3, Apache-2.0 AND (Apache-2.0 OR BSD-3-Clause OR EPL-1.0 OR GPL-2.0-or-later OR LGPL-2.1-or-later), approved, #16046 +maven/mavencentral/org.yaml/snakeyaml/2.5, Apache-2.0, approved, #23100 +maven/mavencentral/software.amazon.awssdk/annotations/2.42.34, Apache-2.0, approved, #26484 +maven/mavencentral/software.amazon.awssdk/apache-client/2.42.34, Apache-2.0, approved, #26485 +maven/mavencentral/software.amazon.awssdk/arns/2.42.34, Apache-2.0, approved, #26486 +maven/mavencentral/software.amazon.awssdk/auth/2.42.34, Apache-2.0, approved, #26487 +maven/mavencentral/software.amazon.awssdk/aws-core/2.42.34, Apache-2.0, approved, #26488 +maven/mavencentral/software.amazon.awssdk/aws-query-protocol/2.42.34, Apache-2.0, approved, #26489 +maven/mavencentral/software.amazon.awssdk/aws-xml-protocol/2.42.34, Apache-2.0, approved, #26490 +maven/mavencentral/software.amazon.awssdk/checksums-spi/2.42.34, Apache-2.0, approved, #26491 +maven/mavencentral/software.amazon.awssdk/checksums/2.42.34, Apache-2.0, approved, #26492 +maven/mavencentral/software.amazon.awssdk/crt-core/2.42.34, Apache-2.0, approved, #26493 +maven/mavencentral/software.amazon.awssdk/endpoints-spi/2.42.34, Apache-2.0, approved, #26494 +maven/mavencentral/software.amazon.awssdk/http-auth-aws-eventstream/2.42.34, Apache-2.0, approved, #26495 +maven/mavencentral/software.amazon.awssdk/http-auth-aws/2.42.34, Apache-2.0, approved, #26496 +maven/mavencentral/software.amazon.awssdk/http-auth-spi/2.42.34, Apache-2.0, approved, #26497 +maven/mavencentral/software.amazon.awssdk/http-auth/2.42.34, Apache-2.0, approved, #26498 +maven/mavencentral/software.amazon.awssdk/http-client-spi/2.42.34, Apache-2.0, approved, #26499 +maven/mavencentral/software.amazon.awssdk/iam/2.42.34, Apache-2.0, approved, #30540 +maven/mavencentral/software.amazon.awssdk/identity-spi/2.42.34, Apache-2.0, approved, #26500 +maven/mavencentral/software.amazon.awssdk/json-utils/2.42.34, Apache-2.0, approved, #26501 +maven/mavencentral/software.amazon.awssdk/metrics-spi/2.42.34, Apache-2.0, approved, #26502 +maven/mavencentral/software.amazon.awssdk/netty-nio-client/2.42.34, Apache-2.0, approved, #26503 +maven/mavencentral/software.amazon.awssdk/profiles/2.42.34, Apache-2.0, approved, #26504 +maven/mavencentral/software.amazon.awssdk/protocol-core/2.42.34, Apache-2.0, approved, #26505 +maven/mavencentral/software.amazon.awssdk/regions/2.42.34, Apache-2.0, approved, #26506 +maven/mavencentral/software.amazon.awssdk/retries-spi/2.42.34, Apache-2.0, approved, #26507 +maven/mavencentral/software.amazon.awssdk/retries/2.42.34, Apache-2.0, approved, #26508 +maven/mavencentral/software.amazon.awssdk/s3/2.42.34, Apache-2.0, approved, #26510 +maven/mavencentral/software.amazon.awssdk/sdk-core/2.42.34, Apache-2.0, approved, #26511 +maven/mavencentral/software.amazon.awssdk/sts/2.42.34, Apache-2.0, approved, #27697 +maven/mavencentral/software.amazon.awssdk/third-party-jackson-core/2.42.34, Apache-2.0, approved, #26512 +maven/mavencentral/software.amazon.awssdk/utils-lite/2.42.34, Apache-2.0, approved, #26513 +maven/mavencentral/software.amazon.awssdk/utils/2.42.34, Apache-2.0, approved, #26514 +maven/mavencentral/software.amazon.eventstream/eventstream/1.0.1, Apache-2.0, approved, #19691 +maven/mavencentral/tools.jackson.core/jackson-core/3.1.5, Apache-2.0 AND MIT, approved, #26415 +maven/mavencentral/tools.jackson.core/jackson-databind/3.1.5, Apache-2.0, approved, #26439 +maven/mavencentral/tools.jackson/jackson-bom/3.1.5, Apache-2.0, approved, #26520 diff --git a/build.gradle.kts b/build.gradle.kts index 82be11fe4e..79272dad2e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -63,22 +63,25 @@ allprojects { apply(plugin = "jacoco") dependencies { + implementation("org.slf4j:slf4j-api:2.0.18") - implementation("org.slf4j:slf4j-api:2.0.17") + implementation(platform("io.netty:netty-bom:4.1.137.Final")) { + because("CVE-2026-56819/56745/55833/55831/59901/50010/47691/45674/45416/44249/42587/42584/42579/42583: netty fixed in 4.1.136; CVE-2026-59903 (CorsHandler Vary header cache poisoning) backported to 4.1.137") + } + implementation(platform("org.eclipse.jetty:jetty-bom:12.1.10")) { + because("CVE-2026-10050: jetty-security Digest auth bypass, fixed in 12.1.10") + } + implementation(platform("org.eclipse.jetty.ee10:jetty-ee10-bom:12.1.10")) { + because("CVE-2026-10050: aligns Jetty ee10 modules with jetty-bom 12.1.10") + } + implementation(platform("com.fasterxml.jackson:jackson-bom:2.21.4")) { + because("CVE-2026-54513/54512 + GHSA-r7wm-3cxj-wff9: jackson-databind/core fixed in 2.21.4") + } constraints { plugins.apply("org.gradle.java-test-fixtures") - implementation("org.yaml:snakeyaml:2.6") { - because("version 1.33 has vulnerabilities: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-1471.") - } - implementation("net.minidev:json-smart:2.6.0") { - because("version 2.4.8 has vulnerabilities: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1370.") - } - implementation("com.azure:azure-core-http-netty:1.16.3") { - 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.9.Final") { - because("Version 4.1.123.Final vulnerability: https://www.cve.org/CVERecord?id=CVE-2025-8916") + implementation("org.eclipse.jetty.websocket:jetty-websocket:12.1.10") { + because("CVE-2026-10050: align jetty-websocket aggregator (not in jetty-bom) with jetty 12.1.10") } } } @@ -97,10 +100,6 @@ allprojects { scmUrl.set(txScmUrl) } swagger { - title.set((project.findProperty("apiTitle") ?: "Tractus-X REST API") as String) - description = - (project.findProperty("apiDescription") - ?: "Tractus-X REST APIs - merged by OpenApiMerger") as String outputFilename.set(project.name) outputDirectory.set(file("${rootProject.projectDir.path}/resources/openapi/yaml")) resourcePackages = setOf("org.eclipse.tractusx.edc") diff --git a/charts/tractusx-connector-memory/Chart.yaml b/charts/tractusx-connector-memory/Chart.yaml index 5c2ee996f0..66f45f6f9e 100644 --- a/charts/tractusx-connector-memory/Chart.yaml +++ b/charts/tractusx-connector-memory/Chart.yaml @@ -35,12 +35,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.13.0-SNAPSHOT +version: 0.13.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.13.0-SNAPSHOT" +appVersion: "0.13.0" home: https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector-memory sources: - https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector-memory diff --git a/charts/tractusx-connector-memory/README.md b/charts/tractusx-connector-memory/README.md index 22281ab276..ea04bc239e 100644 --- a/charts/tractusx-connector-memory/README.md +++ b/charts/tractusx-connector-memory/README.md @@ -1,12 +1,12 @@ # tractusx-connector-memory -![Version: 0.13.0-SNAPSHOT](https://img.shields.io/badge/Version-0.13.0--SNAPSHOT-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.13.0-SNAPSHOT](https://img.shields.io/badge/AppVersion-0.13.0--SNAPSHOT-informational?style=flat-square) +![Version: 0.13.0](https://img.shields.io/badge/Version-0.13.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.13.0](https://img.shields.io/badge/AppVersion-0.13.0-informational?style=flat-square) A Helm chart for Tractus-X Eclipse Data Space Connector based on memory. Please only use this for development or testing purposes, never in production workloads! **Homepage:** -## Setting up IATP +## Setting up DCP ### Preconditions @@ -19,15 +19,15 @@ A Helm chart for Tractus-X Eclipse Data Space Connector based on memory. Please - store client secret in the HashiCorp vault using an alias. The exact procedure will depend on your deployment of HashiCorp Vault and is out of scope of this document. But by default, Tractus-X EDC expects to find the secret under `secret/client-secret`. The alias must be configured - using the `iatp.sts.oauth.client.secret_alias` Helm value. + using the `dcp.sts.oauth.client.secret_alias` Helm value. ### Configure the chart Be sure to provide the following configuration entries to your Tractus-X EDC Helm chart: -- `iatp.sts.oauth.token_url`: the token endpoint of DIV -- `iatp.sts.oauth.client.id`: the client ID of your tenant in DIV -- `iatp.sts.oauth.client.secret_alias`: alias under which you saved your DIV client secret in the vault -- `iatp.sts.div.url`: the base URL for DIV +- `dcp.sts.oauth.token_url`: the token endpoint of DIV +- `dcp.sts.oauth.client.id`: the client ID of your tenant in DIV +- `dcp.sts.oauth.client.secret_alias`: alias under which you saved your DIV client secret in the vault +- `dcp.sts.div.url`: the base URL for DIV In addition, in order to map BPNs to DIDs, a new service is required, called the BPN-DID Resolution Service, which must be configured: @@ -41,7 +41,7 @@ Combined, run this shell command to start the in-memory Tractus-X EDC runtime: ```shell helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/tractusx-connector-memory --version 0.13.0-SNAPSHOT \ +helm install my-release tractusx-edc/tractusx-connector-memory --version 0.13.0 \ -f /tractusx-connector-memory-test.yaml \ --set vault.secrets="client-secret:$YOUR_CLIENT_SECRET" ``` @@ -56,23 +56,22 @@ helm install my-release tractusx-edc/tractusx-connector-memory --version 0.13.0- |-----|------|---------|-------------| | customCaCerts | object | `{}` | Add custom ca certificates to the truststore | | customLabels | object | `{}` | Add some custom labels | +| dcp.cache.enabled | bool | `true` | Whether the Verifiable Presentation cache is enabled | +| dcp.cache.validity | int | `86400` | Validity of the Verifiable Presentation cache in seconds | +| dcp.didService.selfRegistration.enabled | bool | `false` | Whether Service Self Registration is enabled | +| dcp.sts.div.url | string | `nil` | URL where connectors can request SI tokens | +| dcp.sts.oauth.client.id | string | `nil` | Client ID for requesting OAuth2 access token for DIV access | +| dcp.sts.oauth.client.secret_alias | string | `nil` | Alias under which the client secret is stored in the vault for requesting OAuth2 access token for DIV access | +| dcp.sts.oauth.token_url | string | `nil` | URL where connectors can request OAuth2 access tokens for DIV access | +| dcp.trustedIssuers | list | `[]` | Configures the trusted issuers for this runtime. If no supportedTypes are specified, the value defaults to "*" for that issuer | | 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.div.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 DIV access | -| iatp.sts.oauth.client.secret_alias | string | `nil` | Alias under which the client secret is stored in the vault for requesting OAuth2 access token for DIV access | -| iatp.sts.oauth.token_url | string | `nil` | URL where connectors can request OAuth2 access tokens for DIV access | -| iatp.trustedIssuers | list | `[]` | Configures the trusted issuers for this runtime. If no supportedTypes are specified, the value defaults to "*" for that issuer | | imagePullSecrets | list | `[]` | Existing image pull secret to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | | 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.bpnl | string | `"BPNLCHANGEME"` | BPNL Number | | participant.contextId | string | `"UUID CHANGEME"` | Participant Context Id - Newly introduced id for a connector instance (needed for multitenancy) | -| participant.id | string | `"BPNLCHANGEME"` | BPN Number | +| participant.id | string | `"did:web:changeme"` | Participant Id, resp. the Decentralized IDentifier (DID) of the connector | | 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/) | | runtime.autoscaling.maxReplicas | int | `100` | Maximum replicas if resource consumption exceeds resource threshholds | @@ -142,6 +141,7 @@ helm install my-release tractusx-edc/tractusx-connector-memory --version 0.13.0- | runtime.livenessProbe.timeoutSeconds | int | `5` | number of seconds after which the probe times out | | runtime.logs.level | string | `"DEBUG"` | Defines the log granularity of the default Console Monitor. | | runtime.nodeSelector | object | `{}` | [node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain pods to nodes | +| runtime.opentelemetry | string | `"otel.javaagent.enabled=false\notel.javaagent.debug=false"` | configuration of the [Open Telemetry Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) to collect and expose metrics | | runtime.podAnnotations | object | `{}` | additional annotations for the pod | | runtime.podLabels | object | `{}` | additional labels for the pod | | runtime.podSecurityContext | object | `{"fsGroup":10001,"runAsGroup":10001,"runAsUser":10001,"seccompProfile":{"type":"RuntimeDefault"}}` | The [pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod) defines privilege and access control settings for a Pod within the deployment | diff --git a/charts/tractusx-connector-memory/README.md.gotmpl b/charts/tractusx-connector-memory/README.md.gotmpl index fc0927964a..119f8fc53a 100644 --- a/charts/tractusx-connector-memory/README.md.gotmpl +++ b/charts/tractusx-connector-memory/README.md.gotmpl @@ -8,7 +8,7 @@ {{ template "chart.homepageLine" . }} -## Setting up IATP +## Setting up DCP ### Preconditions @@ -21,16 +21,16 @@ - store client secret in the HashiCorp vault using an alias. The exact procedure will depend on your deployment of HashiCorp Vault and is out of scope of this document. But by default, Tractus-X EDC expects to find the secret under `secret/client-secret`. The alias must be configured - using the `iatp.sts.oauth.client.secret_alias` Helm value. + using the `dcp.sts.oauth.client.secret_alias` Helm value. ### Configure the chart Be sure to provide the following configuration entries to your Tractus-X EDC Helm chart: -- `iatp.sts.oauth.token_url`: the token endpoint of DIV -- `iatp.sts.oauth.client.id`: the client ID of your tenant in DIV -- `iatp.sts.oauth.client.secret_alias`: alias under which you saved your DIV client secret in the vault -- `iatp.sts.div.url`: the base URL for DIV +- `dcp.sts.oauth.token_url`: the token endpoint of DIV +- `dcp.sts.oauth.client.id`: the client ID of your tenant in DIV +- `dcp.sts.oauth.client.secret_alias`: alias under which you saved your DIV client secret in the vault +- `dcp.sts.div.url`: the base URL for DIV In addition, in order to map BPNs to DIDs, a new service is required, called the BPN-DID Resolution Service, which must be configured: diff --git a/charts/tractusx-connector-memory/templates/configmap-otel.yaml b/charts/tractusx-connector-memory/templates/configmap-otel.yaml new file mode 100644 index 0000000000..04d95e2d8b --- /dev/null +++ b/charts/tractusx-connector-memory/templates/configmap-otel.yaml @@ -0,0 +1,34 @@ +################################################################################# + # Copyright (c) 2023 ZF Friedrichshafen AG + # Copyright (c) 2023 Mercedes-Benz Tech Innovation GmbH + # Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + # Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + # + # 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 + ################################################################################# + + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "txdc.fullname" . }}-runtime + namespace: {{ .Release.Namespace | default "default" | quote }} + labels: + {{- include "txdc.runtime.labels" . | nindent 4 }} +data: + opentelemetry.properties: |- + {{- .Values.runtime.opentelemetry | nindent 4 }} diff --git a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml index 84de5ee953..2e8896ddf4 100644 --- a/charts/tractusx-connector-memory/templates/deployment-runtime.yaml +++ b/charts/tractusx-connector-memory/templates/deployment-runtime.yaml @@ -146,13 +146,13 @@ spec: ## ID CONFIGURATION ## ######################## - name: EDC_PARTICIPANT_ID - value: {{ .Values.iatp.id | required ".Values.iatp.id is required" | quote }} + value: {{ .Values.participant.id | required ".Values.participant.id is required" | quote }} - name: "EDC_IAM_ISSUER_ID" - value: {{ .Values.iatp.id | required ".Values.iatp.id is required" | quote }} + 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: "TRACTUSX_EDC_PARTICIPANT_BPN" - value: {{ .Values.participant.id | required ".Values.participant.id is required" | quote }} + value: {{ .Values.participant.bpnl | required ".Values.participant.bpnl is required" | quote }} ########################### ## LOGGING CONFIGURATION ## @@ -223,21 +223,21 @@ spec: value: {{ printf "%s%s" (include "txdc.runtime.url.protocol" . ) .Values.runtime.endpoints.protocol.path | quote }} ############################# - ## IATP / STS / DIV CONFIG ## + ## DCP / STS / DIV CONFIG ## ############################# - name: "EDC_IAM_STS_OAUTH_TOKEN_URL" - value: {{ .Values.iatp.sts.oauth.token_url | required ".Values.iatp.sts.oauth.token_url is required" | quote}} + value: {{ .Values.dcp.sts.oauth.token_url | required ".Values.dcp.sts.oauth.token_url is required" | quote}} - name: "EDC_IAM_STS_OAUTH_CLIENT_ID" - value: {{ .Values.iatp.sts.oauth.client.id | required ".Values.iatp.sts.oauth.client.id is required" | quote}} + value: {{ .Values.dcp.sts.oauth.client.id | required ".Values.dcp.sts.oauth.client.id is required" | quote}} - name: "EDC_IAM_STS_OAUTH_CLIENT_SECRET_ALIAS" - value: {{ .Values.iatp.sts.oauth.client.secret_alias | required ".Values.iatp.sts.oauth.client.secret_alias is required" | quote}} + value: {{ .Values.dcp.sts.oauth.client.secret_alias | required ".Values.dcp.sts.oauth.client.secret_alias is required" | quote}} - {{- if .Values.iatp.sts.div.url }} + {{- if .Values.dcp.sts.div.url }} - name: "TX_EDC_IAM_STS_DIV_URL" - value: {{ .Values.iatp.sts.div.url | quote }} + value: {{ .Values.dcp.sts.div.url | quote }} {{- end }} - {{- range $index, $issuer := .Values.iatp.trustedIssuers }} + {{- range $index, $issuer := .Values.dcp.trustedIssuers }} {{- if eq (kindOf $issuer) "string" }} - name: "EDC_IAM_TRUSTED-ISSUER_{{$index}}-ISSUER_ID" value: {{ $issuer | quote }} @@ -251,24 +251,22 @@ spec: {{- end }} {{- end }} - name: "TX_EDC_DID_SERVICE_SELF_REGISTRATION_ENABLED" - value: {{ .Values.iatp.didService.selfRegistration.enabled | quote}} + value: {{ .Values.dcp.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 }} + value: {{ .Values.dcp.cache.enabled | quote }} - name: "TX_EDC_DCP_CACHE_VALIDITY_SECONDS" - value: {{ .Values.iatp.cache.validity | quote }} + value: {{ .Values.dcp.cache.validity | quote }} ################# ## BDRS CLIENT ## ################# - - name: "TX_EDC_IAM_IATP_BDRS_SERVER_URL" + - name: "TX_EDC_IAM_DCP_BDRS_SERVER_URL" value: {{ .Values.runtime.bdrs.server.url | required ".Values.runtime.bdrs.server.url is required" | quote }} {{- if .Values.runtime.bdrs.cache_validity_seconds }} - - name: "TX_EDC_IAM_IATP_BDRS_CACHE_VALIDITY" + - name: "TX_EDC_IAM_DCP_BDRS_CACHE_VALIDITY" value: {{ .Values.runtime.bdrs.cache_validity_seconds | quote}} {{- end}} @@ -350,6 +348,9 @@ spec: mountPath: /opt/java/openjdk/lib/security/cacerts subPath: cacerts {{- end }} + - name: "configuration" + mountPath: "/app/opentelemetry.properties" + subPath: "opentelemetry.properties" - name: log4j2-config mountPath: /app/log4j2.yaml subPath: log4j2.yaml @@ -365,6 +366,12 @@ spec: emptyDir: sizeLimit: 1Mi {{- end }} + - name: "configuration" + configMap: + name: {{ include "txdc.fullname" . }}-runtime + items: + - key: "opentelemetry.properties" + path: "opentelemetry.properties" - name: "log4j2-config" configMap: name: {{ include "txdc.fullname" . }}-log4j2 diff --git a/charts/tractusx-connector-memory/values.yaml b/charts/tractusx-connector-memory/values.yaml index 19c83d0fab..fce7417bc4 100644 --- a/charts/tractusx-connector-memory/values.yaml +++ b/charts/tractusx-connector-memory/values.yaml @@ -33,14 +33,14 @@ imagePullSecrets: [] customLabels: {} participant: - # -- BPN Number - id: "BPNLCHANGEME" + # -- Participant Id, resp. the Decentralized IDentifier (DID) of the connector + id: "did:web:changeme" + # -- BPNL Number + bpnl: "BPNLCHANGEME" # -- Participant Context Id - Newly introduced id for a connector instance (needed for multitenancy) contextId: "UUID CHANGEME" -iatp: - # -- Decentralized IDentifier (DID) of the connector - id: "did:web:changeme" +dcp: # -- Configures the trusted issuers for this runtime. If no supportedTypes are specified, the value defaults to "*" for that issuer trustedIssuers: [] # - id: "did:web:example1.com" @@ -63,8 +63,6 @@ iatp: 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: @@ -382,6 +380,11 @@ runtime: # -- targetAverageUtilization of memory provided to a pod targetMemoryUtilizationPercentage: 80 + # -- configuration of the [Open Telemetry Agent](https://opentelemetry.io/docs/instrumentation/java/automatic/agent-config/) to collect and expose metrics + opentelemetry: |- + otel.javaagent.enabled=false + otel.javaagent.debug=false + # -- [node selector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) to constrain pods to nodes nodeSelector: {} # -- [tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) to configure preferred nodes diff --git a/charts/tractusx-connector/Chart.yaml b/charts/tractusx-connector/Chart.yaml index ff4e000e0e..730880e58e 100644 --- a/charts/tractusx-connector/Chart.yaml +++ b/charts/tractusx-connector/Chart.yaml @@ -41,12 +41,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.13.0-SNAPSHOT +version: 0.13.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.13.0-SNAPSHOT" +appVersion: "0.13.0" home: https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector sources: - https://github.com/eclipse-tractusx/tractusx-edc/tree/main/charts/tractusx-connector @@ -54,12 +54,12 @@ dependencies: # HashiCorp Vault - name: vault alias: vault - version: "0.27.0" + version: "0.28.0" repository: https://helm.releases.hashicorp.com condition: install.vault # PostgreSQL - - name: postgresql + - name: postgres alias: postgresql - version: "15.2.1" - repository: https://charts.bitnami.com/bitnami + version: "0.18.3" + repository: oci://registry-1.docker.io/cloudpirates condition: install.postgresql diff --git a/charts/tractusx-connector/README.md b/charts/tractusx-connector/README.md index 8ee56f90a2..cb60fbbcf3 100644 --- a/charts/tractusx-connector/README.md +++ b/charts/tractusx-connector/README.md @@ -1,6 +1,6 @@ # tractusx-connector -![Version: 0.13.0-SNAPSHOT](https://img.shields.io/badge/Version-0.13.0--SNAPSHOT-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.13.0-SNAPSHOT](https://img.shields.io/badge/AppVersion-0.13.0--SNAPSHOT-informational?style=flat-square) +![Version: 0.13.0](https://img.shields.io/badge/Version-0.13.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.13.0](https://img.shields.io/badge/AppVersion-0.13.0-informational?style=flat-square) A Helm chart for Tractus-X Eclipse Data Space Connector. The connector deployment consists of two runtime consists of a Control Plane and a Data Plane. Note that _no_ external dependencies such as a PostgreSQL database and HashiCorp Vault are included. @@ -9,7 +9,7 @@ This chart is intended for use with an _existing_ PostgreSQL database and an _ex **Homepage:** -## Setting up IATP +## Setting up DCP ### Preconditions @@ -22,20 +22,78 @@ This chart is intended for use with an _existing_ PostgreSQL database and an _ex - store client secret in the HashiCorp vault using an alias. The exact procedure will depend on your deployment of HashiCorp Vault and is out of scope of this document. But by default, Tractus-X EDC expects to find the secret under `secret/client-secret`. The alias must be configured - using the `iatp.sts.oauth.client.secret_alias` Helm value. + using the `dcp.sts.oauth.client.secret_alias` Helm value. ### Configure the chart Be sure to provide the following configuration entries to your Tractus-X EDC Helm chart: -- `iatp.sts.oauth.token_url`: the token endpoint of DIV -- `iatp.sts.oauth.client.id`: the client ID of your tenant in DIV -- `iatp.sts.oauth.client.secret_alias`: alias under which you saved your DIV client secret in the vault -- `iatp.sts.div.url`: the base URL for DIV +- `dcp.sts.oauth.token_url`: the token endpoint of DIV +- `dcp.sts.oauth.client.id`: the client ID of your tenant in DIV +- `dcp.sts.oauth.client.secret_alias`: alias under which you saved your DIV client secret in the vault +- `dcp.sts.div.url`: the base URL for DIV In addition, in order to map BPNs to DIDs, a new service is required, called the BPN-DID Resolution Service, which must be configured: - `controlplane.bdrs.server.url`: base URL of the BPN-DID Resolution Service ("BDRS") +#### Database configuration + +When bringing your own database, respectively not installing postgres (`install.postgresql: false`), use the following information: + +```yaml +postgresql: + jdbcUrl: "jdbc:postgresql://{{ .Release.Name }}-postgresql:5432/edc" + auth: + username: "postgres" + password: "password" +``` + +When installing postgres with the chart, it is **RECOMMENDED** to use a custom user beside the admin user: + +> [!note] +> The value `postgresql.jdbUrl` is composed in the deployments using a dependent variable of the database name and the name of the postgress deployment. + +```yaml +postgresql: + auth: + database: "postgres" + username: "postgres" + password: "password" # generated if empty + customUser: # name or exisitng secret must be set + name: "edc" + database: "edc" # must ge different than postgresql.auth.database + password: "password" # generated if empty +``` + +When installing postgres with the chart, you can alteratively not create and use a custom user (**NOT RECOMMENDED**). + +```yaml +postgresql: + auth: + database: "postgres" + username: "postgres" + password: "password" # generated if empty + customUser: + existingSecret: "" # explicitly set to empty + name: "" # explicitly set to empty +``` + +Further you can also reuse existing secrets instead: + +```yaml +postgresql: + auth: + existingSecret: "my-existing-secret-name" + secretKeys: + adminPasswordKey: "postgres-password" + customUser: + existingSecret: "my-existing-secret-name" + secretKeys: + password: "CUSTOM_PASSWORD" + name: "CUSTOM_USER" + database: "CUSTOM_DB" +``` + ### Launching the application As an easy starting point, please consider using [this example configuration](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml) @@ -44,7 +102,7 @@ Combined, run this shell command to start the in-memory Tractus-X EDC runtime: ```shell helm repo add tractusx-edc https://eclipse-tractusx.github.io/charts/dev -helm install my-release tractusx-edc/tractusx-connector --version 0.13.0-SNAPSHOT \ +helm install my-release tractusx-edc/tractusx-connector --version 0.13.0 \ -f /tractusx-connector-test.yaml ``` @@ -56,8 +114,8 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.13.0-SNAPSHO | Repository | Name | Version | |------------|------|---------| -| https://charts.bitnami.com/bitnami | postgresql(postgresql) | 15.2.1 | -| https://helm.releases.hashicorp.com | vault(vault) | 0.27.0 | +| https://helm.releases.hashicorp.com | vault(vault) | 0.28.0 | +| oci://registry-1.docker.io/cloudpirates | postgresql(postgres) | 0.18.3 | ## Values @@ -70,7 +128,7 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.13.0-SNAPSHO | controlplane.autoscaling.targetCPUUtilizationPercentage | int | `80` | targetAverageUtilization of cpu provided to a pod | | controlplane.autoscaling.targetMemoryUtilizationPercentage | int | `80` | targetAverageUtilization of memory provided to a pod | | controlplane.bdrs.cache_validity_seconds | int | `600` | Time that a cached BPN/DID resolution map is valid in seconds, default is 600 seconds (10 min) | -| controlplane.bdrs.server.url | string | `nil` | URL of the BPN/DID Resolution Service | +| controlplane.bdrs.server.url | string | `"CHANGEME"` | URL of the BPN/DID Resolution Service | | controlplane.debug.enabled | bool | `false` | Enables java debugging mode. | | controlplane.debug.port | int | `1044` | Port where the debuggee can connect to. | | controlplane.debug.suspendOnStart | bool | `false` | Defines if the JVM should wait with starting the application until someone connected to the debugging port. | @@ -251,23 +309,21 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.13.0-SNAPSHO | dataplane.token.refresh.expiry_seconds | int | `300` | TTL in seconds for access tokens (also known as EDR token) | | dataplane.token.refresh.expiry_tolerance_seconds | int | `10` | Tolerance for token expiry in seconds | | dataplane.token.refresh.refresh_endpoint | string | `nil` | Optional endpoint for an OAuth2 token refresh. Default endpoint is `/token` | -| dataplane.token.signer.privatekey_alias | string | `nil` | Alias under which the private key (JWK or PEM format) is stored in the vault | -| dataplane.token.verifier.publickey_alias | string | `nil` | Alias under which the public key (JWK or PEM format) is stored in the vault, that belongs to the private key which was referred to at `dataplane.token.signer.privatekey_alias` | +| dataplane.token.signer.privatekey_alias | string | `"CHANGEME"` | Alias under which the private key (JWK or PEM format) is stored in the vault | +| dataplane.token.verifier.publickey_alias | string | `"CHANGEME"` | Alias under which the public key (JWK or PEM format) is stored in the vault, that belongs to the private key which was referred to at `dataplane.token.signer.privatekey_alias` | | dataplane.tolerations | list | `[]` | [tolerations](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) to configure preferred nodes | | dataplane.url.public | string | `""` | Explicitly declared url for reaching the public api (e.g. if ingresses not used) | | 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 | +| dcp.cache.enabled | bool | `true` | Whether the Verifiable Presentation cache is enabled | +| dcp.cache.validity | int | `86400` | Validity of the Verifiable Presentation cache in seconds | +| dcp.didService.selfRegistration.enabled | bool | `false` | Whether Service Self Registration is enabled | +| dcp.sts.div.url | string | `nil` | URL where connectors can request SI tokens | +| dcp.sts.oauth.client.id | string | `"CHANGEME"` | Client ID for requesting OAuth2 access token for DIV access | +| dcp.sts.oauth.client.secret_alias | string | `"CHANGEME"` | Alias under which the client secret is stored in the vault for requesting OAuth2 access token for DIV access | +| dcp.sts.oauth.token_url | string | `"CHANGEME"` | URL where connectors can request OAuth2 access tokens for DIV access | +| dcp.trustedIssuers | list | `[]` | Configures the trusted issuers for this runtime. If no supportedTypes are specified, the value defaults to "*" for that issuer | | 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.div.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 DIV access | -| iatp.sts.oauth.client.secret_alias | string | `nil` | Alias under which the client secret is stored in the vault for requesting OAuth2 access token for DIV access | -| iatp.sts.oauth.token_url | string | `nil` | URL where connectors can request OAuth2 access tokens for DIV access | -| iatp.trustedIssuers | list | `[]` | Configures the trusted issuers for this runtime. If no supportedTypes are specified, the value defaults to "*" for that issuer | | imagePullSecrets | list | `[]` | Existing image pull secret to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | | install.postgresql | bool | `true` | Deploying a PostgreSQL instance | | install.vault | bool | `true` | Deploying a HashiCorp Vault instance | @@ -279,16 +335,31 @@ helm install my-release tractusx-edc/tractusx-connector --version 0.13.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.bpnl | string | `"BPNLCHANGEME"` | BPNL Number | | 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"` | | -| postgresql.auth.username | string | `"user"` | | -| postgresql.image.repository | string | `"bitnamilegacy/postgresql"` | | -| postgresql.image.tag | string | `"16.2.0-debian-12-r10"` | | +| participant.id | string | `"did:web:changeme"` | Participant Id, resp. the Decentralized IDentifier (DID) of the connector | +| postgresql.auth.database | string | postgres | Database of the root user. If an exisisting secret is used, this value is overwritten into the existing secret. | +| postgresql.auth.existingSecret | string | {{ .Release.Name }}-postgresql | Name of the existing secret containing the superuser credentials. | +| postgresql.auth.password | string | Autogenerated random alpha=numeric string with 16 characters (if empty). | Password of the root user. If an existing secret is used, this value is overwritten into the existing secret. | +| postgresql.auth.secretKeys.adminPasswordKey | string | postgres-password | Key of the admin password to use of the existing secret. | +| postgresql.auth.username | string | postgres | Username of the root user. If an existing secret is used, this value is overwritten into the existing secret. | +| postgresql.customUser.database | string | edc | Name for the custom database to be created and assigned to the custom user. If an existing secret is used, this value is overwritten into the existing secret. | +| postgresql.customUser.existingSecret | string | {{ .Release.Name }}-postgresql-custom-user-credentials | Name of the existing secret containing the custom user credentials. | +| postgresql.customUser.name | string | edc | Name of the custom user to be created. If an existing secret is used, this value is overwritten into the existing secret. | +| postgresql.customUser.password | string | Autogenerated random alpha=numeric string with 16 characters (if empty). | Password to be used for the custom user. If an existing secret is used, this value is overwritten into the existing secret. | +| postgresql.customUser.secretKeys.database | string | CUSTOM_DB | Key of the custom user database to use of the existing secret. | +| postgresql.customUser.secretKeys.name | string | CUSTOM_USER | Key of the custom user name to use of the existing secret. | +| postgresql.customUser.secretKeys.password | string | CUSTOM_PASSWORD | Key of the custom user password to use of the existing secret. | +| postgresql.image.registry | string | `"docker.io"` | | +| postgresql.image.repository | string | `"postgres"` | | | postgresql.jdbcUrl | string | `"jdbc:postgresql://{{ .Release.Name }}-postgresql:5432/edc"` | | -| postgresql.primary.persistence.enabled | bool | `false` | | -| postgresql.readReplicas.persistence.enabled | bool | `false` | | +| postgresql.persistence.enabled | bool | `false` | | +| postgresql.persistence.size | string | `"10Gi"` | | +| postgresql.persistence.storageClass | string | `"standard"` | | +| postgresql.resources.limits.cpu | int | `1` | | +| postgresql.resources.limits.memory | string | `"1Gi"` | | +| postgresql.resources.requests.cpu | string | `"250m"` | | +| postgresql.resources.requests.memory | string | `"256Mi"` | | | serviceAccount.annotations | object | `{}` | Annotations to add to the service account | | serviceAccount.create | bool | `true` | Specifies whether a service account should be created | | serviceAccount.imagePullSecrets | list | `[]` | Existing image pull secret bound to the service account to use to [obtain the container image from private registries](https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry) | diff --git a/charts/tractusx-connector/README.md.gotmpl b/charts/tractusx-connector/README.md.gotmpl index 9e9a12b487..eef7c90579 100644 --- a/charts/tractusx-connector/README.md.gotmpl +++ b/charts/tractusx-connector/README.md.gotmpl @@ -8,7 +8,7 @@ {{ template "chart.homepageLine" . }} -## Setting up IATP +## Setting up DCP ### Preconditions @@ -21,21 +21,80 @@ - store client secret in the HashiCorp vault using an alias. The exact procedure will depend on your deployment of HashiCorp Vault and is out of scope of this document. But by default, Tractus-X EDC expects to find the secret under `secret/client-secret`. The alias must be configured - using the `iatp.sts.oauth.client.secret_alias` Helm value. + using the `dcp.sts.oauth.client.secret_alias` Helm value. ### Configure the chart Be sure to provide the following configuration entries to your Tractus-X EDC Helm chart: -- `iatp.sts.oauth.token_url`: the token endpoint of DIV -- `iatp.sts.oauth.client.id`: the client ID of your tenant in DIV -- `iatp.sts.oauth.client.secret_alias`: alias under which you saved your DIV client secret in the vault -- `iatp.sts.div.url`: the base URL for DIV +- `dcp.sts.oauth.token_url`: the token endpoint of DIV +- `dcp.sts.oauth.client.id`: the client ID of your tenant in DIV +- `dcp.sts.oauth.client.secret_alias`: alias under which you saved your DIV client secret in the vault +- `dcp.sts.div.url`: the base URL for DIV In addition, in order to map BPNs to DIDs, a new service is required, called the BPN-DID Resolution Service, which must be configured: - `controlplane.bdrs.server.url`: base URL of the BPN-DID Resolution Service ("BDRS") +#### Database configuration + +When bringing your own database, respectively not installing postgres (`install.postgresql: false`), use the following information: + +```yaml +postgresql: + jdbcUrl: "jdbc:postgresql://{{ "{{" }} .Release.Name }}-postgresql:5432/edc" + auth: + username: "postgres" + password: "password" +``` + +When installing postgres with the chart, it is **RECOMMENDED** to use a custom user beside the admin user: + +> [!note] +> The value `postgresql.jdbUrl` is composed in the deployments using a dependent variable of the database name and the name of the postgress deployment. + +```yaml +postgresql: + auth: + database: "postgres" + username: "postgres" + password: "password" # generated if empty + customUser: # name or exisitng secret must be set + name: "edc" + database: "edc" # must ge different than postgresql.auth.database + password: "password" # generated if empty +``` + +When installing postgres with the chart, you can alteratively not create and use a custom user (**NOT RECOMMENDED**). + +```yaml +postgresql: + auth: + database: "postgres" + username: "postgres" + password: "password" # generated if empty + customUser: + existingSecret: "" # explicitly set to empty + name: "" # explicitly set to empty +``` + +Further you can also reuse existing secrets instead: + +```yaml +postgresql: + auth: + existingSecret: "my-existing-secret-name" + secretKeys: + adminPasswordKey: "postgres-password" + customUser: + existingSecret: "my-existing-secret-name" + secretKeys: + password: "CUSTOM_PASSWORD" + name: "CUSTOM_USER" + database: "CUSTOM_DB" +``` + + ### Launching the application As an easy starting point, please consider using [this example configuration](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/edc-tests/deployment/src/main/resources/helm/tractusx-connector-test.yaml) diff --git a/charts/tractusx-connector/templates/_helpers.tpl b/charts/tractusx-connector/templates/_helpers.tpl index a115b22154..b63e5cb708 100644 --- a/charts/tractusx-connector/templates/_helpers.tpl +++ b/charts/tractusx-connector/templates/_helpers.tpl @@ -1,3 +1,22 @@ +{{- /* +* Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) +* Copyright (c) 2023 Contributors to the Eclipse Foundation +* +* 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 +*/}} {{/* Expand the name of the chart. */}} @@ -180,3 +199,20 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} + +{{/* +Create a default fully qualified app name for PostgreSQL. +*/}} +{{- define "txdc.postgresql.fullname" -}} +{{- if .Values.postgresql.fullnameOverride }} +{{- .Values.postgresql.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else if .Values.postgresql.nameOverride }} +{{- printf "%s-%s" .Release.Name .Values.postgresql.nameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-postgresql" .Release.Name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{- define "txdc.postgresql.custom-user-secret" -}} +{{- printf "%s-custom-user-credentials" (include "txdc.postgresql.fullname" .) -}} +{{- end }} \ No newline at end of file diff --git a/core/core-utils/src/main/java/org/eclipse/tractusx/edc/core/utils/PathUtils.java b/core/core-utils/src/main/java/org/eclipse/tractusx/edc/core/utils/PathUtils.java index f439ab596c..95501d8892 100644 --- a/core/core-utils/src/main/java/org/eclipse/tractusx/edc/core/utils/PathUtils.java +++ b/core/core-utils/src/main/java/org/eclipse/tractusx/edc/core/utils/PathUtils.java @@ -26,7 +26,7 @@ private PathUtils() { public static String removeTrailingSlash(String path) { var fixedPath = path; - if (fixedPath.endsWith("/")) { + if (fixedPath != null && fixedPath.endsWith("/")) { fixedPath = fixedPath.substring(0, fixedPath.length() - 1); } return fixedPath; diff --git a/core/json-ld-core/build.gradle.kts b/core/json-ld-core/build.gradle.kts index 70123e50f9..68d3b018bd 100644 --- a/core/json-ld-core/build.gradle.kts +++ b/core/json-ld-core/build.gradle.kts @@ -27,7 +27,7 @@ dependencies { implementation(libs.edc.spi.core) implementation(libs.edc.spi.jsonld) implementation(libs.dsp.spi.v2025) - implementation(libs.dsp.spi.v08) + implementation(project(":spi:dsp-spi-08")) implementation(libs.edc.lib.management.api) testImplementation(testFixtures(libs.edc.junit)) } diff --git a/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtension.java b/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtension.java index 0287e98845..b54ed1cf01 100644 --- a/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtension.java +++ b/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtension.java @@ -23,18 +23,12 @@ import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; -import java.io.File; -import java.util.Map; - import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_SCOPE; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DSP_SCOPE_V_2025_1; -import static org.eclipse.tractusx.edc.core.utils.FileUtils.getResourceFile; -import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.EDC_CONTEXT; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.TX_AUTH_NS; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.TX_AUTH_PREFIX; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.TX_NAMESPACE; @@ -42,20 +36,8 @@ public class JsonLdExtension implements ServiceExtension { - public static final String CREDENTIALS_V_1 = "https://www.w3.org/2018/credentials/v1"; - - public static final String SECURITY_JWS_V1 = "https://w3id.org/security/suites/jws-2020/v1"; - public static final String SECURITY_ED25519_V1 = "https://w3id.org/security/suites/ed25519-2020/v1"; - public static final String TX_AUTH_CONTEXT = "https://w3id.org/tractusx/auth/v1.0.0"; - private static final String PREFIX = "document" + File.separator; - private static final Map FILES = Map.of( - CREDENTIALS_V_1, PREFIX + "credential-v1.jsonld", - SECURITY_JWS_V1, PREFIX + "security-jws-2020.jsonld", - SECURITY_ED25519_V1, PREFIX + "security-ed25519-2020.jsonld", - TX_AUTH_CONTEXT, PREFIX + "tx-auth-v1.jsonld", - EDC_CONTEXT, PREFIX + "edc-v1.jsonld"); @Inject private JsonLd jsonLdService; @@ -71,13 +53,9 @@ public void initialize(ServiceExtensionContext context) { jsonLdService.registerNamespace(TX_AUTH_PREFIX, TX_AUTH_NS, MANAGEMENT_SCOPE); - FILES.entrySet().stream().map(this::mapToFile) - .forEach(result -> result.onSuccess(entry -> jsonLdService.registerCachedDocument(entry.getKey(), entry.getValue().toURI())) - .onFailure(failure -> monitor.warning("Failed to register cached json-ld document: " + failure.getFailureDetail()))); - } - - private Result> mapToFile(Map.Entry fileEntry) { - return getResourceFile(fileEntry.getValue()) - .map(file1 -> Map.entry(fileEntry.getKey(), file1)); + TxCachedDocumentRegistry.getDocuments().forEach(result -> result + .onSuccess(c -> jsonLdService.registerCachedDocument(c.url(), c.resource())) + .onFailure(failure -> monitor.warning("Failed to register cached json-ld document: " + failure.getFailureDetail())) + ); } } diff --git a/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/TxCachedDocumentRegistry.java b/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/TxCachedDocumentRegistry.java new file mode 100644 index 0000000000..09cf0450b8 --- /dev/null +++ b/core/json-ld-core/src/main/java/org/eclipse/tractusx/edc/jsonld/TxCachedDocumentRegistry.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026 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.jsonld; + +import org.eclipse.edc.jsonld.spi.JsonLdContext; +import org.eclipse.edc.spi.result.Result; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.stream.Stream; + +import static java.lang.String.format; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.EDC_CONTEXT; +import static org.eclipse.tractusx.edc.jsonld.JsonLdExtension.TX_AUTH_CONTEXT; + +public class TxCachedDocumentRegistry { + + public static final String CREDENTIALS_V_1 = "https://www.w3.org/2018/credentials/v1"; + public static final String SECURITY_JWS_V1 = "https://w3id.org/security/suites/jws-2020/v1"; + public static final String SECURITY_ED25519_V1 = "https://w3id.org/security/suites/ed25519-2020/v1"; + + public static Stream> getDocuments() { + return Map.of( + "credential-v1.jsonld", CREDENTIALS_V_1, + "security-jws-2020.jsonld", SECURITY_JWS_V1, + "security-ed25519-2020.jsonld", SECURITY_ED25519_V1, + "tx-auth-v1.jsonld", TX_AUTH_CONTEXT, + "edc-v1.jsonld", EDC_CONTEXT + ).entrySet().stream() + .map(entry -> getResourceUri("document/" + entry.getKey()) + .map(uri -> new JsonLdContext(uri, entry.getValue()))); + } + + static Result getResourceUri(String name) { + var uri = TxCachedDocumentRegistry.class.getClassLoader().getResource(name); + if (uri == null) { + return Result.failure(format("Cannot find resource %s", name)); + } + + try { + return Result.success(uri.toURI()); + } catch (URISyntaxException e) { + return Result.failure(format("Cannot read resource %s: %s", name, e.getMessage())); + } + } +} diff --git a/core/json-ld-core/src/test/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtensionTest.java b/core/json-ld-core/src/test/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtensionTest.java index 0bb5d4115c..35a143111c 100644 --- a/core/json-ld-core/src/test/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtensionTest.java +++ b/core/json-ld-core/src/test/java/org/eclipse/tractusx/edc/jsonld/JsonLdExtensionTest.java @@ -29,9 +29,9 @@ import java.net.URI; -import static org.eclipse.tractusx.edc.jsonld.JsonLdExtension.CREDENTIALS_V_1; -import static org.eclipse.tractusx.edc.jsonld.JsonLdExtension.SECURITY_ED25519_V1; -import static org.eclipse.tractusx.edc.jsonld.JsonLdExtension.SECURITY_JWS_V1; +import static org.eclipse.tractusx.edc.jsonld.TxCachedDocumentRegistry.CREDENTIALS_V_1; +import static org.eclipse.tractusx.edc.jsonld.TxCachedDocumentRegistry.SECURITY_ED25519_V1; +import static org.eclipse.tractusx.edc.jsonld.TxCachedDocumentRegistry.SECURITY_JWS_V1; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; diff --git a/core/json-ld-cx/build.gradle.kts b/core/json-ld-cx/build.gradle.kts index 12f9b9a4a6..b06559e742 100644 --- a/core/json-ld-cx/build.gradle.kts +++ b/core/json-ld-cx/build.gradle.kts @@ -28,7 +28,7 @@ dependencies { implementation(libs.edc.spi.core) implementation(libs.edc.spi.jsonld) implementation(libs.dsp.spi.v2025) - implementation(libs.dsp.spi.v08) + implementation(project(":spi:dsp-spi-08")) implementation(libs.edc.lib.management.api) testImplementation(testFixtures(libs.edc.junit)) } diff --git a/core/json-ld-cx/src/main/java/org/eclipse/tractusx/edc/cx/CxCachedDocumentRegistry.java b/core/json-ld-cx/src/main/java/org/eclipse/tractusx/edc/cx/CxCachedDocumentRegistry.java new file mode 100644 index 0000000000..d06b675188 --- /dev/null +++ b/core/json-ld-cx/src/main/java/org/eclipse/tractusx/edc/cx/CxCachedDocumentRegistry.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026 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.cx; + +import org.eclipse.edc.jsonld.spi.JsonLdContext; +import org.eclipse.edc.spi.result.Result; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.stream.Stream; + +import static java.lang.String.format; +import static org.eclipse.tractusx.edc.cx.CxJsonLdExtension.CX_ODRL_CONTEXT; +import static org.eclipse.tractusx.edc.cx.CxJsonLdExtension.CX_POLICY_2025_09_CONTEXT; + +public class CxCachedDocumentRegistry { + + public static Stream> getDocuments() { + return Map.of( + "cx-policy-v1.jsonld", CX_POLICY_2025_09_CONTEXT, + "cx-odrl.jsonld", CX_ODRL_CONTEXT + ).entrySet().stream() + .map(entry -> getResourceUri("document/" + entry.getKey()) + .map(uri -> new JsonLdContext(uri, entry.getValue()))); + } + + static Result getResourceUri(String name) { + var uri = CxCachedDocumentRegistry.class.getClassLoader().getResource(name); + if (uri == null) { + return Result.failure(format("Cannot find resource %s", name)); + } + + try { + return Result.success(uri.toURI()); + } catch (URISyntaxException e) { + return Result.failure(format("Cannot read resource %s: %s", name, e.getMessage())); + } + } +} diff --git a/core/json-ld-cx/src/main/java/org/eclipse/tractusx/edc/cx/CxJsonLdExtension.java b/core/json-ld-cx/src/main/java/org/eclipse/tractusx/edc/cx/CxJsonLdExtension.java index 4515fb0cc4..2437c646d4 100644 --- a/core/json-ld-cx/src/main/java/org/eclipse/tractusx/edc/cx/CxJsonLdExtension.java +++ b/core/json-ld-cx/src/main/java/org/eclipse/tractusx/edc/cx/CxJsonLdExtension.java @@ -23,17 +23,12 @@ import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; -import java.io.File; -import java.util.Map; - import static org.eclipse.edc.api.management.ManagementApi.MANAGEMENT_SCOPE; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DSP_SCOPE_V_2025_1; -import static org.eclipse.tractusx.edc.core.utils.FileUtils.getResourceFile; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_NS; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_PREFIX; @@ -45,10 +40,6 @@ public class CxJsonLdExtension implements ServiceExtension { public static final String CX_ODRL_CONTEXT = "https://w3id.org/catenax/2025/9/policy/odrl.jsonld"; public static final String CX_POLICY_2025_09_CONTEXT = "https://w3id.org/catenax/2025/9/policy/context.jsonld"; - private static final String PREFIX = "document" + File.separator; - private static final Map FILES = Map.of( - CX_POLICY_2025_09_CONTEXT, PREFIX + "cx-policy-v1.jsonld", - CX_ODRL_CONTEXT, PREFIX + "cx-odrl.jsonld"); @Inject private JsonLd jsonLdService; @@ -63,13 +54,10 @@ public void initialize(ServiceExtensionContext context) { jsonLdService.registerContext(CX_POLICY_2025_09_CONTEXT, MANAGEMENT_SCOPE); - FILES.entrySet().stream().map(this::mapToFile) - .forEach(result -> result.onSuccess(entry -> jsonLdService.registerCachedDocument(entry.getKey(), entry.getValue().toURI())) - .onFailure(failure -> monitor.warning("Failed to register cached json-ld document: " + failure.getFailureDetail()))); + CxCachedDocumentRegistry.getDocuments().forEach(result -> result + .onSuccess(c -> jsonLdService.registerCachedDocument(c.url(), c.resource())) + .onFailure(failure -> monitor.warning("Failed to register cached json-ld document: " + failure.getFailureDetail())) + ); } - private Result> mapToFile(Map.Entry fileEntry) { - return getResourceFile(fileEntry.getValue()) - .map(file1 -> Map.entry(fileEntry.getKey(), file1)); - } } diff --git a/docs/development/decision-records/2026-08-10-refresh-token-retire-on-acknowledgement/README.md b/docs/development/decision-records/2026-08-10-refresh-token-retire-on-acknowledgement/README.md new file mode 100644 index 0000000000..f670362ad9 --- /dev/null +++ b/docs/development/decision-records/2026-08-10-refresh-token-retire-on-acknowledgement/README.md @@ -0,0 +1,106 @@ +# Retire Refresh Tokens on Acknowledgement + +## Decision + +The provider data plane will retire a rotated refresh token only once the consumer has proven that it received the +replacement. The proof of receipt is the consumer presenting the new refresh token. Until that happens, the token it +replaced remains acceptable, and presenting it again returns the current refresh token — the one the consumer failed to +receive — instead of rotating a second time. The access token is issued fresh on both paths. + +This is a behavioural change with no configuration surface and no way to opt out. There is nothing worth switching off: +the old behaviour is the one that breaks transfers on a network timeout, and the new one costs one extra string in the +vault record. The request and response formats are unchanged, so conforming consumers see no difference either way. + +## Rationale + +Token refresh as specified in the +[Tractus-X Refresh Token Grant Profile](https://github.com/eclipse-tractusx/tractusx-profiles/blob/main/tx/refresh/refresh.token.grant.profile.md) +and implemented per [2024-03-05_token_refresh](../2024-03-05_token_refresh/README.md) rotates the refresh token on every +successful call. The implementation treated the moment of *issuing* a new token as the moment the old one dies: the +vault entry was overwritten and the previous token was rejected from then on. + +That is only correct if the response always reaches the consumer, which is not a property an HTTP call has. Observed in +production: a refresh takes longer than a proxy or ingress in front of the public API tolerates, the request is answered +with HTTP 504, or the consumer cancels and the access log shows 499. The provider has rotated; the consumer still holds +the old refresh token; it never saw the new one. Every subsequent refresh is answered with HTTP 401 because the token +presented no longer matches the stored one. The transfer is permanently broken and can only be recovered by +restarting the transfer process — for a failure mode that is a plain network timeout. + +The observation that resolves this is that **the acknowledgement already exists in the protocol**. A consumer that +presents the new refresh token can only have obtained it from the response it is confirming. No extra message, no +profile change, and no timer is needed — the provider simply defers retirement until it sees that evidence. + +The refresh token is sender-constrained: holding one does not authorise a refresh. Every request must also carry an +authentication token signed with the key behind the consumer's DID and issued by the participant the EDR was issued to +(`AuthTokenAudienceRule`), so an intercepted or leaked refresh token is unusable on its own, however often it is +replayed. Retention changes how long the consumer's own copy stays valid, not who is able to use it. + +## Approach + +### Retained state + +`RefreshToken`, the record persisted in the vault under the access token's ID, gains a single component: + +```java +public record RefreshToken(String refreshToken, Long expiresIn, String refreshEndpoint, + @Nullable String previousRefreshToken) { +} +``` + +`previousRefreshToken` is the token that `refreshToken` replaced. Entries written by earlier versions deserialize to +`null` there and are treated as having no predecessor, so no migration of existing vault entries is required. The record +additionally ignores unknown properties, so a further component could be added without breaking records already stored. + +The record holds refresh tokens only. The access token is never stored — every call mints a new one, whether it rotates +the refresh token or serves a repeat, so a consumer retrying late still gets a usable access token. + +At most two generations of refresh token are live at any time, and the older one is retired by the very request that +proves it is no longer needed. + +### Refresh flow + +`RefreshTokenValidationRule` keeps its place in the access token's validation chain and its single vault read, and is +widened to accept the superseded token in addition to the current one. Deciding between the two outcomes needs to know +*which* token matched, which a `Result` cannot express, so the rule exposes the resolved record through +`replayedToken()` — non-null only in the superseded case. Instances are already created per refresh request, so this +stays confined to one request. + +`DataPlaneTokenRefreshServiceImpl.refreshToken()` then branches on that single value when deciding what to pair the +newly issued access token with: + +- `replayedToken()` is non-null — the consumer never saw the response that rotated its token, so it is given the current + refresh token from the stored record. Nothing is rotated and nothing is written. Rotating here would be worse than + useless: the provider cannot tell which of the two tokens the consumer ended up with, so it would strand the consumer + for good. +- otherwise — the presented token is the current one, which is the acknowledgement. A new refresh token is issued and + stored, with the presented token recorded as the new `previousRefreshToken`. + +A token matching neither is still rejected by the rule, with the message unchanged, so client-visible behaviour and the +existing end-to-end assertions are unaffected. + +## Further considerations + +**Security.** The superseded token stays usable for one extra generation, unbounded in time. That widens an existing +window: a captured *complete* refresh request can be replayed for a generation longer, and without the 401 collision +that makes such an intrusion visible. The authentication token must therefore carry an `exp` claim and is validated +against it. + +**Standards conformance.** The behaviour stays within [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749). Section 6 +makes both halves of rotation optional — "The authorization server MAY issue a new refresh token […] The authorization +server MAY revoke the old refresh token after issuing a new refresh token to the client." A new refresh token is issued +and only the optional revocation is deferred, until the client has demonstrably received it, which is also what that +sentence's "to the client" describes. Its three MUSTs are unaffected: client authentication is required, the refresh +token is checked to have been issued to the authenticated client (`AuthTokenAudienceRule` together with the DID +signature on the authentication token), and the refresh token is validated. Section 10.4 holds as well — both +generations stay confidential in the vault, both are bound to the same client through the shared `AccessTokenData`, and +refresh tokens remain unguessable provider-signed JWTs. What is given up is the detection property of +strict single-use rotation: the first appearance of a superseded token no longer signals a possible compromise, because +it is indistinguishable from the legitimate retry this decision exists to support. + +## 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/mock-edc.md b/docs/development/mock-edc.md deleted file mode 100644 index 94001e93d3..0000000000 --- a/docs/development/mock-edc.md +++ /dev/null @@ -1,220 +0,0 @@ -# Using the Mock-Connector for contract-based testing - -Modern testing methodologies are based on small, independent units of code that have a defined behaviour. -Implementations as well as testing should be fast, repeatable, continuous and easily maintainable. In the context of EDC -that means, that downstream projects that are based on EDC should not need to run a fully-fledged connector runtime to -test their workflows. While the Tractus-X EDC project did provide a pure in-memory runtime for testing, that still -requires all the configuration and a complex runtime environment to work, which may be a high barrier of entry. - -For this reason, and to developers who primarily interact with the Management API of a connector, the Tractus-X EDC -project provides a testing framework with an even smaller footprint called the "Mock-Connector". It is a Docker image, that -contains just the Management API plus an instrumentation interface to enable developers to use this in their -unit/component testing and in continuous integration. - -We call this "contract-based testing", as it defines the specified behaviour of an application (here: the connector). -The Mock-Connector's Management API is guaranteed to behave exactly the same, in fact, it even runs the -same code as a "real" EDC. - -## 1. The contract - -The [Management API spec](https://eclipse-edc.github.io/Connector/openapi/management-api/). - -### 1.1 Definition of terms - -- connector: runnable Java application that contains Tractus-X modules. Also referred to as: EDC, runtime, tx-edc -- mock: a replacement for a collaborator object (class, component, application), where the behaviour can be - controlled -- stub: very similar to a mock, but while a mock oftentimes is a drop-in _replacement_, a stub would be a - re-implementation with a fixed behaviour. Also referred to as: dummy -- instrumentation: the process of setting up a mock to behave a certain way. Also referred to as priming the mock. - -## 2. Intended audience - -Developers who build their applications and systems based on EDC, and interact with EDC through the Management API can -use the Mock-Connector to decrease friction by not having to spin up and configure a fully-fledged connector runtime. - -Developers who plan to work with (Tractus-X) EDC in another way, like directly using its Maven artifacts, or even by -implementing a DSP protocol head are kindly redirected to -the [additional references section](#5-references-and-further-reading). - -## 3. Use with TestContainers - -Mock-Connector should be used as Docker image, we publish it as `tractusx/edc-mock`. - -Using the Mock-Connector is very easy, we recommend usage via Testcontainers. For example, setting up a JUnit test for a -client application using Testcontainers could be done as follows: - -```java - -@Testcontainers -@ComponentTest -public class UseMockedEdcSampleTest { - @Container - protected static GenericContainer edcContainer = new GenericContainer<>("tractusx/edc-mock:latest") - .withEnv("WEB_HTTP_PORT", "8080") - .withEnv("WEB_HTTP_PATH", "/api") - .withEnv("WEB_HTTP_MANAGEMENT_PORT", "8081") - .withEnv("WEB_HTTP_MANAGEMENT_PATH", "/api/management") - .withExposedPorts(8080, 8081); - private int managementPort; - private int defaultPort; - - @BeforeEach - void setup() { - managementPort = edcContainer.getMappedPort(8081); - defaultPort = edcContainer.getMappedPort(8080); - } -} -``` - -This downloads and runs the Docker image for the Mock-Connector and supplies it with minimal configuration. Specifically, it -exposes the Management API and the default context, because that is needed to set up the mock. - -> Please note that in -> the [example](../../samples/testing-with-mocked-edc/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockedEdcSampleTest.java), -> the image name is `mock-edc` - that is because in our CI testing we build the image and then run the tests, so we -> can't use the official image. - -### 3.1 Running a simple positive test - -Executing a simple request against the Management API of EDC can be done like this: - -```java - -@Test -void test_getAsset() { - //prime the mock - post a RecordedRequest - setupNextResponse("asset.request.json"); - - // perform the actual Asset API request. In a real test scenario, this would be the client code we're testing, i.e. the - // System-under-Test (SuT). - var assetArray = mgmtRequest() - .contentType(ContentType.JSON) - .body(""" - { - "@context": { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/" - }, - "@type": "QuerySpec" - } - """) - .post("/v3/assets/request") - .then() - .log().ifError() - .statusCode(200) - .extract().body().as(JsonArray.class); - - // assert the response - assertThat(assetArray).hasSize(1); - assertThat(assetArray.get(0).asJsonObject().get("properties")) - .hasFieldOrProperty("prop1") - .hasFieldOrProperty("id") - .hasFieldOrProperty("contenttype"); -} -``` - -### 3.2 Running a test expecting a failure - -```java - -@Test -void test_apiNotAuthenticated_expect400() { - //prime the mock - post a RecordedRequest - setupNextResponse("asset.creation.failure.json"); - - // perform the actual Asset API request. In a real test scenario, this would be the client code we're testing, i.e. the - // System-under-Test (SuT). - var assetArray = mgmtRequest() - .contentType(ContentType.JSON) - .body(""" - { - "@context": { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/" - }, - "@type": "QuerySpec" - } - """) - .post("/v3/assets/request") - .then() - .log().ifError() - .statusCode(400) - .extract().body().as(JsonArray.class); - - // assert the response contains error information - assertThat(assetArray).hasSize(1); - var errorObject = assetArray.get(0).asJsonObject(); - assertThat(errorObject.get("message").toString()).contains("This user is not authorized, This is just a second error message"); -} -``` - -Note that the difference here is that we prime the mock with a different JSON file (more on that later), we expect a -different HTTP response code, i.e. 400, and the response body contains an error object instead of an array of Assets. - -## 4. Request pipeline and the instrumentation API - -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 -[ - { - "message": "Failure: no recorded request left in queue.", - "type": "InvalidRequest", - "path": null, - "invalidValue": null - } -] -``` - -To get beyond that, we need to _prime_ the mock. That means, we need to tell it how to respond to the next request by -inserting a "recorded request" into its request pipeline. In previous code examples, this was done using -the `setupNextResponse()` method. Mock-Connector offers an instrumentation API which can be used to insert recorded requests, -to clear the queue and to get a count. - -### 4.1 Recorded requests - -A `RecordedRequest` is a POJO, that tells the Mock-Connector how to respond to the _next_ Management API request. To that end, -it contains the input parameter type, the data associated with it, plus the return value type plus - most importantly - -the data that is supposed to be returned. - -Recall the [previous example](#31-running-a-simple-positive-test), which tests an Asset request. Thus, we have to prime -the mock such that it responds with a list of `Asset` objects. The semantic being: "on the next request, respond -with ...". - -The contents of the [asset.request.json](../../samples/testing-with-mocked-edc/src/test/resources/asset.request.json) -contains a section that defines the `input`, which in this case is a `QuerySpec`, and the `output` is a list of `Asset` -objects. The `data` section must then contain serialized JSON that matches the `class` property. For instance, -the `data` section of the `input` must contain JSON that can be deserialized into an `Asset`. - -> _Note that the information about input and output datatypes must currently be obtained from the aggregate services. -Here, that would be the `AssetService` interface. In future iterations there will be a more convenient way to obtain -that information._ - -> _Note that input argument type matching is currently not supported, it will come in future releases._ - -### 4.2 Instrumentation API - -The instrumentation is done via a simple REST API: - -```shell -GET /api/instrumentation/count -> returns the number of requests in the queue -GET /api/instrumentation -> returns the list of queued requests -DELETE /api/instrumentation -> clears the queue -POST /api/instrumentation -> adds a new RecordedRequest, JSON must be in the request body -``` - -## 5. References and further reading - -- A complete sample how to run a test using the Mock-Connector in a Testcontainer can be - found [here](../../samples/testing-with-mocked-edc) -- To test compliance with DSP, use the [TCK](https://github.com/eclipse-dataspacetck/cvf) -- A Mock-IATP runtime is planned for future releases. - -## 6. Future improvements - -- matching requests to endpoints to allow for a "from-now-on" semantic -- introducing placeholders for domain objects to increase refactoring robustness -- abstract description of the endpoint's inputs and outputs, so developers don't need to know about service signatures - anymore -- request input matching \ No newline at end of file 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 d2be834af0..dc98d65473 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 @@ -165,3 +165,6 @@ There is an extensive documentation on contracting in Catena-X in the regulatory 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/) + +## Removal of "dcat:endpointUrl" in catalog response +Until EDC 0.10 there were two attributes "dcat:endpointUrl" and "dcat:endpointURL" present in the catalog response. The first one presented an inconsistency with the dataspace protocol specification and thus was removed with the 0.11 release. Applications that use "dcat:endpointUrl" need to switch to "dcat:endpointURL". diff --git a/docs/migration/2026_06-Version_0.12.x_0.13.x.md b/docs/migration/2026_06-Version_0.12.x_0.13.x.md index fa465ce168..4ed3860ea1 100644 --- a/docs/migration/2026_06-Version_0.12.x_0.13.x.md +++ b/docs/migration/2026_06-Version_0.12.x_0.13.x.md @@ -7,8 +7,10 @@ to another. This document is not a comprehensive feature list. -* [Migration Guide `0.12.x -> 0.13.x`](#migration-guide-012x---013x) - * [1. Federate Catalog removal](#2-federate-catalog-removal) +- [Migration Guide `0.12.x -> 0.13.x`](#migration-guide-012x---013x) + - [1. Federate Catalog removal](#1-federate-catalog-removal) + - [2. Deprecated instances](#2-deprecated-instances) + - [3. Postgres Version](#3-postgres-version) ## 1. Federate Catalog removal @@ -18,8 +20,15 @@ The Federate Catalog feature has not been proven to be usable, that's why it wil that `tx.edc.postgresql.migration.federatedcatalog.enabled` is set to `false` before the migration. ## 2. Deprecated instances -The API `/business-partner-groups` has been removed, please use `/v3/business-partner-groups` instead. -Also the API `/v2/edrs` has been removed, please use `/v3/edrs` instead. +API changes: + +| Old API | New API | +|-------------------------------|-------------------------------| +| `/business-partner-groups` | `/v3/business-partner-groups` | +| `/v2/edrs` | `/v3/edrs` | +| `/v4alpha/connectordiscovery` | `/v3/connectordiscovery` | +| `/v4alpha/dataflows` | `/v3/dataflows` | + Additionally, several configuration parameters deprecated since version 0.8.x have also been removed. Please use the updated configuration parameters instead. @@ -37,4 +46,17 @@ Please use the updated configuration parameters instead. | `edc.datasource.edr.name` | `edc.sql.store.edr.datasource` | `EDC_SQL_STORE_EDR_DATASOURCE` | -[2026_06-Version_0.12.x_0.13.x.md](2026_06-Version_0.12.x_0.13.x.md) \ No newline at end of file +[2026_06-Version_0.12.x_0.13.x.md](2026_06-Version_0.12.x_0.13.x.md) + +## 3. Postgres Version + +The new version provides Postgres version 18.0 as dependency in the helm charts referring to the Cloud Pirates +helm charts and the official Postgres docker image instead of the formerly used bitnami charts and docker images. + +As a consequence, for a Kubernetes setup done with the provided helm charts, there is no possibility to do an automatic +upgrade with the provided helm charts, as the two images are not supporting that. Instead, during update, an operator +has to follow the general +[migration guide](https://github.com/eclipse-tractusx/tutorial-resources/blob/main/migration-guides/GENERIC_POSTGRESQL_MIGRATION_GUIDE.md#:~:text=GENERIC_BITNAMI_TO_CLOUDPIRATES_KEYCLOAK_MIGRATION_GUIDE.md-,GENERIC_POSTGRESQL_MIGRATION_GUIDE,-.md) + +Further, the chart now differentiates between admin and custom user. It is recommended to use a custom user. Please +follow the [database configuration section](../../charts/tractusx-connector/README.md#database-configuration) of the helm documentation of the tractusx-connector. diff --git a/docs/usage/management-api-walkthrough/02_policies.md b/docs/usage/management-api-walkthrough/02_policies.md index 4e948f5be7..f3ebd6bb47 100644 --- a/docs/usage/management-api-walkthrough/02_policies.md +++ b/docs/usage/management-api-walkthrough/02_policies.md @@ -39,34 +39,33 @@ please refer to the CX-0152 standard [appendix](https://catenax-ev.github.io/doc | Name | Action | usable in | side-effects | |-----------------------------------|-----------------|-----------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------| -| `BusinessPartnerGroup` | `access` | `permission` | validated against the identity extracted from the `MembershipCredential` | -| `BusinessPartnerNumber` | `access` | `permission` | validated against the identity extracted from the `MembershipCredential` | -| `FrameworkAgreement` | `access`, `use` | `permission` | | -| `Membership` | `access`, `use` | `permission` | validated against the `MembershipCredential` | -| `inForceDate` | `access`, `use` | `permission` | validated continuously - all Transfer Processes relying on an Agreement with this Constraint will be stopped when `inForceDate` is exceeded. | -| `AffiliatesRegion` | `use` | `permission`, `prohibition` | | | `AffiliatesBpnl` | `use` | `permission`, `prohibition` | | -| `DataFrequency` | `use` | `permission` | | -| `VersionChanges` | `use` | `permission` | | -| `ContractTermination` | `use` | `permission` | | +| `AffiliatesRegion` | `use` | `permission`, `prohibition` | | +| `BusinessPartnerGroup` | `access` | `permission` | validated against the identity extracted from the `MembershipCredential` | +| `BusinessPartnerNumber` | `access` | `permission` | validated against the identity extracted from the `MembershipCredential` -> `BpnCredential` | | `ConfidentialInformationMeasures` | `use` | `permission` | | | `ConfidentialInformationSharing` | `use` | `permission` | | -| `ExclusiveUsage` | `use` | `permission` | | -| `Warranty` | `use` | `permission` | | -| `WarrantyDefinition` | `use` | `permission` | | -| `WarrantyDurationMonths` | `use` | `permission` | | -| `Liability` | `use` | `permission` | | -| `JurisdictionLocationReference` | `use` | `permission` | | -| `JurisdictionLocation` | `use` | `permission` | | -| `Precedence` | `use` | `permission` | | -| `DataUsageEndDurationDays` | `use` | `permission` | | +| `ContractReference` | `use` | `permission` | | +| `ContractTermination` | `use` | `permission` | | +| `DataFrequency` | `use` | `permission` | | | `DataUsageEndDate` | `use` | `permission` | | | `DataUsageEndDefinition` | `use` | `permission` | | -| `DataUsageEndDate` | `use` | `permission` | | -| `DataUsageEndDate` | `use` | `permission` | | +| `DataUsageEndDurationDays` | `use` | `permission` | | | `DataProvisioningEndDurationDays` | `use` | `obligation` | | | `DataProvisioningEndDate` | `use` | `obligation` | | +| `ExclusiveUsage` | `use` | `permission` | | +| `FrameworkAgreement` | `access`, `use` | `permission` | | +| `JurisdictionLocation` | `use` | `permission` | | +| `JurisdictionLocationReference` | `use` | `permission` | | +| `Liability` | `use` | `permission` | | +| `Membership` | `access`, `use` | `permission` | validated against the `MembershipCredential` | +| `Precedence` | `use` | `permission` | | +| `UsagePurpose` | `use` | `permission` | | | `UsageRestriction` | `use` | `prohibition` | | +| `VersionChanges` | `use` | `permission` | | +| `Warranty` | `use` | `permission` | | +| `WarrantyDefinition` | `use` | `permission` | | +| `WarrantyDurationMonths` | `use` | `permission` | | ### Policies & Verifiable Credentials (VC) @@ -93,6 +92,19 @@ In EDC, a distinction is made between **Access** and **Usage** Policies. Whether a policy is used as access or usage policy is determined during [contract definition](03_contractdefinitions.md). +#### Mandatory Constraints for Usage Policies + +In the Catena-X dataspace, a usage policy must always contain at least the following two constraints: + +- **`FrameworkAgreement`:** Specifies the data exchange governance framework the consumer must have agreed to (e.g. `DataExchangeGovernance:1.0`). +- **`UsagePurpose`:** Defines the permitted purposes for which the data may be used (e.g. `cx.core.qualityNotifications:1`). + +Both constraints must be combined using an `and` conjunction. + +> **Note:** Policies that do not include both `FrameworkAgreement` and `UsagePurpose` constraints will not be +> accepted in the Catena-X dataspace. Additional constraints may be added depending on the use case. + + ### Creating a Policy Definition Policies can be created in the Connector as follows: @@ -148,7 +160,7 @@ or the [json-ld playground](https://json-ld.org/playground/) helps to be consist If the creation of the `policydefinition` was successful, the Management-API will return HTTP 201. -## Exemplary scenarios +## Exemplary scenario For the following Scenarios, we assume there is a **Partner 1 (provider)** who wants to provide Data for **Partner 2 (consumer)** @@ -156,7 +168,7 @@ For the following Scenarios, we assume there is a **Partner 1 (provider)** who w - Partner 1 (provider) has the Business-Partner-Number BPN12345. - Partner 2 (consumer) has the Business-Partner-Number BPN6789. -Partner 2 (consumer) signed the **Traceability Framework Agreement** and followed all the necessary steps that the +Partner 2 (consumer) signed the **Framework Agreement** and followed all the necessary steps that the Credential appears within Partner 2s identity. When doing a catalog request with @@ -187,18 +199,11 @@ For example: } ``` -For other subsequent requests like Contract negotiation requests and transfer process requests, the presented -credentials are based on the usage/contract policy. This means VC based policies can be used only -in the usage/contract policy. - -#### Scenario 1 +#### Scenario -Partner 1 wants to create an Access Policy, that Partner 2 can receive the Contract Offer if its BPN matches. But a -Contract Agreement should only be created if Partner 2 also signed the Traceability Framework Agreement. So in this -case, Partner 2 should receive the Contract Offer in the first place, regardless if it signed the Traceability Framework -Agreement. The signing of the Agreement should be checked at the time of contract negotiation. +Partner 1 wants to create an Access Policy, that Partner 2 can receive the Contract Offer if its BPN matches. But a Contract Agreement should only be created if Partner 2 also signed the Framework Agreement. So in this case, Partner 2 should receive the Contract Offer in the first place, regardless if it signed the Framework Agreement. The signing of the Agreement should be checked at the time of contract negotiation. -##### Partner 1 - Access Policy Example (Scenario 1) +##### Partner 1 - Access Policy Example ```json { @@ -218,8 +223,8 @@ Agreement. The signing of the Agreement should be checked at the time of contrac "action": "access", "constraint": { "leftOperand": "BusinessPartnerNumber", - "operator": "eq", - "rightOperand": "BPN6789" + "operator": "isAnyOf", + "rightOperand": ["BPN6789", "..."] } } ] @@ -227,7 +232,7 @@ Agreement. The signing of the Agreement should be checked at the time of contrac } ``` -##### Partner 1 - Usage/Contract Policy Example (Scenario 1) +##### Partner 1 - Usage/Contract Policy Example ```json { @@ -246,9 +251,18 @@ Agreement. The signing of the Agreement should be checked at the time of contrac { "action": "use", "constraint": { - "leftOperand": "FrameworkAgreement", - "operator": "eq", - "rightOperand": "DataExchangeGovernance:1.0" + "and": [ + { + "leftOperand": "FrameworkAgreement", + "operator": "eq", + "rightOperand": "DataExchangeGovernance:1.0" + }, + { + "leftOperand": "UsagePurpose", + "operator": "isAnyOf", + "rightOperand": [""] + } + ] } } ] @@ -256,81 +270,16 @@ Agreement. The signing of the Agreement should be checked at the time of contrac } ``` -##### Desired Outcome (Scenario 1) +> **Note:** The Access Policy only checks the BPN at catalog request time. The `FrameworkAgreement` and +> `UsagePurpose` constraints in the Usage Policy are evaluated during contract negotiation. If either +> constraint is not satisfied, the negotiation will be rejected. + +##### Desired Outcome Partner 2 receives the Contract Offer and is able to negotiate the contract because he presents a valid `DataExchangeGovernanceCredential`. -#### Scenario 2 - -Partner 1 wants to create an Access Policy that Partner 2 can receive the Contract Offer if the BPN is matching -but a Contract Agreement should only be created if Partner 2 is identified as a Dismantler (owns the " -DismantlerCredential"). - -##### Partner 1 - Access Policy Example (Scenario 2) - -```json -{ - "@context": [ - "https://w3id.org/dspace/2025/1/odrl-profile.jsonld", - "https://w3id.org/catenax/2025/9/policy/context.jsonld", - { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/" - } - ], - "@type": "PolicyDefinition", - "@id": "{{POLICY_ID}}", - "policy": { - "@type": "Set", - "permission": [ - { - "action": "use", - "constraint": { - "leftOperand": "BusinessPartnerNumber", - "operator": "eq", - "rightOperand": "BPN6789" - } - } - ] - } -} -``` - -##### Partner 1 - Usage/Contract Policy Example (Scenario 2) - -```json -{ - "@context": [ - "https://w3id.org/dspace/2025/1/odrl-profile.jsonld", - "https://w3id.org/catenax/2025/9/policy/context.jsonld", - { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/" - } - ], - "@type": "PolicyDefinition", - "@id": "{{POLICY_ID}}", - "policy": { - "@type": "Set", - "permission": [ - { - "action": "use", - "constraint": { - "leftOperand": "Dismantler", - "operator": "eq", - "rightOperand": "active" - } - } - ] - } -} -``` - -##### Desired Outcome (Scenario 2) - -Partner 2 receives the Contract Offer in the first place. -The contract negotiation, started by Partner 2 fails because he has not been identified as Dismantler and therefore does -not own the Dismantler Credential. #### Writing Policies for the Connector diff --git a/docs/usage/management-api-walkthrough/04_catalog.md b/docs/usage/management-api-walkthrough/04_catalog.md index 69926339d5..dae01d1f59 100644 --- a/docs/usage/management-api-walkthrough/04_catalog.md +++ b/docs/usage/management-api-walkthrough/04_catalog.md @@ -26,7 +26,7 @@ DSP discovery is supported with the following requests For the full flow including the DID document download: ```http request -POST /v4alpha/connectordiscovery/connectors HTTP/1.1 +POST /v3/connectordiscovery/connectors HTTP/1.1 Host: https://consumer-control.plane/api/management X-Api-Key: password Content-Type: application/json @@ -45,7 +45,7 @@ Content-Type: application/json or, if a connector base address is already known: ```http request -POST /v4alpha/connectordiscovery/dspversionparams HTTP/1.1 +POST /v3/connectordiscovery/dspversionparams HTTP/1.1 Host: https://consumer-control.plane/api/management X-Api-Key: password Content-Type: application/json @@ -257,7 +257,7 @@ policies included. ## Reference -- [Connector Discovery API](https://eclipse-tractusx.github.io/tractusx-edc/openapi/control-plane-api/#/Connector%20Discovery/discoverDspVersionParamsV4Alpha) +- [Connector Discovery API](https://eclipse-tractusx.github.io/tractusx-edc/openapi/control-plane-api/#/Connector%20Discovery/discoverDspVersionParamsV3) ## Notice diff --git a/docs/usage/management-api-walkthrough/10_nonfiniteproviderpush/10_nonfiniteproviderpush.md b/docs/usage/management-api-walkthrough/10_nonfiniteproviderpush/10_nonfiniteproviderpush.md index 1831f3b3ac..6aea48f9d4 100644 --- a/docs/usage/management-api-walkthrough/10_nonfiniteproviderpush/10_nonfiniteproviderpush.md +++ b/docs/usage/management-api-walkthrough/10_nonfiniteproviderpush/10_nonfiniteproviderpush.md @@ -56,7 +56,7 @@ available within the management API context and is exposed through the data plan the following request: ```http request -POST /v4alpha/dataflows/{{TRANSFER_PROCESS_ID}}/trigger HTTP/1.1 +POST /v3/dataflows/{{TRANSFER_PROCESS_ID}}/trigger HTTP/1.1 Host: https://provider-data.plane/api/management X-Api-Key: password Content-Type: application/json diff --git a/edc-controlplane/edc-controlplane-base/build.gradle.kts b/edc-controlplane/edc-controlplane-base/build.gradle.kts index 660c369368..d7b3ee03cd 100644 --- a/edc-controlplane/edc-controlplane-base/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-base/build.gradle.kts @@ -29,38 +29,17 @@ configurations.all { // decentralized-claims-sts-remote-client excluded because we have the tx-dcp-sts-div that takes care to define the correct client in case of DIV exclude("org.eclipse.edc", "decentralized-claims-sts-remote-client") + + // We decided to not provide the federated catalog feature with this runtime + exclude("org.eclipse.edc", "federated-catalog-api") + exclude("org.eclipse.edc", "federated-catalog-spi") + exclude("org.eclipse.edc", "federated-catalog-core") + exclude("org.eclipse.edc", "federated-catalog-core-2025") + exclude("org.eclipse.edc", "federated-catalog-cache-sql") } dependencies { - constraints { - runtimeOnly("tools.jackson.core:jackson-core:3.1.0") { - because("older version has vulnerability") - } - runtimeOnly("com.fasterxml.jackson.core:jackson-core:2.21.1") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty:jetty-server:12.1.7") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty:jetty-http:12.1.7") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty:jetty-security:12.1.7") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.7") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty.websocket:jetty-websocket:12.1.7") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty:jetty-session:12.1.7") { - because("older version has vulnerability") - } - } - runtimeOnly(libs.edc.bom.controlplane.base) { - exclude(module = "dsp-2024") - } + runtimeOnly(libs.edc.bom.controlplane.base) runtimeOnly(libs.edc.bom.controlplane.dcp) implementation(project(":core:edr-core")) @@ -78,10 +57,15 @@ dependencies { implementation(project(":edc-extensions:dcp:tx-dcp")) implementation(project(":edc-extensions:dcp:tx-dcp-sts-div")) implementation(project(":edc-extensions:dcp:verifiable-presentation-cache")) + implementation(project(":edc-extensions:dsp:dsp-catalog-08")) + implementation(project(":edc-extensions:dsp:dsp-http-api-configuration-08")) + implementation(project(":edc-extensions:dsp:dsp-http-dispatcher-08")) + implementation(project(":edc-extensions:dsp:dsp-negotiation-08")) + implementation(project(":edc-extensions:dsp:dsp-transfer-process-08")) implementation(project(":edc-extensions:edr:edr-api-v2")) implementation(project(":edc-extensions:edr:edr-callback")) - implementation(project(":edc-extensions:provision-additional-headers")) implementation(project(":edc-extensions:tokenrefresh-handler")) + implementation(project(":edc-extensions:validators:contract-definition-policies")) implementation(project(":edc-extensions:validators:empty-asset-selector")) implementation(project(":edc-extensions:connector-discovery:connector-discovery-api")) implementation(project(":edc-extensions:connector-discovery:cx-connector-discovery")) diff --git a/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-hashicorp-vault/build.gradle.kts index 8349f2fc7c..d134388293 100644 --- a/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-hashicorp-vault/build.gradle.kts @@ -25,9 +25,10 @@ plugins { alias(libs.plugins.shadow) } +val edcVersion = project.property("con-x-edcVersion") as String +val txVersion = project.property("version") as String + dependencies { - val edcVersion = "0.15.1" - val txVersion = "0.12.0" implementation("org.eclipse.edc:controlplane-dcp-bom:$edcVersion") implementation("org.eclipse.edc:controlplane-feature-sql-bom:$edcVersion") @@ -35,7 +36,7 @@ dependencies { implementation("org.eclipse.tractusx.edc:agreements:$txVersion") implementation("org.eclipse.tractusx.edc:retirement-evaluation-store-sql:$txVersion") - implementation("org.eclipse.tractusx.edc:control-plane-migration:$txVersion") + implementation(project(":edc-extensions:agreements:retirement-evaluation-bootstrapping")) implementation("org.eclipse.tractusx.edc:tx-dcp:$txVersion") } diff --git a/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-vault/build.gradle.kts index d0cd959721..8817f7ff78 100644 --- a/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-vault/build.gradle.kts @@ -25,9 +25,10 @@ plugins { alias(libs.plugins.shadow) } +val edcVersion = project.property("con-x-edcVersion") as String +val txVersion = project.property("version") as String + dependencies { - val edcVersion = "0.15.1" - val txVersion = "0.12.0" implementation("org.eclipse.edc:controlplane-dcp-bom:$edcVersion") implementation("org.eclipse.edc:controlplane-feature-sql-bom:$edcVersion") @@ -35,7 +36,7 @@ dependencies { implementation("org.eclipse.tractusx.edc:agreements:$txVersion") implementation("org.eclipse.tractusx.edc:retirement-evaluation-store-sql:$txVersion") - implementation("org.eclipse.tractusx.edc:control-plane-migration:$txVersion") + implementation(project(":edc-extensions:agreements:retirement-evaluation-bootstrapping")) implementation("org.eclipse.tractusx.edc:tx-dcp:$txVersion") } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru index df66b443df..16365f6d63 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru @@ -6,12 +6,12 @@ vars { CONSUMER_IDHUB_ID_API: http://localhost:20100/api/identity CONSUMER_IDHUB_STS_API: http://localhost:20500/api/sts CONSUMER_IDHUB_CREDS_API: http://localhost:20600/api/credentials - ISSUER_APIKEY: ZGlkOndlYjpsb2NhbC1pc3N1ZXItc2VydmljZTpmeC1pc3N1ZXI=.CmYgVcuzneJXqzcbj7vmld5feejy6OguIylflSrz6WTjG1HNQRvO62EafFrQjS/lVlZAwiwz2rwwZgXrbpyhcg== - CONSUMER_IH_APIKEY: ZGlkOndlYjpsb2NhbC11c2VyLWlkaHViOnVzZXI6Y29uc3VtZXI=.ObFly02OtymUNRE43uH9SblWVtsZH0NMddgm1dFYQXRekO3qXX+rHFV7NvM+DUW3lcA2PbILt5rwWYEqm7WNgw== - PROVIDER_IH_APIKEY: ZGlkOndlYjpsb2NhbC11c2VyLWlkaHViOnVzZXI6cHJvdmlkZXI=.Knip+hedL63qedBQfOvZhhrF2ooSCfP2YgjONvtmehofor2ejdw/en0MAXDBZEcXrCwYNppDMNFDsRlG5rB/Mw== + ISSUER_APIKEY: Y29uLXgtaXNzdWVy.P3ROSh354pDFG8vOq4DB+lChxi4A6vIcbI/07UvwnKTIKl43pousUO0hu5fH28YnuQzbVF1meRBWjKU55/PT/g== + CONSUMER_IH_APIKEY: dXNlci1jb25zdW1lcg==.dMQICllhhPpgGtJBMWn2uKnH4+lkLAAD2PcPjn6iAIlMKBXUPmFHShj+HNYUDOY4zPQqwqBZ+cki4Mhtu7oyvw== + PROVIDER_IH_APIKEY: dXNlci1wcm92aWRlcg==.cdX1+2gWJtn9LKO/z2wkYwuybBhBRTQmFw5Se0CSvFq8ZEPSaDjskG15nmUx0iFKfXYEDase6yTEVlqQKoleQQ== VAULTURL: http://localhost:8200 - CONSUMER_STS_SECRET: VD0q6jOEyslSeFV2 - PROVIDER_STS_SECRET: XjDk9ncaJSrSfkQW + CONSUMER_STS_SECRET: fGznFfHH2skwmCJ4 + PROVIDER_STS_SECRET: DA9ujfJuoWgPPafQ PROVIDER_MANAGEMENT: http://localhost:39010/management CONSUMER_MANAGEMENT: http://localhost:29010/management PROVIDER_DATAPLANE_PUBLIC: http://localhost:9500/public @@ -22,14 +22,14 @@ vars { ISS_ID: did:web:local-issuer-wallet:con-x-issuer CONS_ID: did:web:consumer-wallet:user:consumer PROV_ID: did:web:provider-wallet:user:provider - cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLWlkaHViOnVzZXI6Y29uc3VtZXIja2V5LTEiLCJhbGciOiJFZDI1NTE5In0.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLWlkaHViOnVzZXI6Y29uc3VtZXIiLCJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLWlkaHViOnVzZXI6cHJvdmlkZXIiLCJuYmYiOjE3NzAyNzg5NTQsInNjb3BlIjoib3JnLmVjbGlwc2UudHJhY3R1c3gudmMudHlwZTpNZW1iZXJzaGlwQ3JlZGVudGlhbDpyZWFkIiwiaXNzIjoiZGlkOndlYjpjb25zdW1lci1pZGh1Yjp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzcwMjc5MjU0LCJpYXQiOjE3NzAyNzg5NTQsImp0aSI6ImFjY2Vzc3Rva2VuLTcxYThmNTA5LTgwYTktNDMwZC1iMjU0LTMxNGFiYTBkNjY5OSJ9.ipRpdi_Ekh7y4IIqhqLgHU35Fn7NNkv6e6hILXy8pZObdy33y3MKppI61424eyHwqzmh7X2kwV2S5gDy3aOKCQ - prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLWlkaHViOnVzZXI6cHJvdmlkZXIja2V5LTEiLCJhbGciOiJFZDI1NTE5In0.eyJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLWlkaHViOnVzZXI6cHJvdmlkZXIiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLWlkaHViOnVzZXI6Y29uc3VtZXIiLCJuYmYiOjE3NzAyNzg5NTUsImlzcyI6ImRpZDp3ZWI6cHJvdmlkZXItaWRodWI6dXNlcjpwcm92aWRlciIsImV4cCI6MTc3MDI3OTI1NSwiaWF0IjoxNzcwMjc4OTU1LCJqdGkiOiJkNjA0MTVjOS1kMGM0LTRiNWQtYjI4My01ZmNmYjhlMDY2OGQiLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFdsa2FIVmlPblZ6WlhJNlkyOXVjM1Z0WlhJamEyVjVMVEVpTENKaGJHY2lPaUpGWkRJMU5URTVJbjAuZXlKaGRXUWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMV2xrYUhWaU9uVnpaWEk2WTI5dWMzVnRaWElpTENKemRXSWlPaUprYVdRNmQyVmlPbkJ5YjNacFpHVnlMV2xrYUhWaU9uVnpaWEk2Y0hKdmRtbGtaWElpTENKdVltWWlPakUzTnpBeU56ZzVOVFFzSW5OamIzQmxJam9pYjNKbkxtVmpiR2x3YzJVdWRISmhZM1IxYzNndWRtTXVkSGx3WlRwTlpXMWlaWEp6YUdsd1EzSmxaR1Z1ZEdsaGJEcHlaV0ZrSWl3aWFYTnpJam9pWkdsa09uZGxZanBqYjI1emRXMWxjaTFwWkdoMVlqcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94Tnpjd01qYzVNalUwTENKcFlYUWlPakUzTnpBeU56ZzVOVFFzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFRjeFlUaG1OVEE1TFRnd1lUa3RORE13WkMxaU1qVTBMVE14TkdGaVlUQmtOalk1T1NKOS5pcFJwZGlfRWtoN3k0SUlxaHFMZ0hVMzVGbjdOTmt2NmU2aElMWHk4cFpPYmR5MzN5M01LcHBJNjE0MjRleUh3cXptaDdYMmt3VjJTNWdEeTNhT0tDUSJ9.42YDTnuzZ0RprqLjFw6hUoAXrgpPxyzKNFrqLdbWz6HXAiujkq32QAFU-M9gtQ0hMNcjshRZUX6DryBWxhGNDw - offerId: MQ==:YXNzZXRJZA==:MDFhN2ZjYWYtODgzOS00N2JmLTllZDAtM2Y0YjliMTFiOWM5 - negotiation-id: 3745ad50-6b99-4142-bf1a-509b3d0e313d - contractId: 3345f7f1-f735-4c92-8aa2-6e137203b2f9 - transferId: 3ebe55c6-900a-4c03-b213-20c5c3f0274d - pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLWlkaHViOnVzZXI6Y29uc3VtZXIiLCJzdWIiOiJhbm9ueW1vdXMiLCJpYXQiOjE3NzAyNzg5ODgsImp0aSI6IjUwYWQxMjk3LWZhN2QtNDI0ZC1hNjBhLTg5M2MwMGE4OTZhYyJ9.GkSz0qXhFmqPaLQpfPLkAvODX-iekoAQvLh3Kglhm7DApNF3PsGnv-Qzm7m8eNAqTUTWB9XXkRng_XqWmuAd-FWvzwG8d7ZaAahuykkOgX1W7vHWBMdJa-zvNm0cnzm-TQLWYCU-tDSKk_g_UrDUaFf9Jdq-avCoer3wcZrEmrf0K4o_WWs-l5hZEfDIOYHRsgoCY3P8pMcZYRjV57zdLUDl9SvLuCRR0ex0fKxJ2pb7mlaCL5ooD6fRaqWyrLvrIKZaDYfwKrX7IRJT9ePKyls9VKA9JBakh676L0jBr5-2TYG3uE9Xhyv4CZlqyck-_NyiL4Jao8-lL5FVCbPDVQ - ISS_PART_CONT: localissuer - CONS_PART_CONT: consumer - PROV_PART_CONT: provider + cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4ODI1NzYxOCwic2NvcGUiOiJvcmcuZWNsaXBzZS5kc3BhY2UuZGNwLnZjLnR5cGU6TWVtYmVyc2hpcENyZWRlbnRpYWw6cmVhZCIsImlzcyI6ImRpZDp3ZWI6Y29uc3VtZXItd2FsbGV0OnVzZXI6Y29uc3VtZXIiLCJleHAiOjE3ODgyNTc5MTgsImlhdCI6MTc4ODI1NzYxOCwianRpIjoiYWNjZXNzdG9rZW4tNjc1NDk4Y2QtOTQ3ZC00NjZmLWI0OTMtZjgxZTRhMjNlMDg3In0.su14qdbmn1zgBAbhSeamzEOC8Gk66pdb9XUFTQKuLRG3Om7MefQUzUTyjcNNzpqbUCGC2DV6h1jK6RmHfFdYAA + prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyIiwiYXVkIjoiZGlkOndlYjpjb25zdW1lci13YWxsZXQ6dXNlcjpjb25zdW1lciIsIm5iZiI6MTc4ODI1NzYxOSwiaXNzIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsImV4cCI6MTc4ODI1NzkxOSwiaWF0IjoxNzg4MjU3NjE5LCJqdGkiOiJhOTY5ODA4Zi1lZjU0LTRjNWEtOTE3YS05Mjk2NTc3YjQzYWUiLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUkydGxlUzB4SWl3aVlXeG5Jam9pUldReU5UVXhPU0o5LmV5SmhkV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUlpd2ljM1ZpSWpvaVpHbGtPbmRsWWpwd2NtOTJhV1JsY2kxM1lXeHNaWFE2ZFhObGNqcHdjbTkyYVdSbGNpSXNJbTVpWmlJNk1UYzRPREkxTnpZeE9Dd2ljMk52Y0dVaU9pSnZjbWN1WldOc2FYQnpaUzVrYzNCaFkyVXVaR053TG5aakxuUjVjR1U2VFdWdFltVnljMmhwY0VOeVpXUmxiblJwWVd3NmNtVmhaQ0lzSW1semN5STZJbVJwWkRwM1pXSTZZMjl1YzNWdFpYSXRkMkZzYkdWME9uVnpaWEk2WTI5dWMzVnRaWElpTENKbGVIQWlPakUzT0RneU5UYzVNVGdzSW1saGRDSTZNVGM0T0RJMU56WXhPQ3dpYW5ScElqb2lZV05qWlhOemRHOXJaVzR0TmpjMU5EazRZMlF0T1RRM1pDMDBOalptTFdJME9UTXRaamd4WlRSaE1qTmxNRGczSW4wLnN1MTRxZGJtbjF6Z0JBYmhTZWFtekVPQzhHazY2cGRiOVhVRlRRS3VMUkczT203TWVmUVV6VVR5amNOTnpwcWJVQ0dDMkRWNmgxaks2Um1IZkZkWUFBIn0.-1wCoJjyw-KNOerivpCBmT0_hBV6wf2DySFScBKuN9HBt23AjckxNJ7NrQJNlfU5LlWf3RJr3jr6iXDlwryRDw + offerId: MQ==:YXNzZXRJZA==:N2RjZjc1OTktZTQzMy00NmY3LThlOTEtMWVjZTc3YzNlOWM1 + negotiation-id: 50dc06d7-bc96-40b1-9274-1eb7117d6dbb + transferId: 175f3962-2fc2-46ab-ba9e-1da78b7ad685 + pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiYW5vbnltb3VzIiwiaWF0IjoxNzg4MjU3NjQxLCJqdGkiOiIxMzQwZmI3YS1lNDhjLTQ5ODQtYTEwZi0yODI0NGIwOTc2NjkifQ.RmjZ_Ac5iUoWnbRWm3Lz3ylSka5qPGXtAS_Ono1vzE-qUGerYKthdfXMmI0Qa11RLas2ZPbzaYctoHYvIVHoQkv9dYkjjffb_I48QgSMNIJmxOF9snagBmo6ehwMpd0GSO_B2fjeh_qYcLuiHofz8bp6Ff7N1K1dsPKeM5KsPrr1ClBoSzxWB1uCVsh8zM7QIhdliGuVvkPLcWHqIrks4smj4PfNMgkpZTE3PwEu-1LKKPY95_5cbaqPD1F9yeqL1rkr39ZJ-P0hlmVs13a5oSThl1wdtqijfpNeDPvgX-h0TJ4UCSSnTqO8aKCxyjVMhqfBsaz_mCI7VFrC9HOHBQ + ISS_PART_CONT: con-x-issuer + CONS_PART_CONT: user-consumer + PROV_PART_CONT: user-provider + contractId: 6fba2299-5a7e-4bdf-b1b6-c0b609fde33c } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml b/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml index a31e8e50d5..5a1b5352df 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml +++ b/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml @@ -207,18 +207,6 @@ services: pull_policy: never environment: - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 - - tx.edc.postgresql.migration.asset.enabled=false - - tx.edc.postgresql.migration.agreementbpns.enabled=false - - tx.edc.postgresql.migration.bpn.enabled=false - - tx.edc.postgresql.migration.contractdefinition.enabled=false - - tx.edc.postgresql.migration.contractnegotiation.enabled=false - - tx.edc.postgresql.migration.dataplaneinstance.enabled=false - - tx.edc.postgresql.migration.edr.enabled=false - - tx.edc.postgresql.migration.federatedcatalog.enabled=false - - tx.edc.postgresql.migration.jti-validation.enabled=false - - tx.edc.postgresql.migration.policy-monitor.enabled=false - - tx.edc.postgresql.migration.policy.enabled=false - - tx.edc.postgresql.migration.transferprocess.enabled=false - edc.iam.trusted-issuer.example.id=did:web:local-issuer-wallet:con-x-issuer - edc.iam.did.web.use.https=false - edc.iam.sts.oauth.client.secret.alias=consumersecret @@ -248,9 +236,9 @@ services: - edc.vault.hashicorp.url=http://shared-vault:8200 - edc.vault.hashicorp.health.check.enabled=true - edc.vault.hashicorp.token=vaultsecret0123456789 - - tx.edc.iam.iatp.default-scopes.test.alias=org.eclipse.dspace.dcp.vc.type - - tx.edc.iam.iatp.default-scopes.test.type=MembershipCredential - - tx.edc.iam.iatp.default-scopes.test.operation=read + - tx.edc.iam.dcp.default-scopes.test.alias=org.eclipse.dspace.dcp.vc.type + - tx.edc.iam.dcp.default-scopes.test.type=MembershipCredential + - tx.edc.iam.dcp.default-scopes.test.operation=read healthcheck: test: ["CMD-SHELL", "wget --spider http://localhost:9000/api/check/readiness || exit 1"] start_period: 10s @@ -317,18 +305,6 @@ services: pull_policy: never environment: - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 - - tx.edc.postgresql.migration.asset.enabled=false - - tx.edc.postgresql.migration.agreementbpns.enabled=false - - tx.edc.postgresql.migration.bpn.enabled=false - - tx.edc.postgresql.migration.contractdefinition.enabled=false - - tx.edc.postgresql.migration.contractnegotiation.enabled=false - - tx.edc.postgresql.migration.dataplaneinstance.enabled=false - - tx.edc.postgresql.migration.edr.enabled=false - - tx.edc.postgresql.migration.federatedcatalog.enabled=false - - tx.edc.postgresql.migration.jti-validation.enabled=false - - tx.edc.postgresql.migration.policy-monitor.enabled=false - - tx.edc.postgresql.migration.policy.enabled=false - - tx.edc.postgresql.migration.transferprocess.enabled=false - edc.iam.trusted-issuer.example.id=did:web:local-issuer-wallet:con-x-issuer - edc.iam.did.web.use.https=false - edc.iam.sts.oauth.client.secret.alias=providersecret @@ -358,9 +334,9 @@ services: - edc.vault.hashicorp.url=http://shared-vault:8200 - edc.vault.hashicorp.health.check.enabled=true - edc.vault.hashicorp.token=vaultsecret0123456789 - - tx.edc.iam.iatp.default-scopes.test.alias=org.eclipse.dspace.dcp.vc.type - - tx.edc.iam.iatp.default-scopes.test.type=MembershipCredential - - tx.edc.iam.iatp.default-scopes.test.operation=read + - tx.edc.iam.dcp.default-scopes.test.alias=org.eclipse.dspace.dcp.vc.type + - tx.edc.iam.dcp.default-scopes.test.type=MembershipCredential + - tx.edc.iam.dcp.default-scopes.test.operation=read healthcheck: test: [ "CMD-SHELL", "wget --spider http://localhost:9000/api/check/readiness || exit 1" ] start_period: 10s 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 e0e43c2d00..179421d032 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 @@ -21,7 +21,6 @@ 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; @@ -29,16 +28,20 @@ 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; import java.util.stream.Stream; @Extension(value = "Vault seed extension: adds secrets to the vault", categories = { "vault", "security" }) -@BaseExtension public class VaultSeedExtension implements ServiceExtension { - @Setting(value = "Secrets with which the vault gets initially populated. Specify as comma-separated list of key:secret pairs.") - public static final String VAULT_MEMORY_SECRETS_PROPERTY = "tx.edc.vault.secrets"; + static final String VAULT_MEMORY_SECRETS_PROPERTY = "tx.edc.vault.secrets"; + + @Setting( + key = VAULT_MEMORY_SECRETS_PROPERTY, + description = "Secrets with which the vault gets initially populated. Specify as comma-separated list of key:secret pairs.", + required = false) + private String seedSecrets; + public static final String NAME = "Vault Seed Extension"; @Inject @@ -52,9 +55,8 @@ public String name() { } @Provider - public Vault createInMemVault(ServiceExtensionContext context) { + public Vault createInMemVault() { - var seedSecrets = context.getSetting(VAULT_MEMORY_SECRETS_PROPERTY, null); if (seedSecrets != null) { singleParticipantContextSupplier.get().map(ParticipantContext::getParticipantContextId) .onSuccess(participantContextId -> { 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 3649ad5832..d36c126260 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 @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.vault.memory; +import org.eclipse.edc.boot.system.injection.ObjectFactory; import org.eclipse.edc.boot.vault.InMemoryVault; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; @@ -27,15 +28,17 @@ import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.security.Vault; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; 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.ValueSource; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -62,9 +65,16 @@ void name(VaultSeedExtension extension) { @ParameterizedTest @ValueSource(strings = { "key1:", "key1:value1", "key1:value1;", ";key1:value1", ";sdf;key1:value1" }) - void createInMemVault_validString(String secret, ServiceExtensionContext context, VaultSeedExtension extension) { - when(context.getSetting(eq(VaultSeedExtension.VAULT_MEMORY_SECRETS_PROPERTY), eq(null))).thenReturn(secret); - extension.createInMemVault(context); + void createInMemVault_validString(String secret, ServiceExtensionContext context, ObjectFactory factory) { + var configMap = Map.of( + VaultSeedExtension.VAULT_MEMORY_SECRETS_PROPERTY, secret + ); + var config = ConfigFactory.fromMap(configMap); + when(context.getConfig()).thenReturn(config); + + var extension = factory.constructInstance(VaultSeedExtension.class); + + extension.createInMemVault(); verify(monitor, times(1)).debug(anyString()); } } diff --git a/edc-dataplane/edc-dataplane-base/build.gradle.kts b/edc-dataplane/edc-dataplane-base/build.gradle.kts index c31bc0f0fc..97c61a1ba8 100644 --- a/edc-dataplane/edc-dataplane-base/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-base/build.gradle.kts @@ -28,29 +28,6 @@ configurations.all { } dependencies { - constraints { - runtimeOnly("tools.jackson.core:jackson-core:3.1.0") { - because("older version has vulnerability") - } - runtimeOnly("com.fasterxml.jackson.core:jackson-core:2.21.1") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty:jetty-server:12.1.7") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty:jetty-security:12.1.7") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty:jetty-session:12.1.7") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.7") { - because("older version has vulnerability") - } - runtimeOnly("org.eclipse.jetty.websocket:jetty-websocket:12.1.7") { - because("older version has vulnerability") - } - } runtimeOnly(libs.edc.bom.dataplane.base) implementation(project(":core:edr-core")) @@ -76,8 +53,9 @@ 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) runtimeOnly(libs.edc.core.participant.context.config) - runtimeOnly(libs.edc.core.participant.context.single) + runtimeOnly(libs.edc.core.participant.context.classic) runtimeOnly(libs.edc.dpf.awss3) runtimeOnly(libs.edc.aws.provision.s3) runtimeOnly(libs.edc.dpf.azblob) diff --git a/edc-dataplane/edc-dataplane-construct-x/con-x-dataplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-construct-x/con-x-dataplane-postgresql-hashicorp-vault/build.gradle.kts index 092766e100..e48f397ae0 100644 --- a/edc-dataplane/edc-dataplane-construct-x/con-x-dataplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-construct-x/con-x-dataplane-postgresql-hashicorp-vault/build.gradle.kts @@ -29,9 +29,10 @@ configurations.all { exclude(group = "org.eclipse.edc", module = "data-plane-util") } +val edcVersion = project.property("con-x-edcVersion") as String +val txVersion = project.property("version") as String + dependencies { - val edcVersion = "0.15.1" - val txVersion = "0.12.0" implementation("org.eclipse.edc:dataplane-base-bom:$edcVersion") implementation("org.eclipse.edc:dataplane-feature-sql-bom:$edcVersion") implementation("org.eclipse.edc:vault-hashicorp:${edcVersion}") diff --git a/edc-dataplane/edc-dataplane-construct-x/con-x-dataplane-postgresql-vault/build.gradle.kts b/edc-dataplane/edc-dataplane-construct-x/con-x-dataplane-postgresql-vault/build.gradle.kts index 25d3b323eb..dd664ef633 100644 --- a/edc-dataplane/edc-dataplane-construct-x/con-x-dataplane-postgresql-vault/build.gradle.kts +++ b/edc-dataplane/edc-dataplane-construct-x/con-x-dataplane-postgresql-vault/build.gradle.kts @@ -29,9 +29,10 @@ configurations.all { exclude(group = "org.eclipse.edc", module = "data-plane-util") } +val edcVersion = project.property("con-x-edcVersion") as String +val txVersion = project.property("version") as String + dependencies { - val edcVersion = "0.15.1" - val txVersion = "0.12.0" implementation("org.eclipse.edc:dataplane-base-bom:$edcVersion") implementation("org.eclipse.edc:dataplane-feature-sql-bom:$edcVersion") implementation(project(":edc-extensions:sql-vault")) 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 c580be3232..df320866cf 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 @@ -47,8 +47,12 @@ public void on(EventEnvelope envelope) { var agreement = payload.getContractAgreement(); var agreementId = agreement.getId(); - var providerBpn = extractBpn(agreement.getProviderId()); - var consumerBpn = extractBpn(agreement.getConsumerId()); + var providerBpn = extractBpn(agreementId, agreement.getProviderId()); + var consumerBpn = extractBpn(agreementId, agreement.getConsumerId()); + + if (providerBpn == null || consumerBpn == null) { + return; + } var entry = AgreementsBpnsEntry.Builder.newInstance() .withAgreementId(agreementId) @@ -59,9 +63,15 @@ public void on(EventEnvelope envelope) { store.save(entry).onFailure(failure -> monitor.severe(failure.getFailureDetail())); } - private String extractBpn(String id) { - return id.startsWith(DID_PREFIX) - ? bdrsClient.resolveBpn(id) - : id; + private String extractBpn(String agreementId, String id) { + if (!id.startsWith(DID_PREFIX)) { + return id; + } + var bpn = bdrsClient.resolveBpn(id); + if (bpn == null) { + monitor.severe("Could not resolve BPN for DID '%s' on agreement '%s'. The agreement will not be stored." + .formatted(id, agreementId)); + } + return bpn; } } diff --git a/edc-extensions/agreements-bpns/bpns-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/defaults/InMemoryAgreementsBpnsStore.java b/edc-extensions/agreements-bpns/bpns-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/defaults/InMemoryAgreementsBpnsStore.java index 3b265e1dcc..5aeaab40bc 100644 --- a/edc-extensions/agreements-bpns/bpns-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/defaults/InMemoryAgreementsBpnsStore.java +++ b/edc-extensions/agreements-bpns/bpns-evaluation-core/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/defaults/InMemoryAgreementsBpnsStore.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2025 Cofinity-X GmbH + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -42,4 +43,9 @@ public StoreResult save(AgreementsBpnsEntry agreementsBpnsEntry) { cache.put(agreementsBpnsEntry.getAgreementId(), agreementsBpnsEntry); return StoreResult.success(); } + + @Override + public AgreementsBpnsEntry findByAgreementId(String agreementId) { + return cache.get(agreementId); + } } 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 98caf2f427..3a43f434ce 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 @@ -41,6 +41,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -173,4 +175,41 @@ void on_shouldLogSevere_whenStoreSaveFails() { verify(monitor).severe(failureDetail); } + + @Test + void on_shouldLogSevereAndSkipSave_whenBpnResolutionReturnsNull() { + var agreementId = UUID.randomUUID().toString(); + var providerId = "did:provider"; + var consumerId = "did:consumer"; + + var subscriberWithNullResolver = new EventContractNegotiationSubscriber( + store, monitor, new MockBdrsClient((s) -> s, (s) -> null)); + + var agreement = ContractAgreement.Builder.newInstance() + .id(agreementId) + .providerId(providerId) + .consumerId(consumerId) + .assetId("asset") + .policy(Policy.Builder.newInstance().assignee(consumerId).build()) + .build(); + + var event = ContractNegotiationFinalized.Builder.newInstance() + .contractNegotiationId(UUID.randomUUID().toString()) + .contractAgreement(agreement) + .counterPartyAddress("counterPartyAddress") + .counterPartyId("counterPartyId") + .protocol("protocol") + .build(); + + var envelope = EventEnvelope.Builder.newInstance() + .id(UUID.randomUUID().toString()) + .at(System.currentTimeMillis()) + .payload(event) + .build(); + + subscriberWithNullResolver.on(envelope); + + verify(monitor, times(2)).severe(anyString()); + verify(store, never()).save(any()); + } } diff --git a/edc-extensions/agreements-bpns/bpns-evaluation-core/src/testFixtures/java/org/eclipse/tractusx/edc/agreements/bpns/store/AgreementsBpnsStoreTestBase.java b/edc-extensions/agreements-bpns/bpns-evaluation-core/src/testFixtures/java/org/eclipse/tractusx/edc/agreements/bpns/store/AgreementsBpnsStoreTestBase.java index be1c60efdf..5acf45a627 100644 --- a/edc-extensions/agreements-bpns/bpns-evaluation-core/src/testFixtures/java/org/eclipse/tractusx/edc/agreements/bpns/store/AgreementsBpnsStoreTestBase.java +++ b/edc-extensions/agreements-bpns/bpns-evaluation-core/src/testFixtures/java/org/eclipse/tractusx/edc/agreements/bpns/store/AgreementsBpnsStoreTestBase.java @@ -23,6 +23,7 @@ import org.eclipse.tractusx.edc.agreements.bpns.spi.types.AgreementsBpnsEntry; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.tractusx.edc.agreements.bpns.spi.store.AgreementsBpnsStore.ALREADY_EXISTS_TEMPLATE; @@ -39,6 +40,27 @@ void save_whenExists() { .detail().isEqualTo(ALREADY_EXISTS_TEMPLATE.formatted(agreementId)); } + @Test + void findByAgreementId_whenExists() { + var agreementId = "test-agreement-id"; + var entry = createAgreementsBpnsEntry(agreementId, "providerBpn", "consumerBpn"); + getStore().save(entry); + + var found = getStore().findByAgreementId(agreementId); + + assertThat(found).isNotNull(); + assertThat(found.getAgreementId()).isEqualTo(agreementId); + assertThat(found.getProviderBpn()).isEqualTo("providerBpn"); + assertThat(found.getConsumerBpn()).isEqualTo("consumerBpn"); + } + + @Test + void findByAgreementId_whenNotExists() { + var found = getStore().findByAgreementId("unknown-agreement-id"); + + assertThat(found).isNull(); + } + private AgreementsBpnsEntry createAgreementsBpnsEntry(String agreementId, String providerBpn, String consumerBpn) { return AgreementsBpnsEntry.Builder.newInstance() .withAgreementId(agreementId) diff --git a/edc-extensions/agreements-bpns/bpns-evaluation-spi/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/spi/store/AgreementsBpnsStore.java b/edc-extensions/agreements-bpns/bpns-evaluation-spi/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/spi/store/AgreementsBpnsStore.java index 0b93691965..537d075772 100644 --- a/edc-extensions/agreements-bpns/bpns-evaluation-spi/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/spi/store/AgreementsBpnsStore.java +++ b/edc-extensions/agreements-bpns/bpns-evaluation-spi/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/spi/store/AgreementsBpnsStore.java @@ -22,10 +22,14 @@ import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; import org.eclipse.edc.spi.result.StoreResult; import org.eclipse.tractusx.edc.agreements.bpns.spi.types.AgreementsBpnsEntry; +import org.jetbrains.annotations.Nullable; @ExtensionPoint public interface AgreementsBpnsStore { String ALREADY_EXISTS_TEMPLATE = "Contract Agreement %s already exists."; StoreResult save(AgreementsBpnsEntry agreementsBpnsEntry); + + @Nullable + AgreementsBpnsEntry findByAgreementId(String agreementId); } diff --git a/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/docs/schema.sql b/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/docs/schema.sql index 1882b44df4..3309b3a14b 100644 --- a/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/docs/schema.sql +++ b/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/docs/schema.sql @@ -27,7 +27,8 @@ CREATE TABLE IF NOT EXISTS edc_contract_agreement INSERT INTO edc_contract_agreement (id, provider_agent_id, consumer_agent_id, signing_date, start_date, end_date, asset_id, policy_id, serialized_policy) VALUES - ('test-agreement-id', 'default-provider-agent', 'default-consumer-agent', 0, 0, 0, 'default-asset', 'default-policy-id', '{}'); + ('test-agreement-id', 'default-provider-agent', 'default-consumer-agent', 0, 0, 0, 'default-asset', 'default-policy-id', '{}') +ON CONFLICT DO NOTHING; CREATE TABLE IF NOT EXISTS edc_contract_agreement_bpns ( diff --git a/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/SqlAgreementsBpnsStoreExtension.java b/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/SqlAgreementsBpnsStoreExtension.java index 8a9fb4c72f..061ab8235f 100644 --- a/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/SqlAgreementsBpnsStoreExtension.java +++ b/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/SqlAgreementsBpnsStoreExtension.java @@ -24,7 +24,6 @@ import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.sql.QueryExecutor; import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; @@ -39,9 +38,11 @@ public class SqlAgreementsBpnsStoreExtension implements ServiceExtension { protected static final String NAME = "SQL Agreement BPNs Store."; - @Setting(description = "Datasource name for the SQL AgreementsBpns store", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE) private static final String DATASOURCE_SETTING_NAME = "edc.sql.store.contractnegotiation.datasource"; + @Setting(key = DATASOURCE_SETTING_NAME, description = "Datasource name for the SQL AgreementsBpns store", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE) + private String dataSourceName; + @Inject private DataSourceRegistry dataSourceRegistry; @@ -58,8 +59,7 @@ public class SqlAgreementsBpnsStoreExtension implements ServiceExtension { private SqlAgreementsBpnsStatements statements; @Provider - public AgreementsBpnsStore sqlStore(ServiceExtensionContext context) { - var dataSourceName = context.getConfig().getString(DATASOURCE_SETTING_NAME, DataSourceRegistry.DEFAULT_DATASOURCE); + public AgreementsBpnsStore sqlStore() { return new SqlAgreementsBpnsStore(dataSourceRegistry, dataSourceName, transactionContext, typeManager.getMapper(), queryExecutor, getStatements()); } diff --git a/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/sql/SqlAgreementsBpnsStatements.java b/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/sql/SqlAgreementsBpnsStatements.java index 8e39d2513b..dd4ca7ca42 100644 --- a/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/sql/SqlAgreementsBpnsStatements.java +++ b/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/sql/SqlAgreementsBpnsStatements.java @@ -43,4 +43,8 @@ default String getTable() { } String insertWithOnConflict(); + + default String findByAgreementIdTemplate() { + return "SELECT * FROM %s WHERE %s = ?".formatted(getTable(), getAgreementIdColumn()); + } } diff --git a/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/sql/SqlAgreementsBpnsStore.java b/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/sql/SqlAgreementsBpnsStore.java index 0bd6c3555f..ca805679e6 100644 --- a/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/sql/SqlAgreementsBpnsStore.java +++ b/edc-extensions/agreements-bpns/bpns-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/bpns/store/sql/SqlAgreementsBpnsStore.java @@ -29,6 +29,7 @@ import org.eclipse.tractusx.edc.agreements.bpns.spi.store.AgreementsBpnsStore; import org.eclipse.tractusx.edc.agreements.bpns.spi.types.AgreementsBpnsEntry; +import java.sql.ResultSet; import java.sql.SQLException; import java.util.Objects; @@ -66,4 +67,24 @@ public StoreResult save(AgreementsBpnsEntry agreementsBpnsEntry) { } }); } + + @Override + public AgreementsBpnsEntry findByAgreementId(String agreementId) { + return transactionContext.execute(() -> { + try (var connection = getConnection()) { + return queryExecutor.single(connection, false, this::mapRow, + statements.findByAgreementIdTemplate(), agreementId); + } catch (SQLException e) { + throw new EdcPersistenceException(e); + } + }); + } + + private AgreementsBpnsEntry mapRow(ResultSet rs) throws SQLException { + return AgreementsBpnsEntry.Builder.newInstance() + .withAgreementId(rs.getString(statements.getAgreementIdColumn())) + .withProviderBpn(rs.getString(statements.getProviderBpnColumn())) + .withConsumerBpn(rs.getString(statements.getConsumerBpnColumn())) + .build(); + } } diff --git a/edc-extensions/agreements/retirement-evaluation-bootstrapping/README.md b/edc-extensions/agreements/retirement-evaluation-bootstrapping/README.md new file mode 100644 index 0000000000..1254bb6a56 --- /dev/null +++ b/edc-extensions/agreements/retirement-evaluation-bootstrapping/README.md @@ -0,0 +1,8 @@ +## RetirementEvaluationBootstrappingExtension + +This extension will utilize the SqlSchemaBootstrapper in order to make sure that the SQL table `edc_agreement_retirement` gets created. + +This is because the retirement-evaluation-store-sql extension does not take care of the creation of this table itself, but instead relies on the controlplane-migration extension. + +So this extension is useful as a lightweight-replacement, if you want to use the agreement-retirement extension with SQL persistence, but don't want to use the database migration extension. + diff --git a/edc-extensions/agreements/retirement-evaluation-bootstrapping/build.gradle.kts b/edc-extensions/agreements/retirement-evaluation-bootstrapping/build.gradle.kts new file mode 100644 index 0000000000..a95aacec4b --- /dev/null +++ b/edc-extensions/agreements/retirement-evaluation-bootstrapping/build.gradle.kts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) + * + * 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 { + id("java") + id("application") +} + +repositories { mavenCentral() } + +val edcVersion = project.property("con-x-edcVersion") as String + +dependencies { + implementation("org.eclipse.edc:sql-lib:${edcVersion}") + implementation("org.eclipse.edc:sql-bootstrapper:${edcVersion}") + implementation("org.eclipse.edc:core-spi:${edcVersion}") + + testImplementation("org.eclipse.edc:junit:${edcVersion}") { + exclude(group = "org.junit.jupiter") + exclude(group = "org.junit.platform") + exclude(group = "org.junit") + } +} + diff --git a/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/main/java/de/fraunhofer/isst/edc/extension/retirement_bootstrapper/dev/AgreementRetirementBootstrappingExtension.java b/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/main/java/de/fraunhofer/isst/edc/extension/retirement_bootstrapper/dev/AgreementRetirementBootstrappingExtension.java new file mode 100644 index 0000000000..9128e0bc38 --- /dev/null +++ b/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/main/java/de/fraunhofer/isst/edc/extension/retirement_bootstrapper/dev/AgreementRetirementBootstrappingExtension.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) + * + * 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 de.fraunhofer.isst.edc.extension.retirement_bootstrapper.dev; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.sql.bootstrapper.SqlSchemaBootstrapper; +import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; + + +@Extension("Agreement Retirement Bootstrapping Extension") +public class AgreementRetirementBootstrappingExtension implements ServiceExtension { + + @Setting(description = "The datasource to be used", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE, key = "tx.edc.sql.store.agreementretirement.datasource") + private String dataSourceName; + @Inject + private SqlSchemaBootstrapper sqlSchemaBootstrapper; + + @Override + public String name() { + return "Agreement Retirement Bootstrapping Extension"; + } + + @Override + public void initialize(ServiceExtensionContext context) { + sqlSchemaBootstrapper.addStatementFromResource(dataSourceName, "V0_0_1__Init_ContractAgreementRetirement.sql"); + } + +} diff --git a/edc-extensions/provision-additional-headers/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension similarity index 78% rename from edc-extensions/provision-additional-headers/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension rename to edc-extensions/agreements/retirement-evaluation-bootstrapping/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension index 4f1e44009a..ab5043307a 100644 --- a/edc-extensions/provision-additional-headers/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ b/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -1,6 +1,5 @@ ################################################################################# -# Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) -# Copyright (c) 2021,2023 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -18,4 +17,4 @@ # SPDX-License-Identifier: Apache-2.0 ################################################################################# -org.eclipse.tractusx.edc.provision.additionalheaders.ProvisionAdditionalHeadersExtension +de.fraunhofer.isst.edc.extension.retirement_bootstrapper.dev.AgreementRetirementBootstrappingExtension \ No newline at end of file diff --git a/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/main/resources/V0_0_1__Init_ContractAgreementRetirement.sql b/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/main/resources/V0_0_1__Init_ContractAgreementRetirement.sql new file mode 100644 index 0000000000..53c0724c7a --- /dev/null +++ b/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/main/resources/V0_0_1__Init_ContractAgreementRetirement.sql @@ -0,0 +1,22 @@ +-- +-- Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Mercedes-Benz Tech Innovation GmbH - Initial Database Schema +-- + +-- +-- table: edc_agreement_retirement +-- +CREATE TABLE IF NOT EXISTS edc_agreement_retirement +( + contract_agreement_id VARCHAR PRIMARY KEY, + reason TEXT NOT NULL, + agreement_retirement_date BIGINT NOT NULL +); \ No newline at end of file diff --git a/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/test/java/SqlVaultTest.java b/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/test/java/SqlVaultTest.java new file mode 100644 index 0000000000..e5e580deef --- /dev/null +++ b/edc-extensions/agreements/retirement-evaluation-bootstrapping/src/test/java/SqlVaultTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) + * + * 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 + */ + +import static org.mockito.Mockito.mock; + +public class SqlVaultTest { + + +} diff --git a/edc-extensions/agreements/retirement-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/store/SqlAgreementsRetirementStoreExtension.java b/edc-extensions/agreements/retirement-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/store/SqlAgreementsRetirementStoreExtension.java index 543aa6c7f0..8e6c149628 100644 --- a/edc-extensions/agreements/retirement-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/store/SqlAgreementsRetirementStoreExtension.java +++ b/edc-extensions/agreements/retirement-evaluation-store-sql/src/main/java/org/eclipse/tractusx/edc/agreements/retirement/store/SqlAgreementsRetirementStoreExtension.java @@ -24,7 +24,6 @@ import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.sql.QueryExecutor; import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; @@ -39,9 +38,11 @@ public class SqlAgreementsRetirementStoreExtension implements ServiceExtension { protected static final String NAME = "SQL Agreement Retirement Store."; - @Setting(value = "Datasource name for the SQL AgreementsRetirement store", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE) private static final String DATASOURCE_SETTING_NAME = "tx.edc.sql.store.agreementretirement.datasource"; + @Setting(key = DATASOURCE_SETTING_NAME, description = "Datasource name for the SQL AgreementsRetirement store", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE) + private String dataSourceName; + @Inject private DataSourceRegistry dataSourceRegistry; @@ -58,8 +59,7 @@ public class SqlAgreementsRetirementStoreExtension implements ServiceExtension { private SqlAgreementsRetirementStatements statements; @Provider - public AgreementsRetirementStore sqlStore(ServiceExtensionContext context) { - var dataSourceName = context.getConfig().getString(DATASOURCE_SETTING_NAME, DataSourceRegistry.DEFAULT_DATASOURCE); + public AgreementsRetirementStore sqlStore() { return new SqlAgreementsRetirementStore(dataSourceRegistry, dataSourceName, transactionContext, typeManager.getMapper(), queryExecutor, getStatements()); } diff --git a/edc-extensions/bdrs-client/README.md b/edc-extensions/bdrs-client/README.md new file mode 100644 index 0000000000..e3d5d52ac6 --- /dev/null +++ b/edc-extensions/bdrs-client/README.md @@ -0,0 +1,4 @@ +## Purpose +The BPN-DID Resolution Service (BDRS) Client extension provides a client for the BDRS server, which maintains mappings between Business Partner Numbers (BPNs) and Decentralized Identifiers (DIDs) in the Catena-X dataspace. +The client maintains a local cache of these mappings for efficient resolution. +For dataspaces that don't require mapping between a did and a different identifier, this extension (and the extensions injecting it) is obsolete. \ No newline at end of file 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 c1c60d59bb..bbefa82c68 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 @@ -25,10 +25,12 @@ import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; 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.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.types.TypeManager; @@ -36,7 +38,6 @@ import java.util.function.Supplier; -import static org.eclipse.tractusx.edc.core.utils.ConfigUtil.missingMandatoryProperty; import static org.eclipse.tractusx.edc.identity.mapper.BdrsClientExtension.NAME; @Extension(value = NAME) @@ -44,17 +45,21 @@ public class BdrsClientExtension implements ServiceExtension { public static final String NAME = "BPN/DID Resolution Service Client Extension"; public static final int DEFAULT_BDRS_CACHE_VALIDITY = 15 * 60; // 15 minutes - @Setting(value = "Base URL of the BDRS service", required = true) - public static final String BDRS_SERVER_URL_PROPERTY = "tx.edc.iam.iatp.bdrs.server.url"; - @Setting(value = "Base URL of the CredentialService, that belongs to this connector runtime. If not specified, the URL is resolved from this participant's DID document.") - public static final String CREDENTIAL_SERVICE_BASE_URL_PROPERTY = "tx.edc.iam.iatp.credentialservice.url"; + static final String BDRS_SERVER_URL_PROPERTY = "tx.edc.iam.dcp.bdrs.server.url"; - @Setting(value = "Validity period in seconds for the cached BPN/DID mappings. After this period a new resolution request will hit the server.", defaultValue = DEFAULT_BDRS_CACHE_VALIDITY + "") - public static final String BDRS_SERVER_CACHE_VALIDITY_PERIOD = "tx.edc.iam.iatp.bdrs.cache.validity"; + @Setting(key = BDRS_SERVER_URL_PROPERTY, description = "Base URL of the BDRS service") + private String bdrsServerUrl; - // this setting is already defined in IdentityAndTrustExtension - public static final String CONNECTOR_DID_PROPERTY = "edc.iam.issuer.id"; + static final String CREDENTIAL_SERVICE_BASE_URL_PROPERTY = "tx.edc.iam.dcp.credentialservice.url"; + + @Setting(key = CREDENTIAL_SERVICE_BASE_URL_PROPERTY, description = "Base URL of the CredentialService, that belongs to this connector runtime. If not specified, the URL is resolved from this participant's DID document.", required = false) + private String credentialServiceBaseUrl; + + private static final String BDRS_SERVER_CACHE_VALIDITY_PERIOD = "tx.edc.iam.dcp.bdrs.cache.validity"; + + @Setting(key = BDRS_SERVER_CACHE_VALIDITY_PERIOD, description = "Validity period in seconds for the cached BPN/DID mappings. After this period a new resolution request will hit the server.", defaultValue = DEFAULT_BDRS_CACHE_VALIDITY + "") + private int cacheValidityPeriod; @Inject private EdcHttpClient httpClient; @@ -76,21 +81,19 @@ public String name() { @Provider public BdrsClient getBdrsClient(ServiceExtensionContext context) { - var baseUrl = context.getConfig().getString(BDRS_SERVER_URL_PROPERTY); var monitor = context.getMonitor(); - var cacheValidity = context.getSetting(BDRS_SERVER_CACHE_VALIDITY_PERIOD, DEFAULT_BDRS_CACHE_VALIDITY); // get DID - var ownDid = context.getConfig().getString(CONNECTOR_DID_PROPERTY, null); - if (ownDid == null) { - missingMandatoryProperty(monitor, CONNECTOR_DID_PROPERTY); - } + var ownDid = participantContextSupplier.get().map(ParticipantContext::getIdentity).onFailure(f -> { + var message = "This connector is not configured properly, cannot continue. Error is: %s".formatted(f.getFailureDetail()); + monitor.severe(message); + throw new EdcException(message); + }).getContent(); // get CS URL Supplier urlSupplier; - var configuredUrl = context.getSetting(CREDENTIAL_SERVICE_BASE_URL_PROPERTY, null); - if (configuredUrl != null) { - urlSupplier = () -> configuredUrl; + if (credentialServiceBaseUrl != null) { + urlSupplier = () -> credentialServiceBaseUrl; } else { monitor.warning("No config value found for '%s'. As a fallback, the credentialService URL from this connector's DID document will be resolved.".formatted(CREDENTIAL_SERVICE_BASE_URL_PROPERTY)); @@ -104,7 +107,7 @@ public BdrsClient getBdrsClient(ServiceExtensionContext context) { } - return new BdrsClientImpl(baseUrl, cacheValidity, ownDid, urlSupplier, httpClient, monitor, typeManager.getMapper(), + return new BdrsClientImpl(bdrsServerUrl, cacheValidityPeriod, 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 6e33d7b3f4..65a42c24fc 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 @@ -30,7 +30,7 @@ import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; -import org.eclipse.tractusx.edc.TxIatpConstants; +import org.eclipse.tractusx.edc.TxDcpConstants; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import java.io.IOException; @@ -188,7 +188,7 @@ private Result createMembershipPresentation() { SUBJECT, ownDid, AUDIENCE, ownDid ); - var scope = TxIatpConstants.MEMBERSHIP_SCOPE; + var scope = TxDcpConstants.MEMBERSHIP_SCOPE; return participantContextSupplier.get().map(ParticipantContext::getParticipantContextId) .flatMap(result -> { 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 9bf4a10844..69acaae510 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 @@ -23,28 +23,25 @@ import org.eclipse.edc.iam.did.spi.document.Service; import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; -import org.eclipse.edc.spi.EdcException; +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.Result; +import org.eclipse.edc.spi.result.ServiceResult; import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.system.configuration.Config; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import java.util.List; +import java.util.Map; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.eclipse.tractusx.edc.identity.mapper.BdrsClientExtension.BDRS_SERVER_URL_PROPERTY; -import static org.eclipse.tractusx.edc.identity.mapper.BdrsClientExtension.CONNECTOR_DID_PROPERTY; -import static org.eclipse.tractusx.edc.identity.mapper.BdrsClientExtension.CREDENTIAL_SERVICE_BASE_URL_PROPERTY; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @ExtendWith(DependencyInjectionExtension.class) @@ -52,70 +49,39 @@ class BdrsClientImplExtensionTest { private final Monitor monitor = mock(); private final DidResolverRegistry resolverRegistry = mock(); + private final SingleParticipantContextSupplier participantContextSupplier = mock(SingleParticipantContextSupplier.class); @BeforeEach void setup(ServiceExtensionContext context) { context.registerService(Monitor.class, monitor); context.registerService(DidResolverRegistry.class, resolverRegistry); - } - - @Test - void createClient_whenUrlMissing_expectException(ServiceExtensionContext context, BdrsClientExtension extension) { - var cfg = mock(Config.class); - when(cfg.getString(eq(BDRS_SERVER_URL_PROPERTY))).thenThrow(new EdcException(BDRS_SERVER_URL_PROPERTY)); - when(context.getConfig()).thenReturn(cfg); - when(cfg.getString(eq(BDRS_SERVER_URL_PROPERTY), isNull())).thenReturn(null); - when(cfg.getString(eq(CONNECTOR_DID_PROPERTY), isNull())).thenReturn("did:web:self"); - when(cfg.getString(eq(CREDENTIAL_SERVICE_BASE_URL_PROPERTY), isNull())).thenReturn("https://credential.service"); - - assertThatThrownBy(() -> extension.getBdrsClient(context)).isInstanceOf(EdcException.class) - .hasMessageContaining(BDRS_SERVER_URL_PROPERTY); + context.registerService(SingleParticipantContextSupplier.class, participantContextSupplier); + var config = ConfigFactory.fromMap(Map.of(BDRS_SERVER_URL_PROPERTY, "https://bdrs.server")); + when(context.getConfig()).thenReturn(config); } @Test void createClient_whenNoCredentialServiceUrl_shouldInvokeResolver(ServiceExtensionContext context, BdrsClientExtension extension) { - var cfg = mock(Config.class); - when(context.getConfig()).thenReturn(cfg); - when(cfg.getString(eq(BDRS_SERVER_URL_PROPERTY), isNull())).thenReturn("https://bdrs.server"); - when(cfg.getString(eq(CREDENTIAL_SERVICE_BASE_URL_PROPERTY), isNull())).thenReturn(null); - when(cfg.getString(eq(CONNECTOR_DID_PROPERTY), isNull())).thenReturn("did:web:self"); when(resolverRegistry.resolve(anyString())).thenReturn(Result.success(DidDocument.Builder.newInstance().service(List.of(new Service(null, "CredentialService", "http://credential.service"))).build())); + when(participantContextSupplier.get()).thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance().identity("did:example:123").participantContextId("uuid").build())); 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); + verify(monitor).warning("No config value found for 'tx.edc.iam.dcp.credentialservice.url'. As a fallback, the credentialService URL from this connector's DID document will be resolved."); } @Test void createClient_whenResolverFails_expectLogError(ServiceExtensionContext context, BdrsClientExtension extension) { - var cfg = mock(Config.class); - when(context.getConfig()).thenReturn(cfg); - when(cfg.getString(eq(BDRS_SERVER_URL_PROPERTY), isNull())).thenReturn("https://bdrs.server"); - when(cfg.getString(eq(CREDENTIAL_SERVICE_BASE_URL_PROPERTY), isNull())).thenReturn(null); - when(cfg.getString(eq(CONNECTOR_DID_PROPERTY), isNull())).thenReturn("did:web:self"); when(resolverRegistry.resolve(anyString())).thenReturn(Result.failure("test failure")); + when(participantContextSupplier.get()).thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance().identity("did:example:123").participantContextId("uuid").build())); 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."); + verify(monitor).warning("No config value found for 'tx.edc.iam.dcp.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 verify(monitor, never()).severe("Resolving the credentialService URL failed. This runtime won't be able to communicate with BDRS. Error: test failure."); - verifyNoMoreInteractions(monitor); - } - - @Test - void createClient_whenNoDid_expectLogError(ServiceExtensionContext context, BdrsClientExtension extension) { - var cfg = mock(Config.class); - when(context.getConfig()).thenReturn(cfg); - when(cfg.getString(eq(BDRS_SERVER_URL_PROPERTY), isNull())).thenReturn("https://bdrs.server"); - when(cfg.getString(eq(CREDENTIAL_SERVICE_BASE_URL_PROPERTY), isNull())).thenReturn("https://credential.service"); - when(cfg.getString(eq(CONNECTOR_DID_PROPERTY), isNull())).thenReturn(null); - - assertThatThrownBy(() -> extension.getBdrsClient(context)).isInstanceOf(EdcException.class) - .hasMessageContaining(CONNECTOR_DID_PROPERTY); } } \ No newline at end of file diff --git a/edc-extensions/bdrs-client/src/test/resources/Dockerfile b/edc-extensions/bdrs-client/src/test/resources/Dockerfile index be243a8e7d..75ddd98fe2 100644 --- a/edc-extensions/bdrs-client/src/test/resources/Dockerfile +++ b/edc-extensions/bdrs-client/src/test/resources/Dockerfile @@ -1,2 +1,2 @@ -FROM tractusx/bdrs-server-memory:0.5.7 +FROM tractusx/bdrs-server-memory:0.6.0 USER "Dummy" diff --git a/edc-extensions/bpn-validation/business-partner-store-sql/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/store/SqlBusinessPartnerGroupStoreExtension.java b/edc-extensions/bpn-validation/business-partner-store-sql/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/store/SqlBusinessPartnerGroupStoreExtension.java index 1cd5c389ac..0160b7de05 100644 --- a/edc-extensions/bpn-validation/business-partner-store-sql/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/store/SqlBusinessPartnerGroupStoreExtension.java +++ b/edc-extensions/bpn-validation/business-partner-store-sql/src/main/java/org/eclipse/tractusx/edc/validation/businesspartner/store/SqlBusinessPartnerGroupStoreExtension.java @@ -24,7 +24,6 @@ import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.sql.QueryExecutor; import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; @@ -37,8 +36,10 @@ @Extension("Registers an SQL implementation for the BusinessPartnerGroupStore") public class SqlBusinessPartnerGroupStoreExtension implements ServiceExtension { - @Setting(value = "The datasource to be used", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE) - public static final String DATASOURCE_NAME = "edc.sql.store.bpn.datasource"; + private static final String DATASOURCE_NAME = "edc.sql.store.bpn.datasource"; + + @Setting(key = DATASOURCE_NAME, description = "The datasource for the BusinessPartnerGroupStore", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE) + private String dataSourceName; private static final String NAME = "SQL Business Partner Store"; @Inject @@ -53,8 +54,7 @@ public class SqlBusinessPartnerGroupStoreExtension implements ServiceExtension { private BusinessPartnerGroupStatements statements; @Provider - public BusinessPartnerStore sqlStore(ServiceExtensionContext context) { - var dataSourceName = context.getConfig().getString(DATASOURCE_NAME, DataSourceRegistry.DEFAULT_DATASOURCE); + public BusinessPartnerStore sqlStore() { return new SqlBusinessPartnerStore(dataSourceRegistry, dataSourceName, transactionContext, typeManager.getMapper(), queryExecutor, getStatements()); } diff --git a/edc-extensions/connector-discovery/README.md b/edc-extensions/connector-discovery/README.md new file mode 100644 index 0000000000..17249c18b9 --- /dev/null +++ b/edc-extensions/connector-discovery/README.md @@ -0,0 +1,4 @@ +## Purpose +The Connector Discovery extension enables participants to discover other connectors in the dataspace. +The **connector-discovery-api** module provides a REST API and default implementation for **DID-based** discovery with **Dataspace Protocol (DSP) 2025-1**. +It resolves connector endpoints from a participant’s DID document. \ No newline at end of file diff --git a/edc-extensions/connector-discovery/connector-discovery-api/build.gradle.kts b/edc-extensions/connector-discovery/connector-discovery-api/build.gradle.kts index 3f82ef0099..bbdfae4123 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/build.gradle.kts +++ b/edc-extensions/connector-discovery/connector-discovery-api/build.gradle.kts @@ -32,7 +32,7 @@ dependencies { api(libs.edc.spi.http) api(libs.edc.spi.jsonld) api(libs.edc.spi.controlplane) - api(libs.dsp.spi.v08) + api(project(":spi:dsp-spi-08")) api(libs.dsp.spi.v2025) api(libs.edc.spi.identity.did) @@ -40,6 +40,7 @@ dependencies { implementation(libs.edc.lib.validator) implementation(libs.edc.lib.util) implementation(libs.edc.boot) + implementation(libs.edc.api.core) implementation(libs.edc.api.management.config) implementation(libs.jakarta.rsApi) diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryExtension.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/ConnectorDiscoveryExtension.java similarity index 71% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryExtension.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/ConnectorDiscoveryExtension.java index c0e27fef53..7d4e1a8658 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryExtension.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/ConnectorDiscoveryExtension.java @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha; +package org.eclipse.tractusx.edc.discovery; import org.eclipse.edc.http.spi.EdcHttpClient; import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; @@ -36,21 +36,23 @@ import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.tractusx.edc.discovery.v4alpha.api.ConnectorDiscoveryV4AlphaController; -import org.eclipse.tractusx.edc.discovery.v4alpha.service.DefaultConnectorDiscoveryServiceImpl; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.CacheConfig; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryService; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.transformers.JsonObjectToConnectorDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.transformers.JsonObjectToConnectorParamsDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.validators.ConnectorDiscoveryRequestValidator; -import org.eclipse.tractusx.edc.discovery.v4alpha.validators.ConnectorParamsDiscoveryRequestValidator; +import org.eclipse.tractusx.edc.discovery.api.ConnectorDiscoveryController; +import org.eclipse.tractusx.edc.discovery.api.v3.ConnectorDiscoveryV3Controller; +import org.eclipse.tractusx.edc.discovery.api.v4alpha.ConnectorDiscoveryV4AlphaController; +import org.eclipse.tractusx.edc.discovery.service.DefaultConnectorDiscoveryServiceImpl; +import org.eclipse.tractusx.edc.discovery.spi.CacheConfig; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryService; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.transformers.JsonObjectToConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.transformers.JsonObjectToConnectorParamsDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.validators.ConnectorDiscoveryRequestValidator; +import org.eclipse.tractusx.edc.discovery.validators.ConnectorParamsDiscoveryRequestValidator; import java.time.Clock; import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; -import static org.eclipse.tractusx.edc.discovery.v4alpha.ConnectorDiscoveryExtension.NAME; +import static org.eclipse.tractusx.edc.discovery.ConnectorDiscoveryExtension.NAME; @Extension(value = NAME) public class ConnectorDiscoveryExtension implements ServiceExtension { @@ -101,12 +103,21 @@ public void initialize(ServiceExtensionContext context) { managementTypeTransformerRegistry.register(new JsonObjectToConnectorDiscoveryRequest()); validatorRegistry.register(ConnectorDiscoveryRequest.TYPE, ConnectorDiscoveryRequestValidator.instance()); - webService.registerResource(ApiContext.MANAGEMENT, new ConnectorDiscoveryV4AlphaController( - connectorDiscoveryService, managementTypeTransformerRegistry, validatorRegistry, monitor)); + var connectorDiscoveryController = new ConnectorDiscoveryController( + connectorDiscoveryService, managementTypeTransformerRegistry, validatorRegistry, monitor); + + webService.registerResource(ApiContext.MANAGEMENT, + new ConnectorDiscoveryV4AlphaController(connectorDiscoveryController, monitor)); webService.registerDynamicResource( ApiContext.MANAGEMENT, ConnectorDiscoveryV4AlphaController.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, "MANAGEMENT_API")); + webService.registerResource(ApiContext.MANAGEMENT, + new ConnectorDiscoveryV3Controller(connectorDiscoveryController)); + webService.registerDynamicResource( + ApiContext.MANAGEMENT, ConnectorDiscoveryV3Controller.class, + new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, "MANAGEMENT_API")); + } @Provider(isDefault = true) diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/api/ConnectorDiscoveryV4AlphaController.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/ConnectorDiscoveryController.java similarity index 69% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/api/ConnectorDiscoveryV4AlphaController.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/ConnectorDiscoveryController.java index 3ac37951f7..46dd25910c 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/api/ConnectorDiscoveryV4AlphaController.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/ConnectorDiscoveryController.java @@ -18,52 +18,43 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.api; +package org.eclipse.tractusx.edc.discovery.api; import jakarta.json.JsonObject; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; import jakarta.ws.rs.container.AsyncResponse; -import jakarta.ws.rs.container.Suspended; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; import org.eclipse.edc.web.spi.exception.ValidationFailureException; -import org.eclipse.tractusx.edc.discovery.v4alpha.exceptions.UnexpectedResultApiException; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryService; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.exceptions.UnexpectedResultApiException; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryService; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest; import java.util.concurrent.CompletionException; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; - -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/v4alpha/connectordiscovery") -public class ConnectorDiscoveryV4AlphaController implements ConnectorDiscoveryV4AlphaApi { +/** + * Holds the versioned-independent connector discovery logic. Version specific controllers (e.g. v3, v4alpha) + * delegate the actual work to this controller. + */ +public class ConnectorDiscoveryController { private final ConnectorDiscoveryService connectorDiscoveryService; private final TypeTransformerRegistry transformerRegistry; private final JsonObjectValidatorRegistry validator; private final Monitor monitor; - public ConnectorDiscoveryV4AlphaController(ConnectorDiscoveryService connectorDiscoveryService, - TypeTransformerRegistry transformerRegistry, - JsonObjectValidatorRegistry validator, - Monitor monitor) { + public ConnectorDiscoveryController(ConnectorDiscoveryService connectorDiscoveryService, + TypeTransformerRegistry transformerRegistry, + JsonObjectValidatorRegistry validator, + Monitor monitor) { this.connectorDiscoveryService = connectorDiscoveryService; this.transformerRegistry = transformerRegistry; this.validator = validator; this.monitor = monitor; } - @Path("/dspversionparams") - @POST - @Override - public void discoverDspVersionParamsV4Alpha(JsonObject inputJson, @Suspended AsyncResponse response) { + public void discoverDspVersionParams(JsonObject inputJson, AsyncResponse response) { validator.validate(ConnectorParamsDiscoveryRequest.TYPE, inputJson) .orElseThrow(ValidationFailureException::new); @@ -78,10 +69,7 @@ public void discoverDspVersionParamsV4Alpha(JsonObject inputJson, @Suspended Asy }); } - @Path("/connectors") - @POST - @Override - public void discoverConnectorServicesV4Alpha(JsonObject inputJson, @Suspended AsyncResponse response) { + public void discoverConnectorServices(JsonObject inputJson, AsyncResponse response) { validator.validate(ConnectorDiscoveryRequest.TYPE, inputJson) .orElseThrow((ValidationFailureException::new)); diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v3/ConnectorDiscoveryV3Api.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v3/ConnectorDiscoveryV3Api.java new file mode 100644 index 0000000000..8c6337f59e --- /dev/null +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v3/ConnectorDiscoveryV3Api.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 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.discovery.api.v3; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.json.JsonObject; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import org.eclipse.edc.jsonld.spi.JsonLdKeywords; +import org.eclipse.edc.web.spi.ApiErrorDetail; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; + +@OpenAPIDefinition(info = @Info( + description = "With this API clients discover connectors based on published services in a DID document and " + + "discover the right version parameters according to different DSP versions.", + title = "Connector Discovery API")) +@Tag(name = "Connector Discovery") +public interface ConnectorDiscoveryV3Api { + + @Operation(description = "Discover supported connector parameters for a given connector endpoint.", + requestBody = @RequestBody(content = @Content(schema = @Schema(name = "Connector Params Discovery Request", + implementation = ConnectorParamsDiscoveryRequestSchema.class))), + responses = { + @ApiResponse(responseCode = "200", + description = "A list of connector parameters for the DSP version to use with the given connector.", + content = @Content(schema = @Schema(name = "Connector Discovery Response", + implementation = ConnectorParamsDiscoveryResponse.class))), + @ApiResponse(responseCode = "500", description = "Discovery failed due to an internal error", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), + @ApiResponse(responseCode = "502", description = "Discovery failed due to connection issues to counter party", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), + @ApiResponse(responseCode = "400", description = "Request body was malformed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))) + }) + void discoverDspVersionParamsV3(JsonObject querySpecJson, @Suspended AsyncResponse response); + + @Operation(description = "Searches for 'DataService' entries in the DID document of a participant and provides " + + "the connection parameters for all found connectors by applying the connector param discovery request for each.", + requestBody = @RequestBody(content = @Content(schema = @Schema(name = "Connector Discovery Request", + implementation = ConnectorDiscoveryRequestSchema.class))), + responses = { + @ApiResponse(responseCode = "200", + description = "A list of connector endpoint parameters for the version to use for each found connector", + content = @Content( + mediaType = "application/json", + array = @ArraySchema(schema = @Schema( + name = "Service Discovery Response", + implementation = ConnectorParamsDiscoveryResponse.class)) + )), + @ApiResponse(responseCode = "400", description = "Request body was malformed", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), + @ApiResponse(responseCode = "404", description = "Given Id could not be resolved to a DID document", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), + @ApiResponse(responseCode = "500", description = "Discovery failed due to an internal error", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), + @ApiResponse(responseCode = "502", description = "Discovery failed due to connection to counter party", + content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))) + }) + void discoverConnectorServicesV3(JsonObject querySpecJson, @Suspended AsyncResponse response); + + @Schema(name = "ConnectorParamsDiscoveryRequest", + description = "Note: In former versions, the property 'counterPartyId' was named 'bpnl', " + + "for convenience this value is still allowed", + example = ConnectorParamsDiscoveryRequestSchema.EXAMPLE) + record ConnectorParamsDiscoveryRequestSchema( + @Schema(name = CONTEXT, requiredMode = REQUIRED) + Object context, + @Schema(name = JsonLdKeywords.TYPE, example = ConnectorParamsDiscoveryRequest.TYPE) + String type, + @Schema(requiredMode = REQUIRED) + String counterPartyId, + @Schema(requiredMode = REQUIRED) + String counterPartyAddress + ) { + public static final String EXAMPLE = """ + { + "@context": { + "edc": "https://w3id.org/edc/v0.0.1/ns/", + "tx": "https://w3id.org/tractusx/v0.0.1/ns/" + }, + "@type": "tx:ConnectorParamsDiscoveryRequest", + "edc:counterPartyId": "BPNL1234567890", + "edc:counterPartyAddress": "https://provider.domain.com/api/dsp" + } + """; + } + + @Schema(name = "ConnectorParamsDiscoveryResponse", example = ConnectorParamsDiscoveryResponse.EXAMPLE) + record ConnectorParamsDiscoveryResponse( + Object context, + String counterPartyId, + String counterPartyAddress, + String protocol + ) { + + public static final String EXAMPLE = """ + { + "@context": { + "edc": "https://w3id.org/edc/v0.0.1/ns/" + }, + "edc:counterPartyId": "did:web:one-example.com", + "edc:counterPartyAddress": "https://provider.domain.com/api/dsp/2025-1", + "edc:protocol": "dataspace-protocol-http:2025-1" + } + """; + } + + @Schema(name = "ConnectorDiscoveryRequestSchema", example = ConnectorDiscoveryRequestSchema.EXAMPLE) + record ConnectorDiscoveryRequestSchema( + @Schema(name = CONTEXT, requiredMode = REQUIRED) + Object context, + @Schema(name = JsonLdKeywords.TYPE, example = ConnectorDiscoveryRequest.TYPE) + String type, + @Schema(requiredMode = REQUIRED) + String counterPartyId, + @Schema(requiredMode = NOT_REQUIRED) + String[] knownConnectors + ) { + public static final String EXAMPLE = """ + { + "@context": { + "edc": "https://w3id.org/edc/v0.0.1/ns/", + "tx": "https://w3id.org/tractusx/v0.0.1/ns/" + }, + "@type": "tx:ConnectorServiceDiscoveryRequest", + "edc:counterPartyId": "did:web:one-example.com", + "tx:knownConnectors": [ + "https://provider.domain.com/conn1/api/dsp", + "https://provider.domain.com/conn2/api/v1/dsp" + ] + } + """; + } +} diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v3/ConnectorDiscoveryV3Controller.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v3/ConnectorDiscoveryV3Controller.java new file mode 100644 index 0000000000..46e390146e --- /dev/null +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v3/ConnectorDiscoveryV3Controller.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 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.discovery.api.v3; + +import jakarta.json.JsonObject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import org.eclipse.tractusx.edc.discovery.api.ConnectorDiscoveryController; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/v3/connectordiscovery") +public class ConnectorDiscoveryV3Controller implements ConnectorDiscoveryV3Api { + + private final ConnectorDiscoveryController delegate; + + public ConnectorDiscoveryV3Controller(ConnectorDiscoveryController delegate) { + this.delegate = delegate; + } + + @Path("/dspversionparams") + @POST + @Override + public void discoverDspVersionParamsV3(JsonObject inputJson, @Suspended AsyncResponse response) { + delegate.discoverDspVersionParams(inputJson, response); + } + + @Path("/connectors") + @POST + @Override + public void discoverConnectorServicesV3(JsonObject inputJson, @Suspended AsyncResponse response) { + delegate.discoverConnectorServices(inputJson, response); + } +} + diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/api/ConnectorDiscoveryV4AlphaApi.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v4alpha/ConnectorDiscoveryV4AlphaApi.java similarity index 97% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/api/ConnectorDiscoveryV4AlphaApi.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v4alpha/ConnectorDiscoveryV4AlphaApi.java index 11483203c1..ee53a56fff 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/api/ConnectorDiscoveryV4AlphaApi.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v4alpha/ConnectorDiscoveryV4AlphaApi.java @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.api; +package org.eclipse.tractusx.edc.discovery.api.v4alpha; import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.Operation; @@ -34,13 +34,14 @@ import jakarta.ws.rs.container.Suspended; import org.eclipse.edc.jsonld.spi.JsonLdKeywords; import org.eclipse.edc.web.spi.ApiErrorDetail; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +@Deprecated(since = "0.13.0") @OpenAPIDefinition(info = @Info( description = "With this API clients discover connectors based on published services in a DID document and " + "discover the right version parameters according to different DSP versions.", diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v4alpha/ConnectorDiscoveryV4AlphaController.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v4alpha/ConnectorDiscoveryV4AlphaController.java new file mode 100644 index 0000000000..e607abead7 --- /dev/null +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/api/v4alpha/ConnectorDiscoveryV4AlphaController.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 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.discovery.api.v4alpha; + +import jakarta.json.JsonObject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.AsyncResponse; +import jakarta.ws.rs.container.Suspended; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.tractusx.edc.discovery.api.ConnectorDiscoveryController; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.edc.api.ApiWarnings.deprecationWarning; + +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/v4alpha/connectordiscovery") +@Deprecated(since = "0.13.0") +public class ConnectorDiscoveryV4AlphaController implements ConnectorDiscoveryV4AlphaApi { + + private final ConnectorDiscoveryController delegate; + private final Monitor monitor; + + public ConnectorDiscoveryV4AlphaController(ConnectorDiscoveryController delegate, Monitor monitor) { + this.delegate = delegate; + this.monitor = monitor; + } + + @Path("/dspversionparams") + @POST + @Override + public void discoverDspVersionParamsV4Alpha(JsonObject inputJson, @Suspended AsyncResponse response) { + monitor.warning(deprecationWarning("/v4alpha", "/v3")); + delegate.discoverDspVersionParams(inputJson, response); + } + + @Path("/connectors") + @POST + @Override + public void discoverConnectorServicesV4Alpha(JsonObject inputJson, @Suspended AsyncResponse response) { + monitor.warning(deprecationWarning("/v4alpha", "/v3")); + delegate.discoverConnectorServices(inputJson, response); + } +} + diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/exceptions/UnexpectedResultApiException.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/exceptions/UnexpectedResultApiException.java similarity index 95% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/exceptions/UnexpectedResultApiException.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/exceptions/UnexpectedResultApiException.java index e6612406fd..bab7cb0ba0 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/exceptions/UnexpectedResultApiException.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/exceptions/UnexpectedResultApiException.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.exceptions; +package org.eclipse.tractusx.edc.discovery.exceptions; import org.eclipse.edc.web.spi.exception.EdcApiException; diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/service/BaseConnectorDiscoveryServiceImpl.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/service/BaseConnectorDiscoveryServiceImpl.java similarity index 89% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/service/BaseConnectorDiscoveryServiceImpl.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/service/BaseConnectorDiscoveryServiceImpl.java index 7e6946f80a..9700af9d6f 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/service/BaseConnectorDiscoveryServiceImpl.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/service/BaseConnectorDiscoveryServiceImpl.java @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.service; +package org.eclipse.tractusx.edc.discovery.service; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.json.Json; @@ -41,11 +41,11 @@ import org.eclipse.edc.util.collection.TimestampedValue; import org.eclipse.edc.web.spi.exception.BadGatewayException; import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.tractusx.edc.discovery.v4alpha.exceptions.UnexpectedResultApiException; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.CacheConfig; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryService; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.exceptions.UnexpectedResultApiException; +import org.eclipse.tractusx.edc.discovery.spi.CacheConfig; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryService; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest; import java.io.IOException; import java.net.MalformedURLException; @@ -146,16 +146,27 @@ public CompletableFuture discoverVersionParams(ConnectorParamsDiscov .build(); return httpClient.executeAsync(wellKnownRequest, emptyList()) - .thenApply(response -> { + .handle((response, throwable) -> { + if (throwable != null) { + var msg = "Counterparty well-known endpoint call has failed with message: %s" + .formatted(throwable.getLocalizedMessage()); + monitor.warning(msg); + throw new BadGatewayException(msg); + } try (response) { - if (!response.isSuccessful()) { - var msg = "Counterparty well-known endpoint has failed with status %s and message: %s" - .formatted(response.code(), response.message()); - monitor.warning(msg); - throw new BadGatewayException(msg); + ProtocolVersion protocolVersion; + if (response.isSuccessful()) { + protocolVersion = extractLatestSupportedVersion(parseResponseBody(response)); + } else { + protocolVersion = handleSpecialStatusCode(response); + if (protocolVersion == null) { + var msg = "Counterparty well-known endpoint has failed with status %s and message: %s" + .formatted(response.code(), response.message()); + monitor.warning(msg); + throw new BadGatewayException(msg); + } } - var protocolVersion = extractLatestSupportedVersion(parseResponseBody(response)); var resultObject = createResultObjectFromProtocolVersionData(request, protocolVersion); versionsCache.put(versionEndpoint, new TimestampedValue<>(protocolVersion, cacheValidity)); return resultObject; @@ -163,6 +174,18 @@ public CompletableFuture discoverVersionParams(ConnectorParamsDiscov }); } + /** + * This method allows to handle certain status codes returned in the response of the version metadata call. + * Needed for older connector versions, as they had and access-restricted endpoint which results in a 401. + * Intention is, to allow to handle this status code instead of doing an authenticated call, as this is + * a temporary situation. + * + * @param response The response object of the version metadata request + * @return Either a default ProtocolVersion object, if the status code is caught or null, if the response does + * not map to a default value + */ + protected abstract ProtocolVersion handleSpecialStatusCode(Response response); + /** * The connector discovery basically deals with two potential input root patterns. First input pattern is, that * the base root of a connector is provided. This is the root endpoint of all dsp endpoints and is basically the root @@ -182,7 +205,7 @@ public CompletableFuture discoverVersionParams(ConnectorParamsDiscov * @return A joint path cleaned from unneeded slashes following the pattern 'root/subpath' * @throws InvalidRequestException If the created path, i.e., the root parameter is not a correct url. */ - protected String createFullPath(String root, String subpath) { + private String createFullPath(String root, String subpath) { var input = root; if (root.endsWith(DSP_DISCOVERY_PATH)) { input = root.substring(0, root.length() - DSP_DISCOVERY_PATH.length() - 1); diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/service/DefaultConnectorDiscoveryServiceImpl.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/service/DefaultConnectorDiscoveryServiceImpl.java similarity index 87% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/service/DefaultConnectorDiscoveryServiceImpl.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/service/DefaultConnectorDiscoveryServiceImpl.java index 280583ee27..db0e77d824 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/service/DefaultConnectorDiscoveryServiceImpl.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/service/DefaultConnectorDiscoveryServiceImpl.java @@ -18,15 +18,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.service; +package org.eclipse.tractusx.edc.discovery.service; import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.Response; import org.eclipse.edc.http.spi.EdcHttpClient; import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; import org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants; +import org.eclipse.edc.protocol.spi.ProtocolVersion; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.CacheConfig; +import org.eclipse.tractusx.edc.discovery.spi.CacheConfig; import java.util.List; @@ -41,6 +43,11 @@ public DefaultConnectorDiscoveryServiceImpl( super(httpClient, didResolver, mapper, List.of(Dsp2025Constants.V_2025_1_VERSION), cacheConfig, monitor); } + @Override + protected ProtocolVersion handleSpecialStatusCode(Response response) { + return null; + } + @Override protected VersionParameters createVersionParameterForProtocolVersion( String counterPartyId, String versionAddress, String version) { diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/CacheConfig.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/CacheConfig.java similarity index 93% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/CacheConfig.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/CacheConfig.java index b3125d82f2..82fa92dd32 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/CacheConfig.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/CacheConfig.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.spi; +package org.eclipse.tractusx.edc.discovery.spi; import java.time.Clock; diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/ConnectorDiscoveryRequest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/ConnectorDiscoveryRequest.java similarity index 96% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/ConnectorDiscoveryRequest.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/ConnectorDiscoveryRequest.java index 60dc626771..da2b0d94ce 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/ConnectorDiscoveryRequest.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/ConnectorDiscoveryRequest.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.spi; +package org.eclipse.tractusx.edc.discovery.spi; import java.util.Collection; diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/ConnectorDiscoveryService.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/ConnectorDiscoveryService.java similarity index 98% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/ConnectorDiscoveryService.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/ConnectorDiscoveryService.java index 2bd8e2c1cd..b05cd1975f 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/ConnectorDiscoveryService.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/ConnectorDiscoveryService.java @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.spi; +package org.eclipse.tractusx.edc.discovery.spi; import jakarta.json.JsonArray; import jakarta.json.JsonObject; diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/ConnectorParamsDiscoveryRequest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/ConnectorParamsDiscoveryRequest.java similarity index 97% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/ConnectorParamsDiscoveryRequest.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/ConnectorParamsDiscoveryRequest.java index cdcba2efa8..6501bfbb00 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/spi/ConnectorParamsDiscoveryRequest.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/spi/ConnectorParamsDiscoveryRequest.java @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.spi; +package org.eclipse.tractusx.edc.discovery.spi; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.TX_NAMESPACE; diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/transformers/JsonObjectToConnectorDiscoveryRequest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/transformers/JsonObjectToConnectorDiscoveryRequest.java similarity index 87% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/transformers/JsonObjectToConnectorDiscoveryRequest.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/transformers/JsonObjectToConnectorDiscoveryRequest.java index a908915007..0e70289dc0 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/transformers/JsonObjectToConnectorDiscoveryRequest.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/transformers/JsonObjectToConnectorDiscoveryRequest.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.transformers; +package org.eclipse.tractusx.edc.discovery.transformers; import jakarta.json.JsonObject; import jakarta.json.JsonString; @@ -25,7 +25,7 @@ import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.transform.spi.TransformerContext; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,8 +34,8 @@ import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_COUNTERPARTYID_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_KNOWNCONNECTORS_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_COUNTERPARTYID_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_KNOWNCONNECTORS_ATTRIBUTE; public class JsonObjectToConnectorDiscoveryRequest extends AbstractJsonLdTransformer { diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/transformers/JsonObjectToConnectorParamsDiscoveryRequest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/transformers/JsonObjectToConnectorParamsDiscoveryRequest.java similarity index 81% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/transformers/JsonObjectToConnectorParamsDiscoveryRequest.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/transformers/JsonObjectToConnectorParamsDiscoveryRequest.java index c1d7fab441..0b360e51b3 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/transformers/JsonObjectToConnectorParamsDiscoveryRequest.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/transformers/JsonObjectToConnectorParamsDiscoveryRequest.java @@ -18,18 +18,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.transformers; +package org.eclipse.tractusx.edc.discovery.transformers; import jakarta.json.JsonObject; import org.eclipse.edc.jsonld.spi.transformer.AbstractJsonLdTransformer; import org.eclipse.edc.transform.spi.TransformerContext; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_COUNTER_PARTY_ADDRESS_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE_LEGACY; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_COUNTER_PARTY_ADDRESS_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE_LEGACY; public class JsonObjectToConnectorParamsDiscoveryRequest extends AbstractJsonLdTransformer { diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/validators/ConnectorDiscoveryRequestValidator.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/validators/ConnectorDiscoveryRequestValidator.java similarity index 90% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/validators/ConnectorDiscoveryRequestValidator.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/validators/ConnectorDiscoveryRequestValidator.java index f32e57b29f..f7c2a0d4ac 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/validators/ConnectorDiscoveryRequestValidator.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/validators/ConnectorDiscoveryRequestValidator.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.validators; +package org.eclipse.tractusx.edc.discovery.validators; import jakarta.json.JsonObject; import jakarta.json.JsonString; @@ -34,8 +34,8 @@ import java.util.ArrayList; import static org.eclipse.edc.validator.spi.Violation.violation; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_COUNTERPARTYID_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_KNOWNCONNECTORS_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_COUNTERPARTYID_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_KNOWNCONNECTORS_ATTRIBUTE; /** * Validator for the 'ConnectorDiscoveryRequest' as defined in the connector discovery api. diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/validators/ConnectorParamsDiscoveryRequestValidator.java b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/validators/ConnectorParamsDiscoveryRequestValidator.java similarity index 88% rename from edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/validators/ConnectorParamsDiscoveryRequestValidator.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/validators/ConnectorParamsDiscoveryRequestValidator.java index 524b3cedcb..4668aaa3c9 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/v4alpha/validators/ConnectorParamsDiscoveryRequestValidator.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/java/org/eclipse/tractusx/edc/discovery/validators/ConnectorParamsDiscoveryRequestValidator.java @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha.validators; +package org.eclipse.tractusx.edc.discovery.validators; import jakarta.json.JsonObject; import org.eclipse.edc.validator.jsonobject.JsonLdPath; @@ -28,9 +28,9 @@ import org.eclipse.edc.validator.spi.Validator; import static org.eclipse.edc.validator.spi.Violation.violation; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_COUNTER_PARTY_ADDRESS_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE_LEGACY; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_COUNTER_PARTY_ADDRESS_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE_LEGACY; /** * Validator for the 'ConnectorParamsDiscoveryRequest' as defined in the connector discovery api. diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/connector-discovery/connector-discovery-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension index 87c14429e8..43fd17e006 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -17,4 +17,4 @@ # SPDX-License-Identifier: Apache-2.0 ################################################################################# -org.eclipse.tractusx.edc.discovery.v4alpha.ConnectorDiscoveryExtension +org.eclipse.tractusx.edc.discovery.ConnectorDiscoveryExtension diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/ConnectorDiscoveryControllerTest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/ConnectorDiscoveryControllerTest.java new file mode 100644 index 0000000000..f8f2744735 --- /dev/null +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/ConnectorDiscoveryControllerTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 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.discovery; + +import jakarta.json.Json; +import jakarta.ws.rs.container.AsyncResponse; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +import org.eclipse.edc.validator.spi.ValidationResult; +import org.eclipse.edc.validator.spi.Violation; +import org.eclipse.edc.web.spi.exception.ValidationFailureException; +import org.eclipse.tractusx.edc.discovery.api.ConnectorDiscoveryController; +import org.eclipse.tractusx.edc.discovery.exceptions.UnexpectedResultApiException; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryService; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +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 ConnectorDiscoveryControllerTest { + + private final ConnectorDiscoveryService connectorService = mock(); + private final TypeTransformerRegistry transformerRegistry = mock(); + private final JsonObjectValidatorRegistry validator = mock(); + private final Monitor monitor = mock(); + private final AsyncResponse response = mock(); + + private final ConnectorDiscoveryController controller = + new ConnectorDiscoveryController(connectorService, transformerRegistry, validator, monitor); + + @Test + void discoverDspVersionParams_shouldResumeWithResult_whenServiceSucceeds() { + var input = Json.createObjectBuilder().build(); + var expectedJson = Json.createObjectBuilder() + .add("counterPartyId", "did:web:provider") + .add("protocol", "dataspace-protocol-http:2025-1") + .build(); + var discoveryRequest = new ConnectorParamsDiscoveryRequest("test", "test"); + + when(validator.validate(ConnectorParamsDiscoveryRequest.TYPE, input)).thenReturn(ValidationResult.success()); + when(transformerRegistry.transform(input, ConnectorParamsDiscoveryRequest.class)).thenReturn(Result.success(discoveryRequest)); + when(connectorService.discoverVersionParams(discoveryRequest)).thenReturn(CompletableFuture.completedFuture(expectedJson)); + + controller.discoverDspVersionParams(input, response); + + verify(response).resume(expectedJson); + } + + @Test + void discoverDspVersionParams_shouldResumeWithCauseAndWarn_whenServiceFails() { + var input = Json.createObjectBuilder().build(); + var discoveryRequest = new ConnectorParamsDiscoveryRequest("test", "test"); + var error = new UnexpectedResultApiException("test error"); + + when(validator.validate(ConnectorParamsDiscoveryRequest.TYPE, input)).thenReturn(ValidationResult.success()); + when(transformerRegistry.transform(input, ConnectorParamsDiscoveryRequest.class)).thenReturn(Result.success(discoveryRequest)); + when(connectorService.discoverVersionParams(discoveryRequest)).thenReturn(CompletableFuture.failedFuture(error)); + + controller.discoverDspVersionParams(input, response); + + verify(response).resume(error); + verify(monitor).warning(eq("Exception thrown during connector discovery"), any(Throwable.class)); + } + + @Test + void discoverDspVersionParams_shouldThrowValidationFailure_whenValidationFails() { + var input = Json.createObjectBuilder().build(); + + when(validator.validate(eq(ConnectorParamsDiscoveryRequest.TYPE), any())) + .thenThrow(new ValidationFailureException(List.of(new Violation("invalidField", "invalidField", "Invalid field")))); + + assertThatThrownBy(() -> controller.discoverDspVersionParams(input, response)) + .isInstanceOf(ValidationFailureException.class); + } + + @Test + void discoverDspVersionParams_shouldThrowUnexpectedResult_whenTransformFails() { + var input = Json.createObjectBuilder().build(); + + when(validator.validate(ConnectorParamsDiscoveryRequest.TYPE, input)).thenReturn(ValidationResult.success()); + when(transformerRegistry.transform(input, ConnectorParamsDiscoveryRequest.class)).thenReturn(Result.failure("cannot transform")); + + assertThatThrownBy(() -> controller.discoverDspVersionParams(input, response)) + .isInstanceOf(UnexpectedResultApiException.class); + } + + @Test + void discoverConnectorServices_shouldResumeWithResult_whenServiceSucceeds() { + var input = Json.createObjectBuilder().build(); + var expectedJson = Json.createArrayBuilder().add(Json.createObjectBuilder() + .add("counterPartyAddress", "https://example.com/api/v1/dsp/2025-1") + .add("counterPartyId", "did:web:provider") + .add("protocol", "dataspace-protocol-http:2025-1")) + .build(); + var discoveryRequest = new ConnectorDiscoveryRequest("test", List.of("https://example.com/api/v1/dsp")); + + when(validator.validate(ConnectorDiscoveryRequest.TYPE, input)).thenReturn(ValidationResult.success()); + when(transformerRegistry.transform(input, ConnectorDiscoveryRequest.class)).thenReturn(Result.success(discoveryRequest)); + when(connectorService.discoverConnectors(discoveryRequest)).thenReturn(CompletableFuture.completedFuture(expectedJson)); + + controller.discoverConnectorServices(input, response); + + verify(response).resume(expectedJson); + } + + @Test + void discoverConnectorServices_shouldResumeWithCauseAndWarn_whenServiceFails() { + var input = Json.createObjectBuilder().build(); + var discoveryRequest = new ConnectorDiscoveryRequest("test", List.of("https://example.com/api/v1/dsp")); + var error = new UnexpectedResultApiException("test error"); + + when(validator.validate(ConnectorDiscoveryRequest.TYPE, input)).thenReturn(ValidationResult.success()); + when(transformerRegistry.transform(input, ConnectorDiscoveryRequest.class)).thenReturn(Result.success(discoveryRequest)); + when(connectorService.discoverConnectors(discoveryRequest)).thenReturn(CompletableFuture.failedFuture(error)); + + controller.discoverConnectorServices(input, response); + + verify(response).resume(error); + verify(monitor).warning(eq("Exception thrown during connector discovery"), any(Throwable.class)); + } + + @Test + void discoverConnectorServices_shouldThrowValidationFailure_whenValidationFails() { + var input = Json.createObjectBuilder().build(); + + when(validator.validate(eq(ConnectorDiscoveryRequest.TYPE), any())) + .thenThrow(new ValidationFailureException(List.of(new Violation("invalidField", "invalidField", "Invalid field")))); + + assertThatThrownBy(() -> controller.discoverConnectorServices(input, response)) + .isInstanceOf(ValidationFailureException.class); + } + + @Test + void discoverConnectorServices_shouldThrowUnexpectedResult_whenTransformFails() { + var input = Json.createObjectBuilder().build(); + + when(validator.validate(ConnectorDiscoveryRequest.TYPE, input)).thenReturn(ValidationResult.success()); + when(transformerRegistry.transform(input, ConnectorDiscoveryRequest.class)).thenReturn(Result.failure("cannot transform")); + + assertThatThrownBy(() -> controller.discoverConnectorServices(input, response)) + .isInstanceOf(UnexpectedResultApiException.class); + } +} + + + diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryRequestValidatorTest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/ConnectorDiscoveryRequestValidatorTest.java similarity index 93% rename from edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryRequestValidatorTest.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/ConnectorDiscoveryRequestValidatorTest.java index d634c1b4f4..6e63b10fd4 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryRequestValidatorTest.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/ConnectorDiscoveryRequestValidatorTest.java @@ -17,13 +17,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha; +package org.eclipse.tractusx.edc.discovery; import jakarta.json.Json; import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; import org.eclipse.edc.validator.spi.Validator; -import org.eclipse.tractusx.edc.discovery.v4alpha.validators.ConnectorDiscoveryRequestValidator; +import org.eclipse.tractusx.edc.discovery.validators.ConnectorDiscoveryRequestValidator; import org.junit.jupiter.api.Test; import java.util.Collection; @@ -35,8 +35,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_COUNTERPARTYID_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_KNOWNCONNECTORS_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_COUNTERPARTYID_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_KNOWNCONNECTORS_ATTRIBUTE; public class ConnectorDiscoveryRequestValidatorTest { diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorParamsDiscoveryRequestValidatorTest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/ConnectorParamsDiscoveryRequestValidatorTest.java similarity index 88% rename from edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorParamsDiscoveryRequestValidatorTest.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/ConnectorParamsDiscoveryRequestValidatorTest.java index e3824ae985..126cd3f075 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorParamsDiscoveryRequestValidatorTest.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/ConnectorParamsDiscoveryRequestValidatorTest.java @@ -18,13 +18,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha; +package org.eclipse.tractusx.edc.discovery; import jakarta.json.Json; import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; import org.eclipse.edc.validator.spi.Validator; -import org.eclipse.tractusx.edc.discovery.v4alpha.validators.ConnectorParamsDiscoveryRequestValidator; +import org.eclipse.tractusx.edc.discovery.validators.ConnectorParamsDiscoveryRequestValidator; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -38,9 +38,9 @@ import static jakarta.json.Json.createObjectBuilder; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_COUNTER_PARTY_ADDRESS_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE_LEGACY; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_COUNTER_PARTY_ADDRESS_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE_LEGACY; import static org.junit.jupiter.params.provider.Arguments.of; class ConnectorParamsDiscoveryRequestValidatorTest { diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/DefaultConnectorDiscoveryServiceImplTest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/DefaultConnectorDiscoveryServiceImplTest.java similarity index 91% rename from edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/DefaultConnectorDiscoveryServiceImplTest.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/DefaultConnectorDiscoveryServiceImplTest.java index c78629a8e2..9e2482ccaa 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/DefaultConnectorDiscoveryServiceImplTest.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/DefaultConnectorDiscoveryServiceImplTest.java @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha; +package org.eclipse.tractusx.edc.discovery; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.json.Json; @@ -36,12 +36,12 @@ import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.web.spi.exception.BadGatewayException; import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.tractusx.edc.discovery.v4alpha.exceptions.UnexpectedResultApiException; -import org.eclipse.tractusx.edc.discovery.v4alpha.service.DefaultConnectorDiscoveryServiceImpl; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.CacheConfig; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryService; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.exceptions.UnexpectedResultApiException; +import org.eclipse.tractusx.edc.discovery.service.DefaultConnectorDiscoveryServiceImpl; +import org.eclipse.tractusx.edc.discovery.spi.CacheConfig; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryService; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -49,6 +49,7 @@ import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; +import java.io.IOException; import java.time.Clock; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -131,7 +132,7 @@ void discoverVersionParams_shouldReturnDsp2025_whenDsp2025Available() { } @Test - void discoverVersionParams_shouldReturnUseCacheDuringSucccess() throws InterruptedException { + void discoverVersionParams_shouldReturnUseCacheDuringSuccess() throws InterruptedException { var paramsDiscoveryRequest = new ConnectorParamsDiscoveryRequest(TEST_DID, TEST_ADDRESS); var expectedJson = Json.createObjectBuilder() @@ -180,7 +181,7 @@ void discoverVersionParams_shouldReturnException_whenOnlyDsp08Available() { } @Test - void discoverVersionParams_shouldReturnException_whenMetadataEndpointHasError() { + void discoverVersionParams_shouldReturnException_whenMetadataEndpointNotFound() { var paramsDiscoveryRequest = new ConnectorParamsDiscoveryRequest(TEST_DID, TEST_ADDRESS); when(httpClient.executeAsync(any(), any())) @@ -194,6 +195,34 @@ void discoverVersionParams_shouldReturnException_whenMetadataEndpointHasError() .hasMessageContaining("404"); } + @Test + void discoverVersionParams_shouldReturnException_whenMetadataEndpointNotAuthenticated() { + var paramsDiscoveryRequest = new ConnectorParamsDiscoveryRequest(TEST_DID, TEST_ADDRESS); + + when(httpClient.executeAsync(any(), any())) + .thenReturn(CompletableFuture.completedFuture( + dummyResponseBuilder(401, "Unauthorized", "Unauthorized").build())); + + assertThatThrownBy(() -> testee.discoverVersionParams(paramsDiscoveryRequest).join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(BadGatewayException.class) + .hasMessageContaining("Counterparty well-known endpoint has failed with status") + .hasMessageContaining("401"); + } + + @Test + void discoverVersionParams_shouldReturnException_whenHttpClientThrowsException() { + var paramsDiscoveryRequest = new ConnectorParamsDiscoveryRequest(TEST_DID, TEST_ADDRESS); + + when(httpClient.executeAsync(any(), any())) + .thenReturn(CompletableFuture.failedFuture(new IOException("Failed Call"))); + assertThatThrownBy(() -> testee.discoverVersionParams(paramsDiscoveryRequest).join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(BadGatewayException.class) + .hasMessageContaining("Failed Call"); + + } + @Test void discoverVersionParams_shouldReturnException_whenVersionRequestHasMissingProps() { var paramsDiscoveryRequest = new ConnectorParamsDiscoveryRequest(TEST_DID, TEST_ADDRESS); diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/JsonObjectToConnectorDiscoveryRequestTest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/JsonObjectToConnectorDiscoveryRequestTest.java similarity index 89% rename from edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/JsonObjectToConnectorDiscoveryRequestTest.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/JsonObjectToConnectorDiscoveryRequestTest.java index 1255b1137b..6265176a3a 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/JsonObjectToConnectorDiscoveryRequestTest.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/JsonObjectToConnectorDiscoveryRequestTest.java @@ -17,16 +17,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha; +package org.eclipse.tractusx.edc.discovery; import jakarta.json.Json; import org.eclipse.edc.transform.spi.TransformerContext; -import org.eclipse.tractusx.edc.discovery.v4alpha.transformers.JsonObjectToConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.transformers.JsonObjectToConnectorDiscoveryRequest; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_COUNTERPARTYID_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_KNOWNCONNECTORS_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_COUNTERPARTYID_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest.CONNECTOR_DISCOVERY_REQUEST_KNOWNCONNECTORS_ATTRIBUTE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/JsonObjectToConnectorParamsDiscoveryRequestTest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/JsonObjectToConnectorParamsDiscoveryRequestTest.java similarity index 88% rename from edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/JsonObjectToConnectorParamsDiscoveryRequestTest.java rename to edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/JsonObjectToConnectorParamsDiscoveryRequestTest.java index babb44303b..200ccf0f06 100644 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/JsonObjectToConnectorParamsDiscoveryRequestTest.java +++ b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/JsonObjectToConnectorParamsDiscoveryRequestTest.java @@ -18,11 +18,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.discovery.v4alpha; +package org.eclipse.tractusx.edc.discovery; import jakarta.json.Json; import org.eclipse.edc.transform.spi.TransformerContext; -import org.eclipse.tractusx.edc.discovery.v4alpha.transformers.JsonObjectToConnectorParamsDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.transformers.JsonObjectToConnectorParamsDiscoveryRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -33,9 +33,9 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_COUNTER_PARTY_ADDRESS_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE; -import static org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE_LEGACY; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_COUNTER_PARTY_ADDRESS_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE; +import static org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest.DISCOVERY_PARAMS_REQUEST_IDENTIFIER_ATTRIBUTE_LEGACY; import static org.junit.jupiter.params.provider.Arguments.of; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; diff --git a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryV4AlphaControllerTest.java b/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryV4AlphaControllerTest.java deleted file mode 100644 index 3a5b0001b0..0000000000 --- a/edc-extensions/connector-discovery/connector-discovery-api/src/test/java/org/eclipse/tractusx/edc/discovery/v4alpha/ConnectorDiscoveryV4AlphaControllerTest.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2026 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.discovery.v4alpha; - -import io.restassured.http.ContentType; -import io.restassured.specification.RequestSpecification; -import jakarta.json.Json; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.transform.spi.TypeTransformerRegistry; -import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; -import org.eclipse.edc.validator.spi.ValidationResult; -import org.eclipse.edc.validator.spi.Violation; -import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.edc.web.spi.exception.ValidationFailureException; -import org.eclipse.tractusx.edc.discovery.v4alpha.api.ConnectorDiscoveryV4AlphaController; -import org.eclipse.tractusx.edc.discovery.v4alpha.exceptions.UnexpectedResultApiException; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryService; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest; -import org.junit.jupiter.api.Test; - -import java.io.StringReader; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import static io.restassured.RestAssured.given; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class ConnectorDiscoveryV4AlphaControllerTest extends RestControllerTestBase { - - private final ConnectorDiscoveryService connectorService = mock(); - private final TypeTransformerRegistry transformerRegistry = mock(); - private final JsonObjectValidatorRegistry validator = mock(); - private final Monitor monitor = mock(); - - @Override - protected Object controller() { - return new ConnectorDiscoveryV4AlphaController(connectorService, transformerRegistry, validator, monitor); - } - - @Test - void connectorParamsDiscovery_shouldReturnSuccess() { - var input = Json.createObjectBuilder().build(); - var expectedJson = Json.createObjectBuilder() - .add("counterPartyId", "did:web:provider") - .add("protocol", "dataspace-protocol-http:2025-1") - .build(); - - var discoveryRequest = new ConnectorParamsDiscoveryRequest("test", "test"); - - when(validator.validate(ConnectorParamsDiscoveryRequest.TYPE, input)) - .thenReturn(ValidationResult.success()); - when(transformerRegistry.transform(input, ConnectorParamsDiscoveryRequest.class)) - .thenReturn(Result.success(discoveryRequest)); - when(connectorService.discoverVersionParams(discoveryRequest)) - .thenReturn(CompletableFuture.completedFuture(expectedJson)); - - var resultString = baseRequest("/dspversionparams") - .contentType(ContentType.JSON) - .body(input) - .post() - .then() - .log().ifError() - .statusCode(200) - .extract().body().asString(); - - var resultJson = Json.createReader(new StringReader(resultString)).readObject(); - - assertThat(resultJson).isEqualTo(expectedJson); - } - - @Test - void connectorParamsDiscovery_shouldReturnFailure_whenServiceFails() { - - var input = Json.createObjectBuilder().build(); - var discoveryRequest = new ConnectorParamsDiscoveryRequest("test", "test"); - - when(validator.validate(ConnectorParamsDiscoveryRequest.TYPE, input)) - .thenReturn(ValidationResult.success()); - when(transformerRegistry.transform(input, ConnectorParamsDiscoveryRequest.class)) - .thenReturn(Result.success(discoveryRequest)); - when(connectorService.discoverVersionParams(discoveryRequest)) - .thenReturn(CompletableFuture.failedFuture(new UnexpectedResultApiException("test error"))); - - baseRequest("/dspversionparams") - .contentType(ContentType.JSON) - .body(input) - .post() - .then() - .log().ifError() - .statusCode(500); - } - - @Test - void connectorParamsDiscovery_shouldReturnFailureBadRequest_whenServiceFails() { - - var input = Json.createObjectBuilder().build(); - var discoveryRequest = new ConnectorParamsDiscoveryRequest("test", "test"); - - when(validator.validate(ConnectorParamsDiscoveryRequest.TYPE, input)) - .thenReturn(ValidationResult.success()); - when(transformerRegistry.transform(input, ConnectorParamsDiscoveryRequest.class)) - .thenReturn(Result.success(discoveryRequest)); - when(connectorService.discoverVersionParams(discoveryRequest)) - .thenReturn(CompletableFuture.failedFuture(new InvalidRequestException("test error"))); - - baseRequest("/dspversionparams") - .contentType(ContentType.JSON) - .body(input) - .post() - .then() - .log().ifError() - .statusCode(400); - } - - @Test - void connectorParamsDiscovery_shouldReturnValidationFailure_whenValidationFails() { - - when(validator.validate(eq(ConnectorParamsDiscoveryRequest.TYPE), any())) - .thenThrow(new ValidationFailureException(List.of(new Violation("invalidField", "invalidField", "Invalid field")))); - - baseRequest("/dspversionparams") - .contentType(ContentType.JSON) - .body("") - .post() - .then() - .log().ifError() - .statusCode(400); - } - - @Test - void connectorServiceDiscovery_shouldReturnSuccess() { - var input = Json.createObjectBuilder().build(); - var expectedJson = Json.createArrayBuilder().add(Json.createObjectBuilder() - .add("counterPartyAddress", "https://example.com/api/v1/dsp/2025-1") - .add("counterPartyId", "did:web:provider") - .add("protocol", "dataspace-protocol-http:2025-1")) - .build(); - - var discoveryRequest = new ConnectorDiscoveryRequest("test", List.of("https://example.com/api/v1/dsp")); - - when(validator.validate(ConnectorDiscoveryRequest.TYPE, input)) - .thenReturn(ValidationResult.success()); - when(transformerRegistry.transform(input, ConnectorDiscoveryRequest.class)) - .thenReturn(Result.success(discoveryRequest)); - when(connectorService.discoverConnectors(discoveryRequest)) - .thenReturn(CompletableFuture.completedFuture(expectedJson)); - - var resultString = baseRequest("/connectors") - .contentType(ContentType.JSON) - .body(input) - .post() - .then() - .log().ifError() - .statusCode(200) - .extract().body().asString(); - - var resultJson = Json.createReader(new StringReader(resultString)).readArray(); - - assertThat(resultJson).isEqualTo(expectedJson); - } - - @Test - void connectorServiceDiscovery_shouldReturnFailure_whenServiceFails() { - - var input = Json.createObjectBuilder().build(); - var discoveryRequest = new ConnectorDiscoveryRequest("test", List.of("https://example.com/api/v1/dsp")); - - when(validator.validate(ConnectorDiscoveryRequest.TYPE, input)) - .thenReturn(ValidationResult.success()); - when(transformerRegistry.transform(input, ConnectorDiscoveryRequest.class)) - .thenReturn(Result.success(discoveryRequest)); - when(connectorService.discoverConnectors(discoveryRequest)) - .thenReturn(CompletableFuture.failedFuture(new UnexpectedResultApiException("test error"))); - - baseRequest("/connectors") - .contentType(ContentType.JSON) - .body(input) - .post() - .then() - .log().ifError() - .statusCode(500); - } - - @Test - void connectorServiceDiscovery_shouldReturnFailureBadRequest_whenServiceFails() { - - var input = Json.createObjectBuilder().build(); - var discoveryRequest = new ConnectorDiscoveryRequest("test", List.of("https://example.com/api/v1/dsp")); - - when(validator.validate(ConnectorDiscoveryRequest.TYPE, input)) - .thenReturn(ValidationResult.success()); - when(transformerRegistry.transform(input, ConnectorDiscoveryRequest.class)) - .thenReturn(Result.success(discoveryRequest)); - when(connectorService.discoverConnectors(discoveryRequest)) - .thenReturn(CompletableFuture.failedFuture(new InvalidRequestException("test error"))); - - baseRequest("/connectors") - .contentType(ContentType.JSON) - .body(input) - .post() - .then() - .log().ifError() - .statusCode(400); - } - - @Test - void connectorServiceDiscovery_shouldReturnValidationFailure_whenValidationFails() { - - when(validator.validate(eq(ConnectorDiscoveryRequest.TYPE), any())) - .thenThrow(new ValidationFailureException(List.of(new Violation("invalidField", "invalidField", "Invalid field")))); - - baseRequest("/connectors") - .contentType(ContentType.JSON) - .body("") - .post() - .then() - .log().ifError() - .statusCode(400); - } - - private RequestSpecification baseRequest(String path) { - return given() - .baseUri("http://localhost:" + port) - .basePath("/v4alpha/connectordiscovery" + path) - .when(); - } -} diff --git a/edc-extensions/connector-discovery/cx-connector-discovery/build.gradle.kts b/edc-extensions/connector-discovery/cx-connector-discovery/build.gradle.kts index 059d6438b4..e3bea89774 100644 --- a/edc-extensions/connector-discovery/cx-connector-discovery/build.gradle.kts +++ b/edc-extensions/connector-discovery/cx-connector-discovery/build.gradle.kts @@ -31,7 +31,7 @@ dependencies { api(libs.edc.spi.http) api(libs.edc.spi.jsonld) api(libs.edc.spi.controlplane) - api(libs.dsp.spi.v08) + api(project(":spi:dsp-spi-08")) api(libs.dsp.spi.v2025) api(libs.edc.spi.identity.did) diff --git a/edc-extensions/connector-discovery/cx-connector-discovery/src/main/java/org/eclipse/tractusx/edc/discovery/cx/ConnectorDiscoveryBpnlAndDsp08ServiceExtension.java b/edc-extensions/connector-discovery/cx-connector-discovery/src/main/java/org/eclipse/tractusx/edc/discovery/cx/ConnectorDiscoveryBpnlAndDsp08ServiceExtension.java index fdda0ecca1..8096cb9c08 100644 --- a/edc-extensions/connector-discovery/cx-connector-discovery/src/main/java/org/eclipse/tractusx/edc/discovery/cx/ConnectorDiscoveryBpnlAndDsp08ServiceExtension.java +++ b/edc-extensions/connector-discovery/cx-connector-discovery/src/main/java/org/eclipse/tractusx/edc/discovery/cx/ConnectorDiscoveryBpnlAndDsp08ServiceExtension.java @@ -30,14 +30,14 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.tractusx.edc.discovery.cx.service.BpnlAndDsp08ConnectorDiscoveryServiceImpl; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.CacheConfig; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryService; +import org.eclipse.tractusx.edc.discovery.spi.CacheConfig; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryService; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import java.time.Clock; -import static org.eclipse.tractusx.edc.discovery.v4alpha.ConnectorDiscoveryExtension.DEFAULT_CACHE_EXPIRY_MS; -import static org.eclipse.tractusx.edc.discovery.v4alpha.ConnectorDiscoveryExtension.TX_EDC_CONNECTOR_DISCOVERY_CACHE_EXPIRY; +import static org.eclipse.tractusx.edc.discovery.ConnectorDiscoveryExtension.DEFAULT_CACHE_EXPIRY_MS; +import static org.eclipse.tractusx.edc.discovery.ConnectorDiscoveryExtension.TX_EDC_CONNECTOR_DISCOVERY_CACHE_EXPIRY; @Extension(value = ConnectorDiscoveryBpnlAndDsp08ServiceExtension.NAME) public class ConnectorDiscoveryBpnlAndDsp08ServiceExtension implements ServiceExtension { diff --git a/edc-extensions/connector-discovery/cx-connector-discovery/src/main/java/org/eclipse/tractusx/edc/discovery/cx/service/BpnlAndDsp08ConnectorDiscoveryServiceImpl.java b/edc-extensions/connector-discovery/cx-connector-discovery/src/main/java/org/eclipse/tractusx/edc/discovery/cx/service/BpnlAndDsp08ConnectorDiscoveryServiceImpl.java index 6cc0a4e943..6f2695cf05 100644 --- a/edc-extensions/connector-discovery/cx-connector-discovery/src/main/java/org/eclipse/tractusx/edc/discovery/cx/service/BpnlAndDsp08ConnectorDiscoveryServiceImpl.java +++ b/edc-extensions/connector-discovery/cx-connector-discovery/src/main/java/org/eclipse/tractusx/edc/discovery/cx/service/BpnlAndDsp08ConnectorDiscoveryServiceImpl.java @@ -22,15 +22,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.json.JsonArray; +import okhttp3.Response; import org.eclipse.edc.http.spi.EdcHttpClient; import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; import org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants; import org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants; +import org.eclipse.edc.protocol.spi.ProtocolVersion; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.tractusx.edc.discovery.v4alpha.service.BaseConnectorDiscoveryServiceImpl; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.CacheConfig; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.service.BaseConnectorDiscoveryServiceImpl; +import org.eclipse.tractusx.edc.discovery.spi.CacheConfig; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import java.util.List; @@ -65,6 +67,18 @@ public CompletableFuture discoverConnectors(ConnectorDiscoveryRequest mapToDid(request.counterPartyId()), request.knownConnectors())); } + @Override + protected ProtocolVersion handleSpecialStatusCode(Response response) { + if (response.code() == 401) { + // Connectors of version 0.9.0 and earlier had access-control for the version metadata endpoint + // This way, we assume, that a 401 indicates such a case and therefore default the protocol version to be used + // Wrong endpoint urls result in a 404, so the assumption here should be valid, if not, no big harm is + // done, as the call to a dsp endpoint would simply fail anyway. + return new ProtocolVersion("v0.8", "", ""); + } + return null; + } + @Override protected VersionParameters createVersionParameterForProtocolVersion( String counterPartyId, String versionAddress, String version) { diff --git a/edc-extensions/connector-discovery/cx-connector-discovery/src/test/java/org/eclipse/tractusx/edc/discovery/cx/BpnlAndDsp08ConnectorDiscoveryServiceImplTest.java b/edc-extensions/connector-discovery/cx-connector-discovery/src/test/java/org/eclipse/tractusx/edc/discovery/cx/BpnlAndDsp08ConnectorDiscoveryServiceImplTest.java index 5b20ce33f0..6e1c2b8511 100644 --- a/edc-extensions/connector-discovery/cx-connector-discovery/src/test/java/org/eclipse/tractusx/edc/discovery/cx/BpnlAndDsp08ConnectorDiscoveryServiceImplTest.java +++ b/edc-extensions/connector-discovery/cx-connector-discovery/src/test/java/org/eclipse/tractusx/edc/discovery/cx/BpnlAndDsp08ConnectorDiscoveryServiceImplTest.java @@ -33,11 +33,12 @@ import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.web.spi.exception.BadGatewayException; import org.eclipse.edc.web.spi.exception.InvalidRequestException; import org.eclipse.tractusx.edc.discovery.cx.service.BpnlAndDsp08ConnectorDiscoveryServiceImpl; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.CacheConfig; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorDiscoveryRequest; -import org.eclipse.tractusx.edc.discovery.v4alpha.spi.ConnectorParamsDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.CacheConfig; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorDiscoveryRequest; +import org.eclipse.tractusx.edc.discovery.spi.ConnectorParamsDiscoveryRequest; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; @@ -160,6 +161,42 @@ void discoverVersionParams_shouldReturnDsp08_whenDidDsp2025NotAvailable(String c assertThat(response).isEqualTo(expectedJson); } + @Test + void discoverVersionParams_shouldReturnDsp08_whenMetadataEndpointIsAccessControlled() { + var paramsDiscoveryRequest = new ConnectorParamsDiscoveryRequest(TEST_DID, TEST_ADDRESS); + + when(bdrsClient.resolveBpn(TEST_DID)) + .thenReturn(TEST_BPNL); + when(httpClient.executeAsync(any(), any())) + .thenReturn(CompletableFuture.completedFuture( + dummyResponseBuilder(401, "Unauthorized", "Unauthorized").build())); + + var expectedJson = Json.createObjectBuilder() + .add(CATALOG_REQUEST_COUNTER_PARTY_ID, TEST_BPNL) + .add(CATALOG_REQUEST_PROTOCOL, VERSION_PROTOCOL_OLD) + .add(CATALOG_REQUEST_COUNTER_PARTY_ADDRESS, TEST_ADDRESS) + .build(); + + var response = testee.discoverVersionParams(paramsDiscoveryRequest).join(); + + assertThat(response).isEqualTo(expectedJson); + } + + @Test + void discoverVersionParams_shouldReturnFailure_whenMetadataEndpointReturnsNotFound() { + var paramsDiscoveryRequest = new ConnectorParamsDiscoveryRequest(TEST_DID, TEST_ADDRESS); + + when(httpClient.executeAsync(any(), any())) + .thenReturn(CompletableFuture.completedFuture( + dummyResponseBuilder(404, "Not found", "Not found").build())); + + assertThatThrownBy(() -> testee.discoverVersionParams(paramsDiscoveryRequest).join()) + .isInstanceOf(CompletionException.class) + .hasCauseInstanceOf(BadGatewayException.class) + .hasMessageContaining("Counterparty well-known endpoint has failed with status") + .hasMessageContaining("404"); + } + @Test void discoverVersionParams_shouldReturnFailure_whenDidNotResolvable() { var paramsDiscoveryRequest = new ConnectorParamsDiscoveryRequest(TEST_BPNL, TEST_ADDRESS); @@ -338,9 +375,13 @@ void discoverConnectors_shouldReturnExpectedValues_WithKnownConnectorsProvided(S } static okhttp3.Response.Builder dummyResponseBuilder(String body) { + return dummyResponseBuilder(200, body, "any"); + } + + static okhttp3.Response.Builder dummyResponseBuilder(int code, String body, String message) { return new okhttp3.Response.Builder() - .code(200) - .message("any") + .code(code) + .message(message) .body(ResponseBody.create(body, MediaType.get("application/json"))) .protocol(Protocol.HTTP_1_1) .request(new Request.Builder().url(TEST_ADDRESS).build()); diff --git a/edc-extensions/cx-policy/build.gradle.kts b/edc-extensions/cx-policy/build.gradle.kts index f33e2fd7a8..6ab12b7703 100644 --- a/edc-extensions/cx-policy/build.gradle.kts +++ b/edc-extensions/cx-policy/build.gradle.kts @@ -34,8 +34,12 @@ dependencies { implementation(libs.edc.spi.decentralized.claims) implementation(libs.edc.spi.policyengine) implementation(libs.edc.spi.vc) + implementation(libs.edc.lib.jsonld) implementation(libs.jakartaJson) implementation(libs.edc.spi.jsonld) + implementation(libs.jsonschema) + compileOnly(libs.edc.api.management.validator.jsonschema) + testImplementation(libs.jacksonJsonP) testImplementation(libs.titaniumJsonLd) 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 ffd9963b8e..78df8dae8f 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 @@ -61,6 +61,7 @@ import org.eclipse.tractusx.edc.policy.cx.usage.UsagePurposeConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.usage.UsageRestrictionConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.validator.CxPolicyDefinitionValidator; +import org.eclipse.tractusx.edc.policy.cx.validator.jsonschema.CxJsonSchemaPolicyDefinitionValidator; import org.eclipse.tractusx.edc.policy.cx.versionchange.VersionChangesConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.warranty.WarrantyConstraintFunction; import org.eclipse.tractusx.edc.policy.cx.warranty.WarrantyDefinitionConstraintFunction; @@ -71,7 +72,9 @@ import java.util.Set; import java.util.stream.Stream; +import static org.eclipse.edc.connector.api.management.schema.ManagementApiSchemaValidatorExtension.V_4_PREFIX; import static org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition.EDC_POLICY_DEFINITION_TYPE; +import static org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition.EDC_POLICY_DEFINITION_TYPE_TERM; import static org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext.POLICY_MONITOR_SCOPE; import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_2025_09_NS; @@ -120,6 +123,8 @@ public class CxPolicyExtension implements ServiceExtension { private static final Set RULE_SCOPES = Set.of(CATALOG_REQUEST_SCOPE, NEGOTIATION_REQUEST_SCOPE, TRANSFER_PROCESS_REQUEST_SCOPE, CATALOG_SCOPE, NEGOTIATION_SCOPE, TRANSFER_PROCESS_SCOPE); + private static final String V4_POLICY_DEFINITION_TYPE = V_4_PREFIX + EDC_POLICY_DEFINITION_TYPE_TERM; + private static String withCxPolicyNsPrefix(String name) { return CX_POLICY_2025_09_NS + name; } @@ -181,7 +186,7 @@ public void registerFunctions(PolicyEngine engine) { engine.registerFunction(CatalogPolicyContext.class, Permission.class, withCxPolicyNsPrefix(BUSINESS_PARTNER_GROUP), new BusinessPartnerGroupConstraintFunction<>(store, bdrsClient)); engine.registerFunction(CatalogPolicyContext.class, Permission.class, - withCxPolicyNsPrefix(BUSINESS_PARTNER_NUMBER), new BusinessPartnerNumberConstraintFunction<>(bdrsClient)); + withCxPolicyNsPrefix(BUSINESS_PARTNER_NUMBER), new BusinessPartnerNumberConstraintFunction<>()); // Usage Permission Validators engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, @@ -374,6 +379,7 @@ public String name() { @Override public void prepare() { validatorRegistry.register(EDC_POLICY_DEFINITION_TYPE, CxPolicyDefinitionValidator.instance()); + validatorRegistry.register(V4_POLICY_DEFINITION_TYPE, new CxJsonSchemaPolicyDefinitionValidator()); } @Override diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/businesspartner/BusinessPartnerNumberConstraintFunction.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/businesspartner/BusinessPartnerNumberConstraintFunction.java index 0bebc65e3c..02f6f7b893 100644 --- a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/businesspartner/BusinessPartnerNumberConstraintFunction.java +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/businesspartner/BusinessPartnerNumberConstraintFunction.java @@ -19,14 +19,15 @@ package org.eclipse.tractusx.edc.policy.cx.businesspartner; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyContext; import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.spi.result.Failure; import org.eclipse.edc.spi.result.Result; +import org.eclipse.tractusx.edc.core.utils.credentials.CredentialTypePredicate; import org.eclipse.tractusx.edc.policy.cx.common.ValueValidatingConstraintFunction; -import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.jetbrains.annotations.NotNull; import java.util.Arrays; @@ -38,29 +39,30 @@ import static org.eclipse.edc.spi.result.Result.failure; import static org.eclipse.edc.spi.result.Result.success; -import static org.eclipse.tractusx.edc.spi.identity.mapper.BdrsConstants.DID_PREFIX; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS; +import static org.eclipse.tractusx.edc.policy.cx.common.AbstractDynamicCredentialConstraintFunction.CREDENTIAL_LITERAL; +import static org.eclipse.tractusx.edc.policy.cx.common.AbstractDynamicCredentialConstraintFunction.VC_CLAIM; /** * This is a constraint function that evaluates the BusinessPartnerNumber of a participant agent. + * The BPN is extracted from the {@code bpn} claim of the participant's BpnCredential. */ public class BusinessPartnerNumberConstraintFunction extends ValueValidatingConstraintFunction { public static final String BUSINESS_PARTNER_NUMBER = "BusinessPartnerNumber"; + private static final String BPN_CREDENTIAL_TYPE = "Bpn"; + private static final String BPN_CLAIM = "bpn"; private static final List SUPPORTED_OPERATORS = Arrays.asList( Operator.IS_ANY_OF, Operator.IS_NONE_OF ); - private BdrsClient bdrsClient; - - public BusinessPartnerNumberConstraintFunction(BdrsClient bdrsClient) { + public BusinessPartnerNumberConstraintFunction() { super( Set.of(Operator.IS_ANY_OF, Operator.IS_NONE_OF), "^BPNL[0-9A-Z]{12}$", true ); - - this.bdrsClient = bdrsClient; } @Override @@ -68,19 +70,32 @@ public boolean evaluate(Operator operator, Object rightOperand, Permission permi var participantAgent = context.participantAgent(); if (!SUPPORTED_OPERATORS.contains(operator)) { - var message = "Operator %s is not supported. Supported operators: %s".formatted(operator, SUPPORTED_OPERATORS); - context.reportProblem(message); + context.reportProblem("Operator %s is not supported. Supported operators: %s".formatted(operator, SUPPORTED_OPERATORS)); return false; } - var identity = participantAgent.getIdentity(); - if (identity == null) { - context.reportProblem("Identity of the participant agent cannot be null"); + var vcListClaim = participantAgent.getClaims().get(VC_CLAIM); + if (!(vcListClaim instanceof List vcList) || vcList.isEmpty()) { + context.reportProblem("ParticipantAgent did not contain a valid '%s' claim.".formatted(VC_CLAIM)); return false; } - if (identity.startsWith(DID_PREFIX)) { - identity = bdrsClient.resolveBpn(identity); + var bpnPredicate = new CredentialTypePredicate(CX_CREDENTIAL_NS, BPN_CREDENTIAL_TYPE + CREDENTIAL_LITERAL); + var identity = vcList.stream() + .filter(VerifiableCredential.class::isInstance) + .map(VerifiableCredential.class::cast) + .filter(bpnPredicate) + .flatMap(vc -> vc.getCredentialSubject().stream()) + .flatMap(subject -> subject.getClaims().entrySet().stream()) + .filter(e -> e.getKey().endsWith(BPN_CLAIM)) + .map(Map.Entry::getValue) + .map(String.class::cast) + .findFirst() + .orElse(null); + + if (identity == null) { + context.reportProblem("Could not extract '%s' from BpnCredential".formatted(BPN_CLAIM)); + return false; } return switch (operator) { @@ -106,12 +121,11 @@ private Result checkListContains(String identity, Object rightValue, Op .map(entry -> ((Map) entry).get("@value")) .filter(value -> value instanceof Map) .map(value -> ((Map) value).get("string")) - .anyMatch(bpn -> identity.equals(bpn)); + .anyMatch(identity::equals); return success(containsBpn); } else if (rightValue instanceof String singleNumber) { - boolean containsBpn = identity.equals(singleNumber); - return success(containsBpn); + return success(identity.equals(singleNumber)); } return failure("Invalid right-value: operator '%s' requires a 'List' but got a '%s'" .formatted(operator, Optional.of(rightValue).map(Object::getClass).map(Class::getName).orElse(null))); diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/usage/UsagePurposeConstraintFunction.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/usage/UsagePurposeConstraintFunction.java index 892ed167c6..c648c14bd4 100644 --- a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/usage/UsagePurposeConstraintFunction.java +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/usage/UsagePurposeConstraintFunction.java @@ -22,7 +22,8 @@ import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext; import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.policy.model.Permission; -import org.eclipse.tractusx.edc.policy.cx.common.ValueValidatingConstraintFunction; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.tractusx.edc.policy.cx.common.BaseConstraintFunction; import java.util.Set; @@ -30,44 +31,17 @@ * This is a placeholder constraint function for UsagePurpose. It always returns true but allows * the validation of policies to be strictly enforced. */ -public class UsagePurposeConstraintFunction extends ValueValidatingConstraintFunction { +public class UsagePurposeConstraintFunction extends BaseConstraintFunction { public static final String USAGE_PURPOSE = "UsagePurpose"; public UsagePurposeConstraintFunction() { super( - Set.of(Operator.IS_ANY_OF), - Set.of( - "cx.core.legalRequirementForThirdparty:1", - "cx.core.industrycore:1", - "cx.core.qualityNotifications:1", - "cx.core.digitalTwinRegistry:1", - "cx.pcf.base:1", - "cx.quality.base:1", - "cx.dcm.base:1", - "cx.puris.base:1", - "cx.circular.dpp:1", - "cx.circular.smc:1", - "cx.circular.marketplace:1", - "cx.circular.materialaccounting:1", - "cx.bpdm.gate.upload:1", - "cx.bpdm.gate.download:1", - "cx.bpdm.pool:1", - "cx.bpdm.vas.countryrisk:1", - "cx.bpdm.vas.dataquality.upload:1", - "cx.bpdm.vas.dataquality.download:1", - "cx.bpdm.vas.bdv.upload:1", - "cx.bpdm.vas.bdv.download:1", - "cx.bpdm.vas.fpd.upload:1", - "cx.bpdm.vas.fpd.download:1", - "cx.bpdm.vas.swd.upload:1", - "cx.bpdm.vas.swd.download:1", - "cx.bpdm.vas.nps.upload:1", - "cx.bpdm.vas.nps.download:1", - "cx.ccm.base:1", - "cx.bpdm.poolAll:1", - "cx.logistics.base:1" - ), - true + Set.of(Operator.IS_ANY_OF) ); } + + @Override + protected Result validateRightOperand(Object rightValue) { + return Result.success(); + } } diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/jsonschema/CxJsonSchemaPolicyDefinitionValidator.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/jsonschema/CxJsonSchemaPolicyDefinitionValidator.java new file mode 100644 index 0000000000..3c1258425d --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/jsonschema/CxJsonSchemaPolicyDefinitionValidator.java @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.validator.jsonschema; + +import jakarta.json.JsonObject; +import org.eclipse.edc.validator.spi.ValidationResult; +import org.eclipse.edc.validator.spi.Validator; + +import static org.eclipse.edc.validator.spi.ValidationResult.failure; +import static org.eclipse.edc.validator.spi.Violation.violation; + +public class CxJsonSchemaPolicyDefinitionValidator implements Validator { + + private static final String POLICY_ATTRIBUTE_NAME = "policy"; + + private final CxJsonSchemaPolicyValidator policyValidator; + + public CxJsonSchemaPolicyDefinitionValidator() { + this.policyValidator = new CxJsonSchemaPolicyValidator(); + } + + @Override + public ValidationResult validate(JsonObject input) { + if (!input.containsKey(POLICY_ATTRIBUTE_NAME)) { + return failure(violation("Attribute '%s' is missing from PolicyDefinition.".formatted(POLICY_ATTRIBUTE_NAME), POLICY_ATTRIBUTE_NAME)); + } + + var policy = input.get(POLICY_ATTRIBUTE_NAME); + if (!(policy instanceof JsonObject)) { + return failure(violation("Attribute '%s' is not a valid JSON object.".formatted(POLICY_ATTRIBUTE_NAME), POLICY_ATTRIBUTE_NAME)); + } + + return policyValidator.validate((JsonObject) policy); + } +} diff --git a/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/jsonschema/CxJsonSchemaPolicyValidator.java b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/jsonschema/CxJsonSchemaPolicyValidator.java new file mode 100644 index 0000000000..10d0073480 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/validator/jsonschema/CxJsonSchemaPolicyValidator.java @@ -0,0 +1,76 @@ +/******************************************************************************** + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.validator.jsonschema; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaLocation; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.dialect.Dialects; +import com.networknt.schema.resource.IriResourceLoader; +import jakarta.json.JsonObject; +import org.eclipse.edc.jsonld.util.JacksonJsonLd; +import org.eclipse.edc.validator.spi.ValidationResult; +import org.eclipse.edc.validator.spi.Validator; +import org.eclipse.edc.validator.spi.Violation; + +import java.util.Map; + +public class CxJsonSchemaPolicyValidator implements Validator { + private static final String CX_POLICY_SCHEMA_PREFIX = "https://w3id.org/catenax/2025/9/policy"; + private static final String CX_POLICY_SCHEMA_LOCATION = "classpath:jsonschema/cx-policy"; + + private static final String DSPACE_2025_SCHEMA_PREFIX = "https://w3id.org/dspace/2025/1/negotiation"; + private static final String DSPACE_2025_SCHEMA_LOCATION = "classpath:jsonschema/dspace"; + + private static final String CX_POLICY_SCHEMA = CX_POLICY_SCHEMA_PREFIX + "/schema/policy-schema.json"; + + private final ObjectMapper objectMapper; + private final Schema schemaValidator; + + private static final Map PREFIX_MAPPINGS = Map.of( + CX_POLICY_SCHEMA_PREFIX, CX_POLICY_SCHEMA_LOCATION, + DSPACE_2025_SCHEMA_PREFIX, DSPACE_2025_SCHEMA_LOCATION + ); + + public CxJsonSchemaPolicyValidator() { + this.objectMapper = JacksonJsonLd.createObjectMapper(); + var schemaRegistry = SchemaRegistry.withDialect(Dialects.getDraft201909(), builder -> builder + .schemaIdResolvers(schemaIdResolvers -> PREFIX_MAPPINGS.forEach(schemaIdResolvers::mapPrefix)) + .resourceLoaders(resourceLoaders -> resourceLoaders.add(IriResourceLoader.getInstance()))); + this.schemaValidator = schemaRegistry.getSchema(SchemaLocation.of(CX_POLICY_SCHEMA)); + } + + @Override + public ValidationResult validate(JsonObject input) { + var node = objectMapper.convertValue(input, JsonNode.class); + var response = schemaValidator.validate(node); + if (response.isEmpty()) { + return ValidationResult.success(); + } + + var violations = response.stream() + .map(error -> Violation.violation(error.getMessage(), error.getInstanceLocation().toString())) + .toList(); + + return ValidationResult.failure(violations); + } +} diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/affiliates-bpnl-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/affiliates-bpnl-constraint-schema.json new file mode 100644 index 0000000000..e00d4383e4 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/affiliates-bpnl-constraint-schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "AffiliatesBpnlConstraint", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/AffiliatesBpnlConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/affiliates-bpnl-constraint-schema.json", + "definitions": { + "AffiliatesBpnlConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "AffiliatesBpnl" + }, + "operator": { + "type": "string", + "const": "isAnyOf" + }, + "rightOperand": { + "type":"array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/BpnlRightOperand" + } + } + }, + "additionalProperties": false + }, + "BpnlRightOperand": { + "type": "string", + "pattern": "^BPNL[0-9A-Z]{12}$", + "$comment": "{\"permission\":\"The Data Provider permits the Data Consumer to make the Data available for use by the Affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') ('Sublicensing') specified herein, provided that the Data Consumer ensures that such companies and/or its its Affiliated Companies comply with the terms of the Agreement concluded via the Registered Connector (RC).\",\"prohibition\":\"The Data Consumer is not entitled to make the Data available for use by the companies specified herein ('Sublicensing'), unless the Parties have otherwise agreed on a right to sublicense the Data (either in full or with respect to individual affiliated companies) in the referenced contract ('cx-policy:ContractReference') or have agreed to this separately.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/affiliates-region-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/affiliates-region-constraint-schema.json new file mode 100644 index 0000000000..07cb47eaf5 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/affiliates-region-constraint-schema.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "AffiliatesRegionConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/AffiliatesRegionConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/affiliates-region-constraint-schema.json", + "definitions": { + "AffiliatesRegionConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "AffiliatesRegion" + }, + "operator": { + "type": "string", + "const": "isAnyOf" + }, + "rightOperand": { + "type":"array", + "minItems": 1, + "items": { + "anyOf": [ + {"$ref": "#/definitions/RegionAllRightOperand"}, + {"$ref": "#/definitions/RegionEuropeRightOperand"}, + {"$ref": "#/definitions/RegionNorthAmericaRightOperand"}, + {"$ref": "#/definitions/RegionSouthAmericaRightOperand"}, + {"$ref": "#/definitions/RegionAfricaRightOperand"}, + {"$ref": "#/definitions/RegionAsiaRightOperand"}, + {"$ref": "#/definitions/RegionOceaniaRightOperand"}, + {"$ref": "#/definitions/RegionAntarcticaRightOperand"} + ] + } + } + }, + "additionalProperties": false + }, + "RegionAllRightOperand": { + "type": "string", + "const": "cx.region.all:1", + "$comment": "{\"permission\":\"The Data Provider permits the Data Consumer to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') ('Sublicensing'), provided that the Data Consumer ensures that its Affiliated Companies comply with the terms of this Agreement concluded via the Registered Connector (RC).\",\"prohibition\":\"The Data Consumer is not entitled to make the Data available for use to its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies'), unless the Parties have otherwise agreed on a right of sublicensing in the referenced contract (cx-policy:ContractReference).\"}" + }, + "RegionEuropeRightOperand": { + "type": "string", + "const": "cx.region.europe:1", + "$comment": "{\"permission\":\"The Data Provider permits the Data Consumer to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') ('Sublicensing'), provided that the Data Consumer ensures that its Affiliated Companies comply with the terms of this Agreement concluded via the Registered Connector (RC). The registered offices of the relevant Affiliated Companies must be located in Europe.\",\"prohibition\":\"The Data Consumer is not entitled to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') that are domiciled in Europe, unless the Parties have otherwise agreed on a right of sublicensing (either in full or with respect to individual Affiliated Companies) in the referenced bilateral contract ('cx-policy:ContractReference') or have agreed to this separately.\"}" + }, + "RegionNorthAmericaRightOperand": { + "type": "string", + "const": "cx.region.northAmerica:1", + "$comment": "{\"permission\":\"The Data Provider permits the Data Consumer to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') ('Sublicensing'), provided that the Data Consumer ensures that its Affiliated Companies comply with the terms of this Agreement concluded via the Registered Connector (RC). The registered offices of the relevant Affiliated Companies must be located in North America.\",\"prohibition\":\"The Data Consumer is not entitled to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') that are domiciled in North America, unless the Parties have otherwise agreed on a right of sublicensing (either in full or with respect to individual Affiliated Companies) in the referenced bilateral contract ('cx-policy:ContractReference') or have agreed to this separately.\"}" + }, + "RegionSouthAmericaRightOperand": { + "type": "string", + "const": "cx.region.southAmerica:1", + "$comment": "{\"permission\":\"The Data Provider permits the Data Consumer to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') ('Sublicensing'), provided that the Data Consumer ensures that its Affiliated Companies comply with the terms of this Agreement concluded via the Registered Connector (RC). The registered offices of the relevant Affiliated Companies must be located in South America.\",\"prohibition\":\"The Data Consumer is not entitled to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') that are domiciled in South America, unless the Parties have otherwise agreed on a right of sublicensing (either in full or with respect to individual Affiliated Companies) in the referenced bilateral contract ('cx-policy:ContractReference') or have agreed to this separately.\"}" + }, + "RegionAfricaRightOperand": { + "type": "string", + "const": "cx.region.africa:1", + "$comment": "{\"permission\":\"The Data Provider permits the Data Consumer to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') ('Sublicensing'), provided that the Data Consumer ensures that its Affiliated Companies comply with the terms of this Agreement concluded via the Registered Connector (RC). The registered offices of the relevant Affiliated Companies must be located in Africa.\",\"prohibition\":\"The Data Consumer is not entitled to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') that are domiciled in Africa, unless the Parties have otherwise agreed on a right of sublicensing (either in full or with respect to individual Affiliated Companies) in the referenced bilateral contract ('cx-policy:ContractReference') or have agreed to this separately.\"}" + }, + "RegionAsiaRightOperand": { + "type": "string", + "const": "cx.region.asia:1", + "$comment": "{\"permission\":\"The Data Provider permits the Data Consumer to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') ('Sublicensing'), provided that the Data Consumer ensures that its Affiliated Companies comply with the terms of this Agreement concluded via the Registered Connector (RC). The registered offices of the relevant Affiliated Companies must be located in Asia.\",\"prohibition\":\"The Data Consumer is not entitled to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') that are domiciled in Asia, unless the Parties have otherwise agreed on a right of sublicensing (either in full or with respect to individual Affiliated Companies) in the referenced bilateral contract ('cx-policy:ContractReference') or have agreed to this separately.\"}" + }, + "RegionOceaniaRightOperand": { + "type": "string", + "const": "cx.region.oceania:1", + "$comment": "{\"permission\":\"The Data Provider permits the Data Consumer to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') ('Sublicensing'), provided that the Data Consumer ensures that its Affiliated Companies comply with the terms of this Agreement concluded via the Registered Connector (RC). The registered offices of the relevant Affiliated Companies must be located in Oceania.\",\"prohibition\":\"The Data Consumer is not entitled to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') that are domiciled in Oceania, unless the Parties have otherwise agreed on a right of sublicensing (either in full or with respect to individual Affiliated Companies) in the referenced bilateral contract ('cx-policy:ContractReference') or have agreed to this separately.\"}" + }, + "RegionAntarcticaRightOperand": { + "type": "string", + "const": "cx.region.antarctica:1", + "$comment": "{\"permission\":\"The Data Provider permits the Data Consumer to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') ('Sublicensing'), provided that the Data Consumer ensures that its Affiliated Companies comply with the terms of this Agreement concluded via the Registered Connector (RC). The registered offices of the relevant Affiliated Companies must be located in Antarctica.\",\"prohibition\":\"The Data Consumer is not entitled to make the Data available for use by its affiliated companies within the meaning of Section 15 German Stock Corporation Act ('Affiliated Companies') that are domiciled in Antarctica, unless the Parties have otherwise agreed on a right of sublicensing (either in full or with respect to individual Affiliated Companies) in the referenced bilateral contract ('cx-policy:ContractReference') or have agreed to this separately.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/business-partner-group-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/business-partner-group-constraint-schema.json new file mode 100644 index 0000000000..9ea879b34a --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/business-partner-group-constraint-schema.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "BusinessPartnerGroupConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/BusinessPartnerGroupConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/business-partner-group-constraint-schema.json", + "definitions": { + "BusinessPartnerGroupConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "BusinessPartnerGroup" + }, + "operator": { + "type": "string", + "enum": [ + "isAnyOf", + "isNoneOf" + ] + }, + "rightOperand": { + "type":"array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/BusinessPartnerGroupRightOperand" + } + } + }, + "additionalProperties": false + }, + "BusinessPartnerGroupRightOperand": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/business-partner-number-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/business-partner-number-constraint-schema.json new file mode 100644 index 0000000000..b4ae7a64a2 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/business-partner-number-constraint-schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "BusinessPartnerNumberConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/BusinessPartnerNumberConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/business-partner-number-constraint-schema.json", + "definitions": { + "BusinessPartnerNumberConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "BusinessPartnerNumber" + }, + "operator": { + "type": "string", + "enum": [ + "isAnyOf", + "isNoneOf" + ] + }, + "rightOperand": { + "type":"array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/BusinessPartnerNumberRightOperand" + } + } + }, + "additionalProperties": false + }, + "BusinessPartnerNumberRightOperand": { + "type": "string", + "pattern": "^BPNL[0-9A-Z]{12}$" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/confidential-information-measures-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/confidential-information-measures-constraint-schema.json new file mode 100644 index 0000000000..3810d0d24c --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/confidential-information-measures-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "ConfidentialInformationMeasuresConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ConfidentialInformationMeasuresConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/confidential-information-measures-constraint-schema.json", + "definitions": { + "ConfidentialInformationMeasuresConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "ConfidentialInformationMeasures" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/ConfidentialityMeasuresRightOperand" + } + }, + "additionalProperties": false + }, + "ConfidentialityMeasuresRightOperand": { + "type": "string", + "const": "cx.confidentiality.measures:1", + "$comment": "{\"permission\":\"The Data Consumer is obliged to take all appropriate technical and organisational measures to protect the Confidential Information of the Data Provider, in order to prevent unauthorised disclosure to third parties. The Data Consumer is also obliged to inform the Data Provider without undue delay about any unauthorised disclosure of Confidential Information.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/confidential-information-sharing-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/confidential-information-sharing-constraint-schema.json new file mode 100644 index 0000000000..491bf5e159 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/confidential-information-sharing-constraint-schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "ConfidentialInformationSharingConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ConfidentialInformationSharingConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/confidential-information-sharing-constraint-schema.json", + "definitions": { + "ConfidentialInformationSharingConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "ConfidentialInformationSharing" + }, + "operator": { + "type": "string", + "const": "isAnyOf" + }, + "rightOperand": { + "type": "array", + "minItems": 1, + "items": { + "anyOf": [ + {"$ref": "#/definitions/SharingAffiliatesRightOperand"}, + {"$ref": "#/definitions/SharingManagedLegalEntityRightOperand"} + ] + } + } + }, + "additionalProperties": false + }, + "SharingAffiliatesRightOperand": { + "type": "string", + "const": "cx.sharing.affiliates:1", + "$comment": "{\"permission\":\"The Data Consumer may only disclose Confidential Information to Affiliated Companies if and to the extent that the Data Provider has expressly permitted such disclosure in accordance with the Data Exchange Governance or the cx-policy:affiliates.*. The Data Consumer may only disclose Confidential Information to Affiliated Companies to the extent that the Affiliated Companies and their employees are bound to Confidentiality Obligations at least equivalent to those set forth in this Agreement. Furthermore, access to and use of the relevant Data must be restricted to those employees of the Affiliated Company who require the Data in order to exercise the agreed usage rights ('need to know').\"}" + }, + "SharingManagedLegalEntityRightOperand": { + "type": "string", + "const": "cx.sharing.managedLegalEntity:1", + "$comment": "{\"permission\":\"The Data Consumer may only disclose Confidential Information to those companies for which the Data Consumer acts in an 'is managed by' relationship (within the meaning of the Catena-X Standard 'CX-0074') if and to the extent those companies are expressly listed in cx-policy:managedLegalEntity.*. The Data Consumer may only disclose Confidential Information to those Companies to the extent that those Companies and their employees are bound to Confidentiality Obligations at least equivalent to those set forth in this Agreement. Furthermore, access to and use of the relevant Data must be restricted to those employees of the Company who require the Data in order to exercise the agreed usage rights ('need to know').\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/contract-reference-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/contract-reference-constraint-schema.json new file mode 100644 index 0000000000..3179c1f733 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/contract-reference-constraint-schema.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "ContractReferenceConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ContractReferenceConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/contract-reference-constraint-schema.json", + "definitions": { + "ContractReferenceConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "ContractReference" + }, + "operator": { + "type": "string", + "const": "isAllOf" + }, + "rightOperand": { + "type":"array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/ContractReferenceRightOperand" + } + } + }, + "additionalProperties": false + }, + "ContractReferenceRightOperand": { + "type": "string", + "$comment": "{\"permission\":\"Data Provider and Data Consumer are free to reference an existing, individual contract as a basis of the Agreement concluded via the Registered Connector (RC). This can be a framework agreement or a very specific contract. The rightOperand value for this constraint can be a free to choose reference under which both parties are able to identify their contract. The reference does not have to have a version number.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/contract-termination-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/contract-termination-constraint-schema.json new file mode 100644 index 0000000000..4bcfc40078 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/contract-termination-constraint-schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "ContractTerminationConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ContractTerminationConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/contract-termination-constraint-schema.json", + "definitions": { + "ContractTerminationConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "ContractTermination" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "oneOf": [ + {"$ref": "#/definitions/DataDeletionRightOperand"}, + {"$ref": "#/definitions/DataKeepingRightOperand"} + ] + } + }, + "additionalProperties": false + }, + "DataDeletionRightOperand": { + "type": "string", + "const": "cx.data.deletion:1", + "$comment": "{\"permission\":\"Upon expiry of the period of use (in accordance with cx-policy:DataUsageEnd) as well as in the event of termination, the Data Consumer shall be obliged to delete the Data (including all copies in backup systems that can be deleted with reasonable effort) from all systems and storage media and, upon request, confirm this to the Data Provider in text form. The foregoing obligation shall apply accordingly to the extent that the Data Consumer is permitted to provide the data to Affiliated Companies (in accordance with cx-policy:affiliates.*) or to companies for which the Data Consumer acts in an 'is managed by' relationship (within the meaning of the Catena-X Standard CX-0076) (in accordance with cx-policy:contractingCompany.*). The Data Consumer is entitled to make and retain a copy of the Data for as long as necessary to safeguard legitimate interests vis-à-vis the Data Provider - particularly for the purpose of demonstrating breaches of contractual obligations arising from the provision of the Data.\"}" + }, + "DataKeepingRightOperand": { + "type": "string", + "const": "cx.data.keeping:1", + "$comment": "{\"permission\":\"Upon expiry of the period of use (in accordance with cx-policy:dataUsageEnd) as well as in the event of termination, the Data Consumer shall not be obliged to delete the Data (including all copies in backup systems that can be deleted with reasonable effort) from all systems and storage media. The foregoing shall apply accordingly to the extent that the Data Consumer is permitted to provide the Data to Affiliated Companies (in accordance with leftOperand cx-policy:affiliates.*) or to companies for which the Data Consumer acts in an 'is managed by' relationship (within the meaning of the Catena-X Standard CX-0076) (in accordance with cx-policy:contractingCompany.*). In such case, the Data Consumer shall be entitled to continue using the Data exclusively within the scope of the purpose limitation and other conditions of the contract. The Data Consumer shall take all necessary measures to protect the Data against unauthorised access by third parties.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-frequency-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-frequency-constraint-schema.json new file mode 100644 index 0000000000..1a6566a6e2 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-frequency-constraint-schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "DataFrequencyConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/DataFrequencyConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/data-frequency-constraint-schema.json", + "definitions": { + "DataFrequencyConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "DataFrequency" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "oneOf": [ + {"$ref": "#/definitions/DataFrequencyOnceRightOperand"}, + {"$ref": "#/definitions/DataFrequencyUnlimitedRightOperand"} + ] + } + }, + "additionalProperties": false + }, + "DataFrequencyOnceRightOperand": { + "type": "string", + "const": "cx.dataFrequency.once:1", + "$comment": "{\"permission\":\"This Agreement concluded via the Registered Connector (RC). applies both to a one-time, time-limited data exchange as well as to sequential data exchanges under similar conditions, potentially also in varying quantities (Data as a Service).\"}" + }, + "DataFrequencyUnlimitedRightOperand": { + "type": "string", + "const": "cx.dataFrequency.unlimited:1", + "$comment": "{\"permission\":\"This Agreement concluded via the Registered Connector (RC). applies to the multiple or repeated exchange of similar Data at different times and in different quantities within the scope of the selected Use Case.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-provisioning-end-date-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-provisioning-end-date-constraint-schema.json new file mode 100644 index 0000000000..25bb6d320e --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-provisioning-end-date-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "DataProvisioningEndDateConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/DataProvisioningEndDateConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/data-provisioning-end-date-constraint-schema.json", + "definitions": { + "DataProvisioningEndDateConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "DataProvisioningEndDate" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/DataProvisioningEndDateRightOperand" + } + }, + "additionalProperties": false + }, + "DataProvisioningEndDateRightOperand": { + "type": "string", + "pattern": "^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2}))$", + "$comment": "{\"obligation\":\"The Data Provider shall make the Data available for a limited period until the end date specified herein, commencing from the second the Agreement is concluded via the Registered Connector (RC).\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-provisioning-end-duration-days-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-provisioning-end-duration-days-constraint-schema.json new file mode 100644 index 0000000000..ea53f046c8 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-provisioning-end-duration-days-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "DataProvisioningEndDurationDaysConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/DataProvisioningEndDurationDaysConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/data-provisioning-end-duration-days-constraint-schema.json", + "definitions": { + "DataProvisioningEndDurationDaysConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "DataProvisioningEndDurationDays" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/DataProvisioningEndDurationDaysRightOperand" + } + }, + "additionalProperties": false + }, + "DataProvisioningEndDurationDaysRightOperand": { + "type": "string", + "pattern": "^[1-9][0-9]*$", + "$comment": "{\"obligation\":\"The Data Provider shall make the Data available for the period specified herein in days, commencing from the second the Agreement is concluded via the Registered Connector (RC).\"}" + } + } +} diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-usage-end-date-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-usage-end-date-constraint-schema.json new file mode 100644 index 0000000000..8b6d03419d --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-usage-end-date-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "DataUsageEndDateConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/DataUsageEndDateConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/data-usage-end-date-constraint-schema.json", + "definitions": { + "DataUsageEndDateConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "DataUsageEndDate" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/DataUsageEndDateRightOperand" + } + }, + "additionalProperties": false + }, + "DataUsageEndDateRightOperand": { + "type": "string", + "pattern": "^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2}))$", + "$comment": "{\"permission\":\"The Data Provider shall make the Data available with a usage period permitted until the end date specified herein, in accordance with the usage conditions specified in cx-policy:UsagePurpose, commencing from the second the Agreement is concluded via the Registered Connector (RC). Upon expiry of the usage period, the Data Consumer shall no longer be entitled to use the Data and shall delete the Data in order to prevent any further Use, unless the Data Consumer has received the same Data under another still valid contract. The Agreement shall terminate upon expiry of the usage period of the Data without the need for a separate notice of termination.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-usage-end-definition-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-usage-end-definition-constraint-schema.json new file mode 100644 index 0000000000..1545c154da --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-usage-end-definition-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "DataUsageEndDefinitionConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/DataUsageEndDefinitionConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/data-usage-end-definition-constraint-schema.json", + "definitions": { + "DataUsageEndDefinitionConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "DataUsageEndDefinition" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/DataUsageEndDefinitionRightOperand" + } + }, + "additionalProperties": false + }, + "DataUsageEndDefinitionRightOperand": { + "type": "string", + "const": "cx.dataUsageEnd.unlimited:1", + "$comment": "{\"permission\":\"The Data Provider shall make the Data available for an unlimited period of use in accordance with the usage conditions specified in cx-policy:UsagePurpose, commencing from the second the Agreement is concluded via the Registered Connector (RC).\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-usage-end-duration-days-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-usage-end-duration-days-constraint-schema.json new file mode 100644 index 0000000000..ab640c74c5 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/data-usage-end-duration-days-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "DataUsageEndDurationDaysConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/DataUsageEndDurationDaysConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/data-usage-end-duration-days-constraint-schema.json", + "definitions": { + "DataUsageEndDurationDaysConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "DataUsageEndDurationDays" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/DataUsageEndDurationDaysRightOperand" + } + }, + "additionalProperties": false + }, + "DataUsageEndDurationDaysRightOperand": { + "type": "string", + "pattern": "^[1-9][0-9]*$", + "$comment": "{\"permission\":\"The Data Provider shall make the Data available for the usage period specified herein, measured in days, in accordance with the usage conditions set out in cx-policy:UsagePurpose, commencing from the moment of concluding the Agreement via the Registered Connector (RC). Upon expiry of the permitted usage period, the Data Consumer shall no longer be entitled to use the Data, and shall delete the Data from all systems and storage media in order to prevent any further use, unless the Data Consumer has received the same Data under another valid contract. The Agreement shall terminate automatically upon expiry of the usage period for the Data, without the need for a separate notice of termination.\"}" + } + } +} diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/exclusive-usage-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/exclusive-usage-constraint-schema.json new file mode 100644 index 0000000000..f4d10953f5 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/exclusive-usage-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "ExclusiveUsageConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/ExclusiveUsageConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/exclusive-usage-constraint-schema.json", + "definitions": { + "ExclusiveUsageConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "ExclusiveUsage" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/ExclusiveUsageDataConsumerRightOperand" + } + }, + "additionalProperties": false + }, + "ExclusiveUsageDataConsumerRightOperand": { + "type": "string", + "const": "cx.exclusiveUsage.dataConsumer:1", + "$comment": "{\"permission\":\"The Data Consumer has the exclusive right to use the Data within the scope of the agreed purposes (cx-policy:UsagePurpose). The Data Provider’s right to use the Data it has provided for its own internal purposes remains unaffected.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/framework-agreement-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/framework-agreement-constraint-schema.json new file mode 100644 index 0000000000..573db61734 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/framework-agreement-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "FrameworkAgreementConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/FrameworkAgreementConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/framework-agreement-constraint-schema.json", + "definitions": { + "FrameworkAgreementConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "FrameworkAgreement" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/DataExchangeGovernanceRightOperand" + } + }, + "additionalProperties": false + }, + "DataExchangeGovernanceRightOperand": { + "type": "string", + "const": "DataExchangeGovernance:1.0", + "$comment": "{\"permission\":\"Data Provider and Data Consumer agree to the Data Exchange Governance published by the Catena-X Automotive Network e.V. (Association) as basis for this Agreement concluded via the Registered Connector (RC). The Data Exchange Governance can be found here: https://catenax-ev.github.io/docs/regulatory-framework/20000ft/data-exchange-governance after CX-Saturn has been released. The subject matter of the Agreement concluded via the RC is the provision of the Data for a limited period of one (1) year (unless otherwise agreed in [dataProvisioningEndDate](https://w3id.org/catenax/2025/9/policy/data-provisioning-end-date-constraint-schema.json) or [dataProvisioningEndDurationDays](https://w3id.org/catenax/2025/9/policy/data-provisioning-end-duration-days-constraint-schema.json)and granting a right to use the Data for a period of one (1) year (unless agreed otherwise in [dataUsageEndDate](https://w3id.org/catenax/2025/9/policy/data-usage-end-date-constraint-schema.json) or [dataUsageEndDurationDays](https://w3id.org/catenax/2025/9/policy/data-usage-end-duration-days-constraint-schema.json) or open-ended [DataUsageEndUnlimited](https://w3id.org/catenax/2025/9/policy/data-usage-end-definition-constraint-schema.json)). The provision of the Data shall be effected via the API specified by the dataset attribute dct:type. The Agreement concluded via the RC covers only the exchange of Data effected on the basis of the API version (as specified in the dataset attribute base-URL), the Asset-Version (dataset attribute cx-common:version), and the Aspect Model Version as applicable at the time of concluding the Agreement. Unless agreed otherwise in [VersionChanges](https://w3id.org/catenax/2025/9/policy/version-changes-constraint-schema.json), the Agreement concluded via the RC must be renegotiated in the event of any change to at least one of these versions. Unless otherwise agreed between the Parties within the Agreement concluded via the RC (in accordance with [UsagePurpose](https://w3id.org/catenax/2025/9/policy/usage-purpose-constraint-schema.json), the Data Provider grants the Data Consumer a non-exclusive right, limited in time to the duration of this contract (in accordance with [dataUsageEndDate](https://w3id.org/catenax/2025/9/policy/data-usage-end-date-constraint-schema.json) or [dataUsageEndDurationDays](https://w3id.org/catenax/2025/9/policy/data-usage-end-duration-days-constraint-schema.json) or open-ended [DataUsageEndUnlimited](https://w3id.org/catenax/2025/9/policy/data-usage-end-definition-constraint-schema.json)), to use the Data in accordance with the Contractual Usage Purposes (in accordance with [Usage Purpose](https://w3id.org/catenax/2025/9/policy/usage-purpose-constraint-schema.json)).\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/jurisdiction-location-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/jurisdiction-location-constraint-schema.json new file mode 100644 index 0000000000..33db191be5 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/jurisdiction-location-constraint-schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "JurisdictionLocationConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/JurisdictionLocationConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/jurisdiction-location-constraint-schema.json", + "definitions": { + "JurisdictionLocationConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "JurisdictionLocation" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/LocationStringRightOperand" + } + }, + "additionalProperties": false + }, + "LocationStringRightOperand": { + "type": "string", + "$comment": "{\"permission\":\"The exclusive venue for all disputes arising from the Agreement concluded via the Registered Connector (RC) shall be at the competent court at the location specified herein.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/jurisdiction-location-reference-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/jurisdiction-location-reference-constraint-schema.json new file mode 100644 index 0000000000..00dfe5580e --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/jurisdiction-location-reference-constraint-schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "JurisdictionLocationReferenceConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/JurisdictionLocationReferenceConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/jurisdiction-location-reference-constraint-schema.json", + "definitions": { + "JurisdictionLocationReferenceConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "JurisdictionLocationReference" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "oneOf": [ + {"$ref": "#/definitions/LocationDataConsumerRightOperand"}, + {"$ref": "#/definitions/LocationContractReferenceRightOperand"} + ] + } + }, + "additionalProperties": false + }, + "LocationDataConsumerRightOperand": { + "type": "string", + "const": "cx.location.dataConsumer:1", + "$comment": "{\"permission\":\"The exclusive venue for all disputes arising from the contract concluded via the Registered Connector (RC) shall be at the competent court at the registered office of the Data Consumer.\"}" + }, + "LocationContractReferenceRightOperand": { + "type": "string", + "const": "cx.location.contractReference:1", + "$comment": "{\"permission\":\"The exclusive venue for all disputes arising from the Agreement concluded via the Registered Connector (RC) shall correspond to the venue specified in the referenced contract (as referenced in cx-policy:ContractReference).\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/liability-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/liability-constraint-schema.json new file mode 100644 index 0000000000..2bb19c540a --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/liability-constraint-schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "LiabilityConstraintConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/LiabilityConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/liability-constraint-schema.json", + "definitions": { + "LiabilityConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "Liability" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "oneOf": [ + {"$ref": "#/definitions/GrossNegligenceRightOperand"}, + {"$ref": "#/definitions/SlightNegligenceRightOperand"} + ] + } + }, + "additionalProperties": false + }, + "GrossNegligenceRightOperand": { + "type": "string", + "const": "cx.grossNegligence:1", + "$comment": "{\"permission\":\"The Data Provider's liability is limited to intent and gross negligence. The same applies in regard to the Data Provider's liability for its legal representatives, employees and authorised representatives.\"}" + }, + "SlightNegligenceRightOperand": { + "type": "string", + "const": "cx.slightNegligence:1", + "$comment": "{\"permission\":\"The liability of the Data Provider in cases of ordinary negligence shall be limited to x, as individually agreed by the Parties in the referenced bilateral agreement (cx-policy:ContractReference). The foregoing limitation of liability shall not apply in the event of mandatory statutory liability (in particular under the German Product Liability Act), nor in the event of the assumption of a guarantee or for any culpably caused bodily injury.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/membership-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/membership-constraint-schema.json new file mode 100644 index 0000000000..9ab7a38f7b --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/membership-constraint-schema.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "MembershipConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/MembershipConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/membership-constraint-schema.json", + "definitions": { + "MembershipConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "Membership" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "type": "string", + "const": "active" + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/precedence-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/precedence-constraint-schema.json new file mode 100644 index 0000000000..5ffa8aed55 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/precedence-constraint-schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "PrecedenceConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/PrecedenceConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/precedence-constraint-schema.json", + "definitions": { + "PrecedenceConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "Precedence" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "oneOf": [ + {"$ref": "#/definitions/PrecedenceContractReferenceRightOperand"}, + {"$ref": "#/definitions/PrecedenceRcAgreementRightOperand"} + ] + } + }, + "additionalProperties": false + }, + "PrecedenceContractReferenceRightOperand": { + "type": "string", + "const": "cx.precedence.contractReference:1", + "$comment": "{\"permission\":\"The Parties are free to agree on additional provisions in a separately referenced contract, in addition to the arrangements made via the Registered Connector (RC) Process (referenced in leftOperand: contractReference). In the event of any conflict between the provisions agreed via the RC Process and those of the referenced contract, the provisions of the contract shall take precedence.\"}" + }, + "PrecedenceRcAgreementRightOperand": { + "type": "string", + "const": "cx.precedence.rcAgreement:1", + "$comment": "{\"permission\":\"The Parties are free to agree on additional provisions in a referenced contract (as referenced in leftOperand: contractReference) in addition to the arrangements agreed via the Registered Connector (RC) Process. In such case, the provisions agreed via the RC Process shall take precedence over the provisions of the referenced contract.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/schema/atomic-constraint-schemas.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/schema/atomic-constraint-schemas.json new file mode 100644 index 0000000000..5166dd49cb --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/schema/atomic-constraint-schemas.json @@ -0,0 +1,174 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "AtomicCatenaXConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/AtomicCatenaXConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/schema/atomic-constraint-schemas.json", + "definitions": { + "AtomicCatenaXConstraint": { + "anyOf": [ + { + "$ref": "#/definitions/AtomicObligationConstraint" + }, + { + "$ref": "#/definitions/AtomicPermissionConstraint" + }, + { + "$ref": "#/definitions/AtomicProhibitionConstraint" + } + ] + }, + "AtomicPermissionConstraint": { + "allOf": [ + { + "$ref": "https://w3id.org/dspace/2025/1/negotiation/contract-schema.json#/definitions/AtomicConstraint" + }, + { + "anyOf": [ + { + "$ref": "#/definitions/AtomicAccessPermissionConstraint" + }, + { + "$ref": "#/definitions/AtomicUsagePermissionConstraint" + } + ] + } + ] + }, + "AtomicAccessPermissionConstraint": { + "anyOf": [ + { + "$ref": "https://w3id.org/catenax/2025/9/policy/framework-agreement-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/membership-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/business-partner-number-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/business-partner-group-constraint-schema.json" + } + ] + }, + "AtomicUsagePermissionConstraint": { + "anyOf": [ + { + "$ref": "https://w3id.org/catenax/2025/9/policy/framework-agreement-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/membership-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/usage-purpose-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/contract-reference-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/affiliates-region-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/affiliates-bpnl-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/data-frequency-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/version-changes-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/contract-termination-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/confidential-information-measures-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/confidential-information-sharing-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/exclusive-usage-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/warranty-constraint-schema.json" + }, + { + "oneOf": [ + { + "$ref": "https://w3id.org/catenax/2025/9/policy/warranty-duration-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/warranty-definition-constraint-schema.json" + } + ] + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/liability-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/jurisdiction-location-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/jurisdiction-location-reference-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/precedence-constraint-schema.json" + }, + { + "oneOf": [ + { + "$ref": "https://w3id.org/catenax/2025/9/policy/data-usage-end-duration-days-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/data-usage-end-date-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/data-usage-end-definition-constraint-schema.json" + } + ] + } + ] + }, + "AtomicObligationConstraint": { + "allOf": [ + { + "$ref": "https://w3id.org/dspace/2025/1/negotiation/contract-schema.json#/definitions/AtomicConstraint" + }, + { + "oneOf": [ + { + "$ref": "https://w3id.org/catenax/2025/9/policy/data-provisioning-end-duration-days-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/data-provisioning-end-date-constraint-schema.json" + } + ] + } + ] + }, + "AtomicProhibitionConstraint": { + "allOf": [ + { + "$ref": "https://w3id.org/dspace/2025/1/negotiation/contract-schema.json#/definitions/AtomicConstraint" + }, + { + "anyOf": [ + { + "$ref": "https://w3id.org/catenax/2025/9/policy/affiliates-region-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/affiliates-bpnl-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/usage-restriction-constraint-schema.json" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/schema/context-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/schema/context-schema.json new file mode 100644 index 0000000000..b77fb468f5 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/schema/context-schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "ContextSchema", + "type": "array", + "items": { + "type": "string" + }, + "allOf": [ + { + "$ref": "#/definitions/ContextSchema" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/schema/context-schema.json", + "definitions": { + "ContextSchema": { + "type": "array", + "items": { + "type": "string", + "items": { + "type": "string" + } + }, + "contains": { + "const": "https://w3id.org/catenax/2025/9/policy/context.jsonld" + } + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/schema/policy-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/schema/policy-schema.json new file mode 100644 index 0000000000..87ac1f8f3d --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/schema/policy-schema.json @@ -0,0 +1,300 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "CatenaXPolicySchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/CatenaXPolicy" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/schema/policy-schema.json", + "definitions": { + "CatenaXPolicy": { + "allOf": [ + { + "$ref": "https://w3id.org/dspace/2025/1/negotiation/contract-schema.json#/definitions/PolicyClass" + }, + { + "anyOf": [ + { + "$ref": "#/definitions/AccessPolicy" + }, + { + "$ref": "#/definitions/UsagePolicy" + } + ] + } + ] + }, + "AccessPolicy": { + "type": "object", + "properties": { + "permission": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "https://w3id.org/dspace/2025/1/negotiation/contract-schema.json#/definitions/Rule" + }, + { + "properties": { + "action": { + "type": "string", + "const": "access" + }, + "constraint": { + "type": "array", + "items": { + "$ref": "#/definitions/AccessPermissionConstraint" + }, + "minItems": 1, + "maxItems": 1 + } + } + } + ] + }, + "minItems": 1, + "maxItems": 1 + }, + "prohibition": { + "type": "array", + "maxItems": 0 + }, + "obligation": { + "type": "array", + "maxItems": 0 + } + } + }, + "AccessPermissionConstraint": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/AccessPermissionLogicalConstraint" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/schema/atomic-constraint-schemas.json#/definitions/AtomicAccessPermissionConstraint" + } + ] + }, + "AccessPermissionLogicalConstraint": { + "type": "object", + "properties": { + "and": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/AccessPermissionConstraint" + } + } + }, + "required": [ + "and" + ] + }, + "UsagePolicy": { + "type": "object", + "properties": { + "permission": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "https://w3id.org/dspace/2025/1/negotiation/contract-schema.json#/definitions/Rule" + }, + { + "properties": { + "action": { + "type": "string", + "const": "use" + }, + "constraint": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/definitions/UsagePermissionConstraint" + } + } + }, + "required": [ + "constraint" + ] + } + ] + }, + "minItems": 1, + "maxItems": 1 + }, + "prohibition": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "https://w3id.org/dspace/2025/1/negotiation/contract-schema.json#/definitions/Rule" + }, + { + "properties": { + "action": { + "type": "string", + "const": "use" + }, + "constraint": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "#/definitions/UsageProhibitionConstraint" + } + } + }, + "required": [ + "constraint" + ] + } + ] + }, + "minItems": 1, + "maxItems": 1 + }, + "obligation": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "https://w3id.org/dspace/2025/1/negotiation/contract-schema.json#/definitions/Rule" + }, + { + "properties": { + "action": { + "type": "string", + "const": "use" + }, + "constraint": { + "type": "array", + "minItems": 1, + "maxItems": 1, + "items": { + "$ref": "https://w3id.org/catenax/2025/9/policy/schema/atomic-constraint-schemas.json#/definitions/AtomicObligationConstraint" + } + } + }, + "required": [ + "constraint" + ] + } + ] + }, + "minItems": 1, + "maxItems": 1 + } + }, + "anyOf": [ + { + "required": [ + "permission" + ] + }, + { + "required": [ + "prohibition" + ] + }, + { + "required": [ + "obligation" + ] + } + ] + }, + "UsagePermissionConstraint": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/UsagePermissionLogicalConstraint" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/schema/atomic-constraint-schemas.json#/definitions/AtomicUsagePermissionConstraint" + } + ] + }, + "UsagePermissionLogicalConstraint": { + "type": "object", + "properties": { + "and": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/UsagePermissionConstraint" + }, + "allOf": [ + { + "description": "Rule 1: Maximum ONE Warranty constraint allowed", + "contains": { + "anyOf": [ + { + "$ref": "https://w3id.org/catenax/2025/9/policy/warranty-duration-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/warranty-definition-constraint-schema.json" + } + ] + }, + "maxContains": 1, + "minContains": 0 + }, + { + "description": "Rule 2: Maximum ONE Data Usage End constraint allowed", + "contains": { + "anyOf": [ + { + "$ref": "https://w3id.org/catenax/2025/9/policy/data-usage-end-duration-days-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/data-usage-end-date-constraint-schema.json" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/data-usage-end-definition-constraint-schema.json" + } + ] + }, + "maxContains": 1, + "minContains": 0 + } + ] + } + }, + "required": [ + "and" + ] + }, + "UsageProhibitionConstraint": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/UsageProhibitionLogicalConstraint" + }, + { + "$ref": "https://w3id.org/catenax/2025/9/policy/schema/atomic-constraint-schemas.json#/definitions/AtomicProhibitionConstraint" + } + ] + }, + "UsageProhibitionLogicalConstraint": { + "type": "object", + "properties": { + "and": { + "type": "array", + "items": { + "$ref": "#/definitions/UsageProhibitionConstraint" + } + } + }, + "required": [ + "and" + ] + } + } +} diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/usage-purpose-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/usage-purpose-constraint-schema.json new file mode 100644 index 0000000000..375bdc1a29 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/usage-purpose-constraint-schema.json @@ -0,0 +1,237 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "UsagePurposeConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/UsagePurposeConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/usage-purpose-constraint-schema.json", + "definitions": { + "UsagePurposeConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "UsagePurpose" + }, + "operator": { + "type": "string", + "const": "isAnyOf" + }, + "rightOperand": { + "type":"array", + "minItems": 1, + "items": { + "anyOf": [ + {"$ref": "#/definitions/CoreLegalRequirementForThirdPartyRightOperand"}, + {"$ref": "#/definitions/CoreIndustrycoreRightOperand"}, + {"$ref": "#/definitions/CoreQualityNotificationsRightOperand"}, + {"$ref": "#/definitions/CoreDigitalTwinRegistry"}, + {"$ref": "#/definitions/PcfBaseRightOperand"}, + {"$ref": "#/definitions/QualityBaseRightOperand"}, + {"$ref": "#/definitions/DcmBaseRightOperand"}, + {"$ref": "#/definitions/PurisBaseRightOperand"}, + {"$ref": "#/definitions/CircularDppRightOperand"}, + {"$ref": "#/definitions/CircularSmcRightOperand"}, + {"$ref": "#/definitions/CircularMarketplaceRightOperand"}, + {"$ref": "#/definitions/CircularMaterialaccountingRightOperand"}, + {"$ref": "#/definitions/BpdmGateUploadRightOperand"}, + {"$ref": "#/definitions/BpdmGateDownloadRightOperand"}, + {"$ref": "#/definitions/BpdmPoolRightOperand"}, + {"$ref": "#/definitions/BpdmVasDataqualityUploadRightOperand"}, + {"$ref": "#/definitions/BpdmVasDataqualityDownloadRightOperand"}, + {"$ref": "#/definitions/BpdmVasCountryrisk"}, + {"$ref": "#/definitions/BpdmVasBdvUploadRightOperand"}, + {"$ref": "#/definitions/BpdmVasFpdUploadRightOperand"}, + {"$ref": "#/definitions/BpdmVasFpdDownloadRightOperand"}, + {"$ref": "#/definitions/BpdmVasSwdUploadRightOperand"}, + {"$ref": "#/definitions/BpdmVasSwdDownloadRightOperand"}, + {"$ref": "#/definitions/BpdmVasNpsUploadRightOperand"}, + {"$ref": "#/definitions/BpdmVasNpsDownloadRightOperand"}, + {"$ref": "#/definitions/CcmBaseRightOperand"}, + {"$ref": "#/definitions/BpdmPoolAllRightOperand"}, + {"$ref": "#/definitions/LogisticsBaseRightOperand"}, + {"$ref": "#/definitions/EngineeringBaseRightOperand"}, + {"$ref": "#/definitions/MaterialaccountingBaseRightOperand"}, + {"$ref": "#/definitions/EsdscomBaseRightOperand"}, + {"$ref": "#/definitions/EcuBaseRightOperand"}, + {"$ref": "#/definitions/UsagePurposeIndividualRightOperand"} + ] + } + } + }, + "additionalProperties": false + }, + "CoreLegalRequirementForThirdPartyRightOperand": { + "type": "string", + "const": "cx.core.legalRequirementForThirdparty:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: facilitating compliance with mandatory regulatory requirements for tracking and reporting battery cells, modules & high-voltage batteries.\",\"additionalInformation\":\"Typically used for: Traction Battery Code\"}" + }, + "CoreIndustrycoreRightOperand": { + "type": "string", + "const": "cx.core.industrycore:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: Establishing a digital representation of the automotive supply chain to enable a component specific data exchange.\",\"additionalInformation\":\"Typically used for: SerialPart, Batch, JustInSequencePart,SingleLevelBomAsBuilt, PartAsPlanned, SingleLevelBomAsPlanned, PartSiteInformationAsPlanned, UniqueIDPushAPI\"}" + }, + "CoreQualityNotificationsRightOperand": { + "type": "string", + "const": "cx.core.qualityNotifications:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: quality analyses to identify and select affected components and to send quality notifications to affected customers or suppliers.\",\"additionalInformation\":\"Typically used for: Notification API\"}" + }, + "CoreDigitalTwinRegistry": { + "type": "string", + "const": "cx.core.digitalTwinRegistry:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: Identifying data offers of submodels within the Catena-X ecosystem.\",\"additionalInformation\":\"Typically used for: Digital Twin Registry Asset\"}" + }, + "PcfBaseRightOperand": { + "type": "string", + "const": "cx.pcf.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) sending and receiving product-specific CO2 data and related functionalities such as (but not limited to) certificate exchange and notifications, (ii) conducting plausibility checks and validation measures, (iii) calculating aggregated PCFs of Data Consumer (including calculations operated by a technical service provider that (a) is certified for Catena-X, (b) is not authorized to evaluate data beyond such calculation and (c) provides calculations exclusively for Data Consumer's own purposes.\",\"additionalInformation\":\"Typically used for: PCF Model, PCF Exchange API\"}" + }, + "QualityBaseRightOperand": { + "type": "string", + "const": "cx.quality.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) early identification of anomalies in the use of the product, (ii) collaborative root-cause analysis of a problem / error and determining corrective action, (iii) component tracing to optimize technical actions (in combination with use case Traceability), (iv) confirming corrective action, (v) preventive field observation to detect anomalies, (vi) processing notifications of quality alerts (supply chain bottom-up) and quality investigations (supply chain top-down) (possibly in combination with the use case Traceability).\",\"additionalInformation\":\"Typically used for: Fleet Vehicles, Quality Task, QualityTaskAttachment, PartsAnalysis, ManufacturedPartsQInformation, FleetDiagnosticData, FleetClaim\"}" + }, + "DcmBaseRightOperand": { + "type": "string", + "const": "cx.dcm.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) sending and receiving product-specific demand and capacity data, as well as the associated product functionalities, (ii) early identification of imbalances resulting from demand and capacity comparison, (iii) sending and receiving messages and notifications related to imbalances and to exchanged demand and capacity data, (iv) initiate a collaborative approach to solve imbalances.\",\"additionalInformation\":\"Typically used for: Material Demand, WeekBasedCapacityGroup, IdBasedRequestForUpdate, IdBasedComment\"}" + }, + "PurisBaseRightOperand": { + "type": "string", + "const": "cx.puris.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: optimizing processes, which includes, without limitation, regular exchange of data to prevent and/or solve shortages in the supply chain, conducting plausibility checks against other sources and/or collecting information to facilitate sound decision making, all of the above in the context of predictive unit real-time information relating to production and/or logistics.\",\"additionalInformation\":\"Typically used for: Item Stock, Short-Term Material Demand, Planned Production Output, Delivery Information\"}" + }, + "CircularDppRightOperand": { + "type": "string", + "const": "cx.circular.dpp:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in accordance to those applicable public legal regulation directly requiring digital product passports or affecting the contents or handling of digital product passports.\",\"additionalInformation\":\"Typically used for: Digital Product Pass, Battery Pass\"}" + }, + "CircularSmcRightOperand": { + "type": "string", + "const": "cx.circular.smc:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data about secondary material content (SMC) in line with the following purpose: optimize the SMC-usage.\",\"additionalInformation\":\"Typically used for: SMC-Calculated, SMC-Verifiable\"}" + }, + "CircularMarketplaceRightOperand": { + "type": "string", + "const": "cx.circular.marketplace:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: buy, sell and/or procure parts and materials.\",\"additionalInformation\":\"Typically used for: Market Place Offer\"}" + }, + "CircularMaterialaccountingRightOperand": { + "type": "string", + "const": "cx.circular.materialaccounting:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) display, process, analysis, correlate, modify and amend data, (ii) for (e.g. enablement of) chain of custody processes and commercial transaction related thereto and allocation of material to parts to the supply chain.\"}" + }, + "BpdmGateUploadRightOperand": { + "type": "string", + "const": "cx.bpdm.gate.upload:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: verifying, curating and enriching the Data to create a record of basic information about all entities with a BPN in the Catena-X data space accessible to all participants (Golden Record) and for early warning services (Value Added Services, VASs).\",\"additionalInformation\":\"Typically used for: Gate Data Model\"}" + }, + "BpdmGateDownloadRightOperand": { + "type": "string", + "const": "cx.bpdm.gate.download:1", + "$comment": "{\"permission\":\"The Data Consumer may use the basic information about entities with a BPN in the Catena-X data space provided by the Core Service B Provider for any of the following purposes: (i) identifying counterparty, (ii) usage in Value Added Services (VASs).\",\"additionalInformation\":\"Typically used for: Gate Data Model\"}" + }, + "BpdmPoolRightOperand": { + "type": "string", + "const": "cx.bpdm.pool:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) identifying participants within the Catena-X data space, (ii) identifying counterparty (iii), usage in information processes, (iv) invitation of additional participants into the Catena-X data space, (v) usage in Value Added Services (VASs), \",\"additionalInformation\":\"Typically used for: Pool Data Models\"}" + }, + "BpdmVasCountryrisk": { + "type": "string", + "const": "cx.bpdm.vas.countryrisk:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: screening participants's business data to identify risks when collaborating with a new/existing business partner according to official or company-specific country risk assessments.\",\"additionalInformation\":\"Typically used for: Country Risk Data Model, Gate Data Model, Pool Data Models\"}" + }, + "BpdmVasDataqualityUploadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.dataquality.upload:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) assessing Data Provider's data quality, (ii) creating benchmarks for future screenings of other participants' data to fulfill the goals of the Data Quality Dashboard application.\",\"additionalInformation\":\"Typically used for: Business Partner Data Model\"}" + }, + "BpdmVasDataqualityDownloadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.dataquality.download:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: Assessing quality of own data.\",\"additionalInformation\":\"Typically used for: Data Quality Dashboard Data Model\"}" + }, + "BpdmVasBdvUploadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.bdv.upload:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: screening relevant Data Provider's submitted bank data to verify Data Provider's bank data.\",\"additionalInformation\":\"Typically used for: Gate Data Model, Bank Data Verification Data Model\"}" + }, + "BpdmVasBdvDownloadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.bdv.download:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: verifying Data Consumer's submitted bank data.\",\"additionalInformation\":\"Typically used for: Bank Data Verification Data Model\"}" + }, + "BpdmVasFpdUploadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.fpd.upload:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: screening Data Provider's submitted business partner data to assess occurrence of fraud.\",\"additionalInformation\":\"Typically used for: Fraud Prevention Data Model\"}" + }, + "BpdmVasFpdDownloadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.fpd.download:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: Assessing fraud risks in transactions with another participant.\",\"additionalInformation\":\"Typically used for: Fraud Prevention Data Model\"}" + }, + "BpdmVasSwdUploadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.swd.upload:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: screening Data Provider's submitted beneficial ownership data to assess trade compliance.\",\"additionalInformation\":\"Typically used for: Gate Data Model\"}" + }, + "BpdmVasSwdDownloadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.swd.download:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: assessing trade sanction risks in transactions with another participant.\",\"additionalInformation\":\"Typically used for: Sanction Party Watch List Dashboard Data Model\"}" + }, + "BpdmVasNpsUploadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.nps.upload:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: verifying Data Provider's submitted Business Partner Data against Natural Person data entries.\",\"additionalInformation\":\"Typically used for: Gate Data Model\"}" + }, + "BpdmVasNpsDownloadRightOperand": { + "type": "string", + "const": "cx.bpdm.vas.nps.download:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data in line with the following purpose: verifying its submitted Business Partner Data.\",\"additionalInformation\":\"Typically used for: Natural Person Screening Data Model\"}" + }, + "CcmBaseRightOperand": { + "type": "string", + "const": "cx.ccm.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the exchanged business partner certificates in line with the following purpose: Verification and validation of the existence of a certification.\",\"additionalInformation\":\"Typically used for: Business Partner Certificate\"}" + }, + "BpdmPoolAllRightOperand": { + "type": "string", + "const": "cx.bpdm.poolAll:1", + "$comment": "{\"permission\":\"The Data Consumer may use the basic information about all entities with a BPN in the Catena-X data space provided by the Core Service B Provider for any of the following purposes: (i) identifying internal counterparties, (ii) usage in internal information processes, (iii) usage in Value Added Services (VAS).\",\"additionalInformation\":\"Typically used for: Pool Data Model\"}" + }, + "LogisticsBaseRightOperand": { + "type": "string", + "const": "cx.logistics.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the logistic Data for any of the following purposes: (i) tracking of load carriers, reusable boxes and any packaging, (ii) early identification of delay or damages on transport, and (iii) identification of affected part instances or batch instances on respective transport units.\",\"additionalInformation\":\"Typically used for: asset_tracker_links, batch, global_transport_label, sensor_data, packing_list, serial_part\"}" + }, + "EngineeringBaseRightOperand": { + "type": "string", + "const": "cx.engineering.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) [joint] development of products (e.g., 3D Designs, Simulations) in accordance with the provisions of the ContractReference with the Data Provider, (ii) [joint] development of products (e.g., 3D Designs, Simulations) in accordance with the provisions of the ContractReference, including integrating directed parts of third party manufacturers, as agreed with the Data Provider; the Data Consumer may share the Data with the respective third party manufacturer to the extent required; this relates in particular to such Data that the third party supplier requires, in order to facilitate the final assembly of the directed parts into the product as to be developed by the Data Provider, (iii) regulatory compliance (e.g., material information in master data for secondar material content checks), (iv) mock-up and integration (e.g., collision checks in 3D), (v) versioning & release notifications of products (e.g., new product version that shall be used in a new product generation), (vi) interface alignments (e.g., between interacting systems on physical, logical and functional level). The Data Consumer is prohibited to use the Data for reverse engineering, e.g., by using material classifications for redeveloping and building the respective Data Provider's product as such.\",\"additionalInformation\":\"Typically used for: requirement, digital engineering master data\"}" + }, + "MaterialaccountingBaseRightOperand": { + "type": "string", + "const": "cx.materialaccounting.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) creating material balances along all stages of the reverse value chain, (ii) accounting of secondary material flows and calculating secondary material content, (iii) verifying the fulfillment of regulatory (open or closed loop) secondary material quotas and fulfilling related legal reporting obligations. \",\"additionalInformation\":\"Typically used for: VehicleInformation, WasteCode, RecyclingBatch, Material, RecyclingInformation, Composition\"}" + }, + "EsdscomBaseRightOperand": { + "type": "string", + "const": "cx.esdscom.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) conducting plausibility checks and validation measures, (ii) inhouse processing in data management systems and data bases (e.g., in occupational health and environment management systems, inhouse substance databases), (iii) reporting, registration, and notification duties, (iv) fulfilling import/export requirements of chemicals, (v) dangerous goods classification, (vi) any other not listed data usage required, in order to meet related legal requirements.\",\"additionalInformation\":\"Typically used for: eSDScom\"}" + }, + "EcuBaseRightOperand": { + "type": "string", + "const": "cx.ecu.base:1", + "$comment": "{\"permission\":\"The Data Consumer may use the Data for any of the following purposes: (i) issuing digital certificates for a control unit, e.g., for creating a digital identity for the control unit, (ii) registering a digital device identity already created by the Data Provider, (iii) enabling debug functions on the control unit, (iv) performing updates or customization of the control unit, e.g., overwriting an initial key/password with a value generated by the Data Consumer, (v) dangerous goods classification, (vi) integrating cryptographic keys into products or infrastructure of the Data Consumer to enable secure communication with the control unit, (vii) tracking the software status and changes to a control unit.\",\"additionalInformation\":\"Typically used for: CryptoMaterial,SoftwareInformation\"}" + }, + "UsagePurposeIndividualRightOperand": { + "type": "string", + "$comment": "{\"permission\":\"Data Provider and Data Consumer are free to individually agree this certain purpose of use. The legal meaning of this certain purpose need to be agreed individually between Data Provider and Data Consumer.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/usage-restriction-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/usage-restriction-constraint-schema.json new file mode 100644 index 0000000000..f65e13451b --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/usage-restriction-constraint-schema.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "UsageRestrictionConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/UsageRestrictionConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/usage-restriction-constraint-schema.json", + "definitions": { + "UsageRestrictionConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "UsageRestriction" + }, + "operator": { + "type": "string", + "const": "isAllOf" + }, + "rightOperand": { + "type": "array", + "minItems": 1, + "items": { + "anyOf": [ + {"$ref": "#/definitions/ThirdPartyForbiddenRightOperand"}, + {"$ref": "#/definitions/ManipulationForbiddenRightOperand"}, + {"$ref": "#/definitions/DerivationsForbiddenRightOperand"}, + {"$ref": "#/definitions/ExtraordinaryAnalyticsForbiddenRightOperand"}, + {"$ref": "#/definitions/DataProviderRemovalForbiddenRightOperand"} + ] + } + } + }, + "additionalProperties": false + }, + "ThirdPartyForbiddenRightOperand": { + "type": "string", + "const": "cx.thirdParty.forbidden:1", + "$comment": "{\"prohibition\":\"The Data Consumer is prohibited from making the Data available to third parties, either temporarily or permanently, from reproducing, distributing, or publicly displaying the Data; this also applies insofar as the data constitutes essential or non-essential parts of a database (Section 87a German Act on Copyright and Related Rights (UrhG)), unless otherwise individually agreed between the Parties in the usage purposes for a specific use case (cx-policy:UsagePurpose) or in the referenced bilateral contract (cx-policy:ContractReference). The Data Provider’s right to reproduce the Data provided by it for internal purposes remains unaffected.\"}" + }, + "ManipulationForbiddenRightOperand": { + "type": "string", + "const": "cx.manipulation.forbidden:1", + "$comment": "{\"prohibition\":\"The Data Consumer is prohibited from modifying the Data, separating the associated metadata from the Data, or otherwise altering it, or from attempting any of the aforementioned actions or permitting a third party to perform such actions, unless otherwise individually agreed between the parties in the usage purposes for a specific use case (cx-policy:UsagePurpose) or in the referenced bilateral contract (cx-policy:ContractReference). The Data Provider’s right to reproduce the Data provided by it for internal purposes remains unaffected.\"}" + }, + "DerivationsForbiddenRightOperand": { + "type": "string", + "const": "cx.derivations.forbidden:1", + "$comment": "{\"prohibition\":\"The Data Consumer is prohibited from creating derivative works from the Data (including making substantial changes to any databases provided within the meaning of Section 87a para. 1 sentence 2 German Act on Copyright and Related Rights (UrhG), unless otherwise individually agreed between the Parties in the usage purposes for a specific Use Case (cx-policy:UsagePurpose) or in the referenced bilateral contract (cx-policy:ContractReference).\"}" + }, + "ExtraordinaryAnalyticsForbiddenRightOperand": { + "type": "string", + "const": "cx.extraordinaryAnalytics.forbidden:1", + "$comment": "{\"prohibition\":\"The Data Consumer is prohibited, insofar as the Data constitutes insignificant parts of a database within the meaning of Section 87b para. 1 sentence 1 German Act on Copyright and Related Rights (UrhG), from repeatedly and systematically carrying out actions that conflict with the normal evaluation of a database or unreasonably impair the legitimate interests of the Data Provider (Section 87b para. 1 sentence 2 UrhG), unless otherwise individually agreed between the Parties in the usage purposes for a specific Use Case (cx-policy:UsagePurpose) or in the referenced bilateral contract (cx-policy:ContractReference).\"}" + }, + "DataProviderRemovalForbiddenRightOperand": { + "type": "string", + "const": "cx.dataProviderRemoval.forbidden:1", + "$comment": "{\"prohibition\":\"The Data Consumer is prohibited from removing the company identifiers and/or other references to the Data Provider contained in the Data and/or the associated metadata or databases, unless otherwise individually agreed between the Parties in the usage purposes for a specific Use Case (cx-policy:UsagePurpose) or in the referenced bilateral contract (cx-policy:ContractReference).\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/version-changes-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/version-changes-constraint-schema.json new file mode 100644 index 0000000000..55adc88ddb --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/version-changes-constraint-schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "VersionChangesConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/VersionChangesConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/version-changes-constraint-schema.json", + "definitions": { + "VersionChangesConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "VersionChanges" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "oneOf": [ + {"$ref": "#/definitions/VersionChangesMinorRightOperand"}, + {"$ref": "#/definitions/VersionChangesMajorRightOperand"} + ] + } + }, + "additionalProperties": false + }, + "VersionChangesMinorRightOperand": { + "type": "string", + "const": "cx.versionChanges.minor:1", + "$comment": "{\"permission\":\"The Agreement concluded via the Registered Connector (RC) covers only the exchange of Data effected on the basis of the major API version (as specified in the dataset attribute base-URL), the major Asset-Version (dataset attribute cx-common:version), and/or the major Aspect Model Version as applicable at the time of concluding the Agreement. The Agreement concluded via the RC must be renegotiated in the event of any change to at least one of these major versions. The Agreement does not need to be renegotiated in the event of any change to one of these minor versions. The definitions of major and minor version is based on Semantic Versioning (https://semver.org/).\"}" + }, + "VersionChangesMajorRightOperand": { + "type": "string", + "const": "cx.versionChanges.major:1", + "$comment": "{\"permission\":\"The Agreement concluded via the Registered Connector (RC) covers any data exchange, which shall be effected via the API specified by the dataset attribute dct:type, irrespective of whether the API version applicable at the time of contract conclusion (as part of the dataset attribute base-URL), the Asset Version (dataset attribute cx-common:version), or the Aspect Model version changes.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/warranty-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/warranty-constraint-schema.json new file mode 100644 index 0000000000..2a5be72e5b --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/warranty-constraint-schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "WarrantyConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/WarrantyConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/warranty-constraint-schema.json", + "definitions": { + "WarrantyConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "Warranty" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "oneOf": [ + {"$ref": "#/definitions/WarrantyNoneRightOperand"}, + {"$ref": "#/definitions/WarrantyContractReferenceRightOperand"}, + {"$ref": "#/definitions/WarrantyDataQualityIssuesRightOperand"} + ] + } + }, + "additionalProperties": false + }, + "WarrantyNoneRightOperand": { + "type": "string", + "const": "cx.warranty.none:1", + "$comment": "{\"permission\":\"The provision of the Data is made with the exclusion of any warranty for material defects and defects in title, unless the Data Provider fraudulently conceals such a defect.\"}" + }, + "WarrantyContractReferenceRightOperand": { + "type": "string", + "const": "cx.warranty.contractReference:1", + "$comment": "{\"permission\":\"The provision of the Data is subject to the warranty for material defects and defects in title as agreed in the description of the subject matter of performance according to the referenced contract (leftOperand: ContractReference).\"}" + }, + "WarrantyDataQualityIssuesRightOperand": { + "type": "string", + "const": "cx.warranty.dataQualityIssues:1", + "$comment": "{\"permission\":\"In the event of a material deviation from the contractually agreed data quality, the Data Provider shall be entitled to (i) demand the rectification of defects by provision of Data in the quality owed, (ii) terminate the contract in the event of failure to remedy the defect, and (iii) claim damages in accordance with the liability provisions set out below.\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/warranty-definition-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/warranty-definition-constraint-schema.json new file mode 100644 index 0000000000..ca22d9cdf2 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/warranty-definition-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "WarrantyConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/WarrantyConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/warranty-definition-constraint-schema.json", + "definitions": { + "WarrantyConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "WarrantyDefinition" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/WarrantyContractEndDateRightOperand" + } + }, + "additionalProperties": false + }, + "WarrantyContractEndDateRightOperand": { + "type": "string", + "const": "cx.warranty.contractEndDate:1", + "$comment": "{\"permission\":\"The provision of the Data is subject to a warranty for material defects and defects in title until the end of the Agreement concluded via the Registered Connector (RC).\"}" + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/warranty-duration-constraint-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/warranty-duration-constraint-schema.json new file mode 100644 index 0000000000..639dbfa2b5 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/cx-policy/warranty-duration-constraint-schema.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "WarrantyDurationConstraintSchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/WarrantyDurationConstraint" + } + ], + "$id": "https://w3id.org/catenax/2025/9/policy/warranty-duration-constraint-schema.json", + "definitions": { + "WarrantyDurationConstraint": { + "type": "object", + "properties": { + "leftOperand": { + "type": "string", + "const": "WarrantyDurationMonths" + }, + "operator": { + "type": "string", + "const": "eq" + }, + "rightOperand": { + "$ref": "#/definitions/WarrantyDurationMonthsRightOperand" + } + }, + "additionalProperties": false + }, + "WarrantyDurationMonthsRightOperand": { + "type": "string", + "pattern": "^[1-9][0-9]*$", + "$comment": "{\"permission\":\"The provision of the Data is subject to a warranty for material and legal defects for a period specified herein in months.\"}" + } + } +} diff --git a/edc-extensions/cx-policy/src/main/resources/jsonschema/dspace/contract-schema.json b/edc-extensions/cx-policy/src/main/resources/jsonschema/dspace/contract-schema.json new file mode 100644 index 0000000000..90b43c4078 --- /dev/null +++ b/edc-extensions/cx-policy/src/main/resources/jsonschema/dspace/contract-schema.json @@ -0,0 +1,374 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "PolicySchema", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/Policy" + } + ], + "$id": "https://w3id.org/dspace/2025/1/negotiation/contract-schema.json", + "definitions": { + "Policy": { + "oneOf": [ + { + "$ref": "#/definitions/MessageOffer" + }, + { + "$ref": "#/definitions/Offer" + }, + { + "$ref": "#/definitions/Agreement" + } + ] + }, + "PolicyClass": { + "type": "object", + "properties": { + "@id": { + "type": "string" + }, + "profile": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "permission": { + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + }, + "minItems": 1 + }, + "prohibition": { + "type": "array", + "items": { + "$ref": "#/definitions/Prohibition" + }, + "minItems": 1 + }, + "obligation": { + "type": "array", + "items": { + "$ref": "#/definitions/Duty" + }, + "minItems": 1 + } + }, + "required": [ + "@id" + ] + }, + "MessageOffer": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/PolicyClass" + }, + { + "properties": { + "@type": { + "type": "string", + "const": "Offer" + }, + "target": { + "type": "string" + } + } + }, + { + "anyOf": [ + { + "required": [ + "permission" + ] + }, + { + "required": [ + "prohibition" + ] + } + ] + } + ], + "required": [ + "@type" + ] + }, + "Offer": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/PolicyClass" + }, + { + "properties": { + "@type": { + "type": "string", + "const": "Offer" + } + } + }, + { + "anyOf": [ + { + "required": [ + "permission" + ] + }, + { + "required": [ + "prohibition" + ] + } + ] + } + ], + "not": { + "required": [ + "target" + ] + } + }, + "Agreement": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/PolicyClass" + }, + { + "properties": { + "@type": { + "type": "string", + "const": "Agreement" + }, + "target": { + "type": "string" + }, + "assigner": { + "type": "string" + }, + "assignee": { + "type": "string" + }, + "timestamp": { + "type": "string", + "pattern": "-?([1-9][0-9]{3,}|0[0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?|(24:00:00(\\.0+)?))(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?" + } + } + }, + { + "anyOf": [ + { + "required": [ + "permission" + ] + }, + { + "required": [ + "prohibition" + ] + } + ] + } + ], + "required": [ + "@type", + "target", + "assignee", + "assigner" + ] + }, + "Rule": { + "type": "object", + "properties": { + "action": { + "$ref": "#/definitions/Action" + }, + "constraint": { + "type": "array", + "items": { + "$ref": "#/definitions/Constraint" + } + } + }, + "required": [ + "action" + ] + }, + "Permission": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/Rule" + } + ] + }, + "Prohibition": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/Rule" + } + ] + }, + "Duty": { + "type": "object", + "allOf": [ + { + "properties": { + "action": { + "$ref": "#/definitions/Action" + }, + "constraint": { + "type": "array", + "items": { + "$ref": "#/definitions/Constraint" + } + } + }, + "required": [ + "action" + ] + } + ] + }, + "Action": { + "type": "string" + }, + "Constraint": { + "type": "object", + "oneOf": [ + { + "$ref": "#/definitions/LogicalConstraint" + }, + { + "$ref": "#/definitions/AtomicConstraint" + } + ] + }, + "LogicalConstraint": { + "type": "object", + "properties": { + "and": { + "type": "array", + "items": { + "$ref": "#/definitions/Constraint" + } + }, + "andSequence": { + "type": "array", + "items": { + "$ref": "#/definitions/Constraint" + } + }, + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/Constraint" + } + }, + "xone": { + "type": "array", + "items": { + "$ref": "#/definitions/Constraint" + } + } + }, + "oneOf": [ + { + "required": [ + "and" + ] + }, + { + "required": [ + "andSequence" + ] + }, + { + "required": [ + "or" + ] + }, + { + "required": [ + "xone" + ] + } + ] + }, + "AtomicConstraint": { + "type": "object", + "properties": { + "rightOperand": { + "$ref": "#/definitions/RightOperand" + }, + "leftOperand": { + "$ref": "#/definitions/LeftOperand" + }, + "operator": { + "$ref": "#/definitions/Operator" + } + }, + "required": [ + "rightOperand", + "operator", + "leftOperand" + ] + }, + "Operator": { + "type": "string", + "enum": [ + "eq", + "gt", + "gteq", + "lteq", + "hasPart", + "isA", + "isAllOf", + "isAnyOf", + "isNoneOf", + "isPartOf", + "lt", + "term-lteq", + "neq" + ] + }, + "RightOperand": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object" + }, + { + "type": "array" + } + ] + }, + "LeftOperand": { + "type": "string" + }, + "Reference": { + "type": "object", + "properties": { + "@id": { + "type": "string" + } + }, + "required": [ + "@id" + ] + } + } +} \ No newline at end of file diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/businesspartner/BusinessPartnerNumberConstraintFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/businesspartner/BusinessPartnerNumberConstraintFunctionTest.java index 62fa58321c..c673978610 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/businesspartner/BusinessPartnerNumberConstraintFunctionTest.java +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/businesspartner/BusinessPartnerNumberConstraintFunctionTest.java @@ -20,44 +20,67 @@ package org.eclipse.tractusx.edc.policy.cx.businesspartner; import jakarta.json.Json; +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; import org.eclipse.edc.participant.spi.ParticipantAgent; import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext; import org.eclipse.edc.policy.model.Operator; import org.eclipse.tractusx.edc.policy.cx.TestParticipantAgentPolicyContext; -import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_CREDENTIAL_NS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class BusinessPartnerNumberConstraintFunctionTest { + private static final String TEST_BPN = "BPNL00000000001A"; + private final ParticipantAgent participantAgent = mock(); - private final BdrsClient bdrsClient = mock(); - private final BusinessPartnerNumberConstraintFunction function = new BusinessPartnerNumberConstraintFunction<>(bdrsClient); + private final BusinessPartnerNumberConstraintFunction function = new BusinessPartnerNumberConstraintFunction<>(); private final ParticipantAgentPolicyContext context = new TestParticipantAgentPolicyContext(participantAgent); @Test void evaluate() { - var identity = "BPNL00000000001A"; - var bpn1 = Map.of("string", identity); + var bpn1 = Map.of("string", TEST_BPN); var rightValue = List.of(Map.of("@value", bpn1)); - when(participantAgent.getIdentity()).thenReturn(identity); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", bpnVcList(TEST_BPN))); assertThat(function.evaluate(Operator.IS_ANY_OF, rightValue, null, context)).isTrue(); } @Test - void evaluate_withDid() { - var bpn1 = Map.of("string", "BPNL00000000001A"); + void evaluate_whenIsNoneOf_andBpnNotInList() { + var bpn1 = Map.of("string", "BPNL00000000002B"); var rightValue = List.of(Map.of("@value", bpn1)); - var identity = "did:example:some-identity"; - when(participantAgent.getIdentity()).thenReturn(identity); - when(bdrsClient.resolveBpn(identity)).thenReturn("BPNL00000000001A"); - assertThat(function.evaluate(Operator.IS_ANY_OF, rightValue, null, context)).isTrue(); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", bpnVcList(TEST_BPN))); + assertThat(function.evaluate(Operator.IS_NONE_OF, rightValue, null, context)).isTrue(); + } + + @Test + void evaluate_whenIsNoneOf_andBpnInList_returnsFalse() { + var bpn1 = Map.of("string", TEST_BPN); + var rightValue = List.of(Map.of("@value", bpn1)); + when(participantAgent.getClaims()).thenReturn(Map.of("vc", bpnVcList(TEST_BPN))); + assertThat(function.evaluate(Operator.IS_NONE_OF, rightValue, null, context)).isFalse(); + } + + @Test + void evaluate_whenNoVcClaim_returnsFalse() { + when(participantAgent.getClaims()).thenReturn(Map.of()); + var rightValue = List.of(Map.of("@value", Map.of("string", TEST_BPN))); + assertThat(function.evaluate(Operator.IS_ANY_OF, rightValue, null, context)).isFalse(); + assertThat(context.getProblems()).isNotEmpty(); + } + + @Test + void evaluate_whenNoBpnCredential_returnsFalse() { + when(participantAgent.getClaims()).thenReturn(Map.of("vc", List.of())); + var rightValue = List.of(Map.of("@value", Map.of("string", TEST_BPN))); + assertThat(function.evaluate(Operator.IS_ANY_OF, rightValue, null, context)).isFalse(); + assertThat(context.getProblems()).isNotEmpty(); } @Test @@ -123,4 +146,17 @@ void validate_whenInvalidBpnlFormat_thenFailure() { assertThat(result.failed()).isTrue(); assertThat(result.getFailureDetail()).contains("Invalid right-operand: "); } + + private List bpnVcList(String bpn) { + return List.of(VerifiableCredential.Builder.newInstance() + .types(List.of(CX_CREDENTIAL_NS + "VerifiableCredential", CX_CREDENTIAL_NS + "BpnCredential")) + .id("test-vc-id") + .issuer(new org.eclipse.edc.iam.verifiablecredentials.spi.model.Issuer("did:web:issuer", Map.of())) + .issuanceDate(java.time.Instant.now()) + .credentialSubject(org.eclipse.edc.iam.verifiablecredentials.spi.model.CredentialSubject.Builder.newInstance() + .id("subject-id") + .claim(CX_CREDENTIAL_NS + "bpn", bpn) + .build()) + .build()); + } } diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/usage/UsagePurposeConstraintFunctionTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/usage/UsagePurposeConstraintFunctionTest.java index 51e74035d4..a99ba64812 100644 --- a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/usage/UsagePurposeConstraintFunctionTest.java +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/usage/UsagePurposeConstraintFunctionTest.java @@ -19,18 +19,15 @@ package org.eclipse.tractusx.edc.policy.cx.usage; -import jakarta.json.Json; import org.eclipse.edc.participant.spi.ParticipantAgent; import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext; import org.eclipse.edc.policy.model.Operator; import org.eclipse.tractusx.edc.policy.cx.TestParticipantAgentPolicyContext; import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.Map; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.mockito.Mockito.mock; class UsagePurposeConstraintFunctionTest { @@ -39,29 +36,21 @@ class UsagePurposeConstraintFunctionTest { private final UsagePurposeConstraintFunction function = new UsagePurposeConstraintFunction<>(); private final ParticipantAgentPolicyContext context = new TestParticipantAgentPolicyContext(participantAgent); - @Test - void evaluate() { - assertThat(function.evaluate(Operator.IS_ANY_OF, List.of("cx.core.legalRequirementForThirdparty:1", "cx.core.industrycore:1"), null, context)).isTrue(); - } - - @Test - void validate_whenOperatorAndRightOperandAreValid_thenSuccess() { - var legalRequirementForThirdparty = Json.createValue("cx.core.legalRequirementForThirdparty:1"); - var industrycore = Json.createValue("cx.core.industrycore:1"); - var rightValue = List.of(Map.of("@value", legalRequirementForThirdparty), Map.of("@value", industrycore)); - var result = function.validate(Operator.IS_ANY_OF, rightValue, null); - assertThat(result).isSucceeded(); + @ParameterizedTest + @EnumSource(value = Operator.class, names = "IS_ANY_OF", mode = EnumSource.Mode.EXCLUDE) + void validate_shouldReturnFalse_forAllOperatorsExceptIsAnyOf(Operator operator) { + var result = function.validate(operator, "someValue", null); + assertThat(result.succeeded()).isFalse(); } @Test - void validate_whenInvalidOperator_thenFailure() { - var result = function.validate(Operator.EQ, List.of("cx.core.legalRequirementForThirdparty:1", "cx.core.industrycore:1"), null); - assertThat(result).isFailed().detail().contains("Invalid operator"); + void validate_shouldReturnTrue_forIsAnyOfOperator() { + var result = function.validate(Operator.IS_ANY_OF, "someValue", null); + assertThat(result.succeeded()).isTrue(); } @Test - void validate_whenInvalidValue_thenFailure() { - var result = function.validate(Operator.IS_ANY_OF, List.of("BPNL00000000001A"), null); - assertThat(result).isFailed().detail().contains("Invalid right-operand: "); + void evaluate_shouldReturnTrue_withAnyRightValue() { + assertThat(function.evaluate(Operator.IS_ANY_OF, "anyValue", null, context)).isTrue(); } } diff --git a/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/validator/CxJsonSchemaPolicyDefinitionValidatorTest.java b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/validator/CxJsonSchemaPolicyDefinitionValidatorTest.java new file mode 100644 index 0000000000..9445f710ed --- /dev/null +++ b/edc-extensions/cx-policy/src/test/java/org/eclipse/tractusx/edc/policy/cx/validator/CxJsonSchemaPolicyDefinitionValidatorTest.java @@ -0,0 +1,98 @@ +/******************************************************************************** + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.validator; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonValue; +import org.eclipse.tractusx.edc.policy.cx.validator.jsonschema.CxJsonSchemaPolicyDefinitionValidator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; + +class CxJsonSchemaPolicyDefinitionValidatorTest { + + private final CxJsonSchemaPolicyDefinitionValidator validator = new CxJsonSchemaPolicyDefinitionValidator(); + + @Test + void shouldValidateJsonSchema_whenValidPolicyDefinition() { + var policy = Json.createObjectBuilder() + .add(TYPE, "Set") + .add(ID, "id") + .build(); + var policyDefinition = policyDefinition(policy); + + var result = validator.validate(policyDefinition); + + assertThat(result.succeeded()).isTrue(); + } + + @Test + void shouldResolveReferencedSchemas_whenPolicyContainsConstraints() { + var constraint = Json.createObjectBuilder() + .add("leftOperand", "Membership") + .add("operator", "eq") + .add("rightOperand", "active"); + var permission = Json.createObjectBuilder() + .add("action", "access") + .add("constraint", Json.createArrayBuilder().add(constraint)); + var policy = Json.createObjectBuilder() + .add(TYPE, "Set") + .add(ID, "id") + .add("permission", Json.createArrayBuilder().add(permission)) + .build(); + var policyDefinition = policyDefinition(policy); + + assertThatNoException().isThrownBy(() -> validator.validate(policyDefinition)); + } + + @Test + void shouldReturnFailure_whenPolicyMissing() { + var policyDefinition = policyDefinition(null); + + var result = validator.validate(policyDefinition); + + assertThat(result.failed()).isTrue(); + assertThat(result.getFailureMessages()).anyMatch(message -> message.contains("Attribute 'policy' is missing from PolicyDefinition.")); + } + + @Test + void shouldReturnFailure_whenPolicyNotAnObject() { + var policyDefinition = policyDefinition(Json.createArrayBuilder().build()); + + var result = validator.validate(policyDefinition); + + assertThat(result.failed()).isTrue(); + assertThat(result.getFailureMessages()).anyMatch(message -> message.contains("Attribute 'policy' is not a valid JSON object.")); + } + + private JsonObject policyDefinition(JsonValue policy) { + var builder = Json.createObjectBuilder() + .add(TYPE, "PolicyDefinition"); + if (policy != null) { + builder.add("policy", policy); + } + + return builder.build(); + } +} diff --git a/edc-extensions/data-flow-properties-provider/build.gradle.kts b/edc-extensions/data-flow-properties-provider/build.gradle.kts index 1bb930aa9f..63ec8a74bc 100644 --- a/edc-extensions/data-flow-properties-provider/build.gradle.kts +++ b/edc-extensions/data-flow-properties-provider/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(libs.edc.spi.transfer) implementation(project(":spi:core-spi")) implementation(project(":spi:bdrs-client-spi")) + implementation(project(":edc-extensions:agreements-bpns:bpns-evaluation-spi")) testImplementation(libs.edc.junit) } diff --git a/edc-extensions/data-flow-properties-provider/src/main/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProvider.java b/edc-extensions/data-flow-properties-provider/src/main/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProvider.java index 1a107c460f..46a76bab38 100644 --- a/edc-extensions/data-flow-properties-provider/src/main/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProvider.java +++ b/edc-extensions/data-flow-properties-provider/src/main/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProvider.java @@ -1,6 +1,7 @@ /******************************************************************************** * Copyright (c) 2025 Cofinity-X GmbH * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -17,48 +18,83 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +// Some portions generated by claude-sonnet-4.6 package org.eclipse.tractusx.edc.flow; import org.eclipse.edc.connector.controlplane.transfer.spi.flow.DataFlowPropertiesProvider; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.response.StatusResult; import org.eclipse.edc.spi.types.domain.transfer.DataFlowStartMessage; +import org.eclipse.tractusx.edc.agreements.bpns.spi.store.AgreementsBpnsStore; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import java.util.Map; import static org.eclipse.edc.spi.response.ResponseStatus.FATAL_ERROR; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.AUDIENCE_PROPERTY; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.BPN_PREFIX; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.BPN_PROPERTY; import static org.eclipse.tractusx.edc.spi.identity.mapper.BdrsConstants.DID_PREFIX; + /** * Extension of {@link DataFlowPropertiesProvider} which provides additional properties in the {@link DataFlowStartMessage} - * like the DID of the counter-party BPN. The resolution is made with the {@link BdrsClient} + * such as the consumer DID (audience) and the consumer BPN. Both values are resolved locally without a BDRS call: + * the DID comes directly from {@code policy.getAssignee()} and the BPN is looked up from {@link AgreementsBpnsStore}. */ public class TxDataFlowPropertiesProvider implements DataFlowPropertiesProvider { + private final AgreementsBpnsStore agreementsBpnsStore; private final BdrsClient bdrsClient; + private final Monitor monitor; - public TxDataFlowPropertiesProvider(BdrsClient bdrsClient) { + public TxDataFlowPropertiesProvider(AgreementsBpnsStore agreementsBpnsStore, BdrsClient bdrsClient, Monitor monitor) { + this.agreementsBpnsStore = agreementsBpnsStore; this.bdrsClient = bdrsClient; + this.monitor = monitor.withPrefix(getClass().getSimpleName()); } @Override public StatusResult> propertiesFor(TransferProcess transferProcess, Policy policy) { - try { - if (policy.getAssignee().startsWith(DID_PREFIX)) { - return StatusResult.success(Map.of(AUDIENCE_PROPERTY, policy.getAssignee())); + + if (isDsp2025(policy)) { + var entry = agreementsBpnsStore.findByAgreementId(transferProcess.getContractId()); + if (entry == null) { + return StatusResult.failure(FATAL_ERROR, + "No BPN entry found for agreement %s".formatted(transferProcess.getContractId())); } - - var did = bdrsClient.resolveDid(policy.getAssignee()); + return StatusResult.success(Map.of( + AUDIENCE_PROPERTY, policy.getAssignee(), + BPN_PROPERTY, entry.getConsumerBpn() + )); + } else if (isDsp08(policy)) { + var bpn = policy.getAssignee(); + var did = bdrsClient.resolveDid(bpn); if (did == null) { - return StatusResult.failure(FATAL_ERROR, "Failed to fetch did for BPN %s".formatted(policy.getAssignee())); + return StatusResult.failure(FATAL_ERROR, "Could not resolve DID for BPN '%s'".formatted(bpn)); } - return StatusResult.success(Map.of(AUDIENCE_PROPERTY, did)); - } catch (Exception e) { - return StatusResult.failure(FATAL_ERROR, "Failed to fetch did for BPN %s: %s".formatted(policy.getAssignee(), e.getMessage())); + return StatusResult.success(Map.of( + AUDIENCE_PROPERTY, did, + BPN_PROPERTY, bpn + )); + } else { + monitor.warning("Policy's Assignee is neither a did nor a BPN and was '%s'. This is only ok for test scenarios like the DSP TCK.".formatted(policy.getAssignee())); + return StatusResult.success(Map.of( + AUDIENCE_PROPERTY, policy.getAssignee(), + BPN_PROPERTY, policy.getAssignee() + )); } + + } + + private boolean isDsp08(Policy policy) { + return policy.getAssignee().startsWith(BPN_PREFIX); + } + + private boolean isDsp2025(Policy policy) { + return policy.getAssignee().startsWith(DID_PREFIX); } } diff --git a/edc-extensions/data-flow-properties-provider/src/main/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderExtension.java b/edc-extensions/data-flow-properties-provider/src/main/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderExtension.java index 8493b1a128..48014e7401 100644 --- a/edc-extensions/data-flow-properties-provider/src/main/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderExtension.java +++ b/edc-extensions/data-flow-properties-provider/src/main/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderExtension.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -23,7 +24,9 @@ import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provider; +import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.tractusx.edc.agreements.bpns.spi.store.AgreementsBpnsStore; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import static org.eclipse.tractusx.edc.flow.TxDataFlowPropertiesProviderExtension.NAME; @@ -33,11 +36,17 @@ public class TxDataFlowPropertiesProviderExtension implements ServiceExtension { protected static final String NAME = "Tractus-X Data flow properties provider extension"; + @Inject + private AgreementsBpnsStore agreementsBpnsStore; + @Inject private BdrsClient bdrsClient; + @Inject + private Monitor monitor; + @Provider public DataFlowPropertiesProvider dataFlowPropertiesProvider() { - return new TxDataFlowPropertiesProvider(bdrsClient); + return new TxDataFlowPropertiesProvider(agreementsBpnsStore, bdrsClient, monitor); } } diff --git a/edc-extensions/data-flow-properties-provider/src/test/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderExtensionTest.java b/edc-extensions/data-flow-properties-provider/src/test/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderExtensionTest.java index 11e095ba6e..7a0d4bc292 100644 --- a/edc-extensions/data-flow-properties-provider/src/test/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderExtensionTest.java +++ b/edc-extensions/data-flow-properties-provider/src/test/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderExtensionTest.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -22,6 +23,8 @@ import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.agreements.bpns.spi.store.AgreementsBpnsStore; +import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -37,10 +40,12 @@ class TxDataFlowPropertiesProviderExtensionTest { @BeforeEach void setup(ServiceExtensionContext context) { context.registerService(Monitor.class, monitor); + context.registerService(AgreementsBpnsStore.class, mock()); + context.registerService(BdrsClient.class, mock()); } @Test void createMapper(TxDataFlowPropertiesProviderExtension extension) { assertThat(extension.dataFlowPropertiesProvider()).isInstanceOf(TxDataFlowPropertiesProvider.class); } -} \ No newline at end of file +} diff --git a/edc-extensions/data-flow-properties-provider/src/test/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderTest.java b/edc-extensions/data-flow-properties-provider/src/test/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderTest.java index c49df85655..ed990e6a26 100644 --- a/edc-extensions/data-flow-properties-provider/src/test/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderTest.java +++ b/edc-extensions/data-flow-properties-provider/src/test/java/org/eclipse/tractusx/edc/flow/TxDataFlowPropertiesProviderTest.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -16,78 +17,102 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +// Some portions generated by claude-sonnet-4.6 package org.eclipse.tractusx.edc.flow; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.tractusx.edc.agreements.bpns.spi.store.AgreementsBpnsStore; +import org.eclipse.tractusx.edc.agreements.bpns.spi.types.AgreementsBpnsEntry; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.AUDIENCE_PROPERTY; -import static org.mockito.ArgumentMatchers.any; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.BPN_PROPERTY; +import static org.mockito.Mockito.RETURNS_SELF; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class TxDataFlowPropertiesProviderTest { - private final BdrsClient bdrsClient = mock(); - private final TxDataFlowPropertiesProvider provider = new TxDataFlowPropertiesProvider(bdrsClient); + private static final String CONTRACT_ID = "test-contract-id"; + private static final String CONSUMER_DID = "did:web:consumer"; + private static final String CONSUMER_BPN = "BPNL000000000001"; + + private final AgreementsBpnsStore store = mock(); + private final BdrsClient bdrs = mock(); + private final TxDataFlowPropertiesProvider provider = new TxDataFlowPropertiesProvider(store, bdrs, mock(Monitor.class, RETURNS_SELF)); @Test - void shouldReturnProperties_whenIdIsBpn() { - var bpn = "bpn"; - var did = "did"; - when(bdrsClient.resolveDid(bpn)).thenReturn(did); + void shouldReturnProperties_whenAssigneeIsDidAndEntryFound() { + var entry = AgreementsBpnsEntry.Builder.newInstance() + .withAgreementId(CONTRACT_ID) + .withProviderBpn("BPNL000000000002") + .withConsumerBpn(CONSUMER_BPN) + .build(); + when(store.findByAgreementId(CONTRACT_ID)).thenReturn(entry); - var result = provider.propertiesFor(createTransferProcess(), createPolicy(bpn)); + var result = provider.propertiesFor(createTransferProcess(CONTRACT_ID), createPolicy(CONSUMER_DID)); assertThat(result).isSucceeded().satisfies(properties -> { - assertThat(properties).containsEntry(AUDIENCE_PROPERTY, did); + assertThat(properties).containsEntry(AUDIENCE_PROPERTY, CONSUMER_DID); + assertThat(properties).containsEntry(BPN_PROPERTY, CONSUMER_BPN); }); } - + @Test - void shouldReturnProperties_whenIdIsDid() { - var did = "did:abc"; - - var result = provider.propertiesFor(createTransferProcess(), createPolicy(did)); - + void shouldReturnFatalError_whenAssigneeIsDidAndEntryNotFound() { + when(store.findByAgreementId(CONTRACT_ID)).thenReturn(null); + + var result = provider.propertiesFor(createTransferProcess(CONTRACT_ID), createPolicy(CONSUMER_DID)); + + assertThat(result).isFailed() + .detail().isEqualTo("No BPN entry found for agreement %s".formatted(CONTRACT_ID)); + } + + @Test + void shouldReturnProperties_whenAssigneeIsBpn() { + when(bdrs.resolveDid(CONSUMER_BPN)).thenReturn(CONSUMER_DID); + + var result = provider.propertiesFor(createTransferProcess(CONTRACT_ID), createPolicy(CONSUMER_BPN)); + assertThat(result).isSucceeded().satisfies(properties -> { - assertThat(properties).containsEntry(AUDIENCE_PROPERTY, did); + assertThat(properties).containsEntry(AUDIENCE_PROPERTY, CONSUMER_DID); + assertThat(properties).containsEntry(BPN_PROPERTY, CONSUMER_BPN); }); - verify(bdrsClient, never()).resolveDid(any()); } @Test - void shouldReturnFailure_whenResolutionFails() { - var bpn = "bpn"; - when(bdrsClient.resolveDid(bpn)).thenReturn(null); + void shouldReturnFatalError_whenAssigneeIsBpnAndDidNotResolved() { + when(bdrs.resolveDid(CONSUMER_BPN)).thenReturn(null); - var result = provider.propertiesFor(createTransferProcess(), createPolicy(bpn)); + var result = provider.propertiesFor(createTransferProcess(CONTRACT_ID), createPolicy(CONSUMER_BPN)); - assertThat(result).isFailed().detail().isEqualTo("Failed to fetch did for BPN %s".formatted(bpn)); + assertThat(result).isFailed() + .detail().isEqualTo("Could not resolve DID for BPN '%s'".formatted(CONSUMER_BPN)); } @Test - void shouldReturnFailure_whenResolutionThrowsException() { - var bpn = "bpn"; - when(bdrsClient.resolveDid(bpn)).thenThrow(new RuntimeException("exception")); + void shouldReturnProperties_whenAssigneeIsNeitherDidNorBpn() { + var assignee = "some-unknown-identifier"; - var result = provider.propertiesFor(createTransferProcess(), createPolicy(bpn)); + var result = provider.propertiesFor(createTransferProcess(CONTRACT_ID), createPolicy(assignee)); - assertThat(result).isFailed().detail().contains("exception"); + assertThat(result).isSucceeded().satisfies(properties -> { + assertThat(properties).containsEntry(AUDIENCE_PROPERTY, assignee); + assertThat(properties).containsEntry(BPN_PROPERTY, assignee); + }); } - private TransferProcess createTransferProcess() { - return TransferProcess.Builder.newInstance().build(); + private TransferProcess createTransferProcess(String contractId) { + return TransferProcess.Builder.newInstance().contractId(contractId).build(); } - private Policy createPolicy(String bpn) { - return Policy.Builder.newInstance().assignee(bpn).build(); + private Policy createPolicy(String assignee) { + return Policy.Builder.newInstance().assignee(assignee).build(); } } 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/DataFlowApiController.java similarity index 75% rename from edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiController.java rename to edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/DataFlowApiController.java index 4d82dbc279..2da4d4aba1 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/DataFlowApiController.java @@ -17,25 +17,20 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.dataflow.api.v4alpha; +package org.eclipse.tractusx.edc.dataflow.api; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; import org.eclipse.edc.connector.dataplane.spi.DataFlow; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.tractusx.edc.spi.dataflow.DataFlowService; -import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static java.lang.String.format; import static org.eclipse.edc.web.spi.exception.ServiceResultHandler.exceptionMapper; -@Consumes(APPLICATION_JSON) -@Produces(APPLICATION_JSON) -@Path("/v4alpha/dataflows") -public class DataFlowApiController implements DataFlowApi { +/** + * Holds the version-independent data flow API logic. Version specific controllers (e.g. v3, v4alpha) + * delegate the actual work to this controller. + */ +public class DataFlowApiController { private final Monitor monitor; private final DataFlowService service; @@ -45,13 +40,10 @@ public DataFlowApiController(Monitor monitor, DataFlowService service) { this.service = service; } - @POST - @Path("/{id}/trigger") - @Override - public void triggerDataTransferV4Alpha(@PathParam("id") String id) { + public void trigger(String id) { service.trigger(id) .onSuccess(v -> monitor.debug(format("Trigger requested for dataflow with ID %s", id))) .orElseThrow(exceptionMapper(DataFlow.class, id)); } - } + diff --git a/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/DataFlowApiExtension.java b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/DataFlowApiExtension.java index bb85a56bd5..19fc2b98e1 100644 --- a/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/DataFlowApiExtension.java +++ b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/DataFlowApiExtension.java @@ -1,5 +1,6 @@ /******************************************************************************** * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -26,7 +27,8 @@ 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.tractusx.edc.dataflow.api.v4alpha.DataFlowApiController; +import org.eclipse.tractusx.edc.dataflow.api.v3.DataFlowV3ApiController; +import org.eclipse.tractusx.edc.dataflow.api.v4alpha.DataFlowV4AlphaApiController; import org.eclipse.tractusx.edc.spi.dataflow.DataFlowService; @Extension(DataFlowApiExtension.NAME) @@ -48,7 +50,9 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - webService.registerResource(ApiContext.MANAGEMENT, new DataFlowApiController(monitor, dataFlowService)); + var dataFlowApiController = new DataFlowApiController(monitor, dataFlowService); + webService.registerResource(ApiContext.MANAGEMENT, new DataFlowV4AlphaApiController(dataFlowApiController, monitor)); + webService.registerResource(ApiContext.MANAGEMENT, new DataFlowV3ApiController(dataFlowApiController)); } } diff --git a/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v3/DataFlowV3Api.java b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v3/DataFlowV3Api.java new file mode 100644 index 0000000000..bdcefc398f --- /dev/null +++ b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v3/DataFlowV3Api.java @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2025 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.dataflow.api.v3; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.links.Link; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.eclipse.edc.api.model.ApiCoreSchema; + +@OpenAPIDefinition(info = @Info(version = "v3")) +@Tag(name = "DataFlow API V3") +public interface DataFlowV3Api { + + String ASYNC_WARNING = "Due to the asynchronous nature of transfers, a successful response only indicates that the request was successfully received."; + + @Operation(description = "Requests the trigger of a data transfer. " + ASYNC_WARNING, + responses = { + @ApiResponse(responseCode = "204", description = "Request was sucessfully received", links = @Link(name = "poll-state", operationId = "triggerDataTransferV3")), + @ApiResponse(responseCode = "400", description = "Request was malformed", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))), + @ApiResponse(responseCode = "404", description = "Data flow with the given ID does not exist", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))), + @ApiResponse(responseCode = "409", description = "Data flow is not in a required state", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiCoreSchema.ApiErrorDetailSchema.class)))) + } + ) + void triggerDataTransferV3(String id); + +} diff --git a/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v3/DataFlowV3ApiController.java b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v3/DataFlowV3ApiController.java new file mode 100644 index 0000000000..7545d457ba --- /dev/null +++ b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v3/DataFlowV3ApiController.java @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 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.dataflow.api.v3; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import org.eclipse.tractusx.edc.dataflow.api.DataFlowApiController; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; + +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/v3/dataflows") +public class DataFlowV3ApiController implements DataFlowV3Api { + + private final DataFlowApiController delegate; + + public DataFlowV3ApiController(DataFlowApiController delegate) { + this.delegate = delegate; + } + + @POST + @Path("/{id}/trigger") + @Override + public void triggerDataTransferV3(@PathParam("id") String id) { + delegate.trigger(id); + } + +} diff --git a/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApi.java b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowV4AlphaApi.java similarity index 97% rename from edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApi.java rename to edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowV4AlphaApi.java index 13098dc740..1c52271ee0 100644 --- a/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApi.java +++ b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowV4AlphaApi.java @@ -30,9 +30,10 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.eclipse.edc.api.model.ApiCoreSchema; +@Deprecated(since = "0.13.0") @OpenAPIDefinition(info = @Info(version = "v4alpha")) @Tag(name = "DataFlow API V4Alpha") -public interface DataFlowApi { +public interface DataFlowV4AlphaApi { String ASYNC_WARNING = "Due to the asynchronous nature of transfers, a successful response only indicates that the request was successfully received."; diff --git a/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowV4AlphaApiController.java b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowV4AlphaApiController.java new file mode 100644 index 0000000000..4b17333e48 --- /dev/null +++ b/edc-extensions/dataplane/dataflow/dataflow-api/src/main/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowV4AlphaApiController.java @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 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.dataflow.api.v4alpha; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.tractusx.edc.dataflow.api.DataFlowApiController; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.edc.api.ApiWarnings.deprecationWarning; + +@Deprecated(since = "0.13.0") +@Consumes(APPLICATION_JSON) +@Produces(APPLICATION_JSON) +@Path("/v4alpha/dataflows") +public class DataFlowV4AlphaApiController implements DataFlowV4AlphaApi { + + private final DataFlowApiController delegate; + private final Monitor monitor; + + public DataFlowV4AlphaApiController(DataFlowApiController delegate, Monitor monitor) { + this.delegate = delegate; + this.monitor = monitor; + } + + @POST + @Path("/{id}/trigger") + @Override + public void triggerDataTransferV4Alpha(@PathParam("id") String id) { + monitor.warning(deprecationWarning("/v4alpha", "/v3")); + delegate.trigger(id); + } + +} diff --git a/edc-extensions/dataplane/dataflow/dataflow-api/src/test/java/org/eclipse/tractusx/edc/dataflow/api/DataFlowApiControllerTest.java b/edc-extensions/dataplane/dataflow/dataflow-api/src/test/java/org/eclipse/tractusx/edc/dataflow/api/DataFlowApiControllerTest.java new file mode 100644 index 0000000000..0c2342db24 --- /dev/null +++ b/edc-extensions/dataplane/dataflow/dataflow-api/src/test/java/org/eclipse/tractusx/edc/dataflow/api/DataFlowApiControllerTest.java @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2025 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.dataflow.api; + +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.ServiceResult; +import org.eclipse.edc.web.spi.exception.InvalidRequestException; +import org.eclipse.edc.web.spi.exception.ObjectConflictException; +import org.eclipse.edc.web.spi.exception.ObjectNotFoundException; +import org.eclipse.tractusx.edc.spi.dataflow.DataFlowService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +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 DataFlowApiControllerTest { + + private static final String DATAFLOW_ID = "123"; + + private final Monitor monitor = mock(); + private final DataFlowService service = mock(); + + private DataFlowApiController controller; + + @BeforeEach + void setUp() { + when(monitor.withPrefix(anyString())).thenReturn(monitor); + controller = new DataFlowApiController(monitor, service); + } + + @Test + void trigger_shouldSucceed_whenServiceReturnsSuccess() { + when(service.trigger(DATAFLOW_ID)).thenReturn(ServiceResult.success()); + + assertThatNoException().isThrownBy(() -> controller.trigger(DATAFLOW_ID)); + + verify(service).trigger(DATAFLOW_ID); + } + + @Test + void trigger_shouldThrowObjectNotFound_whenServiceReturnsNotFound() { + when(service.trigger(DATAFLOW_ID)).thenReturn(ServiceResult.notFound("not-found")); + + assertThatThrownBy(() -> controller.trigger(DATAFLOW_ID)).isInstanceOf(ObjectNotFoundException.class); + } + + @Test + void trigger_shouldThrowInvalidRequest_whenServiceReturnsBadRequest() { + when(service.trigger(DATAFLOW_ID)).thenReturn(ServiceResult.badRequest("bad-request")); + + assertThatThrownBy(() -> controller.trigger(DATAFLOW_ID)).isInstanceOf(InvalidRequestException.class); + } + + @Test + void trigger_shouldThrowObjectConflict_whenServiceReturnsConflict() { + when(service.trigger(DATAFLOW_ID)).thenReturn(ServiceResult.conflict("conflict")); + + assertThatThrownBy(() -> controller.trigger(DATAFLOW_ID)).isInstanceOf(ObjectConflictException.class); + } + +} + 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 deleted file mode 100644 index 7926222676..0000000000 --- a/edc-extensions/dataplane/dataflow/dataflow-api/src/test/java/org/eclipse/tractusx/edc/dataflow/api/v4alpha/DataFlowApiControllerTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2025 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.dataflow.api.v4alpha; - -import io.restassured.specification.RequestSpecification; -import org.eclipse.edc.junit.annotations.ApiTest; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.web.jersey.testfixtures.RestControllerTestBase; -import org.eclipse.tractusx.edc.spi.dataflow.DataFlowService; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -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; - -@ApiTest -class DataFlowApiControllerTest extends RestControllerTestBase { - - private static final String DATAFLOW_ID = "123"; - - private final DataFlowService service = mock(); - - @Override - protected Object controller() { - when(monitor.withPrefix(anyString())).thenReturn(monitor); - return new DataFlowApiController(monitor, service); - } - - private RequestSpecification baseRequest() { - return given() - .baseUri("http://localhost:" + port) - .basePath("/v4alpha/dataflows"); - } - - @Nested - class Trigger { - - @Test - void triggerDataTransfer_shouldReturnNotFound_whenServiceReturnsNotFound() { - when(service.trigger(DATAFLOW_ID)).thenReturn(ServiceResult.notFound("not-found")); - - baseRequest() - .when() - .contentType(JSON) - .post("/{id}/trigger", DATAFLOW_ID) - .then() - .statusCode(404); - } - - @Test - void triggerDataTransfer_shouldReturnBadRequest_whenServiceReturnsBadRequest() { - when(service.trigger(DATAFLOW_ID)).thenReturn(ServiceResult.badRequest("bad-request")); - - baseRequest() - .when() - .contentType(JSON) - .post("/{id}/trigger", DATAFLOW_ID) - .then() - .statusCode(400); - } - - @Test - void triggerDataTransfer_shouldReturnConflict_whenServiceReturnsConflict() { - when(service.trigger(DATAFLOW_ID)).thenReturn(ServiceResult.conflict("conflict")); - - baseRequest() - .when() - .contentType(JSON) - .post("/{id}/trigger", DATAFLOW_ID) - .then() - .statusCode(409); - } - - @Test - void triggerDataTransfer_shouldReturnNoContent_whenServiceReturnsSuccess() { - when(service.trigger(DATAFLOW_ID)).thenReturn(ServiceResult.success()); - - baseRequest() - .when() - .contentType(JSON) - .post("/{id}/trigger", DATAFLOW_ID) - .then() - .log().ifError() - .statusCode(204); - } - - } - -} diff --git a/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java b/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java index 65b987d40e..2ade052d43 100644 --- a/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java +++ b/edc-extensions/dataplane/dataplane-proxy/edc-dataplane-proxy-consumer-api/src/main/java/org/eclipse/tractusx/edc/dataplane/proxy/consumer/api/DataPlaneProxyConsumerApiExtension.java @@ -42,7 +42,6 @@ import java.util.UUID; import java.util.concurrent.ExecutorService; -import static java.util.Optional.ofNullable; import static java.util.concurrent.Executors.newFixedThreadPool; /** @@ -57,15 +56,22 @@ public class DataPlaneProxyConsumerApiExtension implements ServiceExtension { private static final String DEFAULT_PROXY_PATH = "/proxy"; private static final int DEFAULT_THREAD_POOL = 10; - @Setting("Vault alias for the Consumer Proxy API key") - public static final String AUTH_SETTING_CONSUMER_PROXY_APIKEY_ALIAS = "tx.edc.dpf.consumer.proxy.auth.apikey.alias"; - @Setting("API key for the Consumer Proxy API") - public static final String AUTH_SETTING_CONSUMER_PROXY_APIKEY = "tx.edc.dpf.consumer.proxy.auth.apikey"; - - @Setting(value = "Data plane proxy API consumer port", type = "int") private static final String CONSUMER_PORT = "tx.edc.dpf.consumer.proxy.port"; - @Setting(value = "Thread pool size for the consumer data plane proxy gateway", type = "int") private static final String THREAD_POOL_SIZE = "tx.edc.dpf.consumer.proxy.thread.pool"; + private static final String AUTH_SETTING_CONSUMER_PROXY_APIKEY = "tx.edc.dpf.consumer.proxy.auth.apikey"; + private static final String AUTH_SETTING_CONSUMER_PROXY_APIKEY_ALIAS = "tx.edc.dpf.consumer.proxy.auth.apikey.alias"; + + @Setting(key = AUTH_SETTING_CONSUMER_PROXY_APIKEY_ALIAS, description = "Vault alias for the Consumer Proxy API key", required = false) + private String apiKeyAlias; + + @Setting(key = AUTH_SETTING_CONSUMER_PROXY_APIKEY, description = "API key for the Consumer Proxy API", required = false) + private String configuredApiKey; + + @Setting(key = CONSUMER_PORT, description = "Data plane proxy API consumer port", defaultValue = DEFAULT_PROXY_PORT + "") + private int port; + + @Setting(key = THREAD_POOL_SIZE, description = "Thread pool size for the consumer data plane proxy gateway", defaultValue = DEFAULT_THREAD_POOL + "") + private int poolSize; @Configuration private DataPlaneProxyConsumerApiConfiguration apiConfiguration; @@ -95,11 +101,9 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { // when deprecated port will be purged, just assign `apiConfiguration.port()` to `port` - var port = context.getSetting(CONSUMER_PORT, DEFAULT_PROXY_PORT); var portMapping = new PortMapping(PROXY, port, apiConfiguration.path()); portMappingRegistry.register(portMapping); - var poolSize = context.getSetting(THREAD_POOL_SIZE, DEFAULT_THREAD_POOL); executorService = newFixedThreadPool(poolSize); var authenticationService = createAuthenticationService(context); @@ -120,10 +124,14 @@ public void shutdown() { } private AuthenticationService createAuthenticationService(ServiceExtensionContext context) { - - var apiKey = ofNullable(context.getSetting(AUTH_SETTING_CONSUMER_PROXY_APIKEY_ALIAS, null)) - .map(alias -> vault.resolveSecret(alias)) - .orElseGet(() -> context.getSetting(AUTH_SETTING_CONSUMER_PROXY_APIKEY, UUID.randomUUID().toString())); + String apiKey; + if (apiKeyAlias != null) { + apiKey = vault.resolveSecret(apiKeyAlias); + } else if (configuredApiKey != null) { + apiKey = configuredApiKey; + } else { + apiKey = UUID.randomUUID().toString(); + } return new TokenBasedAuthenticationService(context.getMonitor().withPrefix("ConsumerProxyAPI"), apiKey); } diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApi.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApi.java index 6d506b7193..9822c0dd89 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApi.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApi.java @@ -21,12 +21,12 @@ import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.security.SecurityScheme; import io.swagger.v3.oas.annotations.tags.Tag; @@ -43,8 +43,7 @@ public interface TokenRefreshApi { @Operation(description = "Resolves all groups for a particular BPN", - parameters = { @Parameter(name = "grant_type", description = "The grant type. Must be \"refresh_token\""), - @Parameter(name = "refresh_token", description = "The refresh token") }, + requestBody = @RequestBody(description = "Form parameters: grant_type=refresh_token&refresh_token="), responses = { @ApiResponse(responseCode = "200", description = "The access token and refresh token were updated. Expiry should be " + "interpreted as starting from the time of message reception, allowing for some leeway.", @@ -54,5 +53,5 @@ public interface TokenRefreshApi { @ApiResponse(responseCode = "400", description = "Request body was malformed, query parameters were missing, etc.", content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))) }) - TokenResponse refreshToken(String grantType, String refreshToken, String bearerToken); + TokenResponse refreshToken(String grantType, String refreshToken, String bearerToken, String formParameters); } diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApiController.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApiController.java index ce0c468d23..8c2635e828 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApiController.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApiController.java @@ -19,6 +19,7 @@ package org.eclipse.tractusx.edc.dataplane.tokenrefresh.api.v1; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.HeaderParam; import jakarta.ws.rs.POST; @@ -32,12 +33,16 @@ import org.eclipse.tractusx.edc.spi.tokenrefresh.dataplane.DataPlaneTokenRefreshService; import org.eclipse.tractusx.edc.spi.tokenrefresh.dataplane.model.TokenResponse; +import java.util.HashMap; +import java.util.Map; + import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION; @Produces({ MediaType.APPLICATION_JSON }) @Path("/token") public class TokenRefreshApiController implements TokenRefreshApi { - private static final String REFRESH_TOKEN_GRANT = "refresh_token"; + private static final String GRANT_TYPE = "grant_type"; + private static final String REFRESH_TOKEN = "refresh_token"; private final DataPlaneTokenRefreshService tokenRefreshService; public TokenRefreshApiController(DataPlaneTokenRefreshService tokenRefreshService) { @@ -47,20 +52,50 @@ public TokenRefreshApiController(DataPlaneTokenRefreshService tokenRefreshServic @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Override - public TokenResponse refreshToken(@QueryParam("grant_type") String grantType, - @QueryParam("refresh_token") String refreshToken, - @HeaderParam(AUTHORIZATION) String bearerToken) { - if (!REFRESH_TOKEN_GRANT.equals(grantType)) { - throw new InvalidRequestException("Grant type MUST be '%s' but was '%s'".formatted(REFRESH_TOKEN_GRANT, grantType)); + public TokenResponse refreshToken(@QueryParam(GRANT_TYPE) String grantType, + @QueryParam(REFRESH_TOKEN) String refreshToken, + @HeaderParam(AUTHORIZATION) String bearerToken, + @RequestBody() String formParams) { + // TODO: This version still supports the deprecated usage of query parameter to provide the + // grant_type and refresh_token. This is due to backward compatibility to ensure interoperability + // with previous versions. This should be removed in the future, if old connectors are not used + // anymore. + + var paramMap = parseFormParameter(formParams); + if (!paramMap.containsKey(GRANT_TYPE)) { + paramMap.put(GRANT_TYPE, grantType); + } + if (!paramMap.containsKey(REFRESH_TOKEN)) { + paramMap.put(REFRESH_TOKEN, refreshToken); + } + + if (!REFRESH_TOKEN.equals(paramMap.get(GRANT_TYPE))) { + throw new InvalidRequestException("Grant type MUST be '%s' but was '%s'".formatted(REFRESH_TOKEN, paramMap.get(GRANT_TYPE))); } - if (StringUtils.isNullOrBlank(refreshToken)) { + if (StringUtils.isNullOrBlank(paramMap.get(REFRESH_TOKEN))) { throw new InvalidRequestException("Parameter 'refresh_token' cannot be null"); } if (StringUtils.isNullOrBlank(bearerToken)) { throw new AuthenticationFailedException("Authorization header missing"); } - return tokenRefreshService.refreshToken(refreshToken, bearerToken) + return tokenRefreshService.refreshToken(paramMap.get(REFRESH_TOKEN), bearerToken) .orElseThrow(f -> new AuthenticationFailedException(f.getFailureDetail())); } + + private Map parseFormParameter(String formParams) { + var result = new HashMap(); + if (!StringUtils.isNullOrBlank(formParams)) { + var params = formParams.split("&"); + for (String param : params) { + if (!StringUtils.isNullOrBlank(param)) { + var keyValuePair = param.split("="); + if (keyValuePair.length == 2) { + result.put(keyValuePair[0], keyValuePair[1]); + } + } + } + } + return result; + } } diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApiControllerTest.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApiControllerTest.java index d8fc82d13c..bd52dd654e 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApiControllerTest.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-api/src/test/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/api/v1/TokenRefreshApiControllerTest.java @@ -35,6 +35,7 @@ import static io.restassured.RestAssured.given; import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION; +import static java.lang.String.format; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -48,18 +49,17 @@ class TokenRefreshApiControllerTest extends RestControllerTestBase { @Test void refresh_noAuthHeader_expect401() { baseRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", "foo-token") /* missing: .header(AUTHORIZATION, "auth-token") */ .contentType(ContentType.URLENC) + .body("grant_type=refresh_token&refresh_token=foo-token") .then() .statusCode(401); } - @DisplayName("Expect HTTP 200 when the token was successfully refreshed") + @DisplayName("Expect HTTP 200 when the token was successfully refreshed and query params used") @Test - void refresh_expect200() { - when(refreshService.refreshToken(any(), any())).thenReturn(Result.success(new TokenResponse("new-accesstoken", "new-refreshtoken", 3000L, "bearer"))); + void refresh_expect200query() { + when(refreshService.refreshToken(any(), any())).thenReturn(Result.success(new TokenResponse("new-accesstoken", "new-refreshtoken", null, 3000L, "bearer"))); baseRequest() .queryParam("grant_type", "refresh_token") .queryParam("refresh_token", "foo-token") @@ -70,16 +70,28 @@ void refresh_expect200() { .body(Matchers.isA(TokenResponse.class)); } + @DisplayName("Expect HTTP 200 when the token was successfully refreshed and correct body used") + @Test + void refresh_expect200body() { + when(refreshService.refreshToken(any(), any())).thenReturn(Result.success(new TokenResponse("new-accesstoken", "new-refreshtoken", 3000L, null, "bearer"))); + baseRequest() + .header(AUTHORIZATION, "auth-token") + .contentType(ContentType.URLENC) + .body("grant_type=refresh_token&refresh_token=foo-token") + .then() + .statusCode(200) + .body(Matchers.isA(TokenResponse.class)); + } + @DisplayName("Expect HTTP 400 when an invalid grant type was provided") @ParameterizedTest(name = "Invalid grant_type: {0}") @ValueSource(strings = { "REFRESH_TOKEN", "refreshToken", "invalid_grant", "client_credentials", "" }) @NullSource void refresh_invalidGrantType_expect400(String grant) { baseRequest() - .queryParam("grant_type", grant) - .queryParam("refresh_token", "foo-token") .header(AUTHORIZATION, "auth-token") .contentType(ContentType.URLENC) + .body(format("grant_type=%s&refresh_token=foo-token", grant)) .then() .statusCode(400); } @@ -90,10 +102,9 @@ void refresh_invalidGrantType_expect400(String grant) { @EmptySource void refresh_invalidRefreshToken_expect400(String refreshToken) { baseRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) .header(AUTHORIZATION, "auth-token") .contentType(ContentType.URLENC) + .body(format("grant_type=refresh_token&refresh_token=%s", refreshToken)) .then() .statusCode(400); } @@ -102,16 +113,16 @@ void refresh_invalidRefreshToken_expect400(String refreshToken) { @Test void refresh_queryParamsMissing() { baseRequest() - .queryParam("grant_type", "refresh_token") .header(AUTHORIZATION, "auth-token") .contentType(ContentType.URLENC) + .body("grant_type=refresh_token") .then() .statusCode(400); baseRequest() - .queryParam("refresh_token", "foo-token") .header(AUTHORIZATION, "auth-token") .contentType(ContentType.URLENC) + .body("refresh_token=foo-token") .then() .statusCode(400); } @@ -122,10 +133,9 @@ void refresh_tokenInvalid_expect401() { when(refreshService.refreshToken(any(), any())).thenReturn(Result.failure("Invalid auth token")); baseRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", "foo-token") .header(AUTHORIZATION, "auth-token") .contentType(ContentType.URLENC) + .body("grant_type=refresh_token&refresh_token=foo-token") .then() .statusCode(401) .body(containsString("Invalid auth token")); 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 814b5029b5..1c1bf72304 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 @@ -29,7 +29,6 @@ dependencies { implementation(libs.edc.spi.dataplane.dataplane) implementation(libs.edc.spi.identity.did) 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) 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 9fa4feb770..016e6d4307 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 @@ -22,7 +22,7 @@ import org.eclipse.edc.connector.dataplane.spi.iam.DataPlaneAccessTokenService; import org.eclipse.edc.connector.dataplane.spi.store.AccessTokenDataStore; import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; -import org.eclipse.edc.jwt.signer.spi.JwsSignerProvider; +import org.eclipse.edc.jwt.spi.signer.JwsSignerProvider; import org.eclipse.edc.keys.spi.LocalPublicKeyService; import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; import org.eclipse.edc.runtime.metamodel.annotation.Extension; @@ -42,32 +42,48 @@ import java.time.Clock; -import static org.eclipse.tractusx.edc.core.utils.ConfigUtil.missingMandatoryProperty; import static org.eclipse.tractusx.edc.dataplane.tokenrefresh.core.DataPlaneTokenRefreshServiceExtension.NAME; @Extension(value = NAME) public class DataPlaneTokenRefreshServiceExtension implements ServiceExtension { public static final String NAME = "DataPlane Token Refresh Service extension"; - public static final int DEFAULT_TOKEN_EXPIRY_TOLERANCE_SECONDS = 5; - public static final long DEFAULT_TOKEN_EXPIRY_SECONDS = 300L; - @Setting(value = "Token expiry tolerance period in seconds to allow for clock skew", defaultValue = "" + DEFAULT_TOKEN_EXPIRY_TOLERANCE_SECONDS) - public static final String TOKEN_EXPIRY_TOLERANCE_SECONDS_PROPERTY = "tx.edc.dataplane.token.expiry.tolerance"; - @Setting(value = "The HTTP endpoint where clients can request a renewal of their access token for the public dataplane API") - public static final String REFRESH_ENDPOINT_PROPERTY = "tx.edc.dataplane.token.refresh.endpoint"; + private static final int DEFAULT_TOKEN_EXPIRY_TOLERANCE_SECONDS = 5; + private static final long DEFAULT_TOKEN_EXPIRY_SECONDS = 300L; + private static final int DEFAULT_WEB_PORT = 8185; + private static final String DEFAULT_WEB_PATH = "/api/v2/public"; - @Setting(value = "Alias of private key used for signing tokens, retrieved from private key resolver") - public static final String TOKEN_SIGNER_PRIVATE_KEY_ALIAS = "edc.transfer.proxy.token.signer.privatekey.alias"; + private static final String TOKEN_SIGNER_PRIVATE_KEY_ALIAS = "edc.transfer.proxy.token.signer.privatekey.alias"; + private static final String TOKEN_VERIFIER_PUBLIC_KEY_ALIAS = "edc.transfer.proxy.token.verifier.publickey.alias"; + private static final String TOKEN_EXPIRY_TOLERANCE_SECONDS_PROPERTY = "tx.edc.dataplane.token.expiry.tolerance"; + private static final String TOKEN_EXPIRY_SECONDS_PROPERTY = "tx.edc.dataplane.token.expiry"; + private static final String REFRESH_ENDPOINT_PROPERTY = "tx.edc.dataplane.token.refresh.endpoint"; + private static final String WEB_PORT = "web.http.public.port"; + private static final String WEB_PATH = "web.http.public.path"; - @Setting(value = "Alias of public key used for verifying the tokens, retrieved from the vault") - public static final String TOKEN_VERIFIER_PUBLIC_KEY_ALIAS = "edc.transfer.proxy.token.verifier.publickey.alias"; + @Setting(key = REFRESH_ENDPOINT_PROPERTY, description = "The HTTP endpoint where clients can request a renewal of their access token for the public dataplane API", required = false) + private String refreshEndpointConfig; - @Setting(value = "Expiry time of access token in seconds", defaultValue = DEFAULT_TOKEN_EXPIRY_SECONDS + "") - public static final String TOKEN_EXPIRY_SECONDS_PROPERTY = "tx.edc.dataplane.token.expiry"; + @Setting(key = TOKEN_SIGNER_PRIVATE_KEY_ALIAS, description = "Alias of private key used for signing tokens, retrieved from private key resolver") + private String tokenSignerPrivateKeyAlias; - @Setting(value = "DID of this connector", required = true) - private static final String PARTICIPANT_DID_PROPERTY = "edc.iam.issuer.id"; + @Setting(key = TOKEN_VERIFIER_PUBLIC_KEY_ALIAS, description = "Alias of public key used for verifying the tokens, retrieved from the vault") + private String tokenVerifierPublicKeyAlias; + + @Setting(key = TOKEN_EXPIRY_TOLERANCE_SECONDS_PROPERTY, description = "Token expiry tolerance period in seconds to allow for clock skew", defaultValue = DEFAULT_TOKEN_EXPIRY_TOLERANCE_SECONDS + "") + private int tokenExpiryToleranceSeconds; + + @Setting(key = TOKEN_EXPIRY_SECONDS_PROPERTY, description = "Expiry time of access token in seconds", defaultValue = DEFAULT_TOKEN_EXPIRY_SECONDS + "") + private long tokenExpirySeconds; + + @Setting (key = WEB_PORT, defaultValue = DEFAULT_WEB_PORT + "") + private int webPort; + + @Setting (key = WEB_PATH, defaultValue = DEFAULT_WEB_PATH) + private String webPath; + @Inject + private Monitor monitor; @Inject private TokenValidationService tokenValidationService; @Inject @@ -87,7 +103,7 @@ public class DataPlaneTokenRefreshServiceExtension implements ServiceExtension { @Inject private JwsSignerProvider jwsSignerProvider; @Inject - private SingleParticipantContextSupplier singleParticipantContextSupplier; + private SingleParticipantContextSupplier participantContextSupplier; private DataPlaneTokenRefreshServiceImpl tokenRefreshService; @@ -108,47 +124,26 @@ public DataPlaneTokenRefreshService createRefreshTokenService(ServiceExtensionCo return getTokenRefreshService(context); } - private int getExpiryToleranceConfig(ServiceExtensionContext context) { - return context.getSetting(TOKEN_EXPIRY_TOLERANCE_SECONDS_PROPERTY, DEFAULT_TOKEN_EXPIRY_TOLERANCE_SECONDS); - } - @NotNull private DataPlaneTokenRefreshServiceImpl getTokenRefreshService(ServiceExtensionContext context) { if (tokenRefreshService == null) { var monitor = context.getMonitor().withPrefix("DataPlane Token Refresh"); - var expiryTolerance = getExpiryToleranceConfig(context); - var refreshEndpoint = getRefreshEndpointConfig(context, monitor); - var tokenExpiry = getExpiryConfig(context); + var refreshEndpoint = getRefreshEndpointConfig(monitor); monitor.debug("Token refresh endpoint: %s".formatted(refreshEndpoint)); - monitor.debug("Token refresh time tolerance: %d s".formatted(expiryTolerance)); + monitor.debug("Token refresh time tolerance: %d s".formatted(tokenExpiryToleranceSeconds)); tokenRefreshService = new DataPlaneTokenRefreshServiceImpl(clock, tokenValidationService, didPkResolver, localPublicKeyService, accessTokenDataStore, new JwtGenerationService(jwsSignerProvider), - () -> 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); + () -> tokenSignerPrivateKeyAlias, context.getMonitor(), refreshEndpoint, tokenExpiryToleranceSeconds, tokenExpirySeconds, + () -> tokenVerifierPublicKeyAlias, vault, typeManager.getMapper(), participantContextSupplier); } return tokenRefreshService; } - private Long getExpiryConfig(ServiceExtensionContext context) { - return context.getSetting(TOKEN_EXPIRY_SECONDS_PROPERTY, DEFAULT_TOKEN_EXPIRY_SECONDS); - } - - private String getRefreshEndpointConfig(ServiceExtensionContext context, Monitor monitor) { - var refreshEndpoint = context.getSetting(REFRESH_ENDPOINT_PROPERTY, null); + private String getRefreshEndpointConfig(Monitor monitor) { + var refreshEndpoint = refreshEndpointConfig; if (refreshEndpoint == null) { - var port = context.getConfig().getInteger("web.http.public.port", 8185); - var path = context.getConfig().getString("web.http.public.path", "/api/v2/public"); - refreshEndpoint = "http://%s:%d%s".formatted(hostname.get(), port, path); + refreshEndpoint = "http://%s:%d%s".formatted(hostname.get(), webPort, webPath); monitor.warning("Config property '%s' was not specified, the default '%s' will be used.".formatted(REFRESH_ENDPOINT_PROPERTY, refreshEndpoint)); } return refreshEndpoint; } - - private String getOwnDid(ServiceExtensionContext context) { - var did = context.getConfig().getString(PARTICIPANT_DID_PROPERTY, null); - if (did == null) { - missingMandatoryProperty(context.getMonitor().withPrefix("DataPlane Token Refresh"), PARTICIPANT_DID_PROPERTY); - } - return did; - } - } 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 602fd3737c..0bf8485767 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 @@ -38,6 +38,7 @@ import org.eclipse.edc.spi.query.QuerySpec; 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.security.Vault; import org.eclipse.edc.spi.types.domain.DataAddress; import org.eclipse.edc.token.rules.ExpirationIssuedAtValidationRule; @@ -59,6 +60,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -66,7 +68,9 @@ import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE; import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.EXPIRATION_TIME; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.AGREEMENT_ID_PROPERTY; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.AUDIENCE_PROPERTY; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.BPN_PROPERTY; 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; @@ -78,6 +82,7 @@ public class DataPlaneTokenRefreshServiceImpl implements DataPlaneTokenRefreshService, DataPlaneAccessTokenService { public static final String ACCESS_TOKEN_CLAIM = "token"; public static final String TOKEN_ID_CLAIM = "jti"; + private static final long SLOW_PHASE_THRESHOLD_MS = 5000; private final long tokenExpirySeconds; private final List authenticationTokenValidationRules; private final ParticipantContextSupplier participantContextSupplier; @@ -128,6 +133,7 @@ public DataPlaneTokenRefreshServiceImpl(Clock clock, new ClaimIsPresentRule(AUDIENCE), // we don't check the contents, only it is present new ClaimIsPresentRule(ACCESS_TOKEN_CLAIM), new ClaimIsPresentRule(TOKEN_ID_CLAIM), + new ExpirationIssuedAtValidationRule(clock, tokenExpiryToleranceSeconds, false), new AuthTokenAudienceRule(accessTokenDataStore)); this.participantContextSupplier = participantContextSupplier; accessTokenAuthorizationRules = List.of(new IssuerEqualsSubjectRule(), @@ -146,7 +152,8 @@ public DataPlaneTokenRefreshServiceImpl(Clock clock, *
  • verify the token's signature
  • *
  • assert {@code iss} and {@code sub} claims are identical
  • *
  • assert the the token contains an {@code token} claim, and that the value is identical to the access token we have on record
  • - *
  • assert that the {@code refreshToken} parameter is identical to the refresh token we have on record
  • + *
  • assert that the {@code refreshToken} parameter is identical to the refresh token we have on record, or to + * the one that record superseded
  • * * * @param refreshToken The refresh token that was issued in the original/previous token request. @@ -155,9 +162,11 @@ public DataPlaneTokenRefreshServiceImpl(Clock clock, @Override public Result refreshToken(String refreshToken, String authenticationToken) { - authenticationToken = authenticationToken.replace("Bearer", "").trim(); + var authToken = authenticationToken.replace("Bearer", "").trim(); - var authTokenRes = tokenValidationService.validate(authenticationToken, publicKeyResolver, authenticationTokenValidationRules); + var authTokenRes = timed("validate-authentication-token [DID resolution]", + () -> tokenValidationService.validate(authToken, + publicKeyResolver, authenticationTokenValidationRules)); if (authTokenRes.failed()) { var msg = "Authentication token validation failed: %s".formatted(authTokenRes.getFailureDetail()); monitor.debug(msg); @@ -175,8 +184,10 @@ public Result refreshToken(String refreshToken, String authentica // 2. extract access token and validate it var accessToken = authTokenRes.getContent().getStringClaim("token"); var refreshTokenValidationRule = new RefreshTokenValidationRule(vault, refreshToken, objectMapper, participantContext); - var accessTokenDataResult = tokenValidationService.validate(accessToken, localPublicKeyService, refreshTokenValidationRule) - .map(accessTokenClaims -> accessTokenDataStore.getById(accessTokenClaims.getStringClaim(JwtRegisteredClaimNames.JWT_ID))); + + Result accessTokenDataResult = timed("validate-access-token [Vault resolveSecret + store getById]", + () -> tokenValidationService.validate(accessToken, localPublicKeyService, refreshTokenValidationRule) + .map(accessTokenClaims -> accessTokenDataStore.getById(accessTokenClaims.getStringClaim(JwtRegisteredClaimNames.JWT_ID)))); if (accessTokenDataResult.failed()) { var msg = "Access token validation failed: %s".formatted(accessTokenDataResult.getFailureDetail()); @@ -191,7 +202,11 @@ public Result refreshToken(String refreshToken, String authentica .build(); var newAccessToken = createToken(newTokenParams).map(tr -> tr.tokenRepresentation().getToken()); - var newRefreshToken = createToken(TokenParameters.Builder.newInstance().build()).map(tr -> tr.tokenRepresentation().getToken()); + + var replayed = refreshTokenValidationRule.replayedToken(); + var newRefreshToken = replayed != null + ? ServiceResult.success(replayed.refreshToken()) + : createToken(TokenParameters.Builder.newInstance().build()).map(tr -> tr.tokenRepresentation().getToken()); if (newAccessToken.failed() || newRefreshToken.failed()) { var errors = new ArrayList<>(newAccessToken.getFailureMessages()); errors.addAll(newRefreshToken.getFailureMessages()); @@ -200,19 +215,25 @@ public Result refreshToken(String refreshToken, String authentica return Result.failure(msg); } - storeRefreshToken(existingAccessTokenData.id(), new RefreshToken(newRefreshToken.getContent(), tokenExpirySeconds, refreshEndpoint), participantContext); + if (replayed != null) { + monitor.info("Refresh token for '%s' was already rotated, handing out the current one again.".formatted(existingAccessTokenData.id())); + return Result.success(new TokenResponse(newAccessToken.getContent(), newRefreshToken.getContent(), tokenExpirySeconds, tokenExpirySeconds, "bearer")); + } + + timed("store-refresh-token [Vault storeSecret]", + () -> storeRefreshToken(existingAccessTokenData.id(), new RefreshToken(newRefreshToken.getContent(), tokenExpirySeconds, refreshEndpoint, refreshToken), 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); + var storeResult = timed("update-access-token [store update]", () -> accessTokenDataStore.update(accessTokenData)); 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")); + return Result.success(new TokenResponse(newAccessToken.getContent(), newRefreshToken.getContent(), tokenExpirySeconds, tokenExpirySeconds, "bearer")); } @Override @@ -220,15 +241,6 @@ public Result obtainToken(TokenParameters tokenParameters, Objects.requireNonNull(tokenParameters, "TokenParameters must be non-null."); Objects.requireNonNull(backendDataAddress, "DataAddress must be non-null."); - - //create a refresh token - var refreshTokenResult = createToken(TokenParameters.Builder.newInstance().build()); - if (refreshTokenResult.failed()) { - 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()) { var msg = "Could not generate access token: %s".formatted(accessTokenResult.getFailureDetail()); @@ -236,15 +248,13 @@ public Result obtainToken(TokenParameters tokenParameters, return Result.failure(msg); } - // the edrAdditionalData contains the refresh token, which is NOT supposed to be put in the DB - // note: can't use DBI (double-bracket initialization) here, because SonarCloud will complain about it - var additionalDataForStorage = new HashMap<>(additionalTokenData); - additionalDataForStorage.put("authType", "bearer"); + var accessToken = accessTokenResult.getContent(); + var storeResult = storeAccessTokenData(tokenParameters, backendDataAddress, additionalTokenData, accessToken); - // the ClaimToken is created based solely on the TokenParameters. The additional information (refresh token...) is persisted separately - var claimToken = ClaimToken.Builder.newInstance().claims(tokenParameters.getClaims()).build(); - var accessTokenData = new AccessTokenData(accessTokenResult.getContent().id(), claimToken, backendDataAddress, additionalDataForStorage); - var storeResult = accessTokenDataStore.store(accessTokenData); + if (storeResult.failed()) { + monitor.severe("Could not store AccessTokenData: %s".formatted(storeResult.getFailureDetail())); + return Result.failure(storeResult.getFailureMessages()); + } var participantContextServiceResult = participantContextSupplier.get(); if (participantContextServiceResult.failed()) { @@ -254,11 +264,18 @@ public Result obtainToken(TokenParameters tokenParameters, } var participantContext = participantContextServiceResult.getContent(); - storeRefreshToken(accessTokenResult.getContent().id(), new RefreshToken(refreshTokenResult.getContent().tokenRepresentation().getToken(), + var refreshTokenResult = createToken(TokenParameters.Builder.newInstance().build()); + if (refreshTokenResult.failed()) { + var msg = "Could not generate refresh token: %s".formatted(refreshTokenResult.getFailureDetail()); + monitor.debug(msg); + return Result.failure(msg); + } + + storeRefreshToken(accessToken.id(), new RefreshToken(refreshTokenResult.getContent().tokenRepresentation().getToken(), tokenExpirySeconds, refreshEndpoint), participantContext); // the refresh token information must be returned in the EDR - var audience = additionalDataForStorage.get(AUDIENCE_PROPERTY); + var audience = additionalTokenData.get(AUDIENCE_PROPERTY); if (audience == null) { var msg = "Missing audience in the additional properties"; @@ -273,16 +290,11 @@ public Result obtainToken(TokenParameters tokenParameters, edrAdditionalData.put(EDR_PROPERTY_REFRESH_AUDIENCE, audience); var edrTokenRepresentation = TokenRepresentation.Builder.newInstance() - .token(accessTokenResult.getContent().tokenRepresentation().getToken()) // the access token + .token(accessToken.tokenRepresentation().getToken()) // the access token .additional(edrAdditionalData) //contains additional properties and the refresh token .expiresIn(tokenExpirySeconds) //todo: needed? .build(); - if (storeResult.failed()) { - monitor.severe("Could not store AccessTokenData: %s".formatted(storeResult.getFailureDetail())); - return Result.failure(storeResult.getFailureMessages()); - } - return Result.success(edrTokenRepresentation); } @@ -319,6 +331,26 @@ public ServiceResult revoke(String transferProcessId, String reason) { }); } + private StoreResult storeAccessTokenData(TokenParameters tokenParameters, DataAddress backendDataAddress, + Map additionalTokenData, TokenRepresentationWithId token) { + var additionalDataForStorage = new HashMap<>(additionalTokenData); + additionalDataForStorage.put("authType", "bearer"); + + var claimToken = ClaimToken.Builder.newInstance().claims(tokenParameters.getClaims()).build(); + + var sourceAddressBuilder = backendDataAddress.toBuilder(); + + Optional.ofNullable(additionalTokenData.get(AGREEMENT_ID_PROPERTY)) + .ifPresent(agreementId -> sourceAddressBuilder.property("header:Edc-Contract-Agreement-Id", agreementId)); + + Optional.ofNullable(additionalTokenData.get(BPN_PROPERTY)) + .ifPresent(bpn -> sourceAddressBuilder.property("header:Edc-Bpn", bpn)); + + var accessTokenData = new AccessTokenData(token.id(), claimToken, sourceAddressBuilder.build(), additionalDataForStorage); + + return accessTokenDataStore.store(accessTokenData); + } + private Result deleteTokenData(AccessTokenData tokenData) { var deletionResult = vault.deleteSecret(tokenData.id()); if (deletionResult.failed()) { @@ -380,6 +412,24 @@ private Result toJson(Object object) { } } + /** + * Executes the given action and records how long it took. Phases exceeding {@link #SLOW_PHASE_THRESHOLD_MS} are + * logged at DEBUG so that a slow/stalled external dependency (DID resolution, Vault, database) can be identified + * from the logs even when the overall request eventually completes. + */ + private T timed(String phase, Supplier action) { + var start = System.nanoTime(); + try { + return action.get(); + } finally { + var elapsedMs = (System.nanoTime() - start) / 1_000_000; + var msg = "refreshToken phase '%s' took %d ms".formatted(phase, elapsedMs); + if (elapsedMs >= SLOW_PHASE_THRESHOLD_MS) { + monitor.debug(msg); + } + } + } + /** * container object for a TokenRepresentation and an ID */ diff --git a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/RefreshToken.java b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/RefreshToken.java index 57873e5bbf..653e84e2e8 100644 --- a/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/RefreshToken.java +++ b/edc-extensions/dataplane/dataplane-token-refresh/token-refresh-core/src/main/java/org/eclipse/tractusx/edc/dataplane/tokenrefresh/core/RefreshToken.java @@ -19,6 +19,14 @@ package org.eclipse.tractusx.edc.dataplane.tokenrefresh.core; -public record RefreshToken(String refreshToken, Long expiresIn, String refreshEndpoint) { +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.jetbrains.annotations.Nullable; +@JsonIgnoreProperties(ignoreUnknown = true) +public record RefreshToken(String refreshToken, Long expiresIn, String refreshEndpoint, + @Nullable String previousRefreshToken) { + + public RefreshToken(String refreshToken, Long expiresIn, String refreshEndpoint) { + this(refreshToken, expiresIn, refreshEndpoint, null); + } } 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 63b7c53bac..3ce6022a43 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 @@ -39,12 +39,18 @@ /** * Validates that the refresh token information associated with a token's ID ({@code jti}), that is stored in the {@link Vault} * matches a refresh token string. The refresh token in question is passed into the CTor. + *

    + * The token that the last refresh replaced is accepted as well: a client presenting it never received the response of + * that refresh, and retiring it before the client has proven receipt would strand the transfer for good. In that case + * {@link #replayedToken()} carries the stored record, so that the current refresh token can be handed out again + * instead of rotating a second time. Instances are therefore single-use, one per refresh request. */ public class RefreshTokenValidationRule implements TokenValidationRule { private final Vault vault; private final String incomingRefreshToken; private final ObjectMapper objectMapper; private final ParticipantContext participantContext; + private RefreshToken replayedToken; public RefreshTokenValidationRule(Vault vault, String incomingRefreshToken, ObjectMapper objectMapper, ParticipantContext participantContext) { this.vault = vault; @@ -62,9 +68,20 @@ public Result checkRule(@NotNull ClaimToken accessToken, @Nullable Map incomingRefreshToken.equals(rt.refreshToken()) ? - success() : - failure("Provided refresh token does not match the stored refresh token.")); + .compose(rt -> { + if (incomingRefreshToken.equals(rt.refreshToken())) { + return success(); + } + if (incomingRefreshToken.equals(rt.previousRefreshToken())) { + replayedToken = rt; + return success(); + } + return failure("Provided refresh token does not match the stored refresh token."); + }); + } + + public @Nullable RefreshToken replayedToken() { + return replayedToken; } private Result parse(String storedRefreshTokenJson) { 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 a5a2e3d7a3..c6ff02156f 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 @@ -57,7 +57,11 @@ import java.text.ParseException; import java.time.Clock; +import java.time.Duration; import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Date; import java.util.List; import java.util.Map; @@ -86,11 +90,13 @@ class DataPlaneTokenRefreshServiceImplComponentTest { private final ParticipantContextSupplier participantContextSupplier = () -> ServiceResult.success( ParticipantContext.Builder.newInstance().participantContextId("participantContextId").identity("identity").build() ); + private static final long TOKEN_EXPIRY_SECONDS = 300L; private DataPlaneTokenRefreshServiceImpl tokenRefreshService; 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 final MutableClock clock = new MutableClock(Instant.now()); private ECKey consumerKey; private ECKey providerKey; @@ -101,7 +107,7 @@ void setup() throws JOSEException { consumerKey = new ECKeyGenerator(Curve.P_384).keyID(CONSUMER_DID + "#consumer-key").keyUse(KeyUse.SIGNATURE).generate(); when(monitor.withPrefix(anyString())).thenReturn(monitor); - tokenRefreshService = new DataPlaneTokenRefreshServiceImpl(Clock.systemUTC(), + tokenRefreshService = new DataPlaneTokenRefreshServiceImpl(clock, new TokenValidationServiceImpl(), didPkResolverMock, localPublicKeyService, @@ -111,7 +117,7 @@ void setup() throws JOSEException { monitor, TEST_REFRESH_ENDPOINT, 1, - 300L, + TOKEN_EXPIRY_SECONDS, () -> providerKey.getKeyID(), vault, objectMapper, participantContextSupplier); @@ -177,6 +183,93 @@ void refresh_success() throws JOSEException { .doesNotContainKey("refreshToken"); } + @DisplayName("Verify that a refresh whose response got lost can be repeated with the same refresh token") + @Test + void refresh_whenResponseWasLost_returnsCurrentRefreshTokenAndFreshAccessToken() throws JOSEException { + var tokenId = "test-token-id"; + var edr = tokenRefreshService.obtainToken(tokenParams(tokenId), DataAddress.Builder.newInstance().type("test-type").build(), Map.of(AUDIENCE_PROPERTY, CONSUMER_DID)) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); + + var refreshToken = edr.getAdditional().get(EDR_PROPERTY_REFRESH_TOKEN).toString(); + + // the client never sees this response, e.g. because a proxy in between timed out + var lostResponse = tokenRefreshService.refreshToken(refreshToken, createAuthToken(tokenId, edr.getToken())) + .orElseThrow(f -> new AssertionError(f.getFailureDetail())); + + clock.advanceBy(Duration.ofSeconds(10)); + + // ...so it retries with the only refresh token it has, and gets the very same pair back + var retriedResponse = tokenRefreshService.refreshToken(refreshToken, createAuthToken(tokenId, edr.getToken())); + + assertThat(retriedResponse).withFailMessage(retriedResponse::getFailureDetail).isSucceeded() + // the refresh token is the one the client failed to receive, not a rotated one + .satisfies(tr -> assertThat(tr.refreshToken()).isEqualTo(lostResponse.refreshToken())) + // the access token is minted fresh, so the client gets a usable one however late it retries + .satisfies(tr -> assertThat(tokenRefreshService.resolve(tr.accessToken())).isSucceeded()); + } + + @DisplayName("Verify that the token pair handed out on a repeat can be refreshed again") + @Test + void refresh_afterRepeat_newTokenIsUsable() throws JOSEException { + var tokenId = "test-token-id"; + var edr = tokenRefreshService.obtainToken(tokenParams(tokenId), DataAddress.Builder.newInstance().type("test-type").build(), Map.of(AUDIENCE_PROPERTY, CONSUMER_DID)) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); + + var refreshToken = edr.getAdditional().get(EDR_PROPERTY_REFRESH_TOKEN).toString(); + + tokenRefreshService.refreshToken(refreshToken, createAuthToken(tokenId, edr.getToken())) + .orElseThrow(f -> new AssertionError(f.getFailureDetail())); + var repeated = tokenRefreshService.refreshToken(refreshToken, createAuthToken(tokenId, edr.getToken())) + .orElseThrow(f -> new AssertionError(f.getFailureDetail())); + + var nextResponse = tokenRefreshService.refreshToken(repeated.refreshToken(), createAuthToken(tokenId, repeated.accessToken())); + + assertThat(nextResponse).withFailMessage(nextResponse::getFailureDetail).isSucceeded() + .satisfies(tr -> assertThat(tr.refreshToken()).isNotEqualTo(repeated.refreshToken())); + } + + @DisplayName("Verify that a rotated refresh token is rejected once the client proved it received the new one") + @Test + void refresh_whenSupersededTokenWasAcknowledged_shouldFail() throws JOSEException { + var tokenId = "test-token-id"; + var edr = tokenRefreshService.obtainToken(tokenParams(tokenId), DataAddress.Builder.newInstance().type("test-type").build(), Map.of(AUDIENCE_PROPERTY, CONSUMER_DID)) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); + + var firstRefreshToken = edr.getAdditional().get(EDR_PROPERTY_REFRESH_TOKEN).toString(); + var second = tokenRefreshService.refreshToken(firstRefreshToken, createAuthToken(tokenId, edr.getToken())) + .orElseThrow(f -> new AssertionError(f.getFailureDetail())); + + // using the second token proves that the client received it, which retires the first one + tokenRefreshService.refreshToken(second.refreshToken(), createAuthToken(tokenId, second.accessToken())) + .orElseThrow(f -> new AssertionError(f.getFailureDetail())); + + assertThat(tokenRefreshService.refreshToken(firstRefreshToken, createAuthToken(tokenId, edr.getToken()))) + .isFailed() + .detail() + .isEqualTo("Access token validation failed: Provided refresh token does not match the stored refresh token."); + } + + @DisplayName("Verify that a rotated refresh token stays acceptable no matter how long the client takes to retry") + @Test + void refresh_whenResponseWasLostLongAgo_stillSucceeds() throws JOSEException { + var tokenId = "test-token-id"; + var edr = tokenRefreshService.obtainToken(tokenParams(tokenId), DataAddress.Builder.newInstance().type("test-type").build(), Map.of(AUDIENCE_PROPERTY, CONSUMER_DID)) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); + + var refreshToken = edr.getAdditional().get(EDR_PROPERTY_REFRESH_TOKEN).toString(); + var lostResponse = tokenRefreshService.refreshToken(refreshToken, createAuthToken(tokenId, edr.getToken())) + .orElseThrow(f -> new AssertionError(f.getFailureDetail())); + + // the client still has not proven receipt, so its only refresh token must remain usable - there is no window + // after which it is retired + clock.advanceBy(Duration.ofDays(1)); + + var retriedResponse = tokenRefreshService.refreshToken(refreshToken, createAuthToken(tokenId, edr.getToken())); + + assertThat(retriedResponse).withFailMessage(retriedResponse::getFailureDetail).isSucceeded() + .satisfies(tr -> assertThat(tr.refreshToken()).isEqualTo(lostResponse.refreshToken())); + } + @DisplayName("Verify that a stolen refresh token cannot be used to refresh an access token") @Test void refresh_originalTokenWasIssuedToDifferentPrincipal() throws JOSEException { @@ -197,8 +290,7 @@ void refresh_originalTokenWasIssuedToDifferentPrincipal() throws JOSEException { signedAuthToken.sign(CryptoConverter.createSigner(consumerKey)); var tokenResponse = tokenRefreshService.refreshToken(edr.getAdditional().get(EDR_PROPERTY_REFRESH_TOKEN).toString(), signedAuthToken.serialize()); - // todo: once the AuthTokenAudienceRule is re-enabled in the DataPlaneTokenRefreshServiceImpl the following assertion needs to be uncommented - // assertThat(tokenResponse).isFailed().detail().isEqualTo("Authentication token validation failed: Principal 'did:web:bob' is not authorized to refresh this token."); + assertThat(tokenResponse).isFailed().detail().isEqualTo("Authentication token validation failed: Principal 'did:web:bob' is not authorized to refresh this token."); } @DisplayName("Verify that a spoofed refresh attempt is rejected ") @@ -220,7 +312,51 @@ void refresh_issuerNotVerifiable() throws JOSEException { signedAuthToken.sign(CryptoConverter.createSigner(consumerKey)); var tokenResponse = tokenRefreshService.refreshToken(edr.getAdditional().get(EDR_PROPERTY_REFRESH_TOKEN).toString(), signedAuthToken.serialize()); - assertThat(tokenResponse).isFailed().detail().isEqualTo("Authentication token validation failed: Token verification failed"); + assertThat(tokenResponse).isFailed().detail().isEqualTo("Authentication token validation failed: JWT signature not valid"); + } + + @DisplayName("Verify that an authentication token without an expiry is rejected") + @Test + void refresh_whenAuthTokenHasNoExpiry_shouldFail() throws JOSEException { + var tokenId = "test-token-id"; + var edr = tokenRefreshService.obtainToken(tokenParams(tokenId), DataAddress.Builder.newInstance().type("test-type").build(), Map.of(AUDIENCE_PROPERTY, CONSUMER_DID)) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); + + // without an expiry an intercepted refresh request could be replayed indefinitely + var claimsSet = new JWTClaimsSet.Builder() + .jwtID(tokenId) + .issuer(CONSUMER_DID) + .subject(CONSUMER_DID) + .audience(PROVIDER_DID) + .claim("token", edr.getToken()) + .build(); + + var jwsHeader = new JWSHeader.Builder(JWSAlgorithm.ES384).keyID(consumerKey.getKeyID()).build(); + var signedAuthToken = new SignedJWT(jwsHeader, claimsSet); + signedAuthToken.sign(CryptoConverter.createSigner(consumerKey)); + + assertThat(tokenRefreshService.refreshToken(edr.getAdditional().get(EDR_PROPERTY_REFRESH_TOKEN).toString(), signedAuthToken.serialize())) + .isFailed() + .detail() + .contains("Required expiration time (exp) claim is missing in token"); + } + + @DisplayName("Verify that an expired authentication token is rejected") + @Test + void refresh_whenAuthTokenExpired_shouldFail() throws JOSEException { + var tokenId = "test-token-id"; + var edr = tokenRefreshService.obtainToken(tokenParams(tokenId), DataAddress.Builder.newInstance().type("test-type").build(), Map.of(AUDIENCE_PROPERTY, CONSUMER_DID)) + .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); + + var authToken = createAuthToken(tokenId, edr.getToken()); + + // the authentication token outlives its own validity, e.g. because it was captured and replayed later + clock.advanceBy(Duration.ofSeconds(120)); + + assertThat(tokenRefreshService.refreshToken(edr.getAdditional().get(EDR_PROPERTY_REFRESH_TOKEN).toString(), authToken)) + .isFailed() + .detail() + .contains("Token has expired (exp)"); } @DisplayName("Verify that a refresh attempt fails if no \"token\" claim is present") @@ -328,12 +464,21 @@ void revoke_successful() throws JsonProcessingException { assertThat(vault.resolveSecret(tokenId)).isNull(); } + private String createAuthToken(String tokenId, String accessToken) throws JOSEException { + var jwsHeader = new JWSHeader.Builder(JWSAlgorithm.ES384).keyID(consumerKey.getKeyID()).build(); + var signedAuthToken = new SignedJWT(jwsHeader, getAuthTokenClaims(tokenId, accessToken).build()); + signedAuthToken.sign(CryptoConverter.createSigner(consumerKey)); + return signedAuthToken.serialize(); + } + private JWTClaimsSet.Builder getAuthTokenClaims(String tokenId, String accessToken) { return new JWTClaimsSet.Builder() .jwtID(tokenId) .issuer(CONSUMER_DID) .subject(CONSUMER_DID) .audience(PROVIDER_DID) + .issueTime(Date.from(clock.instant())) + .expirationTime(Date.from(clock.instant().plusSeconds(60))) .claim("token", accessToken); } @@ -361,4 +506,34 @@ private Map asClaims(String serializedJwt) { throw new RuntimeException(e); } } + + /** + * Clock that only moves when the test tells it to, so that token expiry can be exercised without waiting. + */ + private static class MutableClock extends Clock { + private Instant instant; + + MutableClock(Instant instant) { + this.instant = instant; + } + + void advanceBy(Duration duration) { + instant = instant.plus(duration); + } + + @Override + public ZoneId getZone() { + return ZoneOffset.UTC; + } + + @Override + public Clock withZone(ZoneId zone) { + return this; + } + + @Override + public Instant instant() { + return instant; + } + } } 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 ce22a6468e..ad9d182330 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 @@ -40,6 +40,7 @@ import org.eclipse.edc.token.spi.TokenValidationService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import java.time.Clock; import java.util.Map; @@ -49,7 +50,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.tractusx.edc.dataplane.tokenrefresh.core.TestFunctions.createJwt; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.AGREEMENT_ID_PROPERTY; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.AUDIENCE_PROPERTY; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.BPN_PROPERTY; 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; @@ -95,15 +98,27 @@ void obtainToken() { 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()); + Map additionalTokenData = Map.of( + "fizz", "buzz", + "refreshToken", "getsOverwritten", + AUDIENCE_PROPERTY, "audience", + AGREEMENT_ID_PROPERTY, "contract-agreement-id", + BPN_PROPERTY, "BPN" + ); + + var result = accessTokenService.obtainToken(params, address, additionalTokenData); - var result = accessTokenService.obtainToken(params, address, Map.of("fizz", "buzz", "refreshToken", "getsOverwritten", AUDIENCE_PROPERTY, "audience")); assertThat(result).isSucceeded().extracting(TokenRepresentation::getToken).isEqualTo("foo-token"); assertThat(result.getContent().getAdditional()) .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(), any(TokenDecorator[].class)); - verify(accessTokenDataStore).store(any(AccessTokenData.class)); + var captor = ArgumentCaptor.forClass(AccessTokenData.class); + verify(accessTokenDataStore).store(captor.capture()); + var storedAccessTokenData = captor.getValue(); + assertThat(storedAccessTokenData.dataAddress().getProperty("header:Edc-Contract-Agreement-Id")).isEqualTo("contract-agreement-id"); + assertThat(storedAccessTokenData.dataAddress().getProperty("header:Edc-Bpn")).isEqualTo("BPN"); } @Test @@ -127,7 +142,6 @@ void obtainToken_invalidParams() { .isInstanceOf(NullPointerException.class); assertThatThrownBy(() -> accessTokenService.obtainToken(TokenParameters.Builder.newInstance().build(), null, Map.of())) .isInstanceOf(NullPointerException.class); - } @Test @@ -170,7 +184,7 @@ void obtainToken_storingFails() { 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(), any(TokenDecorator[].class)); + verify(tokenGenService, times(1)).generate(any(), any(), any(TokenDecorator[].class)); verify(accessTokenDataStore).store(any(AccessTokenData.class)); } 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 c7ed1a2a1a..2934f6486c 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 @@ -22,10 +22,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.spi.security.Vault; +import org.eclipse.tractusx.edc.dataplane.tokenrefresh.core.RefreshToken; import org.junit.jupiter.api.Test; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; import static org.eclipse.tractusx.edc.dataplane.tokenrefresh.core.TestFunctions.createAccessToken; import static org.mockito.Mockito.mock; @@ -35,6 +37,7 @@ class RefreshTokenValidationRuleTest { private static final String TEST_TOKEN_ID = "test-jti"; private static final String TEST_REFRESH_TOKEN = "test-refresh-token"; + private static final String TEST_PREVIOUS_REFRESH_TOKEN = "test-previous-refresh-token"; private final Vault vault = mock(); private final String participantContextId = "participantContextId"; private final ParticipantContext participantContext = ParticipantContext.Builder.newInstance() @@ -77,11 +80,12 @@ void checkRule_refreshTokenNotString() { } @Test - void checkRule_refreshTokenDoesNotMatch() { + void checkRule_refreshTokenDoesNotMatch_shouldNotFlagReplay() { when(vault.resolveSecret(participantContextId, TEST_TOKEN_ID)).thenReturn( """ { - "refreshToken": "someRefreshToken" + "refreshToken": "someRefreshToken", + "previousRefreshToken": "someOtherRefreshToken" } """); @@ -89,19 +93,44 @@ void checkRule_refreshTokenDoesNotMatch() { .isFailed() .detail() .isEqualTo("Provided refresh token does not match the stored refresh token."); + assertThat(rule.replayedToken()).isNull(); } @Test - void checkRule_success() { + void checkRule_success_currentTokenMatches_shouldNotFlagReplay() { when(vault.resolveSecret(participantContextId, TEST_TOKEN_ID)).thenReturn( """ { - "refreshToken": "%s" + "refreshToken": "%s", + "expiresIn": 3600, + "refreshEndpoint": "http://foo.bar/refresh", + "previousRefreshToken": "%s" } - """.formatted(TEST_REFRESH_TOKEN)); + """.formatted(TEST_REFRESH_TOKEN, TEST_PREVIOUS_REFRESH_TOKEN)); assertThat(rule.checkRule(createAccessToken(TEST_TOKEN_ID), Map.of())) .isSucceeded(); + assertThat(rule.replayedToken()).isNull(); + } + + @Test + void checkRule_success_previousTokenMatches_shouldFlagReplay() { + when(vault.resolveSecret(participantContextId, TEST_TOKEN_ID)).thenReturn( + """ + { + "refreshToken": "%s", + "expiresIn": 3600, + "refreshEndpoint": "http://foo.bar/refresh", + "previousRefreshToken": "%s" + } + """.formatted(TEST_REFRESH_TOKEN, TEST_PREVIOUS_REFRESH_TOKEN)); + + var replayRule = new RefreshTokenValidationRule(vault, TEST_PREVIOUS_REFRESH_TOKEN, new ObjectMapper(), participantContext); + + assertThat(replayRule.checkRule(createAccessToken(TEST_TOKEN_ID), Map.of())) + .isSucceeded(); + assertThat(replayRule.replayedToken()) + .isEqualTo(new RefreshToken(TEST_REFRESH_TOKEN, 3600L, "http://foo.bar/refresh", TEST_PREVIOUS_REFRESH_TOKEN)); } @Test diff --git a/edc-extensions/dataplane/kafka/README.md b/edc-extensions/dataplane/kafka/README.md new file mode 100644 index 0000000000..f469087d94 --- /dev/null +++ b/edc-extensions/dataplane/kafka/README.md @@ -0,0 +1,345 @@ +# Kafka Streaming Extension + +The Kafka streaming extension provides the `KafkaBroker-PULL` transfer type, enabling real-time, +event-driven data exchange between sovereign partners over Apache Kafka. + +Unlike a proxied transfer, data flows **directly** from the provider's Kafka broker to the consumer. +The EDC stays in charge of access: the data-plane extension provisions short-lived OAuth2 +credentials (and, optionally, Kafka ACLs) for the duration of a negotiated transfer, so the provider +retains full control over the consumer's access throughout. + + +* [Including the extension in a runtime](#including-the-extension-in-a-runtime) +* [Overview](#overview) +* [Architecture](#architecture) + * [Components](#components) + * [Component diagram](#component-diagram) +* [Transfer Workflow](#transfer-workflow) + * [A. Provisioning/Deprovisioning](#a-provisioningdeprovisioning) + * [B. Initiating the Transfer](#b-initiating-the-transfer) + * [C. Data Streaming](#c-data-streaming) + * [D. Suspending/Terminating](#d-suspendingterminating) +* [DataAddress Schema](#dataaddress-schema) +* [Configuration](#configuration) + * [EDC data plane properties](#edc-data-plane-properties) + * [Kafka broker](#kafka-broker) + * [Keycloak](#keycloak) +* [Security and Token Model](#security-and-token-model) + * [Authentication flow](#authentication-flow) + * [Token expiry and revocation](#token-expiry-and-revocation) + * [Kafka ACL management (optional)](#kafka-acl-management-optional) + * [Transport encryption (SASL_SSL)](#transport-encryption-sasl_ssl) +* [Interoperability and Standards](#interoperability-and-standards) +* [Troubleshooting](#troubleshooting) +* [NOTICE](#notice) + + +## Including the extension in a runtime + +The extension is **not** part of the released `tractusx-connector` runtimes — it is opt-in, so +adopters who need Kafka streaming assemble it into their own runtime. Add the data-plane extension +to the data plane and the data address validator to the control plane: + +```kotlin +// data plane +implementation(project(":edc-extensions:dataplane:kafka:kafka-broker-extension")) + +// control plane +implementation(project(":edc-extensions:dataplane:kafka:validator-data-address-kafka")) +``` + +Both modules are published as regular Maven artifacts under the `org.eclipse.tractusx.edc` group, so +runtimes built outside this repository can depend on them the same way. + +## Overview + +Kafka topics are treated as EDC data assets: a topic carries domain-specific business data (e.g. +semantic models such as `SerialPart` or `Batch`), the provider registers it as an asset with a usage +policy, and consumers gain access through the standard EDC contract negotiation. On successful +negotiation the consumer receives an Endpoint Data Reference (EDR) containing the connection details +and an OAuth2 token, which it uses to subscribe to and poll the topic directly. All exchanges are +tracked in the EDC (contract id, asset id, timestamps) for auditability. + +`SASL/OAUTHBEARER` ([RFC 7628](https://datatracker.ietf.org/doc/html/rfc7628)) is used as the Kafka +authentication mechanism, allowing OAuth2 tokens to authenticate the consumer against the broker. + +Typical Catena-X use cases that benefit from Kafka streaming include Quality Management (predictive +maintenance, early-warning notifications), Digital Twin / Asset Administration Shell (real-time +operational and condition monitoring), Demand & Capacity Management, Traceability, Circular Economy / +Product Pass, and ESG monitoring. + +## Architecture + +### Components + +The extension consists of three modules under `edc-extensions/dataplane/kafka/`: + +1. **Kafka Broker Extension** (`kafka-broker-extension`): a data-plane extension that adds the + `KafkaBroker-PULL` transfer type. It provisions OAuth2 credentials and optional Kafka ACLs, stores + tokens in the EDC Vault, builds the EDR for the consumer, and revokes credentials on + suspend/terminate. +2. **Data Address Kafka** (`data-address-kafka`): defines the `KafkaBroker` data address format + (topic, bootstrap servers, security protocol, SASL mechanism, OAuth2 settings, consumer group). +3. **Validator Data Address Kafka** (`validator-data-address-kafka`): validates that a Kafka data + address contains all required properties with valid values. + +These integrate with: + +- **Eclipse Dataspace Connector (EDC)** — the core framework for negotiation and data exchange +- **Apache Kafka** — the messaging platform for data streaming +- **OAuth2 provider** (e.g. Keycloak) — for authentication and authorization + +The roles involved at runtime are the **Control Plane** (negotiation, policy checks), the **Kafka +Extension** (credential orchestration and Vault access), the **Kafka Service** (broker enforcing +SASL/OAUTHBEARER and topic authorization), the **OAuth Service** (issues and revokes tokens), and the +**Vault** (secure storage of temporary credentials). On the consumer side, the **Consumer Control +Plane** receives the EDR and the **Consumer Application** uses it to instantiate a Kafka consumer. + +### Component diagram + +![Component diagram EDC Kafka Extension](diagrams/Component%20diagram%20EDC%20Kafka%20Extension.png) + +## Transfer Workflow + +The transfer is defined by four phases, each illustrated by a sequence diagram. + +### A. Provisioning/Deprovisioning + +**Purpose:** securely create or delete consumer credentials. + +1. **Provisioning:** the Consumer Control Plane sends a `TransferRequestMessage` to the Provider + Control Plane, which sends a `DataFlowStartMessage` (Data Plane Signaling) to the data plane. + There the Kafka Extension reads the OAuth2 client secret from the Vault, obtains a short-lived + access token from the OAuth Service, stores it in the Vault, and returns the provisioned Kafka + `DataAddress` in the `DataFlowResponseMessage`. +2. **Deprovisioning:** when the transfer is terminated, the Provider Control Plane signals the + termination to the data plane; the Kafka Extension revokes the token via the OAuth Service and + deletes it from the Vault. + +![Sequence diagram EDC Kafka Extension provisioning-deprovisioning](diagrams/Sequence%20diagram%20EDC%20Kafka%20Extension%20provisioning-deprovisioning.png) + +### B. Initiating the Transfer + +**Purpose:** start a transfer process with dynamic credentials and EDR creation. + +1. The Consumer Control Plane instructs the Provider Control Plane to start the transfer; the Provider + Control Plane performs policy and contract verification. +2. The Provider Control Plane sends a `DataFlowStartMessage` (Data Plane Signaling) to the data + plane, where the Kafka Extension requests a fresh OAuth2 access token from the OAuth Service via + the Client Credentials flow (no refresh token) and creates a `DataAddress` containing the + connection details (bootstrap servers, topic, security protocol, SASL mechanism, OAuth token, + poll duration, and consumer group prefix). The Provider Control Plane then sends a + `TransferStartMessage` with the complete DataAddress to the Consumer Control Plane, which + constructs the final EDR. + +![Sequence diagram EDC Kafka Extension start transfer process](diagrams/Sequence%20diagram%20EDC%20Kafka%20Extension%20start%20transfer%20process.png) + +### C. Data Streaming + +**Purpose:** establish a secure, token-based data stream between consumer and Kafka Service. + +The Consumer Application requests the EDR, instantiates a Kafka consumer, and authenticates against +the Kafka Service (which validates the token). It then polls the topic for messages. The token is +short-lived; the consumer polls until it expires — there is no token refresh, a new transfer mints a +new token. + +![Sequence diagram EDC Kafka Extension data streaming](diagrams/Sequence%20diagram%20EDC%20Kafka%20Extension%20data%20streaming.png) + +### D. Suspending/Terminating + +**Purpose:** securely suspend or terminate the transfer by revoking consumer credentials. + +The Provider Control Plane signals the suspension or termination to the data plane. On suspend, the +Kafka Extension revokes the consumer's ACLs (when ACL management is enabled); the short-lived token +remains valid until it expires, and a resume re-creates the ACLs. On terminate, the extension +additionally calls the OAuth Service to revoke the token and removes it from the Vault. The Consumer +Control Plane is notified that the transfer has been suspended or terminated. + +![Sequence diagram EDC Kafka Extension suspending-terminating](diagrams/Sequence%20diagram%20EDC%20Kafka%20Extension%20suspending-terminating.png) + +## DataAddress Schema + +A Kafka asset is published with a `DataAddress` of type `KafkaBroker`: + +| Property | Description | Mandatory | +|---|---|---| +| `type` | Must be `KafkaBroker` | Yes | +| `https://w3id.org/edc/v0.0.1/ns/topic` | Kafka topic name | Yes | +| `https://w3id.org/edc/v0.0.1/ns/kafka.bootstrap.servers` | Kafka bootstrap servers | Yes | +| `https://w3id.org/edc/v0.0.1/ns/kafka.sasl.mechanism` | SASL mechanism (e.g., `OAUTHBEARER`) | Yes | +| `https://w3id.org/edc/v0.0.1/ns/kafka.security.protocol` | Security protocol (e.g., `SASL_PLAINTEXT`, `SASL_SSL`) | Yes | +| `https://w3id.org/edc/v0.0.1/ns/tokenUrl` | OAuth2 token endpoint URL | Yes | +| `https://w3id.org/edc/v0.0.1/ns/clientId` | OAuth2 client ID | Yes | +| `https://w3id.org/edc/v0.0.1/ns/clientSecretKey` | Vault entry key for the OAuth2 client secret | Yes | +| `https://w3id.org/edc/v0.0.1/ns/revokeUrl` | OAuth2 token revocation endpoint | No | +| `https://w3id.org/edc/v0.0.1/ns/kafka.poll.duration` | ISO-8601 consumer poll duration (default `PT1S`) | No | +| `https://w3id.org/edc/v0.0.1/ns/kafka.group.prefix` | Consumer group prefix the consumer is authorized to use; also scopes the consumer-group ACL. Defaults to the consumer participant id when omitted. | No | + +> Property keys may be written as full IRIs (as in the table above) or with the `edc:` prefix (as in the +> example below); both are equivalent after JSON-LD expansion. + +Example asset registration: + +```json +{ + "@context": { "edc": "https://w3id.org/edc/v0.0.1/ns/" }, + "@type": "Asset", + "@id": "kafka-asset-1", + "properties": { "name": "My Kafka Stream" }, + "dataAddress": { + "type": "KafkaBroker", + "edc:topic": "my-topic", + "edc:kafka.bootstrap.servers": "kafka:9092", + "edc:kafka.sasl.mechanism": "OAUTHBEARER", + "edc:kafka.security.protocol": "SASL_PLAINTEXT", + "edc:tokenUrl": "http://keycloak:8080/realms/kafka/protocol/openid-connect/token", + "edc:revokeUrl": "http://keycloak:8080/realms/kafka/protocol/openid-connect/revoke", + "edc:clientId": "edc-provider", + "edc:clientSecretKey": "edc-provider-secret" + } +} +``` + +## Configuration + +### EDC data plane properties + +| Property | Description | Default | +|---|---|---| +| `edc.dataplane.kafka.acl.enabled` | Enable Kafka ACL management | `false` | +| `edc.dataplane.kafka.acl.bootstrap.servers` | Kafka broker addresses for admin ACL operations | — | +| `edc.dataplane.kafka.acl.security.protocol` | Security protocol for the admin client | `PLAINTEXT` | +| `edc.dataplane.kafka.acl.sasl.mechanism` | SASL mechanism for the admin client | — | +| `edc.dataplane.kafka.acl.sasl.jaas.config` | JAAS config for the admin client | — | + +> **Note:** The bootstrap servers, security protocol, and SASL settings are only required when ACL +> management is enabled (`edc.dataplane.kafka.acl.enabled=true`). + +### Kafka broker + +Configure the Kafka broker for OAuth/SASL authentication: + +```properties +# KRaft mode (no ZooKeeper) +kafka.process.roles=broker,controller +kafka.controller.quorum.voters=1@kafka:29093 +kafka.controller.listener.names=CONTROLLER + +# Listeners & Protocols +kafka.listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,OIDC:SASL_PLAINTEXT +kafka.listeners=PLAINTEXT://kafka:29092,CONTROLLER://kafka:29093,OIDC://0.0.0.0:9092 +kafka.advertised.listeners=PLAINTEXT://kafka:29092,OIDC://kafka:9092 +kafka.inter.broker.listener.name=PLAINTEXT + +# Enable SASL/OAUTHBEARER authentication +kafka.sasl.enabled.mechanisms=OAUTHBEARER +kafka.sasl.oauthbearer.jwks.endpoint.url=http://keycloak:8080/realms/kafka/protocol/openid-connect/certs +kafka.sasl.oauthbearer.token.endpoint.url=http://keycloak:8080/realms/kafka/protocol/openid-connect/token +kafka.sasl.oauthbearer.expected.audience=account +``` + +### Keycloak + +Set up a realm (e.g. `kafka`) with clients for the EDC provider (`edc-provider`) and consumer +(`edc-consumer`), configure client credentials (client id + secret) for each, and set an appropriate +token TTL (recommended: 5 minutes). + +## Security and Token Model + +### Authentication flow + +The extension uses the OAuth2 Client Credentials flow: + +1. When a transfer process starts, the extension calls the OAuth2 token endpoint with the provider's + client credentials. +2. The returned JWT is stored in the EDC Vault keyed by the transfer process id. +3. The token is included in the EDR sent to the consumer. +4. On terminate, the extension calls the token revocation URL (if configured) and deletes the token + from the Vault. On suspend, broker access is cut by revoking the consumer's ACLs (when ACL + management is enabled) while the short-lived token simply expires. + +### Token expiry and revocation + +The implementation does not use refresh tokens — each transfer mints a single short-lived access +token, so access naturally ends when the token expires. Configure the token TTL in the OAuth2 +provider accordingly (recommended: 5 minutes). + +Because the broker validates the access token by signature and expiry (not by a revocation lookup), a +token that has been revoked at the OAuth2 server can remain usable at the broker until it expires. +Immediate broker-level cutoff is therefore provided by **ACL revocation** when ACL management is +enabled; otherwise access ends at the token's TTL. + +### Kafka ACL management (optional) + +When `edc.dataplane.kafka.acl.enabled=true`: + +1. On transfer start, the extension extracts the `sub` claim from the JWT (used as the Kafka + principal, `User:`). +2. It creates three ACL bindings for that principal: `READ` and `DESCRIBE` on the topic, and `READ` on + the consumer group prefix (the `kafka.group.prefix`, defaulting to the consumer participant id) — the same + prefix handed to the consumer in the EDR, so the broker grant matches what the consumer is told to + use. +3. On suspend/terminate, the ACLs are revoked immediately — closing the access window even before the + token expires. On resume they are re-created. + +### Transport encryption (SASL_SSL) + +Because the extension allows topic consumption across company borders, end-to-end encryption is +recommended for production: switch the security protocol from `SASL_PLAINTEXT` to `SASL_SSL` (set +`kafka.security.protocol` accordingly in the data address). With the default client configuration all +public CA-signed certificates are accepted; the consumer client can also be configured to trust a +custom certificate. + +## Interoperability and Standards + +The extension does not change anything related to IATP, DSP, or policy definitions, ensuring +conformity to [CX-0018 Dataspace Connectivity v.3.1.0](https://catenax-ev.github.io/docs/standards/CX-0018-DataspaceConnectivity) +(chapters 2.1, 2.3, 2.4 and 2.5). + +Since the extension introduces the new transfer type `KafkaBroker-PULL`, the standard should be extended +by this type. An example of such an extension: + +> 2.2.3 KafkaBroker-PULL +> +> A Consumer MUST send a `dspace:TransferRequestMessage` with `dct:format:dspace:KafkaBroker-PULL`. +> +> A Provider MUST send a `dspace:TransferStartMessage` with sufficient information in the +> `dspace:dataAddress` property so that a client connection to the `dspace:endpoint` may succeed when +> initialized with the properties `groupPrefix` and `topic`. +> +> A Provider Connector MUST ensure that the requested backend system has sufficient context from the +> negotiation to evaluate the legitimacy of the request. +> +> A Consumer may then use the provided data to execute requests against the endpoint. Despite the +> token, the endpoint still has the right to refuse serving a request — for instance when a consumer +> requests a different topic than the one specified in the `dspace:dataAddress`. + +For background on EDC extension development, see the +[EDC Contributors Manual](https://eclipse-edc.github.io/documentation/for-contributors/), in +particular [Modules, Runtimes, and Components](https://eclipse-edc.github.io/documentation/for-adopters/modules-runtimes-components/), +[Extensions](https://eclipse-edc.github.io/documentation/for-adopters/extensions/), and the +[Data Plane Signaling interface](https://eclipse-edc.github.io/documentation/for-contributors/data-plane/data-plane-signaling/). + +## Troubleshooting + +**Connection issues** +- Verify Kafka bootstrap servers are reachable from the EDC data plane. +- Check that the security protocol and SASL mechanism match the broker configuration. + +**Authentication issues** +- Verify the OAuth2 client credentials are correct and stored in the Vault. +- Check that the token URL is reachable from the EDC data plane. +- Confirm the Vault key name matches `clientSecretKey` in the data address. + +**ACL issues** (only when ACL management is enabled) +- Verify the admin client has superuser privileges in Kafka. +- Confirm the JWT contains a `sub` claim (used as the Kafka principal). +- Check that the Kafka broker has an authorizer configured (`StandardAuthorizer`). + +## 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: 2025 Contributors to the Eclipse Foundation +- Source URL: diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MatchType.java b/edc-extensions/dataplane/kafka/data-address-kafka/build.gradle.kts similarity index 54% rename from edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MatchType.java rename to edc-extensions/dataplane/kafka/data-address-kafka/build.gradle.kts index 348a09b70a..b0a8b9f0a9 100644 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MatchType.java +++ b/edc-extensions/dataplane/kafka/data-address-kafka/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -17,19 +17,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.mock; +plugins { + `java-library` +} -/** - * Represents how arguments are matched. - *

      - *
    • ClASS: only the type must match, similar to Mockito's {@code isA(SomeType.class}
    • - *
    • PARTIAL: only the specified properties must match, disregarding others
    • - *
    • PARTIAL: all properties must match, those that are not listed are expected to be null
    • - *
    - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public enum MatchType { - CLASS, PARTIAL, EXACT +dependencies { + api(libs.edc.spi.core) } diff --git a/edc-extensions/dataplane/kafka/data-address-kafka/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/dataaddress/KafkaBrokerDataAddressSchema.java b/edc-extensions/dataplane/kafka/data-address-kafka/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/dataaddress/KafkaBrokerDataAddressSchema.java new file mode 100644 index 0000000000..70adcdf389 --- /dev/null +++ b/edc-extensions/dataplane/kafka/data-address-kafka/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/dataaddress/KafkaBrokerDataAddressSchema.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.dataaddress; + +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; + +/** + * Defines the schema of a DataAddress representing a Kafka endpoint. + */ +public interface KafkaBrokerDataAddressSchema { + + /** + * The transfer type. + */ + String KAFKA_TYPE = "KafkaBroker"; + + /** + * The Kafka topic that will be allowed to poll for the consumer. + */ + String TOPIC = EDC_NAMESPACE + "topic"; + + /** + * The kafka.bootstrap.servers property. + */ + String BOOTSTRAP_SERVERS = EDC_NAMESPACE + "kafka.bootstrap.servers"; + + /** + * The kafka.poll.duration property which specifies the duration of the consumer polling. + *

    + * The value should be a ISO-8601 duration e.g. "PT10S" for 10 seconds. + * This parameter is optional. The default value is 1 second. + * + * @see java.time.Duration#parse(CharSequence) for ISO-8601 duration format + */ + String POLL_DURATION = EDC_NAMESPACE + "kafka.poll.duration"; + + /** + * The kafka.group.prefix that will be allowed to use for the consumer. + */ + String GROUP_PREFIX = EDC_NAMESPACE + "kafka.group.prefix"; + + /** + * The security.protocol property. + */ + String PROTOCOL = EDC_NAMESPACE + "kafka.security.protocol"; + + /** + * The sasl.mechanism property. + */ + String MECHANISM = EDC_NAMESPACE + "kafka.sasl.mechanism"; + + /** + * The authentication token. + */ + String TOKEN = EDC_NAMESPACE + "token"; + + /** + * The OAuth token URL for retrieving access tokens. + */ + String OAUTH_TOKEN_URL = EDC_NAMESPACE + "tokenUrl"; + + /** + * The OAuth revoke URL for invalidating tokens. + */ + String OAUTH_REVOKE_URL = EDC_NAMESPACE + "revokeUrl"; + + /** + * The OAuth client ID. + */ + String OAUTH_CLIENT_ID = EDC_NAMESPACE + "clientId"; + + /** + * The OAuth client secret key. + */ + String OAUTH_CLIENT_SECRET_KEY = EDC_NAMESPACE + "clientSecretKey"; +} diff --git a/edc-extensions/dataplane/kafka/diagrams/Component diagram EDC Kafka Extension.png b/edc-extensions/dataplane/kafka/diagrams/Component diagram EDC Kafka Extension.png new file mode 100644 index 0000000000..2d1de24dee Binary files /dev/null and b/edc-extensions/dataplane/kafka/diagrams/Component diagram EDC Kafka Extension.png differ diff --git a/edc-extensions/dataplane/kafka/diagrams/Component diagram EDC Kafka Extension.puml b/edc-extensions/dataplane/kafka/diagrams/Component diagram EDC Kafka Extension.puml new file mode 100644 index 0000000000..73603cae6c --- /dev/null +++ b/edc-extensions/dataplane/kafka/diagrams/Component diagram EDC Kafka Extension.puml @@ -0,0 +1,43 @@ +@startuml +/' +SPDX-License-Identifier: CC-BY-4.0 +SPDX-FileCopyrightText: (c) 2026 Contributors to the Eclipse Foundation +'/ +package "Provider ecosystem" #AliceBlue { + [Provider Application] as ProviderApp +} + +package "Provider cluster" #AliceBlue { + [Control Plane] as ProviderCP + package "Data Plane" { + [Kafka Extension] as ProviderKE + } + [Kafka Service] as Kafka + [OAuth Service] as OAuthService + [Vault] + interface "OAuth Token API" as OAuth2API + OAuth2API - OAuthService +} + +package "Consumer cluster" #LightYellow { + [Control Plane] as ConsumerCP +} +package "Consumer ecosystem" #LightYellow { + [Consumer Application] as ConsumerApp +} + +interface "Producer API" as ProducerAPI +ProducerAPI - Kafka +interface "Consumer API" as ConsumeAPI +ConsumeAPI - Kafka + +ProviderApp --> ProducerAPI : Send stream of data to topic +ConsumerCP <--> ProviderCP : Initiate negotiation, transfer +ProviderCP --> ProviderKE : Start/suspend/terminate data flow\n(Data Plane Signaling) +ProviderKE --> OAuth2API : Mint/revoke access token +[Vault] <--> ProviderKE : Read client secret,\nstore/delete token +ProviderKE --> Kafka : Create/revoke ACLs (optional,\nvia admin client) +Kafka --> OAuth2API : Validate token +ConsumerApp --> ConsumerCP : Get EDR +ConsumerApp --> ConsumeAPI : Poll data (SASL/OAUTHBEARER) +@enduml diff --git a/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension data streaming.png b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension data streaming.png new file mode 100644 index 0000000000..67840bc406 Binary files /dev/null and b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension data streaming.png differ diff --git a/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension data streaming.puml b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension data streaming.puml new file mode 100644 index 0000000000..32350715a6 --- /dev/null +++ b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension data streaming.puml @@ -0,0 +1,28 @@ +@startuml +/' +SPDX-License-Identifier: CC-BY-4.0 +SPDX-FileCopyrightText: (c) 2026 Contributors to the Eclipse Foundation +'/ +actor ConsumerApp as "Consumer" + +box "Consumer cluster" #LightYellow + participant "Control Plane" as ConsumerCP +end box + +box "Provider cluster" #LightBlue + participant "Kafka Service" as Kafka + participant "OAuth Service" as OAuth2 +end box + +== Data streaming == +ConsumerApp -> ConsumerCP : Request for EDR +ConsumerApp <-- ConsumerCP : Response with EDR +ConsumerApp -> Kafka : Authentication +Kafka -> OAuth2 : Authentication +Kafka <-- OAuth2 : Authentication success +ConsumerApp <-- Kafka : Authentication success +loop while token is valid + ConsumerApp -> Kafka : Poll data + ConsumerApp <-- Kafka : Polling data +end loop +@enduml \ No newline at end of file diff --git a/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension provisioning-deprovisioning.png b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension provisioning-deprovisioning.png new file mode 100644 index 0000000000..8ee528979d Binary files /dev/null and b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension provisioning-deprovisioning.png differ diff --git a/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension provisioning-deprovisioning.puml b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension provisioning-deprovisioning.puml new file mode 100644 index 0000000000..e7b2cf31cf --- /dev/null +++ b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension provisioning-deprovisioning.puml @@ -0,0 +1,35 @@ +@startuml +/' +SPDX-License-Identifier: CC-BY-4.0 +SPDX-FileCopyrightText: (c) 2026 Contributors to the Eclipse Foundation +'/ +box "Consumer cluster" #LightYellow + participant "Control Plane" as ConsumerCP +end box + +box "Provider cluster" #LightBlue + participant "Control Plane" as ProviderCP + participant "Data Plane\n(Kafka Extension)" as ProviderDP + participant "Vault" as Vault + participant "OAuth Service" as OAuth2 +end box + +== Provision (transfer start) == +ConsumerCP -> ProviderCP : Send TransferRequestMessage +ProviderCP -> ProviderDP : Send DataFlowStartMessage\n(Data Plane Signaling) +ProviderDP -> Vault : Read OAuth2 client secret +ProviderDP <-- Vault : Client secret +ProviderDP -> OAuth2 : Request access token\n(Client Credentials flow) +ProviderDP <-- OAuth2 : Access token +ProviderDP -> Vault : Store token (keyed by flow id) +ProviderCP <-- ProviderDP : DataFlowResponseMessage\nwith Kafka DataAddress (EDR) + +== Deprovision (transfer terminate) == +ProviderCP -> ProviderDP : Terminate data flow\n(Data Plane Signaling) +ProviderDP -> Vault : Read token +ProviderDP <-- Vault : Token +ProviderDP -> OAuth2 : Revoke token (revokeUrl) +ProviderDP <-- OAuth2 : Token revoked +ProviderDP -> Vault : Delete token +ProviderCP <-- ProviderDP : Flow terminated +@enduml diff --git a/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension start transfer process.png b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension start transfer process.png new file mode 100644 index 0000000000..1ea6397e58 Binary files /dev/null and b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension start transfer process.png differ diff --git a/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension start transfer process.puml b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension start transfer process.puml new file mode 100644 index 0000000000..f16cc3116a --- /dev/null +++ b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension start transfer process.puml @@ -0,0 +1,31 @@ +@startuml +/' +SPDX-License-Identifier: CC-BY-4.0 +SPDX-FileCopyrightText: (c) 2026 Contributors to the Eclipse Foundation +'/ +box "Consumer cluster" #LightYellow + participant "Control Plane" as ConsumerCP +end box + +box "Provider cluster" #LightBlue + participant "Control Plane" as ProviderCP + participant "Data Plane\n(Kafka Extension)" as ProviderDP + participant "OAuth Service" as OAuth2 + participant "Kafka Service" as Kafka +end box + +== Start Transfer Process (assumes contract has already been negotiated) == +ConsumerCP -> ProviderCP : Start transfer process\n(TransferRequestMessage) +ProviderCP -> ProviderCP : Check policy, contract etc. +ProviderCP -> ProviderDP : Send DataFlowStartMessage\n(Data Plane Signaling) +ProviderDP -> OAuth2 : Request access token\n(Client Credentials flow, no refresh token) +ProviderDP <-- OAuth2 : Access token +ProviderDP -> ProviderDP : Build Kafka DataAddress\n(bootstrap servers, topic, security protocol,\nSASL mechanism, group prefix, poll duration, token) +opt ACL management enabled + ProviderDP -> Kafka : Create ACLs for token subject + ProviderDP <-- Kafka : ACLs created +end +ProviderCP <-- ProviderDP : DataFlowResponseMessage\nwith Kafka DataAddress +ProviderCP -> ConsumerCP : Send TransferStartMessage\nattaching DataAddress +ConsumerCP -> ConsumerCP : Create EDR with DataAddress +@enduml diff --git a/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension suspending-terminating.png b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension suspending-terminating.png new file mode 100644 index 0000000000..8df4907fc4 Binary files /dev/null and b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension suspending-terminating.png differ diff --git a/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension suspending-terminating.puml b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension suspending-terminating.puml new file mode 100644 index 0000000000..2a7311a4d1 --- /dev/null +++ b/edc-extensions/dataplane/kafka/diagrams/Sequence diagram EDC Kafka Extension suspending-terminating.puml @@ -0,0 +1,38 @@ +@startuml +/' +SPDX-License-Identifier: CC-BY-4.0 +SPDX-FileCopyrightText: (c) 2026 Contributors to the Eclipse Foundation +'/ +box "Consumer cluster" #LightYellow + participant "Control Plane" as ConsumerCP +end box + +box "Provider cluster" #LightBlue + participant "Control Plane" as ProviderCP + participant "Data Plane\n(Kafka Extension)" as ProviderDP + participant "OAuth Service" as OAuth2 + participant "Kafka Service" as Kafka +end box + +== Suspend == +ProviderCP -> ProviderDP : Suspend data flow\n(Data Plane Signaling) +opt ACL management enabled + ProviderDP -> Kafka : Revoke consumer ACLs + ProviderDP <-- Kafka : ACLs revoked +end +note over ProviderDP : The token is not revoked on suspend —\nit expires on its own; a resume\nre-creates the ACLs +ProviderCP <-- ProviderDP : Flow suspended +ConsumerCP <-- ProviderCP : Transfer suspended + +== Terminate == +ProviderCP -> ProviderDP : Terminate data flow\n(Data Plane Signaling) +opt ACL management enabled + ProviderDP -> Kafka : Revoke consumer ACLs + ProviderDP <-- Kafka : ACLs revoked +end +ProviderDP -> OAuth2 : Revoke token (revokeUrl) +ProviderDP <-- OAuth2 : Token revoked +ProviderDP -> ProviderDP : Delete token from vault +ProviderCP <-- ProviderDP : Flow terminated +ConsumerCP <-- ProviderCP : Transfer terminated +@enduml diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/README.md b/edc-extensions/dataplane/kafka/kafka-broker-extension/README.md new file mode 100644 index 0000000000..3e742ef0dc --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/README.md @@ -0,0 +1,48 @@ +# Kafka Broker Extension + +## Overview + +The Kafka Broker Extension is a data-plane extension that enables secure, dynamic +access to Kafka topics within the Tractus-X EDC. It allows data providers to share +Kafka streams with consumers while maintaining full control over access permissions +through per-transfer OAuth2 credentials. + +## Sibling modules + +This extension lives alongside two supporting modules under `edc-extensions/dataplane/kafka/`: + +- **`data-address-kafka`** — defines the `KafkaBrokerDataAddressSchema` constants for + Kafka data address properties. +- **`validator-data-address-kafka`** — validates that a `DataAddress` of type + `KafkaBroker` carries all required properties before a transfer is initiated. + +## Transfer type + +The extension adds the **`KafkaBroker-PULL`** transfer type to the EDC data plane +via a provisioner and an EDR service. The provisioning lifecycle: + +1. **start** — provisions a fresh OAuth2 access token via the Client Credentials + flow, stores it in the provider vault keyed by the transfer process id, and + returns an EDR containing the bootstrap servers, topic, security protocol, SASL + mechanism, consumer group prefix, poll duration, and token. When ACL + management is enabled, it also creates the consumer's Kafka ACLs. +2. **suspend** — revokes the consumer's ACLs (when ACL management is enabled), cutting + broker access immediately; the short-lived token itself stays in the vault and remains + valid until it expires. On **resume** the ACLs are re-created. +3. **terminate** — revokes the token at the OAuth2 server's revocation endpoint (if a + `revokeUrl` is configured), removes it from the vault, and — when ACL management is + enabled — revokes the consumer's ACLs. + +## Configuration + +Kafka ACL management is optional and disabled by default. When enabled, the data plane manages +broker-level authorization for each transfer through a Kafka admin client, configured via the +`edc.dataplane.kafka.acl.*` settings — see +[Configuration](../README.md#configuration). + +## Further reading + +The `KafkaBroker` data address schema, the full configuration reference (EDC data plane, Kafka +broker, Keycloak), the security and token model, the end-to-end transfer workflow, and how to +include the extension in a runtime are documented in +[Kafka Streaming Extension](../README.md). diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/build.gradle.kts b/edc-extensions/dataplane/kafka/kafka-broker-extension/build.gradle.kts new file mode 100644 index 0000000000..f63d90a807 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + `java-library` +} + +dependencies { + implementation(libs.edc.spi.core) + implementation(libs.edc.spi.dataplane.dataplane) + implementation(libs.edc.spi.http) + implementation(libs.edc.lib.util) + implementation(libs.kafka.clients) + implementation(project(":edc-extensions:dataplane:kafka:data-address-kafka")) + + testImplementation(libs.edc.junit) + testImplementation(libs.awaitility) + testImplementation(libs.testcontainers.junit) + testImplementation(libs.testcontainers.kafka) +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/KafkaBrokerExtension.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/KafkaBrokerExtension.java new file mode 100644 index 0000000000..6f6bd2a6ae --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/KafkaBrokerExtension.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka; + +import org.apache.kafka.clients.admin.AdminClientConfig; +import org.eclipse.edc.connector.dataplane.spi.edr.EndpointDataReferenceServiceRegistry; +import org.eclipse.edc.connector.dataplane.spi.provision.ProvisionerManager; +import org.eclipse.edc.connector.dataplane.spi.provision.ResourceDefinitionGeneratorManager; +import org.eclipse.edc.http.spi.EdcHttpClient; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.security.Vault; +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.dataplane.kafka.acl.KafkaAclService; +import org.eclipse.tractusx.edc.dataplane.kafka.acl.KafkaAclServiceImpl; +import org.eclipse.tractusx.edc.dataplane.kafka.auth.KafkaOauthService; +import org.eclipse.tractusx.edc.dataplane.kafka.auth.KafkaOauthServiceImpl; +import org.eclipse.tractusx.edc.dataplane.kafka.flow.KafkaEndpointDataReferenceService; +import org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaDeprovisioner; +import org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisioner; +import org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaResourceDefinitionGenerator; + +import java.util.Properties; + +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.KAFKA_TYPE; + +/** + * Kafka streaming data-plane extension. + *

    + * Adds the {@code KafkaBroker-PULL} transfer type to the EDC data plane: on transfer start a fresh OAuth2 + * token is minted and (optionally) broker ACLs are created, and the consumer is handed an EDR pointing + * directly at the Kafka broker. On suspend/terminate the ACLs and token are revoked. + *

    + * Set {@code edc.dataplane.kafka.acl.enabled=true} to activate Kafka ACL management for immediate + * broker-level revocation independent of token expiry. + */ +@Extension(value = KafkaBrokerExtension.NAME) +public class KafkaBrokerExtension implements ServiceExtension { + + public static final String NAME = "Kafka stream extension"; + + @Setting(description = "Enable Kafka ACL management for immediate broker-level access revocation on transfer termination", defaultValue = "false") + static final String ACL_ENABLED = "edc.dataplane.kafka.acl.enabled"; + + @Setting(description = "Kafka bootstrap servers used by the ACL admin client (required when ACL management is enabled)") + static final String ACL_BOOTSTRAP_SERVERS = "edc.dataplane.kafka.acl.bootstrap.servers"; + + @Setting(description = "Security protocol for the ACL admin client", defaultValue = "PLAINTEXT") + static final String ACL_SECURITY_PROTOCOL = "edc.dataplane.kafka.acl.security.protocol"; + + @Setting(description = "SASL mechanism for the ACL admin client (e.g. OAUTHBEARER, PLAIN)") + static final String ACL_SASL_MECHANISM = "edc.dataplane.kafka.acl.sasl.mechanism"; + + @Setting(description = "SASL JAAS config for the ACL admin client") + static final String ACL_SASL_JAAS_CONFIG = "edc.dataplane.kafka.acl.sasl.jaas.config"; + + @Inject + private Vault vault; + + @Inject + private TypeManager typeManager; + + @Inject + private EdcHttpClient httpClient; + + @Inject + private ResourceDefinitionGeneratorManager resourceDefinitionGeneratorManager; + + @Inject + private ProvisionerManager provisionerManager; + + @Inject + private EndpointDataReferenceServiceRegistry endpointDataReferenceServiceRegistry; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(final ServiceExtensionContext context) { + var monitor = context.getMonitor(); + KafkaOauthService oauthService = new KafkaOauthServiceImpl(httpClient, typeManager.getMapper()); + KafkaAclService aclService = buildAclService(context); + + resourceDefinitionGeneratorManager.registerProviderGenerator(new KafkaResourceDefinitionGenerator()); + provisionerManager.register(new KafkaProvisioner(vault, oauthService, monitor)); + provisionerManager.register(new KafkaDeprovisioner(vault, oauthService, aclService, monitor)); + endpointDataReferenceServiceRegistry.register(KAFKA_TYPE, new KafkaEndpointDataReferenceService(aclService, monitor, typeManager.getMapper())); + } + + private KafkaAclService buildAclService(ServiceExtensionContext context) { + if (!Boolean.parseBoolean(context.getSetting(ACL_ENABLED, "false"))) { + return null; + } + return new KafkaAclServiceImpl(buildAdminProperties(context), context.getMonitor()); + } + + private Properties buildAdminProperties(ServiceExtensionContext context) { + var props = new Properties(); + props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, context.getSetting(ACL_BOOTSTRAP_SERVERS, "")); + props.put("security.protocol", context.getSetting(ACL_SECURITY_PROTOCOL, "PLAINTEXT")); + + var saslMechanism = context.getSetting(ACL_SASL_MECHANISM, null); + if (saslMechanism != null) { + props.put("sasl.mechanism", saslMechanism); + } + + var jaasConfig = context.getSetting(ACL_SASL_JAAS_CONFIG, null); + if (jaasConfig != null) { + props.put("sasl.jaas.config", jaasConfig); + } + + return props; + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/AdminClientFactory.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/AdminClientFactory.java new file mode 100644 index 0000000000..f4f743390f --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/AdminClientFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.acl; + +import org.apache.kafka.clients.admin.Admin; + +import java.util.Properties; + +public interface AdminClientFactory { + + Admin createAdmin(Properties properties); +} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AbstractServiceStub.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/DefaultAdminClientFactory.java similarity index 59% rename from edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AbstractServiceStub.java rename to edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/DefaultAdminClientFactory.java index 83f26a9899..93bf4f71c2 100644 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AbstractServiceStub.java +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/DefaultAdminClientFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -17,20 +17,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.mock.services; +package org.eclipse.tractusx.edc.dataplane.kafka.acl; -import org.eclipse.tractusx.edc.mock.ResponseQueue; +import org.apache.kafka.clients.admin.Admin; -/** - * For abstract service stubs. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public abstract class AbstractServiceStub { - protected final ResponseQueue responseQueue; +import java.util.Properties; + +public class DefaultAdminClientFactory implements AdminClientFactory { - public AbstractServiceStub(ResponseQueue responseQueue) { - this.responseQueue = responseQueue; + @Override + public Admin createAdmin(Properties properties) { + return Admin.create(properties); } } diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclService.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclService.java new file mode 100644 index 0000000000..6d36377ab0 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclService.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.acl; + +import org.eclipse.edc.spi.result.Result; + +/** + * Manages Kafka ACLs (Access Control Lists) for transfer processes. + *

    + * When ACL management is enabled, each started transfer creates topic-scoped + * READ/DESCRIBE ACLs for the consumer's OAuth subject, and those ACLs are + * revoked immediately on suspend or terminate — providing broker-level + * enforcement that is independent of OAuth token expiry. + */ +public interface KafkaAclService { + + /** + * Creates READ and DESCRIBE ACLs on the given topic plus a READ ACL on the consumer group. + *

    + * The ACL principal is always {@code User:} (the broker identity carried in the + * consumer's OAUTHBEARER token). The consumer-group resource is named by {@code groupPrefix} + * (PREFIXED), which must be the same group prefix that is handed to the consumer in the EDR so the + * broker grant and the consumer instruction cannot diverge. + * + * @param oauthSubject the OAuth {@code sub} claim extracted from the consumer's JWT (the principal) + * @param topicName the Kafka topic to grant access to + * @param groupPrefix the consumer-group prefix to grant READ on (PREFIXED match) + * @param transferProcessId used to track the ACLs for later revocation + */ + Result createAclsForSubject(String oauthSubject, String topicName, String groupPrefix, String transferProcessId); + + /** + * Revokes all ACLs that were created for the given transfer process. + */ + Result revokeAclsForTransferProcess(String transferProcessId); + + /** + * Revokes ACLs for the given subject, topic and group prefix directly, without requiring a transfer + * process ID. The arguments must match those used at {@link #createAclsForSubject} for the deletion + * filters to match the created bindings. + */ + Result revokeAclsForSubject(String oauthSubject, String topicName, String groupPrefix); +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclServiceImpl.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclServiceImpl.java new file mode 100644 index 0000000000..c41776a2d2 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclServiceImpl.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.acl; + +import org.apache.kafka.clients.admin.Admin; +import org.apache.kafka.clients.admin.CreateAclsResult; +import org.apache.kafka.clients.admin.DeleteAclsResult; +import org.apache.kafka.common.acl.AccessControlEntry; +import org.apache.kafka.common.acl.AccessControlEntryFilter; +import org.apache.kafka.common.acl.AclBinding; +import org.apache.kafka.common.acl.AclBindingFilter; +import org.apache.kafka.common.acl.AclOperation; +import org.apache.kafka.common.acl.AclPermissionType; +import org.apache.kafka.common.resource.PatternType; +import org.apache.kafka.common.resource.ResourcePattern; +import org.apache.kafka.common.resource.ResourcePatternFilter; +import org.apache.kafka.common.resource.ResourceType; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; + +public class KafkaAclServiceImpl implements KafkaAclService { + + private final Properties kafkaProperties; + private final Monitor monitor; + private final AdminClientFactory adminClientFactory; + private final Map transferProcessAcls = new ConcurrentHashMap<>(); + + public KafkaAclServiceImpl(Properties kafkaProperties, Monitor monitor) { + this(kafkaProperties, monitor, new DefaultAdminClientFactory()); + } + + public KafkaAclServiceImpl(Properties kafkaProperties, Monitor monitor, AdminClientFactory adminClientFactory) { + this.kafkaProperties = kafkaProperties; + this.monitor = monitor; + this.adminClientFactory = adminClientFactory; + } + + @Override + public Result createAclsForSubject(String oauthSubject, String topicName, String groupPrefix, String transferProcessId) { + monitor.debug("Creating ACLs for OAuth subject: %s, topic: %s, groupPrefix: %s, transferProcessId: %s" + .formatted(oauthSubject, topicName, groupPrefix, transferProcessId)); + + try (Admin adminClient = adminClientFactory.createAdmin(kafkaProperties)) { + Collection aclBindings = buildAclBindings(oauthSubject, topicName, groupPrefix); + CreateAclsResult result = adminClient.createAcls(aclBindings); + result.all().get(); + + transferProcessAcls.put(transferProcessId, new AclTrackingInfo(oauthSubject, topicName, aclBindings)); + + monitor.debug("Successfully created ACLs for OAuth subject: %s, topic: %s, transferProcessId: %s" + .formatted(oauthSubject, topicName, transferProcessId)); + return Result.success(); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + String message = "Interrupted while creating ACLs for subject: %s".formatted(oauthSubject); + monitor.severe(message, e); + return failure(message, e); + } catch (ExecutionException e) { + String message = "Failed to create ACLs for OAuth subject: %s".formatted(oauthSubject); + monitor.severe(message, e); + return failure(message, e); + } + } + + @Override + public Result revokeAclsForTransferProcess(String transferProcessId) { + monitor.debug("Revoking ACLs for transferProcessId: %s".formatted(transferProcessId)); + + AclTrackingInfo aclInfo = transferProcessAcls.remove(transferProcessId); + if (aclInfo == null) { + monitor.debug("No ACLs found for transferProcessId: %s".formatted(transferProcessId)); + return Result.success(); + } + + try (Admin adminClient = adminClientFactory.createAdmin(kafkaProperties)) { + Collection aclFilters = toAclBindingFilters(aclInfo.aclBindings()); + DeleteAclsResult result = adminClient.deleteAcls(aclFilters); + result.all().get(); + + monitor.debug("Successfully revoked ACLs for transferProcessId: %s".formatted(transferProcessId)); + return Result.success(); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + String message = "Interrupted while revoking ACLs for transferProcessId: %s".formatted(transferProcessId); + monitor.severe(message, e); + return failure(message, e); + } catch (ExecutionException e) { + String message = "Failed to revoke ACLs for transferProcessId: %s".formatted(transferProcessId); + monitor.severe(message, e); + return failure(message, e); + } + } + + @Override + public Result revokeAclsForSubject(String oauthSubject, String topicName, String groupPrefix) { + monitor.debug("Revoking ACLs for OAuth subject: %s, topic: %s, groupPrefix: %s".formatted(oauthSubject, topicName, groupPrefix)); + + try (Admin adminClient = adminClientFactory.createAdmin(kafkaProperties)) { + Collection aclFilters = toAclBindingFilters(buildAclBindings(oauthSubject, topicName, groupPrefix)); + DeleteAclsResult result = adminClient.deleteAcls(aclFilters); + result.all().get(); + + monitor.debug("Successfully revoked ACLs for OAuth subject: %s, topic: %s".formatted(oauthSubject, topicName)); + return Result.success(); + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + String message = "Interrupted while revoking ACLs for subject: %s".formatted(oauthSubject); + monitor.severe(message, e); + return failure(message, e); + } catch (ExecutionException e) { + String message = "Failed to revoke ACLs for OAuth subject: %s".formatted(oauthSubject); + monitor.severe(message, e); + return failure(message, e); + } + } + + private @NotNull Result failure(String message, Exception e) { + return Result.failure("%s: %s".formatted(message, e.getMessage())); + } + + private Collection buildAclBindings(String oauthSubject, String topicName, String groupPrefix) { + String principal = "User:" + oauthSubject; + ResourcePattern topicResource = new ResourcePattern(ResourceType.TOPIC, topicName, PatternType.LITERAL); + ResourcePattern groupResource = new ResourcePattern(ResourceType.GROUP, groupPrefix, PatternType.PREFIXED); + + return List.of( + new AclBinding(topicResource, new AccessControlEntry(principal, "*", AclOperation.READ, AclPermissionType.ALLOW)), + new AclBinding(topicResource, new AccessControlEntry(principal, "*", AclOperation.DESCRIBE, AclPermissionType.ALLOW)), + new AclBinding(groupResource, new AccessControlEntry(principal, "*", AclOperation.READ, AclPermissionType.ALLOW)) + ); + } + + private Collection toAclBindingFilters(Collection aclBindings) { + Collection filters = new ArrayList<>(); + for (AclBinding b : aclBindings) { + filters.add(new AclBindingFilter( + new ResourcePatternFilter(b.pattern().resourceType(), b.pattern().name(), b.pattern().patternType()), + new AccessControlEntryFilter(b.entry().principal(), b.entry().host(), b.entry().operation(), b.entry().permissionType()) + )); + } + return filters; + } + + private record AclTrackingInfo(String oauthSubject, String topicName, Collection aclBindings) { + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthService.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthService.java new file mode 100644 index 0000000000..c72d00fef1 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthService.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.auth; + +/** + * Interface for services that handle Oauth2 access token operations for Kafka authentication. + * Defines methods to fetch and revoke Oauth2 access tokens using the Client Credentials flow. + */ +public interface KafkaOauthService { + + /** + * Always performs a client_credentials flow and returns a fresh token. + * + * @param creds The Oauth credentials to use for token acquisition + * @return The acquired access token as a string + */ + String getAccessToken(OauthCredentials creds); + + /** + * Revokes the given token. + * + * @param creds The Oauth credentials used for token revocation + * @param token The token to revoke + */ + void revokeToken(OauthCredentials creds, String token); +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthServiceImpl.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthServiceImpl.java new file mode 100644 index 0000000000..de8d6afee7 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthServiceImpl.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.auth; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.Response; +import org.eclipse.edc.http.spi.EdcHttpClient; + +import java.io.IOException; + +/** + * Stateless service to fetch and revoke Oauth2 access tokens using the Client Credentials flow. + * No token is cached—each getAccessToken() call always retrieves a new token. + */ +public class KafkaOauthServiceImpl implements KafkaOauthService { + static final String ACCESS_TOKEN_KEY = "access_token"; + static final String GRANT_TYPE_KEY = "grant_type"; + static final String CLIENT_CREDENTIALS_GRANT_TYPE = "client_credentials"; + static final String CLIENT_ID_KEY = "client_id"; + static final String CLIENT_SECRET_KEY = "client_secret"; + static final String CONTENT_TYPE_HEADER = "Content-Type"; + static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; + static final String TOKEN_KEY = "token"; + + private final EdcHttpClient httpClient; + private final ObjectMapper objectMapper; + + public KafkaOauthServiceImpl(final EdcHttpClient httpClient, final ObjectMapper objectMapper) { + this.httpClient = httpClient; + this.objectMapper = objectMapper; + } + + /** + * Always performs a client_credentials flow and returns a fresh token. + */ + @Override + public String getAccessToken(final OauthCredentials creds) { + return fetchNewToken(creds); + } + + private String fetchNewToken(final OauthCredentials creds) { + try { + FormBody formBody = new FormBody.Builder() + .add(GRANT_TYPE_KEY, CLIENT_CREDENTIALS_GRANT_TYPE) + .add(CLIENT_ID_KEY, creds.clientId()) + .add(CLIENT_SECRET_KEY, creds.clientSecret()) + .build(); + + Request request = new Request.Builder() + .url(creds.tokenUrl()) + .header(CONTENT_TYPE_HEADER, APPLICATION_X_WWW_FORM_URLENCODED) + .post(formBody) + .build(); + + try (Response response = httpClient.execute(request)) { + if (!response.isSuccessful()) { + throw new RuntimeException("Oauth2 token endpoint returned HTTP " + response.code()); + } + + String responseBody = response.body() != null ? response.body().string() : ""; + JsonNode json = objectMapper.readTree(responseBody); + JsonNode accessToken = json.get(ACCESS_TOKEN_KEY); + if (accessToken == null || accessToken.isNull()) { + throw new RuntimeException("Oauth2 token endpoint response did not contain an '" + ACCESS_TOKEN_KEY + "' field"); + } + return accessToken.asText(); + } + } catch (IOException e) { + throw new RuntimeException("Failed to fetch Oauth2 token", e); + } + } + + /** + * Revokes the given token. + */ + @Override + public void revokeToken(final OauthCredentials creds, final String token) { + if (creds.revocationUrl().isEmpty()) { + return; + } + try { + FormBody formBody = new FormBody.Builder() + .add(TOKEN_KEY, token) + .add(CLIENT_ID_KEY, creds.clientId()) + .add(CLIENT_SECRET_KEY, creds.clientSecret()) + .build(); + + Request request = new Request.Builder() + .url(creds.revocationUrl().get()) + .header(CONTENT_TYPE_HEADER, APPLICATION_X_WWW_FORM_URLENCODED) + .post(formBody) + .build(); + + try (Response response = httpClient.execute(request)) { + if (!response.isSuccessful()) { + throw new RuntimeException("Revoke endpoint returned HTTP " + response.code()); + } + } + } catch (IOException e) { + throw new RuntimeException("Failed to revoke Oauth2 token", e); + } + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/OauthCredentials.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/OauthCredentials.java new file mode 100644 index 0000000000..0041598734 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/OauthCredentials.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.auth; + +import java.util.Optional; + +/** + * Simple immutable holder for the four Oauth2 parameters. + * + * @param tokenUrl The endpoint URL to fetch Oauth2 tokens using client credentials flow. + * @param revocationUrl The optional endpoint URL used to revoke tokens. This may be empty if token revocation is not supported. + * @param clientId The identifier of the client application attempting to authenticate. + * @param clientSecret The secret associated with the client identifier for authentication. + */ +public record OauthCredentials(String tokenUrl, Optional revocationUrl, String clientId, String clientSecret) { +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/flow/KafkaEndpointDataReferenceService.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/flow/KafkaEndpointDataReferenceService.java new file mode 100644 index 0000000000..5aa92b6eba --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/flow/KafkaEndpointDataReferenceService.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.flow; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.connector.dataplane.spi.DataFlow; +import org.eclipse.edc.connector.dataplane.spi.edr.EndpointDataReferenceService; +import org.eclipse.edc.spi.EdcException; +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.tractusx.edc.dataplane.kafka.acl.KafkaAclService; +import org.jetbrains.annotations.Nullable; + +import java.util.Base64; + +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.GROUP_PREFIX; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOKEN; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOPIC; + +/** + * Produces the consumer-facing EDR for a {@code KafkaBroker} PULL flow, and owns the per-activation broker + * ACL lifecycle. + *

    + * {@link #createEndpointDataReference} runs on transfer start and resume, so it (re)creates the + * broker ACLs each time — restoring access on resume after a suspend revoked them. The EDR itself is the + * provisioned {@link DataAddress} ({@link DataFlow#getActualSource()}: broker coordinates, topic, security + * settings, consumer-group prefix and the minted token) built by + * {@link org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisioner}. + *

    + * {@link #revokeEndpointDataReference} runs on suspend and terminate, revoking the ACLs so suspend + * cuts access immediately; the OAuth token is revoked by the deprovisioner on terminate (where the client + * credentials are available). When ACL management is disabled both are no-ops and access ends at the + * token's TTL. + */ +public class KafkaEndpointDataReferenceService implements EndpointDataReferenceService { + + @Nullable + private final KafkaAclService aclService; + private final Monitor monitor; + private final ObjectMapper objectMapper; + + public KafkaEndpointDataReferenceService(@Nullable KafkaAclService aclService, Monitor monitor, ObjectMapper objectMapper) { + this.aclService = aclService; + this.monitor = monitor; + this.objectMapper = objectMapper; + } + + @Override + public Result createEndpointDataReference(DataFlow dataFlow) { + var edr = dataFlow.getActualSource(); + if (edr == null) { + return Result.failure("No provisioned Kafka EDR available for data flow " + dataFlow.getId()); + } + + if (aclService != null) { + try { + var subject = extractOauthSubject(edr.getStringProperty(TOKEN)); + var aclResult = aclService.createAclsForSubject(subject, edr.getStringProperty(TOPIC), + edr.getStringProperty(GROUP_PREFIX), dataFlow.getId()); + if (aclResult.failed()) { + return Result.failure("Failed to create Kafka ACLs: " + aclResult.getFailureDetail()); + } + } catch (EdcException e) { + return Result.failure(e.getMessage()); + } + } + + return Result.success(edr); + } + + @Override + public ServiceResult revokeEndpointDataReference(String transferProcessId, String reason) { + if (aclService == null) { + return ServiceResult.success(); + } + monitor.debug("Revoking Kafka ACLs for data flow %s".formatted(transferProcessId)); + return ServiceResult.from(aclService.revokeAclsForTransferProcess(transferProcessId)); + } + + private String extractOauthSubject(String token) { + try { + var parts = token.split("\\."); + if (parts.length != 3) { + throw new EdcException("Invalid JWT token format"); + } + var payload = new String(Base64.getUrlDecoder().decode(parts[1])); + var subNode = objectMapper.readTree(payload).get("sub"); + if (subNode == null || subNode.isNull()) { + throw new EdcException("No 'sub' claim found in JWT token"); + } + return subNode.asText(); + } catch (EdcException e) { + throw e; + } catch (Exception e) { + throw new EdcException("Failed to extract OAuth subject from token: " + e.getMessage(), e); + } + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaDeprovisioner.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaDeprovisioner.java new file mode 100644 index 0000000000..7969857ac9 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaDeprovisioner.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.provision; + +import org.eclipse.edc.connector.dataplane.spi.provision.DeprovisionedResource; +import org.eclipse.edc.connector.dataplane.spi.provision.Deprovisioner; +import org.eclipse.edc.connector.dataplane.spi.provision.ProvisionResource; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.response.ResponseStatus; +import org.eclipse.edc.spi.response.StatusResult; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.tractusx.edc.dataplane.kafka.acl.KafkaAclService; +import org.eclipse.tractusx.edc.dataplane.kafka.auth.KafkaOauthService; +import org.eclipse.tractusx.edc.dataplane.kafka.auth.OauthCredentials; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_ID; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_SECRET_KEY; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_REVOKE_URL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_TOKEN_URL; +import static org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisionConstants.KAFKA_RESOURCE_TYPE; + +/** + * Data-plane {@link Deprovisioner} for {@code KafkaBroker} flows: revokes the broker ACLs and the OAuth2 + * token when the flow is deprovisioned on terminate, closing the access window independently of token + * expiry. Cleanup is idempotent — a missing token or untracked ACLs is treated as success. + */ +public class KafkaDeprovisioner implements Deprovisioner { + + private final Vault vault; + private final KafkaOauthService oauthService; + @Nullable + private final KafkaAclService aclService; + private final Monitor monitor; + + public KafkaDeprovisioner(Vault vault, KafkaOauthService oauthService, @Nullable KafkaAclService aclService, Monitor monitor) { + this.vault = vault; + this.oauthService = oauthService; + this.aclService = aclService; + this.monitor = monitor; + } + + @Override + public String supportedType() { + return KAFKA_RESOURCE_TYPE; + } + + @Override + public CompletableFuture> deprovision(ProvisionResource resource) { + try { + var flowId = resource.getFlowId(); + + if (aclService != null) { + var aclResult = aclService.revokeAclsForTransferProcess(flowId); + if (aclResult.failed()) { + return completed(StatusResult.failure(ResponseStatus.FATAL_ERROR, "Failed to revoke Kafka ACLs: " + aclResult.getFailureDetail())); + } + } + + var token = vault.resolveSecret(flowId); + if (token != null) { + oauthService.revokeToken(extractOauthCredentials(resource.getDataAddress()), token); + vault.deleteSecret(flowId); + } + + monitor.debug("Deprovisioned Kafka flow %s".formatted(flowId)); + return completed(StatusResult.success(DeprovisionedResource.Builder.newInstance() + .id(resource.getId()) + .flowId(flowId) + .build())); + } catch (Exception e) { + return completed(StatusResult.failure(ResponseStatus.FATAL_ERROR, "Failed to deprovision Kafka data flow: " + e.getMessage())); + } + } + + private OauthCredentials extractOauthCredentials(DataAddress source) { + var clientSecret = Optional.ofNullable(vault.resolveSecret(source.getStringProperty(OAUTH_CLIENT_SECRET_KEY))) + .orElseThrow(() -> new EdcException("Kafka client secret was not found in the vault")); + return new OauthCredentials( + source.getStringProperty(OAUTH_TOKEN_URL), + Optional.ofNullable(source.getStringProperty(OAUTH_REVOKE_URL)), + source.getStringProperty(OAUTH_CLIENT_ID), + clientSecret); + } + + private static CompletableFuture> completed(StatusResult result) { + return CompletableFuture.completedFuture(result); + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaProvisionConstants.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaProvisionConstants.java new file mode 100644 index 0000000000..78fb228da4 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaProvisionConstants.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.provision; + +/** + * Constants shared by the Kafka data-plane provisioning components. + */ +public interface KafkaProvisionConstants { + + /** + * Provision-resource type for Kafka flows. Matches the {@code KafkaBroker} source DataAddress type so + * the data plane advertises {@code KafkaBroker} as an allowed source type and {@code KafkaBroker-PULL} + * as an allowed transfer type. + */ + String KAFKA_RESOURCE_TYPE = "KafkaBroker"; + + /** + * ProvisionResource property carrying the consumer participant id, used as the consumer-group prefix + * fallback when the {@code kafka.group.prefix} DataAddress property is absent. + */ + String CONSUMER_GROUP_PREFIX_PROPERTY = "tx:kafka:consumerGroupPrefix"; + + /** + * Default consumer poll duration (ISO-8601) when {@code kafka.poll.duration} is not set. + */ + String DEFAULT_POLL_DURATION = "PT1S"; +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaProvisioner.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaProvisioner.java new file mode 100644 index 0000000000..8c729942ef --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaProvisioner.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.provision; + +import org.eclipse.edc.connector.dataplane.spi.provision.ProvisionResource; +import org.eclipse.edc.connector.dataplane.spi.provision.ProvisionedResource; +import org.eclipse.edc.connector.dataplane.spi.provision.Provisioner; +import org.eclipse.edc.spi.EdcException; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.response.ResponseStatus; +import org.eclipse.edc.spi.response.StatusResult; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.tractusx.edc.dataplane.kafka.auth.KafkaOauthService; +import org.eclipse.tractusx.edc.dataplane.kafka.auth.OauthCredentials; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.BOOTSTRAP_SERVERS; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.GROUP_PREFIX; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.KAFKA_TYPE; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.MECHANISM; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_ID; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_SECRET_KEY; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_REVOKE_URL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_TOKEN_URL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.POLL_DURATION; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.PROTOCOL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOKEN; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOPIC; +import static org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisionConstants.CONSUMER_GROUP_PREFIX_PROPERTY; +import static org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisionConstants.DEFAULT_POLL_DURATION; +import static org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisionConstants.KAFKA_RESOURCE_TYPE; + +/** + * Data-plane {@link Provisioner} for the {@code KafkaBroker} source type. On provision it mints a fresh + * OAuth2 access token (Client Credentials flow) and builds the provisioned {@link DataAddress} — the EDR + * handed to the consumer: the broker coordinates, topic, security settings, consumer-group prefix and the + * minted token. The token is also stored in the vault keyed by the data-flow id so + * {@link KafkaDeprovisioner} can revoke it on terminate. Broker ACLs are managed per activation by + * {@code KafkaEndpointDataReferenceService} (so suspend/resume toggle access), not here. + */ +public class KafkaProvisioner implements Provisioner { + + private final Vault vault; + private final KafkaOauthService oauthService; + private final Monitor monitor; + + public KafkaProvisioner(Vault vault, KafkaOauthService oauthService, Monitor monitor) { + this.vault = vault; + this.oauthService = oauthService; + this.monitor = monitor; + } + + @Override + public String supportedType() { + return KAFKA_RESOURCE_TYPE; + } + + @Override + public CompletableFuture> provision(ProvisionResource resource) { + try { + var flowId = resource.getFlowId(); + var source = resource.getDataAddress(); + + var groupPrefix = Optional.ofNullable(source.getStringProperty(GROUP_PREFIX)) + .orElse((String) resource.getProperty(CONSUMER_GROUP_PREFIX_PROPERTY)); + if (groupPrefix == null || groupPrefix.isBlank()) { + return completed(StatusResult.failure(ResponseStatus.FATAL_ERROR, + "Cannot determine Kafka consumer-group prefix: neither the '%s' property nor a consumer participant id is present" + .formatted(GROUP_PREFIX))); + } + + var token = oauthService.getAccessToken(extractOauthCredentials(source)); + vault.storeSecret(flowId, token); + + var pollDuration = Optional.ofNullable(source.getStringProperty(POLL_DURATION)).orElse(DEFAULT_POLL_DURATION); + + var edr = DataAddress.Builder.newInstance() + .type(KAFKA_TYPE) + .property(BOOTSTRAP_SERVERS, source.getStringProperty(BOOTSTRAP_SERVERS)) + .property(TOPIC, source.getStringProperty(TOPIC)) + .property(PROTOCOL, source.getStringProperty(PROTOCOL)) + .property(MECHANISM, source.getStringProperty(MECHANISM)) + .property(TOKEN, token) + .property(POLL_DURATION, pollDuration) + .property(GROUP_PREFIX, groupPrefix) + .build(); + + var provisioned = ProvisionedResource.Builder.newInstance() + .id(resource.getId()) + .flowId(flowId) + .dataAddress(edr) + .build(); + + monitor.debug("Provisioned Kafka EDR for flow %s, topic %s".formatted(flowId, source.getStringProperty(TOPIC))); + return completed(StatusResult.success(provisioned)); + } catch (Exception e) { + return completed(StatusResult.failure(ResponseStatus.FATAL_ERROR, "Failed to provision Kafka data flow: " + e.getMessage())); + } + } + + private OauthCredentials extractOauthCredentials(DataAddress source) { + var clientSecret = Optional.ofNullable(vault.resolveSecret(source.getStringProperty(OAUTH_CLIENT_SECRET_KEY))) + .orElseThrow(() -> new EdcException("Kafka client secret was not found in the vault")); + return new OauthCredentials( + source.getStringProperty(OAUTH_TOKEN_URL), + Optional.ofNullable(source.getStringProperty(OAUTH_REVOKE_URL)), + source.getStringProperty(OAUTH_CLIENT_ID), + clientSecret); + } + + private static CompletableFuture> completed(StatusResult result) { + return CompletableFuture.completedFuture(result); + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaResourceDefinitionGenerator.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaResourceDefinitionGenerator.java new file mode 100644 index 0000000000..5f7f178ad6 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaResourceDefinitionGenerator.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.provision; + +import org.eclipse.edc.connector.dataplane.spi.DataFlow; +import org.eclipse.edc.connector.dataplane.spi.provision.ProvisionResource; +import org.eclipse.edc.connector.dataplane.spi.provision.ResourceDefinitionGenerator; + +import java.util.UUID; + +import static org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisionConstants.CONSUMER_GROUP_PREFIX_PROPERTY; +import static org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisionConstants.KAFKA_RESOURCE_TYPE; + +/** + * Provider-side {@link ResourceDefinitionGenerator} for the {@code KafkaBroker} source type. Registering it + * makes the data plane advertise {@code KafkaBroker} as an allowed source type (and, via the EDR service, + * {@code KafkaBroker-PULL} as a transfer type) and triggers {@link KafkaProvisioner} on transfer start. + */ +public class KafkaResourceDefinitionGenerator implements ResourceDefinitionGenerator { + + @Override + public String supportedType() { + return KAFKA_RESOURCE_TYPE; + } + + @Override + public ProvisionResource generate(DataFlow dataFlow) { + return ProvisionResource.Builder.newInstance() + .id(UUID.randomUUID().toString()) + .flowId(dataFlow.getId()) + .type(KAFKA_RESOURCE_TYPE) + .dataAddress(dataFlow.getSource()) + .property(CONSUMER_GROUP_PREFIX_PROPERTY, dataFlow.getParticipantId()) + .build(); + } +} diff --git a/edc-tests/runtime/mock-connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension similarity index 86% rename from edc-tests/runtime/mock-connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension rename to edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension index 3de76bc517..6c8d0be418 100644 --- a/edc-tests/runtime/mock-connector/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -1,5 +1,5 @@ ################################################################################# -# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. @@ -17,5 +17,4 @@ # SPDX-License-Identifier: Apache-2.0 ################################################################################# -org.eclipse.tractusx.edc.mock.MockServiceExtension - +org.eclipse.tractusx.edc.dataplane.kafka.KafkaBrokerExtension diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/KafkaBrokerExtensionTest.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/KafkaBrokerExtensionTest.java new file mode 100644 index 0000000000..8b2e3315f9 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/KafkaBrokerExtensionTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka; + +import org.eclipse.edc.connector.dataplane.spi.edr.EndpointDataReferenceServiceRegistry; +import org.eclipse.edc.connector.dataplane.spi.provision.ProvisionerManager; +import org.eclipse.edc.connector.dataplane.spi.provision.ResourceDefinitionGeneratorManager; +import org.eclipse.edc.http.spi.EdcHttpClient; +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.tractusx.edc.dataplane.kafka.flow.KafkaEndpointDataReferenceService; +import org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaDeprovisioner; +import org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisioner; +import org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaResourceDefinitionGenerator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.KAFKA_TYPE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ExtendWith(DependencyInjectionExtension.class) +class KafkaBrokerExtensionTest { + + private final ResourceDefinitionGeneratorManager generatorManager = mock(); + private final ProvisionerManager provisionerManager = mock(); + private final EndpointDataReferenceServiceRegistry edrRegistry = mock(); + private final Vault vault = mock(); + private final EdcHttpClient httpClient = mock(); + + @BeforeEach + void setUp(final ServiceExtensionContext context) { + context.registerService(ResourceDefinitionGeneratorManager.class, generatorManager); + context.registerService(ProvisionerManager.class, provisionerManager); + context.registerService(EndpointDataReferenceServiceRegistry.class, edrRegistry); + context.registerService(Vault.class, vault); + context.registerService(EdcHttpClient.class, httpClient); + } + + @Test + void initialize_RegistersKafkaDataPlaneComponents(final KafkaBrokerExtension extension, final ServiceExtensionContext context) { + extension.initialize(context); + + verify(generatorManager).registerProviderGenerator(any(KafkaResourceDefinitionGenerator.class)); + verify(provisionerManager).register(any(KafkaProvisioner.class)); + verify(provisionerManager).register(any(KafkaDeprovisioner.class)); + verify(edrRegistry).register(eq(KAFKA_TYPE), any(KafkaEndpointDataReferenceService.class)); + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclServiceImplTest.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclServiceImplTest.java new file mode 100644 index 0000000000..42c97fbf3b --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclServiceImplTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.acl; + +import org.apache.kafka.clients.admin.Admin; +import org.apache.kafka.clients.admin.CreateAclsResult; +import org.apache.kafka.clients.admin.DeleteAclsResult; +import org.apache.kafka.common.KafkaFuture; +import org.apache.kafka.common.acl.AclBinding; +import org.apache.kafka.common.acl.AclOperation; +import org.apache.kafka.common.resource.PatternType; +import org.apache.kafka.common.resource.ResourceType; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Collection; +import java.util.Collections; +import java.util.Properties; +import java.util.concurrent.ExecutionException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +class KafkaAclServiceImplTest { + + private static final String OAUTH_SUBJECT = "test-oauth-subject"; + private static final String TOPIC = "test-topic"; + // Distinct from the OAuth subject so the GROUP ACL is verified to be named by the group prefix + // (PREFIXED) while the principal stays the subject. + private static final String GROUP_PREFIX = "test-group-prefix"; + private static final String TRANSFER_PROCESS_ID = "test-transfer-process-id"; + + private final Monitor monitor = mock(); + private final Admin mockAdmin = mock(); + private final AdminClientFactory adminClientFactory = mock(); + private KafkaAclServiceImpl aclService; + + @BeforeEach + void setUp() { + var kafkaProperties = new Properties(); + kafkaProperties.put("bootstrap.servers", "localhost:9092"); + when(adminClientFactory.createAdmin(any(Properties.class))).thenReturn(mockAdmin); + aclService = new KafkaAclServiceImpl(kafkaProperties, monitor, adminClientFactory); + } + + @Test + void createAclsForSubject_shouldSucceed() throws ExecutionException, InterruptedException { + setupSuccessfulCreate(); + + Result result = aclService.createAclsForSubject(OAUTH_SUBJECT, TOPIC, GROUP_PREFIX, TRANSFER_PROCESS_ID); + + assertThat(result.succeeded()).isTrue(); + // 3 bindings: topic READ + DESCRIBE (LITERAL) and consumer-group READ (PREFIXED by the group + // prefix, not the subject), all for principal User:. + verify(mockAdmin).createAcls(argThat(bindings -> + bindings.size() == 3 && + hasBinding(bindings, ResourceType.TOPIC, TOPIC, PatternType.LITERAL, AclOperation.READ) && + hasBinding(bindings, ResourceType.TOPIC, TOPIC, PatternType.LITERAL, AclOperation.DESCRIBE) && + hasBinding(bindings, ResourceType.GROUP, GROUP_PREFIX, PatternType.PREFIXED, AclOperation.READ))); + } + + private static boolean hasBinding(Collection bindings, ResourceType resourceType, String name, + PatternType patternType, AclOperation operation) { + return bindings.stream().anyMatch(b -> + b.pattern().resourceType() == resourceType && + b.pattern().name().equals(name) && + b.pattern().patternType() == patternType && + b.entry().principal().equals("User:" + OAUTH_SUBJECT) && + b.entry().operation() == operation); + } + + @Test + void createAclsForSubject_shouldFail_whenExecutionExceptionOccurs() throws ExecutionException, InterruptedException { + CreateAclsResult createResult = mock(CreateAclsResult.class); + KafkaFuture future = mock(KafkaFuture.class); + when(mockAdmin.createAcls(anyCollection())).thenReturn(createResult); + when(createResult.all()).thenReturn(future); + when(future.get()).thenThrow(new ExecutionException("error", new RuntimeException())); + + Result result = aclService.createAclsForSubject(OAUTH_SUBJECT, TOPIC, GROUP_PREFIX, TRANSFER_PROCESS_ID); + + assertThat(result.failed()).isTrue(); + assertThat(result.getFailureDetail()).contains("Failed to create ACLs for OAuth subject: " + OAUTH_SUBJECT); + } + + @Test + void revokeAclsForTransferProcess_shouldSucceed_afterCreate() throws Exception { + setupSuccessfulCreate(); + aclService.createAclsForSubject(OAUTH_SUBJECT, TOPIC, GROUP_PREFIX, TRANSFER_PROCESS_ID); + + setupSuccessfulDelete(); + + Result result = aclService.revokeAclsForTransferProcess(TRANSFER_PROCESS_ID); + + assertThat(result.succeeded()).isTrue(); + verify(mockAdmin).deleteAcls(anyCollection()); + } + + @Test + void revokeAclsForTransferProcess_shouldSucceed_whenNoAclsTracked() { + Result result = aclService.revokeAclsForTransferProcess("unknown-id"); + + assertThat(result.succeeded()).isTrue(); + verifyNoInteractions(mockAdmin); + verify(adminClientFactory, never()).createAdmin(any()); + } + + @Test + void revokeAclsForTransferProcess_shouldFail_whenExecutionExceptionOccurs() throws Exception { + setupSuccessfulCreate(); + aclService.createAclsForSubject(OAUTH_SUBJECT, TOPIC, GROUP_PREFIX, TRANSFER_PROCESS_ID); + + DeleteAclsResult deleteResult = mock(DeleteAclsResult.class); + KafkaFuture> future = mock(KafkaFuture.class); + when(mockAdmin.deleteAcls(anyCollection())).thenReturn(deleteResult); + when(deleteResult.all()).thenReturn(future); + when(future.get()).thenThrow(new ExecutionException("error", new RuntimeException())); + + Result result = aclService.revokeAclsForTransferProcess(TRANSFER_PROCESS_ID); + + assertThat(result.failed()).isTrue(); + assertThat(result.getFailureDetail()).contains("Failed to revoke ACLs for transferProcessId: " + TRANSFER_PROCESS_ID); + } + + @Test + void revokeAclsForSubject_shouldSucceed() throws Exception { + setupSuccessfulDelete(); + + Result result = aclService.revokeAclsForSubject(OAUTH_SUBJECT, TOPIC, GROUP_PREFIX); + + assertThat(result.succeeded()).isTrue(); + verify(mockAdmin).deleteAcls(anyCollection()); + } + + @Test + void createAndRevoke_shouldTrackAndCleanUpAcls() throws Exception { + setupSuccessfulCreate(); + setupSuccessfulDelete(); + + aclService.createAclsForSubject(OAUTH_SUBJECT, TOPIC, GROUP_PREFIX, TRANSFER_PROCESS_ID); + aclService.revokeAclsForTransferProcess(TRANSFER_PROCESS_ID); + + // After revoke, re-revoking for the same ID should be a no-op (no admin call) + verify(adminClientFactory, times(2)).createAdmin(any()); + Result secondRevoke = aclService.revokeAclsForTransferProcess(TRANSFER_PROCESS_ID); + assertThat(secondRevoke.succeeded()).isTrue(); + verify(adminClientFactory, times(2)).createAdmin(any()); // still 2, no extra call + } + + private void setupSuccessfulCreate() throws ExecutionException, InterruptedException { + CreateAclsResult createResult = mock(CreateAclsResult.class); + KafkaFuture future = mock(KafkaFuture.class); + when(mockAdmin.createAcls(anyCollection())).thenReturn(createResult); + when(createResult.all()).thenReturn(future); + when(future.get()).thenReturn(null); + } + + private void setupSuccessfulDelete() throws ExecutionException, InterruptedException { + DeleteAclsResult deleteResult = mock(DeleteAclsResult.class); + KafkaFuture> future = mock(KafkaFuture.class); + when(mockAdmin.deleteAcls(anyCollection())).thenReturn(deleteResult); + when(deleteResult.all()).thenReturn(future); + when(future.get()).thenReturn(Collections.emptyList()); + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclServiceImplTestcontainersTest.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclServiceImplTestcontainersTest.java new file mode 100644 index 0000000000..7f75d75a44 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/acl/KafkaAclServiceImplTestcontainersTest.java @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.acl; + +import org.apache.kafka.clients.CommonClientConfigs; +import org.apache.kafka.clients.admin.Admin; +import org.apache.kafka.clients.admin.AdminClientConfig; +import org.apache.kafka.clients.admin.CreateTopicsResult; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.common.acl.AclBinding; +import org.apache.kafka.common.acl.AclBindingFilter; +import org.apache.kafka.common.acl.AclOperation; +import org.apache.kafka.common.config.SaslConfigs; +import org.apache.kafka.common.errors.GroupAuthorizationException; +import org.apache.kafka.common.errors.TopicAuthorizationException; +import org.apache.kafka.common.resource.ResourceType; +import org.apache.kafka.common.security.auth.SecurityProtocol; +import org.apache.kafka.common.security.plain.internals.PlainSaslServer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.eclipse.edc.spi.monitor.ConsoleMonitor; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.result.Result; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.kafka.KafkaContainer; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.ExecutionException; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.awaitility.Awaitility.await; + +@Testcontainers +class KafkaAclServiceImplTestcontainersTest { + + public static final String GROUP_ID = "groupId"; + private static final String TEST_TOPIC = "test-topic"; + private static final TopicPartition TEST_PARTITION = new TopicPartition(TEST_TOPIC, 0); + private static final Duration POLL_TIMEOUT = Duration.ofSeconds(2); + private static final Duration ACL_PROPAGATION_TIMEOUT = Duration.ofSeconds(30); + private static final Duration ACL_POLL_INTERVAL = Duration.ofMillis(200); + private static final String TEST_OAUTH_SUBJECT = "test-user"; + // Deliberately distinct from the OAuth subject: the GROUP ACL is named by the group prefix (PREFIXED), + // while the ACL principal remains the subject. + private static final String TEST_GROUP_PREFIX = "test-group-prefix"; + private static final String TEST_TRANSFER_PROCESS_ID = "transfer-process-123"; + private static final String UNAUTHORIZED_USER = "unauthorized-user"; + + private static final String ADMIN_LOGIN_MODULE = "org.apache.kafka.common.security.plain.PlainLoginModule required " + + "username=\"admin\" password=\"password\";"; + + @Container + static KafkaContainer kafkaContainer = new KafkaContainer(DockerImageName.parse("apache/kafka:4.0.0")) + .withEnv("KAFKA_AUTHORIZER_CLASS_NAME", "org.apache.kafka.metadata.authorizer.StandardAuthorizer") + .withEnv("KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND", "false") + .withEnv("KAFKA_SUPER_USERS", "User:admin;User:ANONYMOUS") + .withEnv("KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", "PLAINTEXT:SASL_PLAINTEXT,BROKER:PLAINTEXT,CONTROLLER:PLAINTEXT") + .withEnv("KAFKA_SASL_ENABLED_MECHANISMS", "PLAIN") + .withEnv("KAFKA_INTER_BROKER_LISTENER_NAME", "PLAINTEXT") + .withEnv("KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL", "PLAIN") + .withEnv("KAFKA_SASL_JAAS_CONFIG", ADMIN_LOGIN_MODULE) + .withEnv("KAFKA_LISTENER_NAME_PLAINTEXT_PLAIN_SASL_JAAS_CONFIG", + "org.apache.kafka.common.security.plain.PlainLoginModule required " + + "user_admin=\"password\" user_test-user=\"password\" user_unauthorized-user=\"password\";"); + + private KafkaAclServiceImpl aclService; + private Admin adminClient; + + @BeforeEach + void setUp() throws ExecutionException, InterruptedException { + Properties adminProperties = new Properties(); + adminProperties.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaContainer.getBootstrapServers()); + adminProperties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_PLAINTEXT.name()); + adminProperties.put(SaslConfigs.SASL_MECHANISM, PlainSaslServer.PLAIN_MECHANISM); + adminProperties.put(SaslConfigs.SASL_JAAS_CONFIG, ADMIN_LOGIN_MODULE); + + adminClient = Admin.create(adminProperties); + + Monitor monitor = new ConsoleMonitor(); + aclService = new KafkaAclServiceImpl(adminProperties, monitor); + + NewTopic newTopic = new NewTopic(TEST_TOPIC, 1, (short) 1); + CreateTopicsResult createResult = adminClient.createTopics(List.of(newTopic)); + createResult.all().get(); + } + + @AfterEach + void tearDown() { + if (adminClient != null) { + adminClient.deleteTopics(List.of(TEST_TOPIC)); + adminClient.deleteAcls(List.of(AclBindingFilter.ANY)); + adminClient.close(); + } + } + + @Test + void createAclsForSubject_shouldCreateAclsSuccessfully() { + Result result = aclService.createAclsForSubject(TEST_OAUTH_SUBJECT, TEST_TOPIC, TEST_GROUP_PREFIX, TEST_TRANSFER_PROCESS_ID); + + assertThat(result.succeeded()).isTrue(); + + Collection aclBindings = awaitAcls(acls -> acls.size() == 3); + + assertThat(aclBindings).hasSize(3); + + boolean hasTopicReadAcl = aclBindings.stream() + .anyMatch(acl -> acl.pattern().name().equals(TEST_TOPIC) && + acl.entry().principal().equals("User:" + TEST_OAUTH_SUBJECT) && + acl.entry().operation().equals(AclOperation.READ)); + + boolean hasTopicDescribeAcl = aclBindings.stream() + .anyMatch(acl -> acl.pattern().name().equals(TEST_TOPIC) && + acl.entry().principal().equals("User:" + TEST_OAUTH_SUBJECT) && + acl.entry().operation().equals(AclOperation.DESCRIBE)); + + boolean hasGroupReadAcl = aclBindings.stream() + .anyMatch(acl -> acl.pattern().name().equals(TEST_GROUP_PREFIX) && + acl.entry().principal().equals("User:" + TEST_OAUTH_SUBJECT) && + acl.entry().operation().equals(AclOperation.READ) && + acl.pattern().resourceType().equals(ResourceType.GROUP)); + + assertThat(hasTopicReadAcl).isTrue(); + assertThat(hasTopicDescribeAcl).isTrue(); + assertThat(hasGroupReadAcl).isTrue(); + } + + @Test + void topicAccess_withValidAcls_shouldBeAllowed() throws Exception { + Result aclResult = aclService.createAclsForSubject(TEST_OAUTH_SUBJECT, TEST_TOPIC, TEST_GROUP_PREFIX, TEST_TRANSFER_PROCESS_ID); + assertThat(aclResult.succeeded()).isTrue(); + + produceTestMessage(TEST_TOPIC, "test-key", "test-value"); + + Properties consumerProps = createConsumerProperties(TEST_OAUTH_SUBJECT); + + try (KafkaConsumer consumer = new KafkaConsumer<>(consumerProps)) { + consumer.assign(List.of(TEST_PARTITION)); + consumer.seekToBeginning(List.of(TEST_PARTITION)); + + ConsumerRecords records = pollUntilRecords(consumer); + + assertThat(records.count()).isEqualTo(1); + assertThat(records.iterator().next().value()).isEqualTo("test-value"); + } + } + + @Test + void topicAccess_withWrongUser_shouldBeBlocked() { + Result aclResult = aclService.createAclsForSubject(TEST_OAUTH_SUBJECT, TEST_TOPIC, TEST_GROUP_PREFIX, TEST_TRANSFER_PROCESS_ID); + assertThat(aclResult.succeeded()).isTrue(); + + Properties consumerProps = createConsumerProperties(UNAUTHORIZED_USER); + + try (KafkaConsumer consumer = new KafkaConsumer<>(consumerProps)) { + consumer.assign(List.of(TEST_PARTITION)); + assertThatThrownBy(() -> consumer.poll(POLL_TIMEOUT)).isInstanceOf(TopicAuthorizationException.class); + } + } + + @Test + void revokeAclsForTransferProcess_shouldRemoveAclsSuccessfully() { + Result createResult = aclService.createAclsForSubject(TEST_OAUTH_SUBJECT, TEST_TOPIC, TEST_GROUP_PREFIX, TEST_TRANSFER_PROCESS_ID); + assertThat(createResult.succeeded()).isTrue(); + + Collection aclsBeforeRevoke = awaitAcls(acls -> acls.size() == 3); + + Result revokeResult = aclService.revokeAclsForTransferProcess(TEST_TRANSFER_PROCESS_ID); + + assertThat(revokeResult.succeeded()).isTrue(); + + Collection aclsAfterRevoke = awaitAcls(Collection::isEmpty); + + assertThat(aclsBeforeRevoke).hasSize(3); + assertThat(aclsAfterRevoke).isEmpty(); + } + + @Test + void revokeAclsForSubject_shouldRemoveAclsSuccessfully() { + Result createResult = aclService.createAclsForSubject(TEST_OAUTH_SUBJECT, TEST_TOPIC, TEST_GROUP_PREFIX, TEST_TRANSFER_PROCESS_ID); + assertThat(createResult.succeeded()).isTrue(); + + Collection aclsBeforeRevoke = awaitAcls(acls -> acls.size() == 3); + + Result revokeResult = aclService.revokeAclsForSubject(TEST_OAUTH_SUBJECT, TEST_TOPIC, TEST_GROUP_PREFIX); + + assertThat(revokeResult.succeeded()).isTrue(); + + Collection aclsAfterRevoke = awaitAcls(Collection::isEmpty); + + assertThat(aclsBeforeRevoke).hasSize(3); + assertThat(aclsAfterRevoke).isEmpty(); + } + + @Test + void topicAccess_afterAclRevocation_shouldBeBlocked() throws Exception { + Result createResult = aclService.createAclsForSubject(TEST_OAUTH_SUBJECT, TEST_TOPIC, TEST_GROUP_PREFIX, TEST_TRANSFER_PROCESS_ID); + assertThat(createResult.succeeded()).isTrue(); + + produceTestMessage(TEST_TOPIC, "test-key", "test-value"); + + Properties consumerProps = createConsumerProperties(TEST_OAUTH_SUBJECT); + try (KafkaConsumer consumer = new KafkaConsumer<>(consumerProps)) { + consumer.assign(List.of(TEST_PARTITION)); + consumer.seekToBeginning(List.of(TEST_PARTITION)); + ConsumerRecords records = pollUntilRecords(consumer); + assertThat(records.count()).isEqualTo(1); + } + + Result revokeResult = aclService.revokeAclsForTransferProcess(TEST_TRANSFER_PROCESS_ID); + assertThat(revokeResult.succeeded()).isTrue(); + + try (KafkaConsumer consumer = new KafkaConsumer<>(consumerProps)) { + consumer.assign(List.of(TEST_PARTITION)); + assertThatThrownBy(() -> consumer.poll(POLL_TIMEOUT)).isInstanceOf(TopicAuthorizationException.class); + } + } + + @Test + void revokeAclsForTransferProcess_withNonExistentId_shouldSucceed() { + Result result = aclService.revokeAclsForTransferProcess("non-existent-id"); + + assertThat(result.succeeded()).isTrue(); + } + + @Test + void multipleTransferProcesses_shouldTrackAclsIndependently() { + String transferProcess1 = "transfer-1"; + String transferProcess2 = "transfer-2"; + String user1 = "user1"; + String user2 = "user2"; + + Result result1 = aclService.createAclsForSubject(user1, TEST_TOPIC, TEST_GROUP_PREFIX, transferProcess1); + Result result2 = aclService.createAclsForSubject(user2, TEST_TOPIC, TEST_GROUP_PREFIX, transferProcess2); + + assertThat(result1.succeeded()).isTrue(); + assertThat(result2.succeeded()).isTrue(); + + Result revokeResult = aclService.revokeAclsForTransferProcess(transferProcess1); + assertThat(revokeResult.succeeded()).isTrue(); + + Collection remainingAcls = awaitAcls(acls -> + acls.stream().noneMatch(acl -> acl.entry().principal().equals("User:" + user1)) && + acls.stream().anyMatch(acl -> acl.entry().principal().equals("User:" + user2))); + + boolean user2AclsExist = remainingAcls.stream() + .anyMatch(acl -> acl.entry().principal().equals("User:" + user2)); + + boolean user1AclsExist = remainingAcls.stream() + .anyMatch(acl -> acl.entry().principal().equals("User:" + user1)); + + assertThat(user2AclsExist).isTrue(); + assertThat(user1AclsExist).isFalse(); + } + + @Test + void consumerGroupJoin_withPrefixedGroup_shouldBeAllowed() throws Exception { + Result aclResult = aclService.createAclsForSubject(TEST_OAUTH_SUBJECT, TEST_TOPIC, TEST_GROUP_PREFIX, TEST_TRANSFER_PROCESS_ID); + assertThat(aclResult.succeeded()).isTrue(); + + produceTestMessage(TEST_TOPIC, "test-key", "test-value"); + + // group.id starts with the granted prefix. Joining the coordinator via subscribe() requires READ + // on the GROUP resource, which the fix scopes to the group prefix (not the subject) — so the same + // prefix handed to the consumer in the EDR is the one the broker authorizes. + Properties consumerProps = createConsumerProperties(TEST_OAUTH_SUBJECT, TEST_GROUP_PREFIX + "-consumer-1"); + + try (KafkaConsumer consumer = new KafkaConsumer<>(consumerProps)) { + consumer.subscribe(List.of(TEST_TOPIC)); + + int count = 0; + String value = null; + long deadline = System.currentTimeMillis() + 20_000; + while (count == 0 && System.currentTimeMillis() < deadline) { + ConsumerRecords records = consumer.poll(POLL_TIMEOUT); + count = records.count(); + if (count > 0) { + value = records.iterator().next().value(); + } + } + + assertThat(count).isEqualTo(1); + assertThat(value).isEqualTo("test-value"); + } + } + + @Test + void consumerGroupJoin_withNonPrefixedGroup_shouldBeBlocked() throws Exception { + Result aclResult = aclService.createAclsForSubject(TEST_OAUTH_SUBJECT, TEST_TOPIC, TEST_GROUP_PREFIX, TEST_TRANSFER_PROCESS_ID); + assertThat(aclResult.succeeded()).isTrue(); + + produceTestMessage(TEST_TOPIC, "test-key", "test-value"); + + // group.id outside the granted prefix: the subject has topic READ/DESCRIBE, but the GROUP READ ACL + // does not cover this group, so the coordinator join is denied. Guards against widening the ACL. + Properties consumerProps = createConsumerProperties(TEST_OAUTH_SUBJECT, "other-group"); + + try (KafkaConsumer consumer = new KafkaConsumer<>(consumerProps)) { + consumer.subscribe(List.of(TEST_TOPIC)); + assertThatThrownBy(() -> { + long deadline = System.currentTimeMillis() + 20_000; + while (System.currentTimeMillis() < deadline) { + consumer.poll(POLL_TIMEOUT); + } + }).isInstanceOf(GroupAuthorizationException.class); + } + } + + private Collection awaitAcls(Predicate> condition) { + return await() + .atMost(ACL_PROPAGATION_TIMEOUT) + .pollInterval(ACL_POLL_INTERVAL) + .until(() -> adminClient.describeAcls(AclBindingFilter.ANY).values().get(), condition); + } + + private ConsumerRecords pollUntilRecords(KafkaConsumer consumer) { + ConsumerRecords records = ConsumerRecords.empty(); + long deadline = System.currentTimeMillis() + ACL_PROPAGATION_TIMEOUT.toMillis(); + while (records.isEmpty() && System.currentTimeMillis() < deadline) { + records = consumer.poll(POLL_TIMEOUT); + } + return records; + } + + private Properties createConsumerProperties(String username) { + return createConsumerProperties(username, GROUP_ID); + } + + private Properties createConsumerProperties(String username, String groupId) { + Properties props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaContainer.getBootstrapServers()); + props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false"); + + props.put(SaslConfigs.SASL_MECHANISM, PlainSaslServer.PLAIN_MECHANISM); + props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_PLAINTEXT.name()); + props.put(SaslConfigs.SASL_JAAS_CONFIG, + ("org.apache.kafka.common.security.plain.PlainLoginModule required " + + "username=\"%s\" password=\"%s\";").formatted(username, "password")); + + return props; + } + + private void produceTestMessage(String topic, String key, String value) throws ExecutionException, InterruptedException { + Properties producerProps = new Properties(); + producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaContainer.getBootstrapServers()); + producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + producerProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_PLAINTEXT.name()); + producerProps.put(SaslConfigs.SASL_MECHANISM, PlainSaslServer.PLAIN_MECHANISM); + producerProps.put(SaslConfigs.SASL_JAAS_CONFIG, ADMIN_LOGIN_MODULE); + + try (KafkaProducer producer = new KafkaProducer<>(producerProps)) { + ProducerRecord producerRecord = new ProducerRecord<>(topic, key, value); + producer.send(producerRecord).get(); + producer.flush(); + } + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthServiceComponentTest.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthServiceComponentTest.java new file mode 100644 index 0000000000..c9bc1e2334 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthServiceComponentTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.auth; + +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.eclipse.edc.http.spi.EdcHttpClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.any; +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 KafkaOauthServiceComponentTest { + + private KafkaOauthServiceImpl oauthService; + private EdcHttpClient mockHttpClient; + + @BeforeEach + void setUp() { + mockHttpClient = mock(EdcHttpClient.class); + oauthService = new KafkaOauthServiceImpl(mockHttpClient, new ObjectMapper()); + } + + @Test + void shouldSuccessfullyRetrieveAccessToken() throws IOException { + OauthCredentials credentials = new OauthCredentials( + "https://example.com/token", + Optional.empty(), + "test-client", + "test-secret" + ); + + Response mockResponse = mock(Response.class); + ResponseBody mockResponseBody = mock(ResponseBody.class); + + when(mockResponse.isSuccessful()).thenReturn(true); + when(mockResponse.code()).thenReturn(200); + when(mockResponse.body()).thenReturn(mockResponseBody); + when(mockResponseBody.string()).thenReturn("{\"access_token\":\"test-access-token\",\"token_type\":\"Bearer\"}"); + + when(mockHttpClient.execute(any(Request.class))) + .thenAnswer(invocation -> { + Request request = invocation.getArgument(0); + + assertThat(request.method()).isEqualTo("POST"); + assertThat(request.url().encodedPath()).isEqualTo("/token"); + assertThat(request.header("Content-Type")).isEqualTo("application/x-www-form-urlencoded"); + + return mockResponse; + }); + + String accessToken = oauthService.getAccessToken(credentials); + + assertThat(accessToken).isEqualTo("test-access-token"); + verify(mockHttpClient, times(1)).execute(any(Request.class)); + } + + @Test + void shouldThrow_WhenResponseHasNoAccessToken() throws IOException { + OauthCredentials credentials = new OauthCredentials( + "https://example.com/token", + Optional.empty(), + "test-client", + "test-secret" + ); + + Response mockResponse = mock(Response.class); + ResponseBody mockResponseBody = mock(ResponseBody.class); + + when(mockResponse.isSuccessful()).thenReturn(true); + when(mockResponse.code()).thenReturn(200); + when(mockResponse.body()).thenReturn(mockResponseBody); + when(mockResponseBody.string()).thenReturn("{\"token_type\":\"Bearer\"}"); + + when(mockHttpClient.execute(any(Request.class))).thenReturn(mockResponse); + + assertThatThrownBy(() -> oauthService.getAccessToken(credentials)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("access_token"); + } + + @Test + void shouldHandleTokenRetrievalFailure() throws IOException { + OauthCredentials credentials = new OauthCredentials( + "https://example.com/token", + Optional.empty(), + "test-client", + "test-secret" + ); + + Response mockResponse = mock(Response.class); + + when(mockResponse.isSuccessful()).thenReturn(false); + when(mockResponse.code()).thenReturn(401); + + when(mockHttpClient.execute(any(Request.class))) + .thenAnswer(invocation -> { + Request request = invocation.getArgument(0); + + assertThat(request.method()).isEqualTo("POST"); + assertThat(request.url().encodedPath()).isEqualTo("/token"); + assertThat(request.header("Content-Type")).isEqualTo("application/x-www-form-urlencoded"); + + return mockResponse; + }); + + assertThatThrownBy(() -> oauthService.getAccessToken(credentials)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Oauth2 token endpoint returned HTTP 401"); + verify(mockHttpClient, times(1)).execute(any(Request.class)); + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthServiceImplTest.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthServiceImplTest.java new file mode 100644 index 0000000000..2e5a62f5b6 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/auth/KafkaOauthServiceImplTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.auth; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.eclipse.edc.http.spi.EdcHttpClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class KafkaOauthServiceImplTest { + + public static final String ACCESS_TOKEN_KEY = "access_token"; + private static final String TOKEN_URL = "https://token.url"; + private static final String REVOKE_URL = "https://revoke.url"; + private static final String CLIENT_ID = "clientId"; + private static final String CLIENT_SECRET = "clientSecret"; + private static final String TEST_TOKEN = "test-token"; + private static final String IO_ERROR_MESSAGE = "IO error"; + + private EdcHttpClient mockHttpClient; + private ObjectMapper mockObjectMapper; + private KafkaOauthServiceImpl oauthService; + private Response mockResponse; + + @BeforeEach + void setUp() { + mockHttpClient = mock(EdcHttpClient.class); + mockObjectMapper = mock(ObjectMapper.class); + mockResponse = mock(Response.class); + oauthService = new KafkaOauthServiceImpl(mockHttpClient, mockObjectMapper); + } + + @Nested + class RevokeTokenTests { + private OauthCredentials createCredentialsWithRevocationUrl() { + return new OauthCredentials(TOKEN_URL, Optional.of(REVOKE_URL), CLIENT_ID, CLIENT_SECRET); + } + + private OauthCredentials createCredentialsWithoutRevocationUrl() { + return new OauthCredentials(TOKEN_URL, Optional.empty(), CLIENT_ID, CLIENT_SECRET); + } + + @Test + void shouldExecuteSuccessfully_whenResponseIsSuccessful() throws IOException { + when(mockResponse.isSuccessful()).thenReturn(true); + when(mockHttpClient.execute(any(Request.class))).thenReturn(mockResponse); + + oauthService.revokeToken(createCredentialsWithRevocationUrl(), TEST_TOKEN); + + verify(mockHttpClient, times(1)).execute(any(Request.class)); + } + + @Test + void shouldThrowException_whenResponseIsNotSuccessful() throws IOException { + when(mockResponse.isSuccessful()).thenReturn(false); + when(mockResponse.code()).thenReturn(403); + when(mockHttpClient.execute(any(Request.class))).thenReturn(mockResponse); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> oauthService.revokeToken(createCredentialsWithRevocationUrl(), TEST_TOKEN)); + assertEquals("Revoke endpoint returned HTTP 403", exception.getMessage()); + verify(mockHttpClient, times(1)).execute(any(Request.class)); + } + + @Test + void shouldThrowException_whenIoExceptionOccurs() throws IOException { + when(mockHttpClient.execute(any(Request.class))).thenThrow(new IOException(IO_ERROR_MESSAGE)); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> oauthService.revokeToken(createCredentialsWithRevocationUrl(), TEST_TOKEN)); + assertEquals("Failed to revoke Oauth2 token", exception.getMessage()); + verify(mockHttpClient, times(1)).execute(any(Request.class)); + } + + @Test + void shouldDoNothing_whenRevocationUrlIsEmpty() throws IOException { + oauthService.revokeToken(createCredentialsWithoutRevocationUrl(), TEST_TOKEN); + + verify(mockHttpClient, never()).execute(any(Request.class)); + } + } + + @Nested + class GetAccessTokenTests { + private OauthCredentials createCredentials() { + return new OauthCredentials(TOKEN_URL, Optional.empty(), CLIENT_ID, CLIENT_SECRET); + } + + @Test + void shouldReturnAccessToken_whenResponseIsSuccessful() throws IOException { + String mockResponseBody = "{\"access_token\": \"test-token\"}"; + ResponseBody mockResponseBodyObj = mock(ResponseBody.class); + JsonNode mockJsonNode = mock(JsonNode.class); + JsonNode mockTokenNode = mock(JsonNode.class); + + when(mockResponse.isSuccessful()).thenReturn(true); + when(mockResponse.body()).thenReturn(mockResponseBodyObj); + when(mockResponseBodyObj.string()).thenReturn(mockResponseBody); + when(mockJsonNode.get(ACCESS_TOKEN_KEY)).thenReturn(mockTokenNode); + when(mockTokenNode.asText()).thenReturn(TEST_TOKEN); + when(mockHttpClient.execute(any(Request.class))).thenReturn(mockResponse); + when(mockObjectMapper.readTree(mockResponseBody)).thenReturn(mockJsonNode); + + String accessToken = oauthService.getAccessToken(createCredentials()); + + assertEquals(TEST_TOKEN, accessToken); + verify(mockHttpClient, times(1)).execute(any(Request.class)); + } + + @Test + void shouldThrowException_whenResponseIsNotSuccessful() throws IOException { + when(mockResponse.isSuccessful()).thenReturn(false); + when(mockResponse.code()).thenReturn(401); + when(mockHttpClient.execute(any(Request.class))).thenReturn(mockResponse); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> oauthService.getAccessToken(createCredentials())); + assertEquals("Oauth2 token endpoint returned HTTP 401", exception.getMessage()); + verify(mockHttpClient, times(1)).execute(any(Request.class)); + } + + @Test + void shouldThrowException_whenIoExceptionOccurs() throws IOException { + when(mockHttpClient.execute(any(Request.class))).thenThrow(new IOException(IO_ERROR_MESSAGE)); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> oauthService.getAccessToken(createCredentials())); + assertEquals("Failed to fetch Oauth2 token", exception.getMessage()); + verify(mockHttpClient, times(1)).execute(any(Request.class)); + } + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/flow/KafkaEndpointDataReferenceServiceTest.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/flow/KafkaEndpointDataReferenceServiceTest.java new file mode 100644 index 0000000000..898ff9b649 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/flow/KafkaEndpointDataReferenceServiceTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.flow; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.edc.connector.dataplane.spi.DataFlow; +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.tractusx.edc.dataplane.kafka.acl.KafkaAclService; +import org.junit.jupiter.api.Test; + +import java.util.Base64; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.GROUP_PREFIX; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.KAFKA_TYPE; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOKEN; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOPIC; +import static org.mockito.ArgumentMatchers.any; +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 KafkaEndpointDataReferenceServiceTest { + + private static final String FLOW_ID = "flow-1"; + private static final String GROUP = "group-prefix"; + + private final KafkaAclService aclService = mock(); + private final KafkaEndpointDataReferenceService service = + new KafkaEndpointDataReferenceService(aclService, mock(Monitor.class), new ObjectMapper()); + + @Test + void createEndpointDataReference_createsAcls_andReturnsEdr() { + var edr = edr(jwtWithSub("kafka-subject"), "test-topic", GROUP); + var dataFlow = dataFlow(edr); + when(aclService.createAclsForSubject(any(), any(), any(), any())).thenReturn(Result.success()); + + var result = service.createEndpointDataReference(dataFlow); + + assertThat(result.succeeded()).isTrue(); + assertThat(result.getContent()).isSameAs(edr); + verify(aclService).createAclsForSubject("kafka-subject", "test-topic", GROUP, FLOW_ID); + } + + @Test + void createEndpointDataReference_fails_whenNoActualSource() { + var dataFlow = mock(DataFlow.class); + when(dataFlow.getId()).thenReturn(FLOW_ID); + when(dataFlow.getActualSource()).thenReturn(null); + + assertThat(service.createEndpointDataReference(dataFlow).failed()).isTrue(); + } + + @Test + void createEndpointDataReference_fails_whenAclCreationFails() { + var dataFlow = dataFlow(edr(jwtWithSub("kafka-subject"), "test-topic", GROUP)); + when(aclService.createAclsForSubject(any(), any(), any(), any())).thenReturn(Result.failure("broker error")); + + assertThat(service.createEndpointDataReference(dataFlow).failed()).isTrue(); + } + + @Test + void createEndpointDataReference_returnsEdr_whenAclManagementDisabled() { + var noAcl = new KafkaEndpointDataReferenceService(null, mock(Monitor.class), new ObjectMapper()); + var edr = edr("any-token", "test-topic", GROUP); + + var result = noAcl.createEndpointDataReference(dataFlow(edr)); + + assertThat(result.succeeded()).isTrue(); + assertThat(result.getContent()).isSameAs(edr); + verifyNoInteractions(aclService); + } + + @Test + void revoke_revokesAcls_onSuspendOrTerminate() { + when(aclService.revokeAclsForTransferProcess(FLOW_ID)).thenReturn(Result.success()); + + var result = service.revokeEndpointDataReference(FLOW_ID, "suspended"); + + assertThat(result.succeeded()).isTrue(); + verify(aclService).revokeAclsForTransferProcess(FLOW_ID); + } + + @Test + void revoke_fails_whenAclRevocationFails() { + when(aclService.revokeAclsForTransferProcess(FLOW_ID)).thenReturn(Result.failure("broker error")); + + assertThat(service.revokeEndpointDataReference(FLOW_ID, "terminated").failed()).isTrue(); + } + + @Test + void revoke_isNoOp_whenAclManagementDisabled() { + var noAcl = new KafkaEndpointDataReferenceService(null, mock(Monitor.class), new ObjectMapper()); + + var result = noAcl.revokeEndpointDataReference(FLOW_ID, "suspended"); + + assertThat(result.succeeded()).isTrue(); + verifyNoInteractions(aclService); + } + + private static DataFlow dataFlow(DataAddress actualSource) { + var dataFlow = mock(DataFlow.class); + when(dataFlow.getId()).thenReturn(FLOW_ID); + when(dataFlow.getActualSource()).thenReturn(actualSource); + return dataFlow; + } + + private static DataAddress edr(String token, String topic, String groupPrefix) { + return DataAddress.Builder.newInstance() + .type(KAFKA_TYPE) + .property(TOKEN, token) + .property(TOPIC, topic) + .property(GROUP_PREFIX, groupPrefix) + .build(); + } + + private static String jwtWithSub(String sub) { + var enc = Base64.getUrlEncoder().withoutPadding(); + var header = enc.encodeToString("{\"alg\":\"none\"}".getBytes()); + var payload = enc.encodeToString(("{\"sub\":\"" + sub + "\"}").getBytes()); + var sig = enc.encodeToString("sig".getBytes()); + return header + "." + payload + "." + sig; + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaDeprovisionerTest.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaDeprovisionerTest.java new file mode 100644 index 0000000000..2fd20d9e72 --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaDeprovisionerTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.provision; + +import org.eclipse.edc.connector.dataplane.spi.provision.DeprovisionedResource; +import org.eclipse.edc.connector.dataplane.spi.provision.ProvisionResource; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.response.StatusResult; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.tractusx.edc.dataplane.kafka.acl.KafkaAclService; +import org.eclipse.tractusx.edc.dataplane.kafka.auth.KafkaOauthService; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.KAFKA_TYPE; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_ID; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_SECRET_KEY; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_TOKEN_URL; +import static org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisionConstants.KAFKA_RESOURCE_TYPE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class KafkaDeprovisionerTest { + + private static final String FLOW_ID = "flow-1"; + private static final String SECRET_KEY = "client-secret-key"; + private static final String TOKEN_VALUE = "minted-token"; + + private final Vault vault = mock(); + private final KafkaOauthService oauthService = mock(); + private final KafkaAclService aclService = mock(); + private final KafkaDeprovisioner deprovisioner = new KafkaDeprovisioner(vault, oauthService, aclService, mock(Monitor.class)); + + @Test + void deprovision_revokesAcls_token_andDeletesVaultSecret() throws Exception { + when(aclService.revokeAclsForTransferProcess(FLOW_ID)).thenReturn(Result.success()); + when(vault.resolveSecret(FLOW_ID)).thenReturn(TOKEN_VALUE); + when(vault.resolveSecret(SECRET_KEY)).thenReturn("secret-value"); + + StatusResult result = deprovisioner.deprovision(resource()).get(); + + assertThat(result.succeeded()).isTrue(); + verify(aclService).revokeAclsForTransferProcess(FLOW_ID); + verify(oauthService).revokeToken(any(), eq(TOKEN_VALUE)); + verify(vault).deleteSecret(FLOW_ID); + } + + @Test + void deprovision_isIdempotent_whenTokenAlreadyGone() throws Exception { + when(aclService.revokeAclsForTransferProcess(FLOW_ID)).thenReturn(Result.success()); + when(vault.resolveSecret(FLOW_ID)).thenReturn(null); + + StatusResult result = deprovisioner.deprovision(resource()).get(); + + assertThat(result.succeeded()).isTrue(); + verify(oauthService, never()).revokeToken(any(), any()); + verify(vault, never()).deleteSecret(any()); + } + + @Test + void deprovision_revokesToken_whenAclManagementDisabled() throws Exception { + var noAcl = new KafkaDeprovisioner(vault, oauthService, null, mock(Monitor.class)); + when(vault.resolveSecret(FLOW_ID)).thenReturn(TOKEN_VALUE); + when(vault.resolveSecret(SECRET_KEY)).thenReturn("secret-value"); + + StatusResult result = noAcl.deprovision(resource()).get(); + + assertThat(result.succeeded()).isTrue(); + verify(oauthService).revokeToken(any(), eq(TOKEN_VALUE)); + verify(vault).deleteSecret(FLOW_ID); + } + + @Test + void deprovision_fails_whenAclRevocationFails() throws Exception { + when(aclService.revokeAclsForTransferProcess(FLOW_ID)).thenReturn(Result.failure("broker error")); + + StatusResult result = deprovisioner.deprovision(resource()).get(); + + assertThat(result.failed()).isTrue(); + verify(oauthService, never()).revokeToken(any(), any()); + } + + @Test + void deprovision_fails_whenClientSecretMissing() throws Exception { + when(aclService.revokeAclsForTransferProcess(FLOW_ID)).thenReturn(Result.success()); + when(vault.resolveSecret(FLOW_ID)).thenReturn(TOKEN_VALUE); + when(vault.resolveSecret(SECRET_KEY)).thenReturn(null); + + StatusResult result = deprovisioner.deprovision(resource()).get(); + + assertThat(result.failed()).isTrue(); + } + + private ProvisionResource resource() { + var source = DataAddress.Builder.newInstance() + .type(KAFKA_TYPE) + .property(OAUTH_TOKEN_URL, "http://localhost:8080/token") + .property(OAUTH_CLIENT_ID, "client-id") + .property(OAUTH_CLIENT_SECRET_KEY, SECRET_KEY) + .build(); + return ProvisionResource.Builder.newInstance() + .id("resource-1") + .flowId(FLOW_ID) + .type(KAFKA_RESOURCE_TYPE) + .dataAddress(source) + .build(); + } +} diff --git a/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaProvisionerTest.java b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaProvisionerTest.java new file mode 100644 index 0000000000..03db19c00c --- /dev/null +++ b/edc-extensions/dataplane/kafka/kafka-broker-extension/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/provision/KafkaProvisionerTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.provision; + +import org.eclipse.edc.connector.dataplane.spi.provision.ProvisionResource; +import org.eclipse.edc.connector.dataplane.spi.provision.ProvisionedResource; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.response.StatusResult; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.tractusx.edc.dataplane.kafka.auth.KafkaOauthService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.BOOTSTRAP_SERVERS; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.GROUP_PREFIX; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.KAFKA_TYPE; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.MECHANISM; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_ID; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_SECRET_KEY; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_TOKEN_URL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.PROTOCOL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOKEN; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOPIC; +import static org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisionConstants.CONSUMER_GROUP_PREFIX_PROPERTY; +import static org.eclipse.tractusx.edc.dataplane.kafka.provision.KafkaProvisionConstants.KAFKA_RESOURCE_TYPE; +import static org.mockito.ArgumentMatchers.any; +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 KafkaProvisionerTest { + + private static final String FLOW_ID = "flow-1"; + private static final String SECRET_KEY = "client-secret-key"; + private static final String TOKEN_VALUE = "access-token"; + private static final String DATA_ADDRESS_GROUP_PREFIX = "provider-group-prefix"; + private static final String CONSUMER_BPN = "consumer-bpn"; + + private final Vault vault = mock(); + private final KafkaOauthService oauthService = mock(); + private KafkaProvisioner provisioner; + + @BeforeEach + void setUp() { + provisioner = new KafkaProvisioner(vault, oauthService, mock(Monitor.class)); + when(vault.resolveSecret(SECRET_KEY)).thenReturn("secret-value"); + when(oauthService.getAccessToken(any())).thenReturn(TOKEN_VALUE); + } + + @Test + void provision_mintsToken_andBuildsEdr() throws Exception { + StatusResult result = provisioner.provision(resource(DATA_ADDRESS_GROUP_PREFIX, CONSUMER_BPN)).get(); + + assertThat(result.succeeded()).isTrue(); + DataAddress edr = result.getContent().getDataAddress(); + assertThat(edr.getType()).isEqualTo(KAFKA_TYPE); + assertThat(edr.getStringProperty(TOPIC)).isEqualTo("test-topic"); + assertThat(edr.getStringProperty(TOKEN)).isEqualTo(TOKEN_VALUE); + // the kafka.group.prefix property is honored over the consumer BPN fallback + assertThat(edr.getStringProperty(GROUP_PREFIX)).isEqualTo(DATA_ADDRESS_GROUP_PREFIX); + + verify(vault).storeSecret(eq(FLOW_ID), eq(TOKEN_VALUE)); + } + + @Test + void provision_fallsBackToConsumerBpn_whenGroupPrefixAbsent() throws Exception { + StatusResult result = provisioner.provision(resource(null, CONSUMER_BPN)).get(); + + assertThat(result.succeeded()).isTrue(); + assertThat(result.getContent().getDataAddress().getStringProperty(GROUP_PREFIX)).isEqualTo(CONSUMER_BPN); + } + + @Test + void provision_fails_whenGroupPrefixUnresolvable() throws Exception { + StatusResult result = provisioner.provision(resource(null, null)).get(); + + assertThat(result.failed()).isTrue(); + assertThat(result.getFailureDetail()).contains("consumer-group prefix"); + } + + private ProvisionResource resource(String groupPrefix, String consumerBpn) { + var builder = DataAddress.Builder.newInstance() + .type(KAFKA_TYPE) + .property(TOPIC, "test-topic") + .property(BOOTSTRAP_SERVERS, "localhost:9092") + .property(PROTOCOL, "SASL_PLAINTEXT") + .property(MECHANISM, "OAUTHBEARER") + .property(OAUTH_TOKEN_URL, "http://localhost:8080/token") + .property(OAUTH_CLIENT_ID, "client-id") + .property(OAUTH_CLIENT_SECRET_KEY, SECRET_KEY); + if (groupPrefix != null) { + builder.property(GROUP_PREFIX, groupPrefix); + } + var resourceBuilder = ProvisionResource.Builder.newInstance() + .id("resource-1") + .flowId(FLOW_ID) + .type(KAFKA_RESOURCE_TYPE) + .dataAddress(builder.build()); + if (consumerBpn != null) { + resourceBuilder.property(CONSUMER_GROUP_PREFIX_PROPERTY, consumerBpn); + } + return resourceBuilder.build(); + } +} diff --git a/edc-extensions/dataplane/kafka/validator-data-address-kafka/build.gradle.kts b/edc-extensions/dataplane/kafka/validator-data-address-kafka/build.gradle.kts new file mode 100644 index 0000000000..216812a98c --- /dev/null +++ b/edc-extensions/dataplane/kafka/validator-data-address-kafka/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + `java-library` +} + +dependencies { + implementation(libs.edc.spi.core) + implementation(libs.edc.lib.validator) + implementation(project(":edc-extensions:dataplane:kafka:data-address-kafka")) + + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidator.java b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidator.java new file mode 100644 index 0000000000..b0950b6796 --- /dev/null +++ b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidator.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.validator; + +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.validator.spi.ValidationResult; +import org.eclipse.edc.validator.spi.Validator; +import org.eclipse.edc.validator.spi.Violation; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.BOOTSTRAP_SERVERS; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.MECHANISM; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_ID; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_SECRET_KEY; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_TOKEN_URL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.PROTOCOL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOPIC; + +public class KafkaBrokerDataAddressValidator implements Validator { + + public KafkaBrokerDataAddressValidator() { + } + + @Override + public ValidationResult validate(final DataAddress input) { + List violations = Stream.of( + TOPIC, + BOOTSTRAP_SERVERS, + MECHANISM, + PROTOCOL, + OAUTH_TOKEN_URL, + OAUTH_CLIENT_ID, + OAUTH_CLIENT_SECRET_KEY + ).map((final String it) -> { + String value = input.getStringProperty(it); + return value != null && !value.isBlank() ? null : Violation.violation("'%s' is a mandatory attribute".formatted(it), it, value); + }).filter(Objects::nonNull).toList(); + return violations.isEmpty() ? ValidationResult.success() : ValidationResult.failure(violations); + } +} diff --git a/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidatorExtension.java b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidatorExtension.java new file mode 100644 index 0000000000..8fbd27207e --- /dev/null +++ b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/main/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidatorExtension.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.validator; + +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.validator.spi.DataAddressValidatorRegistry; + +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.KAFKA_TYPE; + +@Extension(value = KafkaBrokerDataAddressValidatorExtension.NAME) +public class KafkaBrokerDataAddressValidatorExtension implements ServiceExtension { + public static final String NAME = "DataAddress KafkaBroker Validator"; + + @Inject + private DataAddressValidatorRegistry dataAddressValidatorRegistry; + + public KafkaBrokerDataAddressValidatorExtension() { + } + + @Override + public void initialize(final ServiceExtensionContext context) { + var validator = new KafkaBrokerDataAddressValidator(); + this.dataAddressValidatorRegistry.registerSourceValidator(KAFKA_TYPE, validator); + this.dataAddressValidatorRegistry.registerDestinationValidator(KAFKA_TYPE, validator); + } +} diff --git a/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..bb1387e57f --- /dev/null +++ b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# 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.dataplane.kafka.validator.KafkaBrokerDataAddressValidatorExtension diff --git a/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidatorExtensionTest.java b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidatorExtensionTest.java new file mode 100644 index 0000000000..5ff04c9b22 --- /dev/null +++ b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidatorExtensionTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.validator; + +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.validator.spi.DataAddressValidatorRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +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; + +@ExtendWith(DependencyInjectionExtension.class) +class KafkaBrokerDataAddressValidatorExtensionTest { + + private final DataAddressValidatorRegistry dataAddressValidatorRegistry = mock(); + + @BeforeEach + void setUp(final ServiceExtensionContext context) { + context.registerService(DataAddressValidatorRegistry.class, dataAddressValidatorRegistry); + } + + @Test + void initialize_shouldRegisterValidatorsWithKafkaType(final KafkaBrokerDataAddressValidatorExtension extension, final ServiceExtensionContext context) { + extension.initialize(context); + + verify(dataAddressValidatorRegistry, times(1)) + .registerSourceValidator(anyString(), any(KafkaBrokerDataAddressValidator.class)); + verify(dataAddressValidatorRegistry, times(1)) + .registerDestinationValidator(anyString(), any(KafkaBrokerDataAddressValidator.class)); + } +} diff --git a/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidatorTest.java b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidatorTest.java new file mode 100644 index 0000000000..6baa96ef06 --- /dev/null +++ b/edc-extensions/dataplane/kafka/validator-data-address-kafka/src/test/java/org/eclipse/tractusx/edc/dataplane/kafka/validator/KafkaBrokerDataAddressValidatorTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.dataplane.kafka.validator; + +import org.eclipse.edc.spi.types.domain.DataAddress; +import org.eclipse.edc.validator.spi.ValidationFailure; +import org.eclipse.edc.validator.spi.Violation; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.BOOTSTRAP_SERVERS; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.MECHANISM; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_ID; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_CLIENT_SECRET_KEY; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.OAUTH_TOKEN_URL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.PROTOCOL; +import static org.eclipse.tractusx.edc.dataplane.kafka.dataaddress.KafkaBrokerDataAddressSchema.TOPIC; + +class KafkaBrokerDataAddressValidatorTest { + + private final KafkaBrokerDataAddressValidator validator = new KafkaBrokerDataAddressValidator(); + + @Test + void shouldPass_whenDataAddressIsValid() { + var dataAddress = DataAddress.Builder.newInstance() + .type("Kafka") + .property(TOPIC, "topic.name") + .property(BOOTSTRAP_SERVERS, "any:98123") + .property(MECHANISM, "OAUTHBEARER") + .property(PROTOCOL, "SASL_PLAINTEXT") + .property(OAUTH_TOKEN_URL, "http://keycloak/token") + .property(OAUTH_CLIENT_ID, "client-id") + .property(OAUTH_CLIENT_SECRET_KEY, "clientSecretKey") + .build(); + + var result = validator.validate(dataAddress); + + assertThat(result).isSucceeded(); + } + + @Test + void shouldFail_whenRequiredFieldsAreMissing() { + var dataAddress = DataAddress.Builder.newInstance() + .type("Kafka") + .build(); + + var result = validator.validate(dataAddress); + + assertThat(result).isFailed().extracting(ValidationFailure::getViolations) + .satisfies(violations -> assertThat(violations).extracting(Violation::path) + .containsExactlyInAnyOrder(TOPIC, BOOTSTRAP_SERVERS, MECHANISM, PROTOCOL, OAUTH_TOKEN_URL, + OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET_KEY)); + } +} diff --git a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts index c8e1e9d1fe..57c39887dd 100644 --- a/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts +++ b/edc-extensions/dataspace-protocol/cx-dataspace-protocol/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { implementation(libs.edc.ih.spi.credentials) implementation(libs.dsp.spi.http) - implementation(libs.dsp.spi.v08) + implementation(project(":spi:dsp-spi-08")) implementation(libs.edc.spi.participant) implementation(libs.edc.spi.protocol) diff --git a/edc-extensions/dcp/README.md b/edc-extensions/dcp/README.md new file mode 100644 index 0000000000..0c3d1a1096 --- /dev/null +++ b/edc-extensions/dcp/README.md @@ -0,0 +1,3 @@ +## Purpose +The DCP extension provides implementations for the Decentralized Credential Protocol. +It handles credential scope extraction (from policies), Secure Token Service (STS) integration, and verifiable presentation caching. diff --git a/edc-extensions/dcp/cx-dcp/build.gradle.kts b/edc-extensions/dcp/cx-dcp/build.gradle.kts index d97a7b5593..0e9a7e9b5d 100644 --- a/edc-extensions/dcp/cx-dcp/build.gradle.kts +++ b/edc-extensions/dcp/cx-dcp/build.gradle.kts @@ -31,7 +31,7 @@ dependencies { implementation(libs.edc.spi.transfer) implementation(libs.edc.spi.catalog) implementation(libs.dsp.spi.v2025) - implementation(libs.dsp.spi.v08) + implementation(project(":spi:dsp-spi-08")) implementation(project(":spi:core-spi")) implementation(project(":edc-extensions:dcp:tx-dcp")) diff --git a/edc-extensions/dcp/cx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cx/CxDcpDefaultScopeExtension.java b/edc-extensions/dcp/cx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cx/CxDcpDefaultScopeExtension.java index b2b2bed45b..3439038ff7 100644 --- a/edc-extensions/dcp/cx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cx/CxDcpDefaultScopeExtension.java +++ b/edc-extensions/dcp/cx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cx/CxDcpDefaultScopeExtension.java @@ -29,7 +29,7 @@ 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.iam.iatp.scope.DefaultScopeExtractor; +import org.eclipse.tractusx.edc.iam.dcp.scope.DefaultScopeExtractor; import java.util.HashMap; import java.util.Map; @@ -38,8 +38,8 @@ import static java.lang.String.format; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DSP_SCOPE_V_2025_1; -import static org.eclipse.tractusx.edc.TxIatpConstants.DEFAULT_SCOPES; -import static org.eclipse.tractusx.edc.TxIatpConstants.V08_DEFAULT_SCOPES; +import static org.eclipse.tractusx.edc.TxDcpConstants.DEFAULT_SCOPES; +import static org.eclipse.tractusx.edc.TxDcpConstants.V08_DEFAULT_SCOPES; import static org.eclipse.tractusx.edc.iam.dcp.cx.CxDcpDefaultScopeExtension.NAME; @Extension(NAME) @@ -59,13 +59,13 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var defaultScopes = defaultScopes(context); + var defaultScopes = defaultScopes(); policyEngine.registerPostValidator(RequestCatalogPolicyContext.class, new DefaultScopeExtractor<>(defaultScopes)); policyEngine.registerPostValidator(RequestContractNegotiationPolicyContext.class, new DefaultScopeExtractor<>(defaultScopes)); policyEngine.registerPostValidator(RequestTransferProcessPolicyContext.class, new DefaultScopeExtractor<>(defaultScopes)); } - private Map> defaultScopes(ServiceExtensionContext context) { + private Map> defaultScopes() { var scopesByVersion = new HashMap>(); scopesByVersion.put(DSP_SCOPE_V_08, V08_DEFAULT_SCOPES); scopesByVersion.put(DSP_SCOPE_V_2025_1, DEFAULT_SCOPES); diff --git a/edc-extensions/dcp/cx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cx/scope/CxCredentialScopeExtractor.java b/edc-extensions/dcp/cx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cx/scope/CxCredentialScopeExtractor.java index b85d8cfda0..cda5d3ae50 100644 --- a/edc-extensions/dcp/cx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cx/scope/CxCredentialScopeExtractor.java +++ b/edc-extensions/dcp/cx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/cx/scope/CxCredentialScopeExtractor.java @@ -35,7 +35,7 @@ import java.util.Set; import static java.util.Collections.emptySet; -import static org.eclipse.tractusx.edc.TxIatpConstants.CREDENTIAL_TYPE_NAMESPACE; +import static org.eclipse.tractusx.edc.TxDcpConstants.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; diff --git a/edc-extensions/dcp/cx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cx/CxDcpDefaultScopeExtensionTest.java b/edc-extensions/dcp/cx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cx/CxDcpDefaultScopeExtensionTest.java index 1f7afdf0c5..8be7e32a4b 100644 --- a/edc-extensions/dcp/cx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cx/CxDcpDefaultScopeExtensionTest.java +++ b/edc-extensions/dcp/cx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cx/CxDcpDefaultScopeExtensionTest.java @@ -27,7 +27,7 @@ import org.eclipse.edc.policy.engine.spi.PolicyEngine; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.configuration.ConfigFactory; -import org.eclipse.tractusx.edc.iam.iatp.scope.DefaultScopeExtractor; +import org.eclipse.tractusx.edc.iam.dcp.scope.DefaultScopeExtractor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -39,9 +39,9 @@ import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DSP_SCOPE_V_2025_1; -import static org.eclipse.tractusx.edc.TxIatpConstants.DEFAULT_SCOPES; -import static org.eclipse.tractusx.edc.TxIatpConstants.V08_DEFAULT_SCOPES; -import static org.eclipse.tractusx.edc.iam.iatp.IatpDefaultScopeExtension.TX_IATP_DEFAULT_SCOPE_PREFIX; +import static org.eclipse.tractusx.edc.TxDcpConstants.DEFAULT_SCOPES; +import static org.eclipse.tractusx.edc.TxDcpConstants.V08_DEFAULT_SCOPES; +import static org.eclipse.tractusx.edc.iam.dcp.DcpDefaultScopeExtension.TX_DCP_DEFAULT_SCOPE_PREFIX; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -80,7 +80,7 @@ void initialize_withConfiguredScopes(ServiceExtensionContext context, CxDcpDefau "bar.type", "BarCredential", "bar.operation", "write" )); - when(context.getConfig(TX_IATP_DEFAULT_SCOPE_PREFIX)).thenReturn(cfg); + when(context.getConfig(TX_DCP_DEFAULT_SCOPE_PREFIX)).thenReturn(cfg); extension.initialize(context); var scopes = new HashMap>(); diff --git a/edc-extensions/dcp/cx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cx/scope/CxCredentialScopeExtractorTest.java b/edc-extensions/dcp/cx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cx/scope/CxCredentialScopeExtractorTest.java index 9d0be57f2e..318fae0ce0 100644 --- a/edc-extensions/dcp/cx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cx/scope/CxCredentialScopeExtractorTest.java +++ b/edc-extensions/dcp/cx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/cx/scope/CxCredentialScopeExtractorTest.java @@ -49,7 +49,7 @@ 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.TxDcpConstants.CREDENTIAL_TYPE_NAMESPACE; import static org.eclipse.tractusx.edc.iam.dcp.cx.scope.CxCredentialScopeExtractor.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; diff --git a/edc-extensions/dcp/tx-dcp-sts-div/build.gradle.kts b/edc-extensions/dcp/tx-dcp-sts-div/build.gradle.kts index 45cbeedf6d..809c7c2cef 100644 --- a/edc-extensions/dcp/tx-dcp-sts-div/build.gradle.kts +++ b/edc-extensions/dcp/tx-dcp-sts-div/build.gradle.kts @@ -30,7 +30,6 @@ dependencies { implementation(libs.edc.spi.participant.context.single) implementation(libs.edc.auth.oauth2.client) - testImplementation(libs.edc.junit) testImplementation(testFixtures(libs.edc.lib.http)) } diff --git a/edc-extensions/dcp/tx-dcp-sts-div/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtension.java b/edc-extensions/dcp/tx-dcp-sts-div/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtension.java index 7ff3d5452f..75fd503aac 100644 --- a/edc-extensions/dcp/tx-dcp-sts-div/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtension.java +++ b/edc-extensions/dcp/tx-dcp-sts-div/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtension.java @@ -32,7 +32,6 @@ import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.security.Vault; 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.div.DivSecureTokenService; @@ -43,8 +42,10 @@ @Extension(RemoteTokenServiceClientExtension.NAME) public class RemoteTokenServiceClientExtension implements ServiceExtension { - @Setting(value = "STS Div endpoint") - public static final String DIV_URL = "tx.edc.iam.sts.div.url"; + static final String DIV_URL = "tx.edc.iam.sts.div.url"; + + @Setting(key = DIV_URL, description = "STS Div endpoint", required = false) + private String divUrlConfig; protected static final String NAME = "Secure Token Service (STS) client extension"; @@ -69,8 +70,7 @@ public String name() { } @Provider - public SecureTokenService secureTokenService(ServiceExtensionContext context) { - var divUrlConfig = context.getSetting(DIV_URL, null); + public SecureTokenService secureTokenService() { return ofNullable(divUrlConfig) .map(PathUtils::removeTrailingSlash) .map(divUrl -> { diff --git a/edc-extensions/dcp/tx-dcp-sts-div/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtension.java b/edc-extensions/dcp/tx-dcp-sts-div/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtension.java index 4994e281a5..7d9e8961a6 100644 --- a/edc-extensions/dcp/tx-dcp-sts-div/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtension.java +++ b/edc-extensions/dcp/tx-dcp-sts-div/src/main/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtension.java @@ -24,29 +24,28 @@ import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Setting; import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.tractusx.edc.core.utils.PathUtils; -import static java.util.Optional.ofNullable; -import static org.eclipse.tractusx.edc.core.utils.ConfigUtil.missingMandatoryProperty; - /** * Configuration Extension for the STS OAuth2 client */ @Extension(StsClientConfigurationExtension.NAME) public class StsClientConfigurationExtension implements ServiceExtension { - @Setting(value = "STS OAuth2 endpoint for requesting a token") - public static final String TOKEN_URL = "edc.iam.sts.oauth.token.url"; + static final String TOKEN_URL = "edc.iam.sts.oauth.token.url"; + static final String CLIENT_ID = "edc.iam.sts.oauth.client.id"; + static final String CLIENT_SECRET_ALIAS = "edc.iam.sts.oauth.client.secret.alias"; - @Setting(value = "STS OAuth2 client id") - public static final String CLIENT_ID = "edc.iam.sts.oauth.client.id"; + @Setting(key = TOKEN_URL, description = "STS OAuth2 endpoint for requesting a token") + private String tokenUrl; - @Setting(value = "Vault alias of STS OAuth2 client secret") - public static final String CLIENT_SECRET_ALIAS = "edc.iam.sts.oauth.client.secret.alias"; + @Setting(key = CLIENT_ID, description = "STS OAuth2 client id") + private String clientId; - protected static final String NAME = "Secure Token Service (STS) client configuration extension"; + @Setting(key = CLIENT_SECRET_ALIAS, description = "Vault alias of STS OAuth2 client secret") + private String clientSecretAlias; + protected static final String NAME = "Secure Token Service (STS) client configuration extension"; @Override public String name() { @@ -54,25 +53,7 @@ public String name() { } @Provider - public StsRemoteClientConfiguration clientConfiguration(ServiceExtensionContext context) { - - var tokenUrl = ofNullable(context.getConfig().getString(TOKEN_URL, null)) - .map(PathUtils::removeTrailingSlash).orElse(null); - var clientId = context.getConfig().getString(CLIENT_ID, null); - var clientSecretAlias = context.getConfig().getString(CLIENT_SECRET_ALIAS, null); - - var monitor = context.getMonitor().withPrefix("STS Client for DIV"); - if (tokenUrl == null) { - missingMandatoryProperty(monitor, TOKEN_URL); - } - if (clientId == null) { - missingMandatoryProperty(monitor, CLIENT_ID); - } - if (clientSecretAlias == null) { - missingMandatoryProperty(monitor, CLIENT_SECRET_ALIAS); - } - return new StsRemoteClientConfiguration(tokenUrl, clientId, clientSecretAlias); + public StsRemoteClientConfiguration clientConfiguration() { + return new StsRemoteClientConfiguration(PathUtils.removeTrailingSlash(tokenUrl), clientId, clientSecretAlias); } - - } diff --git a/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtensionTest.java b/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtensionTest.java index ae5ab124ef..8ce2ba4d33 100644 --- a/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtensionTest.java +++ b/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/RemoteTokenServiceClientExtensionTest.java @@ -19,40 +19,46 @@ package org.eclipse.tractusx.edc.iam.dcp.sts; +import org.eclipse.edc.boot.system.injection.ObjectFactory; 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; -import org.eclipse.edc.spi.system.configuration.Config; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.tractusx.edc.iam.dcp.sts.div.DivSecureTokenService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import java.util.HashMap; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.edc.iam.dcp.sts.RemoteTokenServiceClientExtension.DIV_URL; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(DependencyInjectionExtension.class) public class RemoteTokenServiceClientExtensionTest { @Test - void initialize(ServiceExtensionContext context, RemoteTokenServiceClientExtension extension) { - var config = mock(Config.class); + void initialize(ServiceExtensionContext context, ObjectFactory factory) { + var configMap = Map.of( + DIV_URL, "url" + ); + var config = ConfigFactory.fromMap(configMap); when(context.getConfig()).thenReturn(config); - when(config.getString(DIV_URL, null)).thenReturn("url"); - assertThat(extension.secureTokenService(context)).isInstanceOf(DivSecureTokenService.class); + + var extension = factory.constructInstance(RemoteTokenServiceClientExtension.class); + assertThat(extension.secureTokenService()).isInstanceOf(DivSecureTokenService.class); } @Test - void initialize_whenUrlIsMissing_fallsBackToRemoteSts(ServiceExtensionContext context, RemoteTokenServiceClientExtension extension) { - var monitor = context.getMonitor(); - var prefixeMonitor = mock(Monitor.class); - when(monitor.withPrefix(anyString())).thenReturn(prefixeMonitor); + void initialize_whenUrlIsMissing_fallsBackToRemoteSts(ServiceExtensionContext context, ObjectFactory f) { + var configMap = new HashMap(); + var config = ConfigFactory.fromMap(configMap); + when(context.getConfig()).thenReturn(config); - assertThat(extension.secureTokenService(context)) + var extension = f.constructInstance(RemoteTokenServiceClientExtension.class); + + assertThat(extension.secureTokenService()) .isInstanceOf(RemoteSecureTokenService.class); } - } diff --git a/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtensionTest.java b/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtensionTest.java index 92414a90a2..22326a93d9 100644 --- a/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtensionTest.java +++ b/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/StsClientConfigurationExtensionTest.java @@ -21,29 +21,36 @@ import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.system.configuration.Config; +import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.tractusx.edc.iam.dcp.sts.StsClientConfigurationExtension.CLIENT_ID; import static org.eclipse.tractusx.edc.iam.dcp.sts.StsClientConfigurationExtension.CLIENT_SECRET_ALIAS; import static org.eclipse.tractusx.edc.iam.dcp.sts.StsClientConfigurationExtension.TOKEN_URL; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(DependencyInjectionExtension.class) public class StsClientConfigurationExtensionTest { - @Test - void initialize(ServiceExtensionContext context, StsClientConfigurationExtension extension) { - var config = mock(Config.class); + @BeforeEach + void setup(ServiceExtensionContext context) { + var configMap = Map.of( + TOKEN_URL, "url", + CLIENT_ID, "clientId", + CLIENT_SECRET_ALIAS, "clientSecretAlias" + ); + var config = ConfigFactory.fromMap(configMap); when(context.getConfig()).thenReturn(config); - when(config.getString(TOKEN_URL, null)).thenReturn("url"); - when(config.getString(CLIENT_ID, null)).thenReturn("clientId"); - when(config.getString(CLIENT_SECRET_ALIAS, null)).thenReturn("clientSecretAlias"); + } - assertThat(extension.clientConfiguration(context)).satisfies(stsConfig -> { + @Test + void initialize(ServiceExtensionContext context, StsClientConfigurationExtension extension) { + assertThat(extension.clientConfiguration()).satisfies(stsConfig -> { assertThat(stsConfig.clientId()).isEqualTo("clientId"); assertThat(stsConfig.clientSecretAlias()).isEqualTo("clientSecretAlias"); assertThat(stsConfig.tokenUrl()).isEqualTo("url"); diff --git a/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/div/DivSecureTokenServiceTest.java b/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/div/DivSecureTokenServiceTest.java index c3eb5e7685..ddba9b060d 100644 --- a/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/div/DivSecureTokenServiceTest.java +++ b/edc-extensions/dcp/tx-dcp-sts-div/src/test/java/org/eclipse/tractusx/edc/iam/dcp/sts/div/DivSecureTokenServiceTest.java @@ -58,7 +58,7 @@ public class DivSecureTokenServiceTest { - static final String DIV_URL = "http://localhost:8080/iatp"; + static final String DIV_URL = "http://localhost:8080/dcp"; private final Monitor monitor = mock(Monitor.class); private final DivOauth2Client oauth2Client = mock(DivOauth2Client.class); private final ObjectMapper mapper = new ObjectMapper(); diff --git a/edc-extensions/dcp/tx-dcp/build.gradle.kts b/edc-extensions/dcp/tx-dcp/build.gradle.kts index 3e92bdd65d..81893fff12 100644 --- a/edc-extensions/dcp/tx-dcp/build.gradle.kts +++ b/edc-extensions/dcp/tx-dcp/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { implementation(libs.edc.spi.transfer) implementation(libs.edc.spi.catalog) implementation(libs.dsp.spi.v2025) - implementation(libs.dsp.spi.v08) + implementation(project(":spi:dsp-spi-08")) implementation(project(":spi:core-spi")) implementation(project(":core:core-utils")) diff --git a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/IatpDefaultScopeExtension.java b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/DcpDefaultScopeExtension.java similarity index 79% rename from edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/IatpDefaultScopeExtension.java rename to edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/DcpDefaultScopeExtension.java index d6bb6b8694..e9f9d8c490 100644 --- a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/IatpDefaultScopeExtension.java +++ b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/DcpDefaultScopeExtension.java @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.iam.iatp; +package org.eclipse.tractusx.edc.iam.dcp; import org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext; import org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext; @@ -31,7 +31,7 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.configuration.Config; -import org.eclipse.tractusx.edc.iam.iatp.scope.DefaultScopeExtractor; +import org.eclipse.tractusx.edc.iam.dcp.scope.DefaultScopeExtractor; import java.util.HashMap; import java.util.Map; @@ -41,23 +41,24 @@ import static java.lang.String.format; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DSP_SCOPE_V_2025_1; -import static org.eclipse.tractusx.edc.iam.iatp.IatpDefaultScopeExtension.NAME; +import static org.eclipse.tractusx.edc.iam.dcp.DcpDefaultScopeExtension.NAME; @Extension(NAME) -public class IatpDefaultScopeExtension implements ServiceExtension { +public class DcpDefaultScopeExtension implements ServiceExtension { - public static final String TX_IATP_DEFAULT_SCOPE_PREFIX = "tx.edc.iam.iatp.default-scopes"; + public static final String TX_DCP_DEFAULT_SCOPE_PREFIX = "tx.edc.iam.dcp.default-scopes"; - public static final String TX_IATP_DEFAULT_SCOPE_PREFIX_CONFIG_ALIAS = TX_IATP_DEFAULT_SCOPE_PREFIX + ".."; + public static final String TX_DCP_DEFAULT_SCOPE_PREFIX_CONFIG_ALIAS = TX_DCP_DEFAULT_SCOPE_PREFIX + ".."; - @Setting(context = TX_IATP_DEFAULT_SCOPE_PREFIX_CONFIG_ALIAS, value = "The alias of the scope e.g. org.eclipse.edc.vc.type", required = true) + @Setting(context = TX_DCP_DEFAULT_SCOPE_PREFIX_CONFIG_ALIAS, description = "The alias of the scope e.g. org.eclipse.edc.vc.type") public static final String ALIAS = "alias"; - @Setting(context = TX_IATP_DEFAULT_SCOPE_PREFIX_CONFIG_ALIAS, value = "The alias of the scope e.g. MembershipCredential", required = true) + @Setting(context = TX_DCP_DEFAULT_SCOPE_PREFIX_CONFIG_ALIAS, description = "The alias of the scope e.g. MembershipCredential") public static final String TYPE = "type"; - @Setting(context = TX_IATP_DEFAULT_SCOPE_PREFIX_CONFIG_ALIAS, value = "The alias of the scope e.g. read", required = true) + @Setting(context = TX_DCP_DEFAULT_SCOPE_PREFIX_CONFIG_ALIAS, description = "The alias of the scope e.g. read") public static final String OPERATION = "operation"; + static final String NAME = "Tractusx default scope extension"; @Inject private PolicyEngine policyEngine; @@ -81,7 +82,7 @@ public void initialize(ServiceExtensionContext context) { } private Map> defaultScopes(ServiceExtensionContext context) { - var config = context.getConfig(TX_IATP_DEFAULT_SCOPE_PREFIX); + var config = context.getConfig(TX_DCP_DEFAULT_SCOPE_PREFIX); var scopes = config.partition().map(this::createScope).collect(Collectors.toSet()); var scopesByVersion = new HashMap>(); if (!scopes.isEmpty()) { diff --git a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/DefaultScopeExtractor.java b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/scope/DefaultScopeExtractor.java similarity index 98% rename from edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/DefaultScopeExtractor.java rename to edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/scope/DefaultScopeExtractor.java index ea6f33edcb..e65ac0212c 100644 --- a/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/iatp/scope/DefaultScopeExtractor.java +++ b/edc-extensions/dcp/tx-dcp/src/main/java/org/eclipse/tractusx/edc/iam/dcp/scope/DefaultScopeExtractor.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.iam.iatp.scope; +package org.eclipse.tractusx.edc.iam.dcp.scope; import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule; diff --git a/edc-extensions/dcp/tx-dcp/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dcp/tx-dcp/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension index 98a37b4e5a..8e3a3544fc 100644 --- a/edc-extensions/dcp/tx-dcp/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ b/edc-extensions/dcp/tx-dcp/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -17,4 +17,4 @@ # SPDX-License-Identifier: Apache-2.0 ################################################################################# -org.eclipse.tractusx.edc.iam.iatp.IatpDefaultScopeExtension +org.eclipse.tractusx.edc.iam.dcp.DcpDefaultScopeExtension diff --git a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/IatpDefaultScopeExtensionTest.java b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/DcpDefaultScopeExtensionTest.java similarity index 89% rename from edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/IatpDefaultScopeExtensionTest.java rename to edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/DcpDefaultScopeExtensionTest.java index f84dd8ee6f..5d319dabf5 100644 --- a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/IatpDefaultScopeExtensionTest.java +++ b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/DcpDefaultScopeExtensionTest.java @@ -18,7 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.iam.iatp; +package org.eclipse.tractusx.edc.iam.dcp; import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; import org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext; @@ -28,7 +28,7 @@ import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.configuration.ConfigFactory; -import org.eclipse.tractusx.edc.iam.iatp.scope.DefaultScopeExtractor; +import org.eclipse.tractusx.edc.iam.dcp.scope.DefaultScopeExtractor; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -41,7 +41,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.DSP_SCOPE_V_2025_1; -import static org.eclipse.tractusx.edc.iam.iatp.IatpDefaultScopeExtension.TX_IATP_DEFAULT_SCOPE_PREFIX; +import static org.eclipse.tractusx.edc.iam.dcp.DcpDefaultScopeExtension.TX_DCP_DEFAULT_SCOPE_PREFIX; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -51,7 +51,7 @@ import static org.mockito.Mockito.when; @ExtendWith(DependencyInjectionExtension.class) -public class IatpDefaultScopeExtensionTest { +public class DcpDefaultScopeExtensionTest { private final PolicyEngine policyEngine = mock(); @@ -61,7 +61,7 @@ void setup(ServiceExtensionContext context) { } @Test - void initialize(ServiceExtensionContext context, IatpDefaultScopeExtension extension) { + void initialize(ServiceExtensionContext context, DcpDefaultScopeExtension extension) { extension.initialize(context); verify(policyEngine, never()).registerPostValidator(eq(RequestCatalogPolicyContext.class), any()); @@ -70,7 +70,7 @@ void initialize(ServiceExtensionContext context, IatpDefaultScopeExtension exten } @Test - void initialize_withConfiguredScopes(ServiceExtensionContext context, IatpDefaultScopeExtension extension) { + void initialize_withConfiguredScopes(ServiceExtensionContext context, DcpDefaultScopeExtension extension) { var cfg = ConfigFactory.fromMap(Map.of( "foo.alias", "org.test.alias.foo", "foo.type", "FooCredential", @@ -79,7 +79,7 @@ void initialize_withConfiguredScopes(ServiceExtensionContext context, IatpDefaul "bar.type", "BarCredential", "bar.operation", "write" )); - when(context.getConfig(TX_IATP_DEFAULT_SCOPE_PREFIX)).thenReturn(cfg); + when(context.getConfig(TX_DCP_DEFAULT_SCOPE_PREFIX)).thenReturn(cfg); extension.initialize(context); var expectedScopes = Set.of("org.test.alias.foo:FooCredential:read", "org.test.alias.bar:BarCredential:write"); @@ -94,11 +94,11 @@ void initialize_withConfiguredScopes(ServiceExtensionContext context, IatpDefaul } @Test - void initialize_fails_withBadConfiguredScopes(ServiceExtensionContext context, IatpDefaultScopeExtension extension) { + void initialize_fails_withBadConfiguredScopes(ServiceExtensionContext context, DcpDefaultScopeExtension extension) { var cfg = ConfigFactory.fromMap(Map.of( "foo.alias", "org.test.alias.foo" )); - when(context.getConfig(TX_IATP_DEFAULT_SCOPE_PREFIX)).thenReturn(cfg); + when(context.getConfig(TX_DCP_DEFAULT_SCOPE_PREFIX)).thenReturn(cfg); assertThatThrownBy(() -> extension.initialize(context)).isInstanceOf(EdcException.class); } diff --git a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/DefaultScopeExtractorTest.java b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/scope/DefaultScopeExtractorTest.java similarity index 98% rename from edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/DefaultScopeExtractorTest.java rename to edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/scope/DefaultScopeExtractorTest.java index 774b31e0c9..da11078033 100644 --- a/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/iatp/scope/DefaultScopeExtractorTest.java +++ b/edc-extensions/dcp/tx-dcp/src/test/java/org/eclipse/tractusx/edc/iam/dcp/scope/DefaultScopeExtractorTest.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.iam.iatp.scope; +package org.eclipse.tractusx.edc.iam.dcp.scope; import org.eclipse.edc.connector.controlplane.catalog.spi.CatalogRequestMessage; import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; diff --git a/edc-extensions/dcp/verifiable-presentation-cache/build.gradle.kts b/edc-extensions/dcp/verifiable-presentation-cache/build.gradle.kts index 859b277f66..504b654ebd 100644 --- a/edc-extensions/dcp/verifiable-presentation-cache/build.gradle.kts +++ b/edc-extensions/dcp/verifiable-presentation-cache/build.gradle.kts @@ -27,10 +27,11 @@ 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.lib.verifiable.credentials) implementation(libs.edc.identity.vc.ldp) implementation(libs.edc.identity.vc.jwt) - implementation(libs.edc.decentralized.claims.service) + implementation(libs.edc.iam.decentralized.claims.core) + implementation(libs.edc.iam.decentralized.claims.service) implementation(project(":spi:dcp-spi")) 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 index df7f00fb2b..a013e5ed59 100644 --- 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 @@ -25,6 +25,7 @@ plugins { dependencies { implementation(project(":spi:did-document-service-spi")) implementation(libs.edc.runtime.metamodel) + implementation(libs.edc.spi.participant.context.single) implementation(libs.edc.spi.identity.did) implementation(libs.dsp.spi.http) diff --git a/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java b/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java index a4c2e9e8ad..1fabf1b1f9 100644 --- a/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java +++ b/edc-extensions/did-document/did-document-service-self-registration/src/main/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtension.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.edc.did.document.service.self.registration; import org.eclipse.edc.iam.did.spi.document.Service; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; 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; @@ -51,13 +52,15 @@ public class DidDocumentServiceSelfRegistrationExtension implements ServiceExten @Inject(required = false) private DidDocumentServiceClient didDocumentServiceClient; + @Inject + private SingleParticipantContextSupplier participantContextSupplier; + @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 @@ -78,7 +81,7 @@ public void shutdown() { private void selfRegisterDidDocumentService(@NotNull DidDocumentServiceClient client) { var wellKnownUrl = String.join("", dspBaseAddress.get(), VERSION_METADATA_ENDPOINT_PATH); - validatedServiceId(serviceId) + validatedServiceId(getServiceId()) .onFailure(failure -> monitor.severe(failure.getFailureDetail())) .map(validatedServiceId -> new Service(validatedServiceId, DATA_SERVICE_TYPE, wellKnownUrl)) .onSuccess(service -> @@ -90,7 +93,7 @@ private void selfRegisterDidDocumentService(@NotNull DidDocumentServiceClient cl private void selfUnregisterDidDocumentService(@NotNull DidDocumentServiceClient client) { - validatedServiceId(serviceId) + validatedServiceId(getServiceId()) .onSuccess(validatedServiceId -> client.deleteById(validatedServiceId) .onFailure(failure -> monitor.severe("Failed to unregister DID Document service: %s, reason: %s".formatted(failure.getFailureDetail(), failure.getReason()))) @@ -98,6 +101,16 @@ private void selfUnregisterDidDocumentService(@NotNull DidDocumentServiceClient ); } + private String getServiceId() { + if (serviceId == null) { + var participantContext = participantContextSupplier.get(); + if (participantContext != null && participantContext.succeeded()) { + serviceId = "urn:" + participantContext.getContent().getParticipantContextId(); + } + } + return serviceId; + } + private Result validatedServiceId(String serviceId) { if (serviceId == null || serviceId.isBlank()) { diff --git a/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java b/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java index 939196717a..505f71f24d 100644 --- a/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java +++ b/edc-extensions/did-document/did-document-service-self-registration/src/test/java/org/eclipse/tractusx/edc/did/document/service/self/registration/DidDocumentServiceSelfRegistrationExtensionTest.java @@ -22,6 +22,8 @@ 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.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.participantcontext.spi.types.ParticipantContext; import org.eclipse.edc.protocol.dsp.http.spi.api.DspBaseWebhookAddress; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.result.ServiceResult; @@ -31,9 +33,6 @@ 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; @@ -50,7 +49,8 @@ @ExtendWith(DependencyInjectionExtension.class) class DidDocumentServiceSelfRegistrationExtensionTest { - private static final String SERVICE_ID = "did:web:example.com:connector1"; + private static final String PARTICIPANT_CONTEXT_ID = "019d8c8d-42ed-7739-b413-eed5dfd74b41"; + private static final String SERVICE_ID = "urn:" + PARTICIPANT_CONTEXT_ID; private static final String DSP_URL = "https://protocol.edc.com/api/v1/dsp"; private final Monitor monitor = mock(Monitor.class); @@ -61,6 +61,9 @@ class DidDocumentServiceSelfRegistrationExtensionTest { void setup(ServiceExtensionContext context) { context.registerService(Monitor.class, monitor); context.registerService(DspBaseWebhookAddress.class, dspBaseAddress); + var participantContext = ParticipantContext.Builder.newInstance().participantContextId(PARTICIPANT_CONTEXT_ID) + .identity("did:web:example.com").build(); + context.registerService(SingleParticipantContextSupplier.class, () -> ServiceResult.success(participantContext)); when(dspBaseAddress.get()).thenReturn(DSP_URL); } @@ -68,8 +71,7 @@ void setup(ServiceExtensionContext context) { void start_shouldSelfRegister_whenEnabledAndClientPresent(ServiceExtensionContext context, ObjectFactory objectFactory) { var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", - "tx.edc.did.service.self.deregistration.enabled", "true", - "tx.edc.did.service.self.registration.id", SERVICE_ID); + "tx.edc.did.service.self.deregistration.enabled", "true"); when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); when(didDocumentServiceClient.update(any(Service.class))).thenReturn(ServiceResult.success()); @@ -94,8 +96,7 @@ void start_shouldSelfRegister_whenEnabledAndClientPresent(ServiceExtensionContex void start_selfRegister_whenEnabledAndClientReturnsFailure(ServiceExtensionContext context, ObjectFactory objectFactory) { var settings = Map.of("tx.edc.did.service.self.registration.enabled", "true", - "tx.edc.did.service.self.deregistration.enabled", "true", - "tx.edc.did.service.self.registration.id", SERVICE_ID); + "tx.edc.did.service.self.deregistration.enabled", "true"); when(context.getConfig()).thenReturn(ConfigFactory.fromMap(settings)); context.registerService(DidDocumentServiceClient.class, didDocumentServiceClient); when(didDocumentServiceClient.update(any(Service.class))).thenReturn(ServiceResult.unexpected()); @@ -145,61 +146,4 @@ void start_shouldNotSelfRegister_whenDisabledAndClientPresent(ServiceExtensionCo 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/dsp/dsp-catalog-08/build.gradle.kts b/edc-extensions/dsp/dsp-catalog-08/build.gradle.kts new file mode 100644 index 0000000000..de7120d266 --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/build.gradle.kts @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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` +} + +dependencies { + api(project(":edc-extensions:dsp:dsp-catalog-08:dsp-catalog-http-api-08")) + api(project(":edc-extensions:dsp:dsp-catalog-08:dsp-catalog-transform-08")) +} diff --git a/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/build.gradle.kts b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/build.gradle.kts new file mode 100644 index 0000000000..0bfccacdc9 --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/build.gradle.kts @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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` + id(libs.plugins.swagger.get().pluginId) +} + +dependencies { + api(project(":spi:dsp-spi-08")) + api(libs.dsp.spi.http) + api(libs.edc.spi.core) + api(libs.edc.spi.web) + api(libs.edc.spi.jsonld) + + api(libs.edc.spi.controlplane) + + implementation(libs.edc.lib.jersey.providers) + implementation(libs.edc.lib.dsp.catalog.validation) + implementation(libs.edc.lib.dsp.catalog.http.api) + + implementation(libs.jakarta.rsApi) + + testImplementation(testFixtures(libs.edc.core.jersey)) + testImplementation(libs.edc.junit) + testImplementation(libs.restAssured) + testImplementation(testFixtures(libs.edc.lib.dsp.catalog.http.api)) +} + +edcBuild { + swagger { + apiGroup.set("dsp-api") + } +} diff --git a/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/catalog/http/api/DspCatalogApiV08Extension.java b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/catalog/http/api/DspCatalogApiV08Extension.java new file mode 100644 index 0000000000..d4a01451ca --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/catalog/http/api/DspCatalogApiV08Extension.java @@ -0,0 +1,128 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.catalog.http.api; + +import org.eclipse.edc.connector.controlplane.catalog.spi.DataService; +import org.eclipse.edc.connector.controlplane.catalog.spi.DataServiceRegistry; +import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogProtocolService; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.protocol.dsp.catalog.http.api.controller.DspCatalogApiController08; +import org.eclipse.edc.protocol.dsp.catalog.http.api.decorator.Base64continuationTokenSerDes; +import org.eclipse.edc.protocol.dsp.catalog.http.api.decorator.ContinuationTokenManagerImpl; +import org.eclipse.edc.protocol.dsp.catalog.validation.CatalogRequestMessageValidator; +import org.eclipse.edc.protocol.dsp.http.spi.message.ContinuationTokenManager; +import org.eclipse.edc.protocol.dsp.http.spi.message.DspRequestHandler; +import org.eclipse.edc.protocol.spi.ProtocolWebhookResolver; +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.query.CriterionOperatorRegistry; +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 java.util.Optional; + +import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_TRANSFORMER_CONTEXT_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.DspCatalogPropertyAndTypeNames.DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; + +/** + * Creates and registers the controller for dataspace protocol v0.8 catalog requests. + */ +@Extension(value = DspCatalogApiV08Extension.NAME) +public class DspCatalogApiV08Extension implements ServiceExtension { + + public static final String NAME = "Dataspace Protocol Catalog v08 Extension"; + + @Inject + private WebService webService; + @Inject + private CatalogProtocolService service; + @Inject + private DataServiceRegistry dataServiceRegistry; + @Inject + private JsonObjectValidatorRegistry validatorRegistry; + @Inject + private DspRequestHandler dspRequestHandler; + @Inject + private CriterionOperatorRegistry criterionOperatorRegistry; + @Inject + private ProtocolWebhookResolver protocolWebhookResolver; + @Inject + private TypeTransformerRegistry transformerRegistry; + @Inject + private Monitor monitor; + @Inject + private TypeManager typeManager; + @Inject + private JsonLd jsonLd; + + @Inject + private SingleParticipantContextSupplier participantContextSupplier; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + registerValidators(); + + webService.registerResource(ApiContext.PROTOCOL, new DspCatalogApiController08(service, dspRequestHandler, continuationTokenManager(monitor), participantContextSupplier)); + webService.registerDynamicResource(ApiContext.PROTOCOL, DspCatalogApiController08.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, DSP_SCOPE_V_08)); + } + + @Override + public void prepare() { + registerDataService(); + } + + private void registerDataService() { + dataServiceRegistry.register(DATASPACE_PROTOCOL_HTTP, this::createDataService); + } + + private DataService createDataService(String participantContextId, String protocol) { + return Optional.ofNullable(protocolWebhookResolver.getWebhook(participantContextId, protocol)) + .map(webhook -> DataService.Builder.newInstance() + .endpointDescription("dspace:connector") + .endpointUrl(webhook.url()) + .build()).orElse(null); + } + + private ContinuationTokenManager continuationTokenManager(Monitor monitor) { + var continuationTokenSerDes = new Base64continuationTokenSerDes(transformerRegistry.forContext(DSP_TRANSFORMER_CONTEXT_V_08), jsonLd); + return new ContinuationTokenManagerImpl(continuationTokenSerDes, DSP_NAMESPACE_V_08, monitor); + } + + private void registerValidators() { + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM), CatalogRequestMessageValidator.instance(criterionOperatorRegistry, DSP_NAMESPACE_V_08)); + } +} diff --git a/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/catalog/http/api/controller/DspCatalogApiController08.java b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/catalog/http/api/controller/DspCatalogApiController08.java new file mode 100644 index 0000000000..fd06b5d453 --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/catalog/http/api/controller/DspCatalogApiController08.java @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.edc.protocol.dsp.catalog.http.api.controller; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogProtocolService; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.protocol.dsp.http.spi.message.ContinuationTokenManager; +import org.eclipse.edc.protocol.dsp.http.spi.message.DspRequestHandler; + +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; + +/** + * Provides the HTTP endpoint for receiving catalog requests. + */ +@Consumes({APPLICATION_JSON}) +@Produces({APPLICATION_JSON}) +@Path(BASE_PATH) +public class DspCatalogApiController08 extends BaseDspCatalogApiController { + + public DspCatalogApiController08(CatalogProtocolService service, DspRequestHandler dspRequestHandler, ContinuationTokenManager continuationTokenManager, SingleParticipantContextSupplier participantContextSupplier) { + super(service, dspRequestHandler, continuationTokenManager, participantContextSupplier, DATASPACE_PROTOCOL_HTTP, DSP_NAMESPACE_V_08); + } + +} diff --git a/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..1f45517c84 --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. +# +# 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.edc.protocol.dsp.catalog.http.api.DspCatalogApiV08Extension \ No newline at end of file diff --git a/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/catalog/http/api/DspCatalogApiV08ExtensionTest.java b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/catalog/http/api/DspCatalogApiV08ExtensionTest.java new file mode 100644 index 0000000000..5fa9b90a2e --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/catalog/http/api/DspCatalogApiV08ExtensionTest.java @@ -0,0 +1,62 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.catalog.http.api; + +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.protocol.dsp.spi.transform.DspProtocolTypeTransformerRegistry; +import org.eclipse.edc.protocol.spi.DataspaceProfileContextRegistry; +import org.eclipse.edc.spi.result.Result; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +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.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.DspCatalogPropertyAndTypeNames.DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(DependencyInjectionExtension.class) +class DspCatalogApiV08ExtensionTest { + + private final JsonObjectValidatorRegistry validatorRegistry = mock(); + private final DataspaceProfileContextRegistry versionRegistry = mock(); + private final DspProtocolTypeTransformerRegistry dspTransformerRegistry = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(JsonObjectValidatorRegistry.class, validatorRegistry); + context.registerService(DataspaceProfileContextRegistry.class, versionRegistry); + context.registerService(DspProtocolTypeTransformerRegistry.class, dspTransformerRegistry); + + when(dspTransformerRegistry.forProtocol(any())).thenReturn(Result.success(mock())); + } + + @Test + void shouldRegisterMessageValidator(DspCatalogApiV08Extension extension, ServiceExtensionContext context) { + extension.initialize(context); + + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CATALOG_REQUEST_MESSAGE_TERM)), any()); + } +} diff --git a/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/catalog/http/api/controller/DspCatalogApiController08Test.java b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/catalog/http/api/controller/DspCatalogApiController08Test.java new file mode 100644 index 0000000000..bfa0d0bf22 --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/catalog/http/api/controller/DspCatalogApiController08Test.java @@ -0,0 +1,45 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.catalog.http.api.controller; + +import org.eclipse.edc.jsonld.spi.JsonLdNamespace; +import org.eclipse.edc.junit.annotations.ApiTest; + +import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; + +@ApiTest +class DspCatalogApiController08Test extends DspCatalogApiControllerTestBase { + + @Override + protected String basePath() { + return BASE_PATH; + } + + @Override + protected JsonLdNamespace namespace() { + return DSP_NAMESPACE_V_08; + } + + @Override + protected Object controller() { + return new DspCatalogApiController08(service, dspRequestHandler, continuationTokenManager, participantContextSupplier); + } +} diff --git a/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-transform-08/build.gradle.kts b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-transform-08/build.gradle.kts new file mode 100644 index 0000000000..31f468d992 --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-transform-08/build.gradle.kts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` +} + +dependencies { + api(libs.edc.spi.core) + api(project(":spi:dsp-spi-08")) + + implementation(libs.edc.lib.transform) + implementation(libs.edc.lib.dsp.catalog.transform) +} diff --git a/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-transform-08/src/main/java/org/eclipse/edc/protocol/dsp/catalog/transform/DspCatalogTransformV08Extension.java b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-transform-08/src/main/java/org/eclipse/edc/protocol/dsp/catalog/transform/DspCatalogTransformV08Extension.java new file mode 100644 index 0000000000..513b598c3d --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-transform-08/src/main/java/org/eclipse/edc/protocol/dsp/catalog/transform/DspCatalogTransformV08Extension.java @@ -0,0 +1,84 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.edc.protocol.dsp.catalog.transform; + +import jakarta.json.Json; +import org.eclipse.edc.participant.spi.ParticipantIdMapper; +import org.eclipse.edc.protocol.dsp.catalog.transform.from.JsonObjectFromCatalogErrorTransformer; +import org.eclipse.edc.protocol.dsp.catalog.transform.from.JsonObjectFromCatalogRequestMessageTransformer; +import org.eclipse.edc.protocol.dsp.catalog.transform.from.JsonObjectFromCatalogTransformer; +import org.eclipse.edc.protocol.dsp.catalog.transform.from.JsonObjectFromDataServiceTransformer; +import org.eclipse.edc.protocol.dsp.catalog.transform.from.JsonObjectFromDatasetTransformer; +import org.eclipse.edc.protocol.dsp.catalog.transform.from.JsonObjectFromDistributionTransformer; +import org.eclipse.edc.protocol.dsp.catalog.transform.to.JsonObjectToCatalogRequestMessageTransformer; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; + +import java.util.Map; + +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_TRANSFORMER_CONTEXT_V_08; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; + +/** + * Provides the transformers for DSP v0.8 catalog message types via the {@link TypeTransformerRegistry}. + */ +@Extension(value = DspCatalogTransformV08Extension.NAME) +public class DspCatalogTransformV08Extension implements ServiceExtension { + + public static final String NAME = "Dataspace Protocol Catalog Transform v08 Extension"; + + @Inject + private TypeTransformerRegistry registry; + + @Inject + private TypeManager typeManager; + + @Inject + private ParticipantIdMapper participantIdMapper; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + registerTransformers(); + } + + private void registerTransformers() { + var jsonFactory = Json.createBuilderFactory(Map.of()); + + var dspApiTransformerRegistry = registry.forContext(DSP_TRANSFORMER_CONTEXT_V_08); + dspApiTransformerRegistry.register(new JsonObjectFromCatalogRequestMessageTransformer(jsonFactory, DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectToCatalogRequestMessageTransformer(DSP_NAMESPACE_V_08)); + + dspApiTransformerRegistry.register(new JsonObjectFromCatalogTransformer(jsonFactory, typeManager, JSON_LD, participantIdMapper, DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectFromDatasetTransformer(jsonFactory, typeManager, JSON_LD)); + dspApiTransformerRegistry.register(new JsonObjectFromDistributionTransformer(jsonFactory)); + dspApiTransformerRegistry.register(new JsonObjectFromDataServiceTransformer(jsonFactory)); + dspApiTransformerRegistry.register(new JsonObjectFromCatalogErrorTransformer(jsonFactory, DSP_NAMESPACE_V_08)); + } +} diff --git a/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-transform-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-transform-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..0b1721bb5f --- /dev/null +++ b/edc-extensions/dsp/dsp-catalog-08/dsp-catalog-transform-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. +# +# 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.edc.protocol.dsp.catalog.transform.DspCatalogTransformV08Extension \ No newline at end of file diff --git a/edc-extensions/dsp/dsp-http-api-configuration-08/build.gradle.kts b/edc-extensions/dsp/dsp-http-api-configuration-08/build.gradle.kts new file mode 100644 index 0000000000..980f55f957 --- /dev/null +++ b/edc-extensions/dsp/dsp-http-api-configuration-08/build.gradle.kts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` +} + +dependencies { + api(libs.edc.spi.catalog) + api(libs.edc.spi.core) + api(libs.dsp.spi) + api(project(":spi:dsp-spi-08")) + api(libs.dsp.spi.http) + + implementation(libs.edc.lib.transform) + implementation(libs.edc.transform.controlplane) + + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/dsp/dsp-http-api-configuration-08/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationV08Extension.java b/edc-extensions/dsp/dsp-http-api-configuration-08/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationV08Extension.java new file mode 100644 index 0000000000..dda6b6c990 --- /dev/null +++ b/edc-extensions/dsp/dsp-http-api-configuration-08/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationV08Extension.java @@ -0,0 +1,137 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.http.api.configuration; + +import jakarta.json.Json; +import org.eclipse.edc.connector.controlplane.transform.edc.from.JsonObjectFromAssetTransformer; +import org.eclipse.edc.connector.controlplane.transform.edc.to.JsonObjectToAssetTransformer; +import org.eclipse.edc.connector.controlplane.transform.odrl.OdrlTransformersFactory; +import org.eclipse.edc.connector.controlplane.transform.odrl.from.JsonObjectFromPolicyTransformer; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.participant.spi.ParticipantIdMapper; +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.protocol.spi.DefaultParticipantIdExtractionFunction; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.query.CriterionOperatorRegistry; +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.transform.transformer.dspace.from.JsonObjectFromDataAddressDspaceTransformer; +import org.eclipse.edc.transform.transformer.dspace.to.JsonObjectToDataAddressDspaceTransformer; +import org.eclipse.edc.transform.transformer.edc.from.JsonObjectFromCriterionTransformer; +import org.eclipse.edc.transform.transformer.edc.from.JsonObjectFromQuerySpecTransformer; +import org.eclipse.edc.transform.transformer.edc.to.JsonObjectToCriterionTransformer; +import org.eclipse.edc.transform.transformer.edc.to.JsonObjectToQuerySpecTransformer; +import org.eclipse.edc.transform.transformer.edc.to.JsonValueToGenericTypeTransformer; + +import java.util.Map; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; +import static org.eclipse.edc.jsonld.spi.Namespaces.DCAT_PREFIX; +import static org.eclipse.edc.jsonld.spi.Namespaces.DCAT_SCHEMA; +import static org.eclipse.edc.jsonld.spi.Namespaces.DCT_PREFIX; +import static org.eclipse.edc.jsonld.spi.Namespaces.DCT_SCHEMA; +import static org.eclipse.edc.jsonld.spi.Namespaces.DSPACE_PREFIX; +import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_PREFIX; +import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; +import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_TRANSFORMER_CONTEXT_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.V_08; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_PREFIX; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; + +/** + * Registers protocol webhook, API transformers and namespaces for DSP v0.8. + */ +@Extension(value = DspApiConfigurationV08Extension.NAME) +public class DspApiConfigurationV08Extension implements ServiceExtension { + + public static final String NAME = "Dataspace Protocol API Configuration v08 Extension"; + + @Inject + private TypeManager typeManager; + @Inject + private JsonLd jsonLd; + @Inject + private TypeTransformerRegistry transformerRegistry; + @Inject + private ParticipantIdMapper participantIdMapper; + @Inject + CriterionOperatorRegistry criterionOperatorRegistry; + @Inject + private DspBaseWebhookAddress dspWebhookAddress; + @Inject + private DataspaceProfileContextRegistry dataspaceProfileContextRegistry; + @Inject + private DefaultParticipantIdExtractionFunction participantIdExtractionFunction; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + dataspaceProfileContextRegistry.registerDefault(new DataspaceProfileContext(DATASPACE_PROTOCOL_HTTP, V_08, () -> dspWebhookAddress.get(), participantIdExtractionFunction)); + + // registers ns for DSP scope + registerNamespaces(); + registerTransformers(); + } + + private void registerNamespaces() { + jsonLd.registerNamespace(DCAT_PREFIX, DCAT_SCHEMA, DSP_SCOPE_V_08); + jsonLd.registerNamespace(DCT_PREFIX, DCT_SCHEMA, DSP_SCOPE_V_08); + jsonLd.registerNamespace(ODRL_PREFIX, ODRL_SCHEMA, DSP_SCOPE_V_08); + jsonLd.registerNamespace(DSPACE_PREFIX, DSP_NAMESPACE_V_08.namespace(), DSP_SCOPE_V_08); + jsonLd.registerNamespace(VOCAB, EDC_NAMESPACE, DSP_SCOPE_V_08); + jsonLd.registerNamespace(EDC_PREFIX, EDC_NAMESPACE, DSP_SCOPE_V_08); + } + + private void registerTransformers() { + var jsonBuilderFactory = Json.createBuilderFactory(Map.of()); + + // EDC model to JSON-LD transformers + var dspApiTransformerRegistry = transformerRegistry.forContext(DSP_TRANSFORMER_CONTEXT_V_08); + dspApiTransformerRegistry.register(new JsonObjectFromAssetTransformer(jsonBuilderFactory, typeManager, JSON_LD)); + dspApiTransformerRegistry.register(new JsonObjectFromQuerySpecTransformer(jsonBuilderFactory)); + dspApiTransformerRegistry.register(new JsonObjectFromCriterionTransformer(jsonBuilderFactory, typeManager, JSON_LD)); + + // JSON-LD to EDC model transformers + // ODRL Transformers + OdrlTransformersFactory.jsonObjectToOdrlTransformers(participantIdMapper).forEach(dspApiTransformerRegistry::register); + + dspApiTransformerRegistry.register(new JsonValueToGenericTypeTransformer(typeManager, JSON_LD)); + dspApiTransformerRegistry.register(new JsonObjectToAssetTransformer()); + dspApiTransformerRegistry.register(new JsonObjectToQuerySpecTransformer()); + dspApiTransformerRegistry.register(new JsonObjectToCriterionTransformer(criterionOperatorRegistry)); + dspApiTransformerRegistry.register(new JsonObjectToDataAddressDspaceTransformer(DSP_NAMESPACE_V_08)); + + dspApiTransformerRegistry.register(new JsonObjectFromPolicyTransformer(jsonBuilderFactory, participantIdMapper)); + dspApiTransformerRegistry.register(new JsonObjectFromDataAddressDspaceTransformer(jsonBuilderFactory, typeManager, JSON_LD, DSP_NAMESPACE_V_08)); + } +} diff --git a/edc-extensions/dsp/dsp-http-api-configuration-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dsp/dsp-http-api-configuration-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..b32d3c89f4 --- /dev/null +++ b/edc-extensions/dsp/dsp-http-api-configuration-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. +# +# 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.edc.protocol.dsp.http.api.configuration.DspApiConfigurationV08Extension \ No newline at end of file diff --git a/edc-extensions/dsp/dsp-http-api-configuration-08/src/test/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationV08ExtensionTest.java b/edc-extensions/dsp/dsp-http-api-configuration-08/src/test/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationV08ExtensionTest.java new file mode 100644 index 0000000000..a4b624b77c --- /dev/null +++ b/edc-extensions/dsp/dsp-http-api-configuration-08/src/test/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationV08ExtensionTest.java @@ -0,0 +1,109 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.edc.protocol.dsp.http.api.configuration; + +import org.eclipse.edc.boot.system.injection.ObjectFactory; +import org.eclipse.edc.jsonld.spi.JsonLd; +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.edc.spi.system.configuration.ConfigFactory; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.util.Map; + +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; +import static org.eclipse.edc.jsonld.spi.Namespaces.DCAT_PREFIX; +import static org.eclipse.edc.jsonld.spi.Namespaces.DCAT_SCHEMA; +import static org.eclipse.edc.jsonld.spi.Namespaces.DCT_PREFIX; +import static org.eclipse.edc.jsonld.spi.Namespaces.DCT_SCHEMA; +import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_PREFIX; +import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; +import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_PREFIX; +import static org.mockito.ArgumentMatchers.any; +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 DspApiConfigurationV08ExtensionTest { + + private final String webhookUrl = "http://webhook"; + + private final TypeManager typeManager = mock(); + private final JsonLd jsonLd = mock(); + private final DataspaceProfileContextRegistry dataspaceProfileContextRegistry = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(TypeManager.class, typeManager); + context.registerService(JsonLd.class, jsonLd); + context.registerService(DspBaseWebhookAddress.class, () -> webhookUrl); + context.registerService(DataspaceProfileContextRegistry.class, dataspaceProfileContextRegistry); + TypeTransformerRegistry typeTransformerRegistry = mock(); + when(typeTransformerRegistry.forContext(any())).thenReturn(mock()); + context.registerService(TypeTransformerRegistry.class, typeTransformerRegistry); + + when(typeManager.getMapper(any())).thenReturn(mock()); + } + + @Test + void shouldUseInjectedBaseWebhook(DspApiConfigurationV08Extension extension, ServiceExtensionContext context) { + when(context.getConfig()).thenReturn(ConfigFactory.empty()); + + extension.initialize(context); + + verify(dataspaceProfileContextRegistry).registerDefault(argThat(it -> it.name().equals(DATASPACE_PROTOCOL_HTTP) && it.webhook().url().equals(webhookUrl))); + } + + @Test + void shouldRegisterCorrectProtocolWebhooks_whenWellKnownPathsEnabled(ServiceExtensionContext context, ObjectFactory factory) { + when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of( + "edc.dsp.well-known-path.enabled", "true")) + ); + var extension = factory.constructInstance(DspApiConfigurationV08Extension.class); + + extension.initialize(context); + + verify(dataspaceProfileContextRegistry).registerDefault(argThat(it -> it.name().equals(DATASPACE_PROTOCOL_HTTP) && it.webhook().url().equals(webhookUrl))); + } + + @Test + void initialize_shouldRegisterNamespaces(DspApiConfigurationV08Extension extension, ServiceExtensionContext context) { + extension.initialize(context); + + verify(jsonLd).registerNamespace(DCAT_PREFIX, DCAT_SCHEMA, DSP_SCOPE_V_08); + verify(jsonLd).registerNamespace(DCT_PREFIX, DCT_SCHEMA, DSP_SCOPE_V_08); + verify(jsonLd).registerNamespace(ODRL_PREFIX, ODRL_SCHEMA, DSP_SCOPE_V_08); + verify(jsonLd).registerNamespace(VOCAB, EDC_NAMESPACE, DSP_SCOPE_V_08); + verify(jsonLd).registerNamespace(EDC_PREFIX, EDC_NAMESPACE, DSP_SCOPE_V_08); + verify(jsonLd).registerNamespace(ODRL_PREFIX, ODRL_SCHEMA, DSP_SCOPE_V_08); + } + +} diff --git a/edc-extensions/dsp/dsp-http-dispatcher-08/build.gradle.kts b/edc-extensions/dsp/dsp-http-dispatcher-08/build.gradle.kts new file mode 100644 index 0000000000..e8ef99b108 --- /dev/null +++ b/edc-extensions/dsp/dsp-http-dispatcher-08/build.gradle.kts @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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` +} + +dependencies { + api(libs.edc.spi.core) + api(libs.dsp.spi.http) +} diff --git a/edc-extensions/dsp/dsp-http-dispatcher-08/src/main/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpDispatcherV08Extension.java b/edc-extensions/dsp/dsp-http-dispatcher-08/src/main/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpDispatcherV08Extension.java new file mode 100644 index 0000000000..ef10aa9345 --- /dev/null +++ b/edc-extensions/dsp/dsp-http-dispatcher-08/src/main/java/org/eclipse/edc/protocol/dsp/http/dispatcher/DspHttpDispatcherV08Extension.java @@ -0,0 +1,44 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.http.dispatcher; + +import org.eclipse.edc.protocol.dsp.http.spi.dispatcher.DspHttpRemoteMessageDispatcher; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.message.RemoteMessageDispatcherRegistry; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; + +/** + * Registers the message dispatcher for DSP v0.8. + */ +public class DspHttpDispatcherV08Extension implements ServiceExtension { + + @Inject + private RemoteMessageDispatcherRegistry dispatcherRegistry; + @Inject + private DspHttpRemoteMessageDispatcher dispatcher; + + @Override + public void initialize(ServiceExtensionContext context) { + dispatcherRegistry.register(DATASPACE_PROTOCOL_HTTP, dispatcher); + } +} diff --git a/edc-extensions/dsp/dsp-http-dispatcher-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dsp/dsp-http-dispatcher-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..5417f334e5 --- /dev/null +++ b/edc-extensions/dsp/dsp-http-dispatcher-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2025 Cofinity-X +# +# 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.edc.protocol.dsp.http.dispatcher.DspHttpDispatcherV08Extension diff --git a/edc-extensions/dsp/dsp-negotiation-08/build.gradle.kts b/edc-extensions/dsp/dsp-negotiation-08/build.gradle.kts new file mode 100644 index 0000000000..1a3c2582d0 --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/build.gradle.kts @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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` +} + +dependencies { + api(project(":edc-extensions:dsp:dsp-negotiation-08:dsp-negotiation-http-api-08")) + api(project(":edc-extensions:dsp:dsp-negotiation-08:dsp-negotiation-transform-08")) +} diff --git a/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/build.gradle.kts b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/build.gradle.kts new file mode 100644 index 0000000000..be54080411 --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/build.gradle.kts @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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` + id(libs.plugins.swagger.get().pluginId) +} + +dependencies { + api(libs.dsp.spi) + api(project(":spi:dsp-spi-08")) + api(libs.dsp.spi.http) + api(libs.edc.spi.core) + api(libs.edc.spi.web) + api(libs.edc.spi.controlplane) + api(libs.edc.ext.jsonld) + + implementation(libs.edc.lib.dsp.negotiation.validation) + implementation(libs.edc.lib.dsp.negotiation.http.api) + implementation(libs.edc.lib.jersey.providers) + + + implementation(libs.jakarta.rsApi) + + testImplementation(libs.edc.junit) + testImplementation(testFixtures(libs.edc.core.jersey)) + testImplementation(libs.restAssured) + testImplementation(testFixtures(libs.edc.lib.dsp.negotiation.http.api)) + +} + +edcBuild { + swagger { + apiGroup.set("dsp-api") + } +} diff --git a/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/DspNegotiationApiV08Extension.java b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/DspNegotiationApiV08Extension.java new file mode 100644 index 0000000000..dea1a7be2d --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/DspNegotiationApiV08Extension.java @@ -0,0 +1,99 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.negotiation.http.api; + +import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationProtocolService; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.protocol.dsp.http.spi.message.DspRequestHandler; +import org.eclipse.edc.protocol.dsp.negotiation.http.api.controller.DspNegotiationApiController08; +import org.eclipse.edc.protocol.dsp.negotiation.validation.ContractAgreementMessageValidator; +import org.eclipse.edc.protocol.dsp.negotiation.validation.ContractAgreementVerificationMessageValidator; +import org.eclipse.edc.protocol.dsp.negotiation.validation.ContractNegotiationEventMessageValidator; +import org.eclipse.edc.protocol.dsp.negotiation.validation.ContractNegotiationTerminationMessageValidator; +import org.eclipse.edc.protocol.dsp.negotiation.validation.ContractOfferMessageValidator; +import org.eclipse.edc.protocol.dsp.negotiation.validation.ContractRequestMessageValidator; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.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 static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_AGREEMENT_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_AGREEMENT_VERIFICATION_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_NEGOTIATION_EVENT_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_NEGOTIATION_TERMINATION_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_OFFER_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_REQUEST_MESSAGE_TERM; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; + +/** + * Creates and registers the controller for dataspace protocol v0.8 negotiation requests. + */ +@Extension(value = DspNegotiationApiV08Extension.NAME) +public class DspNegotiationApiV08Extension implements ServiceExtension { + + public static final String NAME = "Dataspace Protocol Negotiation Api v08"; + + @Inject + private WebService webService; + @Inject + private ContractNegotiationProtocolService protocolService; + @Inject + private JsonObjectValidatorRegistry validatorRegistry; + @Inject + private DspRequestHandler dspRequestHandler; + @Inject + private JsonLd jsonLd; + + @Inject + private TypeManager typeManager; + + @Inject + private SingleParticipantContextSupplier participantContextSupplier; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + registerValidators(); + + webService.registerResource(ApiContext.PROTOCOL, new DspNegotiationApiController08(protocolService, dspRequestHandler, participantContextSupplier)); + webService.registerDynamicResource(ApiContext.PROTOCOL, DspNegotiationApiController08.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, DSP_SCOPE_V_08)); + } + + private void registerValidators() { + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_REQUEST_MESSAGE_TERM), ContractRequestMessageValidator.instance(DSP_NAMESPACE_V_08)); + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_OFFER_MESSAGE_TERM), ContractOfferMessageValidator.instance(DSP_NAMESPACE_V_08)); + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_NEGOTIATION_EVENT_MESSAGE_TERM), ContractNegotiationEventMessageValidator.instance(DSP_NAMESPACE_V_08)); + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_AGREEMENT_MESSAGE_TERM), ContractAgreementMessageValidator.instance(DSP_NAMESPACE_V_08)); + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_AGREEMENT_VERIFICATION_MESSAGE_TERM), ContractAgreementVerificationMessageValidator.instance(DSP_NAMESPACE_V_08)); + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_NEGOTIATION_TERMINATION_MESSAGE_TERM), ContractNegotiationTerminationMessageValidator.instance(DSP_NAMESPACE_V_08)); + } +} diff --git a/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/controller/DspNegotiationApiController08.java b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/controller/DspNegotiationApiController08.java new file mode 100644 index 0000000000..35cc3affc6 --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/controller/DspNegotiationApiController08.java @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.edc.protocol.dsp.negotiation.http.api.controller; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.edc.connector.controlplane.services.spi.contractnegotiation.ContractNegotiationProtocolService; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.protocol.dsp.http.spi.message.DspRequestHandler; + +import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.protocol.dsp.negotiation.http.api.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; + +/** + * Provides consumer and provider endpoints for the contract negotiation according to the http binding + * of the dataspace protocol. + */ +@Consumes({MediaType.APPLICATION_JSON}) +@Produces({MediaType.APPLICATION_JSON}) +@Path(BASE_PATH) +public class DspNegotiationApiController08 extends BaseDspNegotiationApiController { + + + public DspNegotiationApiController08(ContractNegotiationProtocolService protocolService, + DspRequestHandler dspRequestHandler, SingleParticipantContextSupplier participantContextSupplier) { + + super(protocolService, dspRequestHandler, participantContextSupplier, DATASPACE_PROTOCOL_HTTP, DSP_NAMESPACE_V_08); + } + +} diff --git a/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..e19e030d5f --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. +# +# 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.edc.protocol.dsp.negotiation.http.api.DspNegotiationApiV08Extension \ No newline at end of file diff --git a/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/DspNegotiationApiV08ExtensionTest.java b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/DspNegotiationApiV08ExtensionTest.java new file mode 100644 index 0000000000..b389fe8c89 --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/DspNegotiationApiV08ExtensionTest.java @@ -0,0 +1,62 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.negotiation.http.api; + +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +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.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_AGREEMENT_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_AGREEMENT_VERIFICATION_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_NEGOTIATION_EVENT_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_NEGOTIATION_TERMINATION_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_OFFER_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspNegotiationPropertyAndTypeNames.DSPACE_TYPE_CONTRACT_REQUEST_MESSAGE_TERM; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ExtendWith(DependencyInjectionExtension.class) +class DspNegotiationApiV08ExtensionTest { + + private final JsonObjectValidatorRegistry validatorRegistry = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(JsonObjectValidatorRegistry.class, validatorRegistry); + } + + @Test + void shouldRegisterMessageValidators(DspNegotiationApiV08Extension extension, ServiceExtensionContext context) { + extension.initialize(context); + + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_REQUEST_MESSAGE_TERM)), any()); + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_OFFER_MESSAGE_TERM)), any()); + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_NEGOTIATION_EVENT_MESSAGE_TERM)), any()); + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_AGREEMENT_MESSAGE_TERM)), any()); + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_AGREEMENT_VERIFICATION_MESSAGE_TERM)), any()); + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_CONTRACT_NEGOTIATION_TERMINATION_MESSAGE_TERM)), any()); + } +} diff --git a/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/controller/DspNegotiationApiController08Test.java b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/controller/DspNegotiationApiController08Test.java new file mode 100644 index 0000000000..1de27565b9 --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/negotiation/http/api/controller/DspNegotiationApiController08Test.java @@ -0,0 +1,45 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.negotiation.http.api.controller; + +import org.eclipse.edc.jsonld.spi.JsonLdNamespace; +import org.eclipse.edc.junit.annotations.ApiTest; + +import static org.eclipse.edc.protocol.dsp.negotiation.http.api.NegotiationApiPaths.BASE_PATH; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; + +@ApiTest +class DspNegotiationApiController08Test extends DspNegotiationApiControllerTestBase { + + @Override + protected String basePath() { + return BASE_PATH; + } + + @Override + protected JsonLdNamespace namespace() { + return DSP_NAMESPACE_V_08; + } + + @Override + protected Object controller() { + return new DspNegotiationApiController08(protocolService, dspRequestHandler, participantContextSupplier); + } +} diff --git a/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-transform-08/build.gradle.kts b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-transform-08/build.gradle.kts new file mode 100644 index 0000000000..82ca6d5637 --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-transform-08/build.gradle.kts @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` +} + +dependencies { + api(libs.edc.spi.core) + api(libs.edc.spi.transform) + api(project(":spi:dsp-spi-08")) + + implementation(libs.edc.lib.dsp.negotiation.transform) +} diff --git a/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-transform-08/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/transform/DspNegotiationTransformV08Extension.java b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-transform-08/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/transform/DspNegotiationTransformV08Extension.java new file mode 100644 index 0000000000..536df1b47f --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-transform-08/src/main/java/org/eclipse/edc/protocol/dsp/negotiation/transform/DspNegotiationTransformV08Extension.java @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.edc.protocol.dsp.negotiation.transform; + +import jakarta.json.Json; +import org.eclipse.edc.protocol.dsp.negotiation.transform.from.JsonObjectFromContractAgreementMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.from.JsonObjectFromContractAgreementVerificationMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.from.JsonObjectFromContractNegotiationErrorTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.from.JsonObjectFromContractNegotiationEventMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.from.JsonObjectFromContractNegotiationTerminationMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.from.JsonObjectFromContractNegotiationTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.from.JsonObjectFromContractOfferMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.from.JsonObjectFromContractRequestMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.to.JsonObjectToContractAgreementMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.to.JsonObjectToContractAgreementVerificationMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.to.JsonObjectToContractNegotiationAckTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.to.JsonObjectToContractNegotiationEventMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.to.JsonObjectToContractNegotiationTerminationMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.to.JsonObjectToContractOfferMessageTransformer; +import org.eclipse.edc.protocol.dsp.negotiation.transform.to.JsonObjectToContractRequestMessageTransformer; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; + +import java.util.Map; + +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_TRANSFORMER_CONTEXT_V_08; + +/** + * Provides the transformers for DSP v0.8 negotiation message types via the {@link TypeTransformerRegistry}. + */ +@Extension(value = DspNegotiationTransformV08Extension.NAME) +public class DspNegotiationTransformV08Extension implements ServiceExtension { + + public static final String NAME = "Dataspace Protocol Negotiation Transform v08 Extension"; + + @Inject + private TypeTransformerRegistry registry; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + registerTransformers(); + } + + private void registerTransformers() { + var builderFactory = Json.createBuilderFactory(Map.of()); + + var dspApiTransformerRegistry = registry.forContext(DSP_TRANSFORMER_CONTEXT_V_08); + dspApiTransformerRegistry.register(new JsonObjectFromContractNegotiationErrorTransformer(builderFactory, DSP_NAMESPACE_V_08)); + + dspApiTransformerRegistry.register(new JsonObjectToContractAgreementMessageTransformer(DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectToContractAgreementVerificationMessageTransformer(DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectToContractNegotiationEventMessageTransformer(DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectToContractRequestMessageTransformer(DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectToContractNegotiationTerminationMessageTransformer(DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectToContractOfferMessageTransformer(DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectToContractNegotiationAckTransformer(DSP_NAMESPACE_V_08)); + + dspApiTransformerRegistry.register(new JsonObjectFromContractNegotiationTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectFromContractRequestMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectFromContractOfferMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectFromContractAgreementMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectFromContractAgreementVerificationMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectFromContractNegotiationEventMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspApiTransformerRegistry.register(new JsonObjectFromContractNegotiationTerminationMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + } +} \ No newline at end of file diff --git a/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-transform-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-transform-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..ace8e483fe --- /dev/null +++ b/edc-extensions/dsp/dsp-negotiation-08/dsp-negotiation-transform-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. +# +# 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.edc.protocol.dsp.negotiation.transform.DspNegotiationTransformV08Extension \ No newline at end of file diff --git a/edc-extensions/dsp/dsp-transfer-process-08/build.gradle.kts b/edc-extensions/dsp/dsp-transfer-process-08/build.gradle.kts new file mode 100644 index 0000000000..0c943c0079 --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/build.gradle.kts @@ -0,0 +1,27 @@ +/******************************************************************************** + * 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` +} + +dependencies { + api(project(":edc-extensions:dsp:dsp-transfer-process-08:dsp-transfer-process-http-api-08")) + api(project(":edc-extensions:dsp:dsp-transfer-process-08:dsp-transfer-process-transform-08")) +} diff --git a/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/build.gradle.kts b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/build.gradle.kts new file mode 100644 index 0000000000..73f806d7b8 --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/build.gradle.kts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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` + id(libs.plugins.swagger.get().pluginId) +} + +dependencies { + api(libs.edc.spi.web) + api(libs.edc.spi.transfer) + api(libs.edc.spi.controlplane) + api(libs.dsp.spi) + api(project(":spi:dsp-spi-08")) + api(libs.dsp.spi.http) + + api(libs.edc.spi.jsonld) + implementation(libs.edc.lib.dsp.transfer.process.validation) + implementation(libs.edc.lib.dsp.transfer.process.http.api) + implementation(libs.edc.lib.jersey.providers) + + implementation(libs.jakarta.rsApi) + + testImplementation(libs.edc.junit) + testImplementation(testFixtures(libs.edc.core.jersey)) + testImplementation(testFixtures(libs.edc.lib.dsp.transfer.http.api)) + + testImplementation(libs.restAssured) +} + +edcBuild { + swagger { + apiGroup.set("dsp-api") + } +} diff --git a/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/DspTransferProcessApiV08Extension.java b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/DspTransferProcessApiV08Extension.java new file mode 100644 index 0000000000..ebba94bbbb --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/DspTransferProcessApiV08Extension.java @@ -0,0 +1,89 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.transferprocess.http.api; + +import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessProtocolService; +import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.protocol.dsp.http.spi.message.DspRequestHandler; +import org.eclipse.edc.protocol.dsp.transferprocess.http.api.controller.DspTransferProcessApiController08; +import org.eclipse.edc.protocol.dsp.transferprocess.validation.TransferCompletionMessageValidator; +import org.eclipse.edc.protocol.dsp.transferprocess.validation.TransferRequestMessageValidator; +import org.eclipse.edc.protocol.dsp.transferprocess.validation.TransferStartMessageValidator; +import org.eclipse.edc.protocol.dsp.transferprocess.validation.TransferTerminationMessageValidator; +import org.eclipse.edc.protocol.spi.DataspaceProfileContextRegistry; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.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 static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_SCOPE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.DspTransferProcessPropertyAndTypeNames.DSPACE_TYPE_TRANSFER_COMPLETION_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspTransferProcessPropertyAndTypeNames.DSPACE_TYPE_TRANSFER_REQUEST_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspTransferProcessPropertyAndTypeNames.DSPACE_TYPE_TRANSFER_START_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspTransferProcessPropertyAndTypeNames.DSPACE_TYPE_TRANSFER_TERMINATION_MESSAGE_TERM; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; + +/** + * Creates and registers the controller for dataspace protocol v0.8 transfer process requests. + */ +@Extension(value = DspTransferProcessApiV08Extension.NAME) +public class DspTransferProcessApiV08Extension implements ServiceExtension { + + public static final String NAME = "Dataspace Protocol: TransferProcess API v08 Extension"; + @Inject + private WebService webService; + @Inject + private TransferProcessProtocolService transferProcessProtocolService; + @Inject + private DspRequestHandler dspRequestHandler; + @Inject + private JsonObjectValidatorRegistry validatorRegistry; + @Inject + private DataspaceProfileContextRegistry versionRegistry; + @Inject + private JsonLd jsonLd; + @Inject + private TypeManager typeManager; + + @Inject + private SingleParticipantContextSupplier participantContextSupplier; + + @Override + public void initialize(ServiceExtensionContext context) { + registerValidators(); + + webService.registerResource(ApiContext.PROTOCOL, new DspTransferProcessApiController08(transferProcessProtocolService, dspRequestHandler, participantContextSupplier)); + webService.registerDynamicResource(ApiContext.PROTOCOL, DspTransferProcessApiController08.class, new JerseyJsonLdInterceptor(jsonLd, typeManager, JSON_LD, DSP_SCOPE_V_08)); + } + + private void registerValidators() { + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_TRANSFER_REQUEST_MESSAGE_TERM), TransferRequestMessageValidator.instance(DSP_NAMESPACE_V_08)); + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_TRANSFER_START_MESSAGE_TERM), TransferStartMessageValidator.instance(DSP_NAMESPACE_V_08)); + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_TRANSFER_COMPLETION_MESSAGE_TERM), TransferCompletionMessageValidator.instance(DSP_NAMESPACE_V_08)); + validatorRegistry.register(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_TRANSFER_TERMINATION_MESSAGE_TERM), TransferTerminationMessageValidator.instance(DSP_NAMESPACE_V_08)); + } +} diff --git a/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/controller/DspTransferProcessApiController08.java b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/controller/DspTransferProcessApiController08.java new file mode 100644 index 0000000000..4f6098bcfe --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/controller/DspTransferProcessApiController08.java @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.edc.protocol.dsp.transferprocess.http.api.controller; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessProtocolService; +import org.eclipse.edc.participantcontext.single.spi.SingleParticipantContextSupplier; +import org.eclipse.edc.protocol.dsp.http.spi.message.DspRequestHandler; + +import static org.eclipse.edc.protocol.dsp.http.spi.types.HttpMessageProtocol.DATASPACE_PROTOCOL_HTTP; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.transferprocess.http.api.TransferProcessApiPaths.BASE_PATH; + +/** + * Provides the endpoints for receiving messages regarding transfers, like initiating, completing + * and terminating a transfer process. + */ +@Consumes({MediaType.APPLICATION_JSON}) +@Produces({MediaType.APPLICATION_JSON}) +@Path(BASE_PATH) +public class DspTransferProcessApiController08 extends BaseDspTransferProcessApiController { + + public DspTransferProcessApiController08(TransferProcessProtocolService protocolService, DspRequestHandler dspRequestHandler, SingleParticipantContextSupplier participantContextSupplier) { + super(protocolService, dspRequestHandler, participantContextSupplier, DATASPACE_PROTOCOL_HTTP, DSP_NAMESPACE_V_08); + } + +} diff --git a/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..5620d7615e --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. +# +# 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.edc.protocol.dsp.transferprocess.http.api.DspTransferProcessApiV08Extension diff --git a/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/DspTransferProcessApiV08ExtensionTest.java b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/DspTransferProcessApiV08ExtensionTest.java new file mode 100644 index 0000000000..f61311bc64 --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/DspTransferProcessApiV08ExtensionTest.java @@ -0,0 +1,59 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.transferprocess.http.api; + +import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; +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.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.DspTransferProcessPropertyAndTypeNames.DSPACE_TYPE_TRANSFER_COMPLETION_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspTransferProcessPropertyAndTypeNames.DSPACE_TYPE_TRANSFER_REQUEST_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspTransferProcessPropertyAndTypeNames.DSPACE_TYPE_TRANSFER_START_MESSAGE_TERM; +import static org.eclipse.edc.protocol.dsp.spi.type.DspTransferProcessPropertyAndTypeNames.DSPACE_TYPE_TRANSFER_TERMINATION_MESSAGE_TERM; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ExtendWith(DependencyInjectionExtension.class) +class DspTransferProcessApiV08ExtensionTest { + + private final JsonObjectValidatorRegistry validatorRegistry = mock(); + + @BeforeEach + void setUp(ServiceExtensionContext context) { + context.registerService(JsonObjectValidatorRegistry.class, validatorRegistry); + } + + @Test + void shouldRegisterMessageValidators(DspTransferProcessApiV08Extension extension, ServiceExtensionContext context) { + extension.initialize(context); + + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_TRANSFER_REQUEST_MESSAGE_TERM)), any()); + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_TRANSFER_START_MESSAGE_TERM)), any()); + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_TRANSFER_COMPLETION_MESSAGE_TERM)), any()); + verify(validatorRegistry).register(eq(DSP_NAMESPACE_V_08.toIri(DSPACE_TYPE_TRANSFER_TERMINATION_MESSAGE_TERM)), any()); + } + +} diff --git a/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/controller/DspTransferProcessApiController08Test.java b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/controller/DspTransferProcessApiController08Test.java new file mode 100644 index 0000000000..7aa26c2070 --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-http-api-08/src/test/java/org/eclipse/edc/protocol/dsp/transferprocess/http/api/controller/DspTransferProcessApiController08Test.java @@ -0,0 +1,45 @@ +/******************************************************************************** + * 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.edc.protocol.dsp.transferprocess.http.api.controller; + +import org.eclipse.edc.jsonld.spi.JsonLdNamespace; +import org.eclipse.edc.junit.annotations.ApiTest; + +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.transferprocess.http.api.TransferProcessApiPaths.BASE_PATH; + +@ApiTest +class DspTransferProcessApiController08Test extends DspTransferProcessApiControllerBaseTest { + + @Override + protected String basePath() { + return BASE_PATH; + } + + @Override + protected JsonLdNamespace namespace() { + return DSP_NAMESPACE_V_08; + } + + @Override + protected Object controller() { + return new DspTransferProcessApiController08(protocolService, dspRequestHandler, participantContextSupplier); + } +} diff --git a/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-transform-08/build.gradle.kts b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-transform-08/build.gradle.kts new file mode 100644 index 0000000000..b8916a92a8 --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-transform-08/build.gradle.kts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` +} + +dependencies { + api(libs.edc.spi.transfer) + api(libs.edc.spi.transform) + api(libs.edc.ext.jsonld) + api(libs.dsp.spi) + api(project(":spi:dsp-spi-08")) + api(libs.dsp.spi.http) + implementation(libs.edc.lib.transform) + implementation(libs.edc.lib.dsp.transfer.process.transform) + + testImplementation(libs.edc.ext.jsonld) + testImplementation(libs.edc.junit) +} diff --git a/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-transform-08/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transform/DspTransferProcessTransformV08Extension.java b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-transform-08/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transform/DspTransferProcessTransformV08Extension.java new file mode 100644 index 0000000000..6f8c7c5b03 --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-transform-08/src/main/java/org/eclipse/edc/protocol/dsp/transferprocess/transform/DspTransferProcessTransformV08Extension.java @@ -0,0 +1,94 @@ +/******************************************************************************** + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. + * + * 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.edc.protocol.dsp.transferprocess.transform; + +import jakarta.json.Json; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.from.JsonObjectFromTransferCompletionMessageTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.from.JsonObjectFromTransferErrorTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.from.JsonObjectFromTransferProcessTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.from.JsonObjectFromTransferRequestMessageTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.from.JsonObjectFromTransferStartMessageTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.from.JsonObjectFromTransferSuspensionMessageTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.from.JsonObjectFromTransferTerminationMessageTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.to.JsonObjectToTransferCompletionMessageTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.to.JsonObjectToTransferProcessAckTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.to.JsonObjectToTransferRequestMessageTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.to.JsonObjectToTransferStartMessageTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.to.JsonObjectToTransferSuspensionMessageTransformer; +import org.eclipse.edc.protocol.dsp.transferprocess.transform.type.to.JsonObjectToTransferTerminationMessageTransformer; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.edc.transform.spi.TypeTransformerRegistry; + +import java.util.Map; + +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_NAMESPACE_V_08; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSP_TRANSFORMER_CONTEXT_V_08; +import static org.eclipse.edc.spi.constants.CoreConstants.JSON_LD; + +/** + * Provides the transformers for DSP v0.8 transferprocess message types via the {@link TypeTransformerRegistry}. + */ +@Extension(value = DspTransferProcessTransformV08Extension.NAME) +public class DspTransferProcessTransformV08Extension implements ServiceExtension { + + public static final String NAME = "Dataspace Protocol Transfer Process Transform v08 Extension"; + + @Inject + private TypeTransformerRegistry registry; + + @Inject + private TypeManager typeManager; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + registerTransformers(); + } + + private void registerTransformers() { + var builderFactory = Json.createBuilderFactory(Map.of()); + + var dspRegistry = registry.forContext(DSP_TRANSFORMER_CONTEXT_V_08); + + dspRegistry.register(new JsonObjectFromTransferErrorTransformer(builderFactory, DSP_NAMESPACE_V_08)); + + dspRegistry.register(new JsonObjectToTransferRequestMessageTransformer(DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectToTransferCompletionMessageTransformer(DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectToTransferStartMessageTransformer(DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectToTransferTerminationMessageTransformer(DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectToTransferProcessAckTransformer(DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectToTransferSuspensionMessageTransformer(typeManager, JSON_LD, DSP_NAMESPACE_V_08)); + + dspRegistry.register(new JsonObjectFromTransferProcessTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectFromTransferRequestMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectFromTransferStartMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectFromTransferCompletionMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectFromTransferTerminationMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + dspRegistry.register(new JsonObjectFromTransferSuspensionMessageTransformer(builderFactory, DSP_NAMESPACE_V_08)); + } +} \ No newline at end of file diff --git a/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-transform-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-transform-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..9111788bc7 --- /dev/null +++ b/edc-extensions/dsp/dsp-transfer-process-08/dsp-transfer-process-transform-08/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2023 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. +# +# 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.edc.protocol.dsp.transferprocess.transform.DspTransferProcessTransformV08Extension \ No newline at end of file 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 8548d5d280..4e1620aabf 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 @@ -95,7 +95,8 @@ public JsonObject initiateEdrNegotiation(JsonObject requestObject) { var participantContext = participantContextSupplier.get() .orElseThrow(exceptionMapper(ContractDefinition.class)); - var contractNegotiation = contractNegotiationService.initiateNegotiation(participantContext, enrichContractRequest(contractRequest)); + var contractNegotiation = contractNegotiationService.initiateNegotiation(participantContext, enrichContractRequest(contractRequest)) + .orElseThrow(InvalidRequestException::new); 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/transform/JsonObjectFromEndpointDataReferenceEntryTransformer.java b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformer.java index e3bd4ecb45..e9efb2e470 100644 --- a/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformer.java +++ b/edc-extensions/edr/edr-api-v2/src/main/java/org/eclipse/tractusx/edc/api/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformer.java @@ -48,15 +48,19 @@ public JsonObjectFromEndpointDataReferenceEntryTransformer(JsonBuilderFactory js @Override public @Nullable JsonObject transform(@NotNull EndpointDataReferenceEntry entry, @NotNull TransformerContext context) { - return jsonFactory.createObjectBuilder() + var builder = jsonFactory.createObjectBuilder() .add(ID, entry.getId()) .add(TYPE, EDR_ENTRY_TYPE) .add(EDR_ENTRY_PROVIDER_ID, entry.getProviderId()) .add(EDR_ENTRY_ASSET_ID, entry.getAssetId()) .add(EDR_ENTRY_AGREEMENT_ID, entry.getAgreementId()) .add(EDR_ENTRY_TRANSFER_PROCESS_ID, entry.getTransferProcessId()) - .add(EDR_ENTRY_CREATED_AT, entry.getCreatedAt()) - .add(EDR_ENTRY_CONTRACT_NEGOTIATION_ID, entry.getContractNegotiationId()) - .build(); + .add(EDR_ENTRY_CREATED_AT, entry.getCreatedAt()); + + if (entry.getContractNegotiationId() != null) { + builder.add(EDR_ENTRY_CONTRACT_NEGOTIATION_ID, entry.getContractNegotiationId()); + } + + return builder.build(); } } 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 a4817967cd..fb5f512d3c 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 @@ -102,7 +102,7 @@ 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(), any())).thenReturn(contractNegotiation); + when(contractNegotiationService.initiateNegotiation(any(), any())).thenReturn(ServiceResult.success(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)); diff --git a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformerTest.java b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformerTest.java index 56ab8170d8..403467b270 100644 --- a/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformerTest.java +++ b/edc-extensions/edr/edr-api-v2/src/test/java/org/eclipse/tractusx/edc/api/edr/transform/JsonObjectFromEndpointDataReferenceEntryTransformerTest.java @@ -66,4 +66,24 @@ void transform() { assertThat(jsonObject.getJsonString(EDR_ENTRY_PROVIDER_ID).getString()).isNotNull().isEqualTo(dto.getProviderId()); } + + @Test + void transform_withoutContractNegotiationId() { + + var dto = EndpointDataReferenceEntry.Builder.newInstance() + .assetId("id") + .transferProcessId("tpId") + .agreementId("aId") + .providerId("providerId") + .build(); + + var jsonObject = transformer.transform(dto, context); + + assertThat(jsonObject).isNotNull(); + assertThat(jsonObject.containsKey(EDR_ENTRY_CONTRACT_NEGOTIATION_ID)).isFalse(); + assertThat(jsonObject.getJsonString(EDR_ENTRY_AGREEMENT_ID).getString()).isEqualTo(dto.getAgreementId()); + assertThat(jsonObject.getJsonString(EDR_ENTRY_ASSET_ID).getString()).isEqualTo(dto.getAssetId()); + assertThat(jsonObject.getJsonString(EDR_ENTRY_TRANSFER_PROCESS_ID).getString()).isEqualTo(dto.getTransferProcessId()); + assertThat(jsonObject.getJsonString(EDR_ENTRY_PROVIDER_ID).getString()).isEqualTo(dto.getProviderId()); + } } diff --git a/edc-extensions/edr/edr-index-lock-sql/src/main/java/org/eclipse/tractusx/edc/edr/index/sql/lock/SqlEdrLockExtension.java b/edc-extensions/edr/edr-index-lock-sql/src/main/java/org/eclipse/tractusx/edc/edr/index/sql/lock/SqlEdrLockExtension.java index 009a7d20d9..fe456f7a0b 100644 --- a/edc-extensions/edr/edr-index-lock-sql/src/main/java/org/eclipse/tractusx/edc/edr/index/sql/lock/SqlEdrLockExtension.java +++ b/edc-extensions/edr/edr-index-lock-sql/src/main/java/org/eclipse/tractusx/edc/edr/index/sql/lock/SqlEdrLockExtension.java @@ -37,8 +37,10 @@ @Extension(value = "Database-level EDR Lock extension (PostgreSQL)") public class SqlEdrLockExtension implements ServiceExtension { - @Setting(value = "The datasource to be used", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE) - public static final String DATASOURCE_NAME = "edc.sql.store.edr.datasource"; + private static final String DATASOURCE_NAME = "edc.sql.store.edr.datasource"; + + @Setting(key = DATASOURCE_NAME, description = "Datasource of edr lock", defaultValue = DataSourceRegistry.DEFAULT_DATASOURCE) + private String dataSourceName; @Inject private DataSourceRegistry dataSourceRegistry; @@ -56,8 +58,6 @@ public class SqlEdrLockExtension implements ServiceExtension { @Override public void initialize(ServiceExtensionContext context) { - var dataSourceName = context.getConfig().getString(DATASOURCE_NAME, DataSourceRegistry.DEFAULT_DATASOURCE); - var statements = new PostgresEdrLockStatements(); var sqlStore = new SqlEdrLock(dataSourceRegistry, dataSourceName, transactionContext, typeManager.getMapper(), queryExecutor, statements); diff --git a/edc-extensions/log4j2-monitor/src/main/java/org/eclipse/edc/monitor/logger/Log4j2Monitor.java b/edc-extensions/log4j2-monitor/src/main/java/org/eclipse/edc/monitor/logger/Log4j2Monitor.java index a11ca96a3d..cc83de758c 100644 --- a/edc-extensions/log4j2-monitor/src/main/java/org/eclipse/edc/monitor/logger/Log4j2Monitor.java +++ b/edc-extensions/log4j2-monitor/src/main/java/org/eclipse/edc/monitor/logger/Log4j2Monitor.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,7 +20,6 @@ package org.eclipse.edc.monitor.logger; -import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.edc.spi.monitor.Monitor; @@ -37,14 +37,24 @@ public class Log4j2Monitor implements Monitor { */ private static final Logger LOGGER = LogManager.getLogger(Log4j2Monitor.class.getName()); + private final Level minLevel; + + public Log4j2Monitor() { + this(Level.getDefaultLevel()); + } + + public Log4j2Monitor(Level level) { + this.minLevel = level; + } + @Override public void severe(final Supplier supplier, final Throwable... errors) { - log(supplier, Level.ERROR, errors); + log(supplier, Level.SEVERE, errors); } @Override public void warning(final Supplier supplier, final Throwable... errors) { - log(supplier, Level.WARN, errors); + log(supplier, Level.WARNING, errors); } @Override @@ -58,10 +68,22 @@ public void debug(final Supplier supplier, final Throwable... errors) { } private void log(final Supplier supplier, final Level level, final Throwable... errors) { + if (level.value() < minLevel.value()) { + return; + } if (errors == null || errors.length == 0) { - LOGGER.log(level, () -> sanitizeMessage(supplier)); + LOGGER.log(levelConverter(level), () -> sanitizeMessage(supplier)); } else { - Arrays.stream(errors).forEach(error -> LOGGER.log(level, sanitizeMessage(supplier), error)); + Arrays.stream(errors).forEach(error -> LOGGER.log(levelConverter(level), sanitizeMessage(supplier), error)); } } + + private org.apache.logging.log4j.Level levelConverter(Level level) { + return switch (level) { + case SEVERE -> org.apache.logging.log4j.Level.ERROR; + case WARNING -> org.apache.logging.log4j.Level.WARN; + case INFO -> org.apache.logging.log4j.Level.INFO; + case DEBUG -> org.apache.logging.log4j.Level.DEBUG; + }; + } } \ No newline at end of file diff --git a/edc-extensions/log4j2-monitor/src/main/java/org/eclipse/edc/monitor/logger/Log4j2MonitorExtension.java b/edc-extensions/log4j2-monitor/src/main/java/org/eclipse/edc/monitor/logger/Log4j2MonitorExtension.java index 3cbd28d589..10c0686524 100644 --- a/edc-extensions/log4j2-monitor/src/main/java/org/eclipse/edc/monitor/logger/Log4j2MonitorExtension.java +++ b/edc-extensions/log4j2-monitor/src/main/java/org/eclipse/edc/monitor/logger/Log4j2MonitorExtension.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -30,7 +31,7 @@ public class Log4j2MonitorExtension implements MonitorExtension { @Override - public Monitor getMonitor() { - return new Log4j2Monitor(); + public Monitor getMonitor(Monitor.Level level, String... programArgs) { + return new Log4j2Monitor(level); } } diff --git a/edc-extensions/log4j2-monitor/src/test/java/org/eclipse/edc/monitor/logger/LoggerMonitorTest.java b/edc-extensions/log4j2-monitor/src/test/java/org/eclipse/edc/monitor/logger/LoggerMonitorTest.java index 076f3d72b5..1d1a04d6a1 100644 --- a/edc-extensions/log4j2-monitor/src/test/java/org/eclipse/edc/monitor/logger/LoggerMonitorTest.java +++ b/edc-extensions/log4j2-monitor/src/test/java/org/eclipse/edc/monitor/logger/LoggerMonitorTest.java @@ -55,7 +55,7 @@ void setUp() { var context = (LoggerContext) LogManager.getContext(false); var config = context.getConfiguration(); listAppender = config.getAppender("LIST"); - sut = new Log4j2Monitor(); + sut = new Log4j2Monitor(Monitor.Level.DEBUG); } @AfterEach diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_6_0__EDC_8_migration.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_6_0__EDC_8_migration.sql new file mode 100644 index 0000000000..9fa2705a7e --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_6_0__EDC_8_migration.sql @@ -0,0 +1,62 @@ +CREATE INDEX IF NOT EXISTS data_plane_state ON edc_data_plane (state,state_time_stamp); + +ALTER TABLE edc_data_plane ADD COLUMN IF NOT EXISTS transfer_type_destination VARCHAR DEFAULT 'HttpData'; + +CREATE INDEX IF NOT EXISTS transfer_process_state ON edc_transfer_process (state,state_time_stamp); + +CREATE INDEX IF NOT EXISTS policy_monitor_state ON edc_policy_monitor (state,state_time_stamp); + +CREATE INDEX IF NOT EXISTS contract_negotiation_state ON edc_contract_negotiation (state,state_timestamp); + +CREATE TABLE IF NOT EXISTS edc_federated_catalog +( + id VARCHAR PRIMARY KEY NOT NULL, + catalog JSON, + marked BOOLEAN DEFAULT FALSE +); + +ALTER TABLE edc_policydefinitions ADD COLUMN IF NOT EXISTS profiles JSON; + +CREATE TABLE IF NOT EXISTS edc_data_plane_instance +( + id VARCHAR NOT NULL PRIMARY KEY, + data JSON, + lease_id VARCHAR + CONSTRAINT data_plane_instance_lease_id_fk + REFERENCES edc_lease + ON DELETE SET NULL +); + + +CREATE TABLE IF NOT EXISTS edc_lease +( + leased_by VARCHAR NOT NULL, + leased_at BIGINT, + lease_duration INTEGER NOT NULL, + lease_id VARCHAR NOT NULL + CONSTRAINT lease_pk + PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS edc_agreement_retirement +( + contract_agreement_id VARCHAR PRIMARY KEY, + reason TEXT NOT NULL, + agreement_retirement_date BIGINT NOT NULL +); + + + + + + + + + + + + + + + + diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_6_1__EDC_9_migration.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_6_1__EDC_9_migration.sql new file mode 100644 index 0000000000..a443c4a9f2 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_6_1__EDC_9_migration.sql @@ -0,0 +1,4 @@ +UPDATE edc_policydefinitions SET profiles='[]'::json where profiles is NULL; + +ALTER TABLE edc_data_plane + ADD COLUMN IF NOT EXISTS runtime_id VARCHAR; diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_6_2__EDC_11_migration.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_6_2__EDC_11_migration.sql new file mode 100644 index 0000000000..d22eaa7818 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_6_2__EDC_11_migration.sql @@ -0,0 +1,32 @@ +CREATE TABLE IF NOT EXISTS edc_contract_agreement_bpns +( + agreement_id VARCHAR + CONSTRAINT contract_agreement_bpns_contract_agreement_id_fk PRIMARY KEY + REFERENCES edc_contract_agreement, + provider_bpn VARCHAR(255) NOT NULL, + consumer_bpn VARCHAR(255) NOT NULL + ); + +CREATE TABLE IF NOT EXISTS edc_jti_validation +( + token_id VARCHAR NOT NULL PRIMARY KEY, + expires_at BIGINT +); + +CREATE INDEX IF NOT EXISTS contract_negotiation_lease_id_index + ON edc_contract_negotiation (lease_id); + +CREATE INDEX IF NOT EXISTS contract_negotiation_agreement_id_index + ON edc_contract_negotiation (agreement_id); + +CREATE INDEX IF NOT EXISTS policy_monitor_lease_id_index + ON edc_policy_monitor (lease_id); + +CREATE INDEX IF NOT EXISTS transfer_process_lease_id_index + ON edc_transfer_process (lease_id); + + +ALTER TABLE edc_data_plane + ADD COLUMN IF NOT EXISTS resource_definitions json DEFAULT '[]'::json; + +CREATE INDEX IF NOT EXISTS data_plane_lease_id ON edc_data_plane (lease_id); diff --git a/edc-extensions/migrations/control-plane-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/federatedcatalog/V0_0_2__Remove_FederatedCatalogCache_Database_Schema.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_7_0__Remove_FederatedCatalogCache_Database_Schema.sql similarity index 100% rename from edc-extensions/migrations/control-plane-migration/src/main/resources/org/eclipse/tractusx/edc/postgresql/migration/federatedcatalog/V0_0_2__Remove_FederatedCatalogCache_Database_Schema.sql rename to edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_7_0__Remove_FederatedCatalogCache_Database_Schema.sql diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_8_0__Add_DataAddressAlias.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_8_0__Add_DataAddressAlias.sql new file mode 100644 index 0000000000..c3694e4aeb --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_8_0__Add_DataAddressAlias.sql @@ -0,0 +1,18 @@ +-- +-- Copyright (c) 2026 Cofinity-X GmbH +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Cofinity-X GmbH - initial API and implementation +-- + +-- +-- table: edc_transfer_process +-- + +ALTER TABLE edc_transfer_process ADD COLUMN IF NOT EXISTS data_address_alias text; diff --git a/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_9_0__Add_ClaimsColumn.sql b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_9_0__Add_ClaimsColumn.sql new file mode 100644 index 0000000000..c0af4d4350 --- /dev/null +++ b/edc-extensions/migrations/connector-migration/src/main/resources/migrations/connector/V1_9_0__Add_ClaimsColumn.sql @@ -0,0 +1,19 @@ +-- +-- Copyright (c) 2026 Cofinity-X GmbH +-- +-- This program and the accompanying materials are made available under the +-- terms of the Apache License, Version 2.0 which is available at +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- SPDX-License-Identifier: Apache-2.0 +-- +-- Contributors: +-- Cofinity-X GmbH - initial API and implementation +-- +-- +-- table: edc_contract_agreement, edc_transfer_process +-- + +ALTER TABLE edc_contract_agreement ADD COLUMN IF NOT EXISTS claims JSON; + +ALTER TABLE edc_transfer_process ADD COLUMN IF NOT EXISTS claims JSON; \ No newline at end of file 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 e04c6f917d..35dc4307ea 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 @@ -20,7 +20,10 @@ package org.eclipse.tractusx.edc.postgresql.migration; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.SettingContext; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.persistence.EdcPersistenceException; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; @@ -29,7 +32,9 @@ import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; import org.flywaydb.core.api.output.MigrateResult; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Properties; import java.util.function.Supplier; @@ -45,14 +50,20 @@ @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") - private static final String MIGRATION_ENABLED_TEMPLATE = "tx.edc.postgresql.migration.%s.enabled"; + private static final Boolean DEFAULT_MIGRATION_ENABLED_VALUE = true; + private static final String MIGRATION_ENABLED_PREFIX = "tx.edc.postgresql.migration"; + private static final String MIGRATION_ENABLED_PROPERTY = "enabled"; + + @SettingContext(MIGRATION_ENABLED_PREFIX) + @Configuration + private Map migrationEnablement; private static final String DEFAULT_MIGRATION_SCHEMA = "public"; - @Setting(value = "Schema used for the migration", defaultValue = DEFAULT_MIGRATION_SCHEMA) private static final String MIGRATION_SCHEMA = "tx.edc.postgresql.migration.schema"; + @Setting(key = MIGRATION_SCHEMA, description = "Schema used for the migration", defaultValue = DEFAULT_MIGRATION_SCHEMA) + private String defaultSchema; + private Supplier migrationExecutor; private boolean enabled; @@ -66,7 +77,10 @@ public void initialize(final ServiceExtensionContext context) { var config = context.getConfig(); var subSystemName = Objects.requireNonNull(getSubsystemName()); - enabled = config.getBoolean(MIGRATION_ENABLED_TEMPLATE.formatted(subSystemName), Boolean.valueOf(DEFAULT_MIGRATION_ENABLED_TEMPLATE)); + enabled = Optional.ofNullable(migrationEnablement) + .map(m -> m.get(subSystemName)) + .map(MigrationSetting::enabled) + .orElse(DEFAULT_MIGRATION_ENABLED_VALUE); if (!enabled) { context.getMonitor().info("Migration for subsystem %s disabled".formatted(subSystemName)); @@ -83,7 +97,6 @@ public void initialize(final ServiceExtensionContext context) { jdbcProperties.putAll(datasourceConfig.getRelativeEntries()); var driverManagerConnectionFactory = new DriverManagerConnectionFactory(); var dataSource = new ConnectionFactoryDataSource(driverManagerConnectionFactory, jdbcUrl, jdbcProperties); - var defaultSchema = config.getString(MIGRATION_SCHEMA, DEFAULT_MIGRATION_SCHEMA); migrationExecutor = () -> FlywayManager.migrate(dataSource, getMigrationSubsystem(), defaultSchema, LATEST); } @@ -110,4 +123,12 @@ protected String getMigrationSubsystem() { return getSubsystemName(); } + @Settings + private record MigrationSetting( + @Setting( + key = MIGRATION_ENABLED_PROPERTY, + description = "Enable/disables subsystem schema migration" + ) + Boolean enabled + ) {} } diff --git a/edc-extensions/provision-additional-headers/README.md b/edc-extensions/provision-additional-headers/README.md deleted file mode 100644 index 61fe14873e..0000000000 --- a/edc-extensions/provision-additional-headers/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Provision: additional headers - -The goal of this extension is to provide additional headers to the request to the backend service done by the provider -in order to retrieve the data that will be given to the consumer. - -This gives for example the provider backend service the possibility to audit the data requests. - -The following headers are added to the `HttpDataAddress`: - -- `Edc-Contract-Agreement-Id`: the id of the contract agreement -- `Edc-Bpn`: the BPN of the consumer diff --git a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisionedResource.java b/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisionedResource.java deleted file mode 100644 index f6d3a67516..0000000000 --- a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisionedResource.java +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ProvisionedContentResource; - -@JsonDeserialize(builder = AdditionalHeadersProvisionedResource.Builder.class) -class AdditionalHeadersProvisionedResource extends ProvisionedContentResource { - - @JsonPOJOBuilder(withPrefix = "") - public static class Builder - extends ProvisionedContentResource.Builder { - - private Builder() { - super(new AdditionalHeadersProvisionedResource()); - } - - @JsonCreator - public static Builder newInstance() { - return new Builder(); - } - } -} diff --git a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisioner.java b/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisioner.java deleted file mode 100644 index 8b8254a126..0000000000 --- a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisioner.java +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import org.eclipse.edc.connector.controlplane.transfer.spi.provision.Provisioner; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.DeprovisionedResource; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ProvisionResponse; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ProvisionedResource; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ResourceDefinition; -import org.eclipse.edc.connector.dataplane.http.spi.HttpDataAddress; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.response.StatusResult; - -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -public class AdditionalHeadersProvisioner implements Provisioner { - - @Override - public boolean canProvision(ResourceDefinition resourceDefinition) { - return resourceDefinition instanceof AdditionalHeadersResourceDefinition; - } - - @Override - public boolean canDeprovision(ProvisionedResource provisionedResource) { - return provisionedResource instanceof AdditionalHeadersProvisionedResource; - } - - @Override - public CompletableFuture> provision(AdditionalHeadersResourceDefinition resourceDefinition, Policy policy) { - - var address = - HttpDataAddress.Builder.newInstance() - .copyFrom(resourceDefinition.getDataAddress()) - .addAdditionalHeader("Edc-Contract-Agreement-Id", resourceDefinition.getContractId()) - .addAdditionalHeader("Edc-Bpn", resourceDefinition.getBpn()) - .build(); - - var provisioned = AdditionalHeadersProvisionedResource.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .resourceDefinitionId(resourceDefinition.getId()) - .transferProcessId(resourceDefinition.getTransferProcessId()) - .dataAddress(address) - .resourceName(UUID.randomUUID().toString()) - .hasToken(false) - .build(); - - var response = ProvisionResponse.Builder.newInstance().resource(provisioned).build(); - var result = StatusResult.success(response); - return CompletableFuture.completedFuture(result); - } - - @Override - public CompletableFuture> deprovision( - AdditionalHeadersProvisionedResource resource, Policy policy) { - return CompletableFuture.completedFuture(StatusResult.success(DeprovisionedResource.Builder.newInstance().provisionedResourceId(resource.getId()).build())); // nothing to deprovision - } -} diff --git a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinition.java b/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinition.java deleted file mode 100644 index 3b2536fd6f..0000000000 --- a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinition.java +++ /dev/null @@ -1,83 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ResourceDefinition; -import org.eclipse.edc.spi.types.domain.DataAddress; - -@JsonDeserialize(builder = AdditionalHeadersResourceDefinition.Builder.class) -@JsonTypeName("dataspaceconnector:additionalheadersresourcedefinition") -class AdditionalHeadersResourceDefinition extends ResourceDefinition { - - private String contractId; - private DataAddress dataAddress; - private String bpn; - - @Override - public Builder toBuilder() { - return initializeBuilder(new Builder()); - } - - public DataAddress getDataAddress() { - return dataAddress; - } - - public String getContractId() { - return contractId; - } - - public String getBpn() { - return bpn; - } - - @JsonPOJOBuilder(withPrefix = "") - public static class Builder - extends ResourceDefinition.Builder { - - protected Builder() { - super(new AdditionalHeadersResourceDefinition()); - } - - @JsonCreator - public static Builder newInstance() { - return new Builder(); - } - - public Builder contractId(String contractId) { - resourceDefinition.contractId = contractId; - return this; - } - - public Builder dataAddress(DataAddress dataAddress) { - resourceDefinition.dataAddress = dataAddress; - return this; - } - - public Builder bpn(String bpn) { - resourceDefinition.bpn = bpn; - return this; - } - } -} diff --git a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinitionGenerator.java b/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinitionGenerator.java deleted file mode 100644 index 4883876e86..0000000000 --- a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinitionGenerator.java +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ProviderResourceDefinitionGenerator; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ResourceDefinition; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; -import org.jetbrains.annotations.Nullable; - -import java.util.Optional; -import java.util.UUID; - -import static org.eclipse.tractusx.edc.spi.identity.mapper.BdrsConstants.DID_PREFIX; - -class AdditionalHeadersResourceDefinitionGenerator implements ProviderResourceDefinitionGenerator { - - private final ContractAgreementService contractAgreementService; - private final BdrsClient bdrsClient; - - AdditionalHeadersResourceDefinitionGenerator(ContractAgreementService contractAgreementService, BdrsClient bdrsClient) { - this.contractAgreementService = contractAgreementService; - this.bdrsClient = bdrsClient; - } - - @Override - public @Nullable ResourceDefinition generate(TransferProcess transferProcess, DataAddress dataAddress, Policy policy) { - var identity = Optional.of(transferProcess.getContractId()) - .map(contractAgreementService::findById) - .map(ContractAgreement::getConsumerId) - .orElse(null); - - if (identity != null && identity.startsWith(DID_PREFIX)) { - identity = bdrsClient.resolveBpn(identity); - } - - return AdditionalHeadersResourceDefinition.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .dataAddress(dataAddress) - .contractId(transferProcess.getContractId()) - .bpn(identity) - .build(); - } - - @Override - public boolean canGenerate(TransferProcess transferProcess, DataAddress dataAddress, Policy policy) { - return "HttpData".equals(dataAddress.getType()); - } -} diff --git a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtension.java b/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtension.java deleted file mode 100644 index 59110d0d32..0000000000 --- a/edc-extensions/provision-additional-headers/src/main/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtension.java +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ProvisionManager; -import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ResourceManifestGenerator; -import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.spi.system.ServiceExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; - -public class ProvisionAdditionalHeadersExtension implements ServiceExtension { - - @Inject - private ResourceManifestGenerator resourceManifestGenerator; - - @Inject - private ProvisionManager provisionManager; - - @Inject - private TypeManager typeManager; - - @Inject - private ContractAgreementService contractAgreementService; - - @Inject - private BdrsClient bdrsClient; - - @Override - public void initialize(ServiceExtensionContext context) { - typeManager.registerTypes(AdditionalHeadersResourceDefinition.class, AdditionalHeadersProvisionedResource.class); - resourceManifestGenerator.registerGenerator(new AdditionalHeadersResourceDefinitionGenerator(contractAgreementService, bdrsClient)); - provisionManager.register(new AdditionalHeadersProvisioner()); - } -} diff --git a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisionedResourceTest.java b/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisionedResourceTest.java deleted file mode 100644 index 9272f7e736..0000000000 --- a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisionedResourceTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import org.eclipse.edc.json.JacksonTypeManager; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -class AdditionalHeadersProvisionedResourceTest { - - @Test - void serdes() { - var typeManager = new JacksonTypeManager(); - var resource = - AdditionalHeadersProvisionedResource.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .resourceDefinitionId(UUID.randomUUID().toString()) - .transferProcessId(UUID.randomUUID().toString()) - .hasToken(false) - .resourceName("name") - .dataAddress(DataAddress.Builder.newInstance().type("type").build()) - .build(); - - var json = typeManager.writeValueAsString(resource); - var deserialized = typeManager.readValue(json, AdditionalHeadersProvisionedResource.class); - - assertThat(deserialized).usingRecursiveComparison().isEqualTo(resource); - } -} diff --git a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisionerTest.java b/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisionerTest.java deleted file mode 100644 index fa930d83bb..0000000000 --- a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersProvisionerTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ProvisionResponse; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ProvisionedDataAddressResource; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ProvisionedResource; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.ResourceDefinition; -import org.eclipse.edc.connector.dataplane.http.spi.HttpDataAddress; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.response.StatusResult; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.map; -import static org.assertj.core.api.InstanceOfAssertFactories.type; -import static org.mockito.Mockito.mock; - -class AdditionalHeadersProvisionerTest { - - private final AdditionalHeadersProvisioner provisioner = new AdditionalHeadersProvisioner(); - - @Test - void canProvisionAdditionalHeadersResourceDefinition() { - assertThat(provisioner.canProvision(mock(AdditionalHeadersResourceDefinition.class))).isTrue(); - assertThat(provisioner.canProvision(mock(ResourceDefinition.class))).isFalse(); - } - - @Test - void cannotDeprovisionAdditionalHeadersResourceDefinition() { - assertThat(provisioner.canDeprovision(mock(AdditionalHeadersProvisionedResource.class))).isTrue(); - assertThat(provisioner.canDeprovision(mock(ProvisionedResource.class))).isFalse(); - } - - @Test - void shouldAddAdditionalHeaders() { - var address = HttpDataAddress.Builder.newInstance().baseUrl("http://any").build(); - var resourceDefinition = - AdditionalHeadersResourceDefinition.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .transferProcessId(UUID.randomUUID().toString()) - .contractId("contractId") - .bpn("bpn") - .dataAddress(address) - .build(); - - var result = provisioner.provision(resourceDefinition, Policy.Builder.newInstance().build()); - assertThat(result) - .succeedsWithin(5, SECONDS) - .matches(StatusResult::succeeded) - .extracting(StatusResult::getContent) - .extracting(ProvisionResponse::getResource) - .asInstanceOf(type(AdditionalHeadersProvisionedResource.class)) - .extracting(ProvisionedDataAddressResource::getDataAddress) - .extracting(a -> HttpDataAddress.Builder.newInstance().copyFrom(a).build()) - .extracting(HttpDataAddress::getAdditionalHeaders) - .asInstanceOf(map(String.class, String.class)) - .containsEntry("Edc-Contract-Agreement-Id", "contractId") - .containsEntry("Edc-Bpn", "bpn"); - } - - @Test - void shouldDeprovision() { - var address = HttpDataAddress.Builder.newInstance().baseUrl("http://any").build(); - var resource = AdditionalHeadersProvisionedResource.Builder.newInstance() - .dataAddress(address).id("id") - .transferProcessId("transferProcessId") - .resourceDefinitionId("definitionId") - .resourceName("name") - .build(); - - var result = provisioner.deprovision(resource, Policy.Builder.newInstance().build()); - assertThat(result) - .succeedsWithin(5, SECONDS) - .matches(StatusResult::succeeded); - } -} diff --git a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinitionGeneratorTest.java b/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinitionGeneratorTest.java deleted file mode 100644 index af519d0368..0000000000 --- a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinitionGeneratorTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ProviderResourceDefinitionGenerator; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess; -import org.eclipse.edc.connector.dataplane.http.spi.HttpDataAddress; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.InstanceOfAssertFactories.type; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class AdditionalHeadersResourceDefinitionGeneratorTest { - - private final ContractAgreementService contractAgreementService = mock(); - private final BdrsClient bdrsClient = mock(); - private final ProviderResourceDefinitionGenerator generator = new AdditionalHeadersResourceDefinitionGenerator(contractAgreementService, bdrsClient); - - private static ContractAgreement contractAgreementWithConsumerId(String bpn) { - return ContractAgreement.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .consumerId(bpn) - .providerId("providerId") - .assetId("assetId") - .policy(Policy.Builder.newInstance().build()) - .build(); - } - - @Test - void canGenerate_shouldReturnFalseForNotHttpDataAddresses() { - var dataAddress = DataAddress.Builder.newInstance().type("any").build(); - var build = Policy.Builder.newInstance().build(); - var transferProcess = TransferProcess.Builder.newInstance().build(); - - var result = generator.canGenerate(transferProcess, dataAddress, build); - - assertThat(result).isFalse(); - } - - @Test - void canGenerate_shouldReturnTrueForHttpDataAddresses() { - var dataAddress = DataAddress.Builder.newInstance().type("HttpData").build(); - var build = Policy.Builder.newInstance().build(); - var transferProcess = TransferProcess.Builder.newInstance().build(); - - var result = generator.canGenerate(transferProcess, dataAddress, build); - - assertThat(result).isTrue(); - } - - @Test - void shouldCreateResourceDefinitionWithDataAddress() { - var dataAddress = HttpDataAddress.Builder.newInstance().baseUrl("http://any").build(); - var build = Policy.Builder.newInstance().build(); - when(contractAgreementService.findById(any())).thenReturn(contractAgreementWithConsumerId("bpn")); - var transferProcess = TransferProcess.Builder.newInstance() - .dataDestination(dataAddress) - .contractId("contractId") - .build(); - - var result = generator.generate(transferProcess, dataAddress, build); - - assertThat(result) - .asInstanceOf(type(AdditionalHeadersResourceDefinition.class)) - .satisfies(resourceDefinition -> { - assertThat(resourceDefinition.getDataAddress()) - .extracting(address -> HttpDataAddress.Builder.newInstance().copyFrom(address).build()) - .extracting(HttpDataAddress::getBaseUrl) - .isEqualTo("http://any"); - assertThat(resourceDefinition.getContractId()).isEqualTo("contractId"); - assertThat(resourceDefinition.getBpn()).isEqualTo("bpn"); - }); - verify(contractAgreementService).findById("contractId"); - } - - @Test - void whenIdIsDid_shouldCallBdrsClientAndCreateResourceDefinitionWithDataAddress() { - var bpn = "bpn"; - var did = "did:web:abc"; - - var dataAddress = HttpDataAddress.Builder.newInstance().baseUrl("http://any").build(); - var build = Policy.Builder.newInstance().build(); - when(contractAgreementService.findById(any())).thenReturn(contractAgreementWithConsumerId(did)); - when(bdrsClient.resolveBpn(did)).thenReturn(bpn); - var transferProcess = TransferProcess.Builder.newInstance() - .dataDestination(dataAddress) - .contractId("contractId") - .build(); - - var result = generator.generate(transferProcess, dataAddress, build); - - assertThat(result) - .asInstanceOf(type(AdditionalHeadersResourceDefinition.class)) - .satisfies(resourceDefinition -> { - assertThat(resourceDefinition.getDataAddress()) - .extracting(address -> HttpDataAddress.Builder.newInstance().copyFrom(address).build()) - .extracting(HttpDataAddress::getBaseUrl) - .isEqualTo("http://any"); - assertThat(resourceDefinition.getContractId()).isEqualTo("contractId"); - assertThat(resourceDefinition.getBpn()).isEqualTo(bpn); - }); - verify(contractAgreementService).findById("contractId"); - } -} diff --git a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinitionTest.java b/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinitionTest.java deleted file mode 100644 index a549ab00cf..0000000000 --- a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/AdditionalHeadersResourceDefinitionTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import org.eclipse.edc.json.JacksonTypeManager; -import org.eclipse.edc.spi.types.domain.DataAddress; -import org.junit.jupiter.api.Test; - -import java.util.UUID; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - -class AdditionalHeadersResourceDefinitionTest { - - @Test - void serdes() { - var typeManager = new JacksonTypeManager(); - var definition = - AdditionalHeadersResourceDefinition.Builder.newInstance() - .id(UUID.randomUUID().toString()) - .transferProcessId(UUID.randomUUID().toString()) - .dataAddress(DataAddress.Builder.newInstance().type("type").build()) - .contractId(UUID.randomUUID().toString()) - .build(); - - var json = typeManager.writeValueAsString(definition); - var deserialized = typeManager.readValue(json, AdditionalHeadersResourceDefinition.class); - - assertThat(deserialized).usingRecursiveComparison().isEqualTo(definition); - } -} diff --git a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java b/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java deleted file mode 100644 index b05713cee0..0000000000 --- a/edc-extensions/provision-additional-headers/src/test/java/org/eclipse/tractusx/edc/provision/additionalheaders/ProvisionAdditionalHeadersExtensionTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation - * - * 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.provision.additionalheaders; - -import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ProvisionManager; -import org.eclipse.edc.connector.controlplane.transfer.spi.provision.ResourceManifestGenerator; -import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; -import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import static org.mockito.ArgumentMatchers.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -@ExtendWith(DependencyInjectionExtension.class) -class ProvisionAdditionalHeadersExtensionTest { - - private final ResourceManifestGenerator resourceManifestGenerator = mock(); - private final ProvisionManager provisionManager = mock(); - - @BeforeEach - void setUp(ServiceExtensionContext context) { - context.registerService(ResourceManifestGenerator.class, resourceManifestGenerator); - context.registerService(ProvisionManager.class, provisionManager); - } - - @Test - void initializeShouldRegisterProvisioner(ProvisionAdditionalHeadersExtension extension, ServiceExtensionContext context) { - extension.initialize(context); - - verify(resourceManifestGenerator).registerGenerator(isA(AdditionalHeadersResourceDefinitionGenerator.class)); - verify(provisionManager).register(isA(AdditionalHeadersProvisioner.class)); - } -} diff --git a/edc-extensions/sql-vault/build.gradle.kts b/edc-extensions/sql-vault/build.gradle.kts index ef2302cbe1..2791209076 100644 --- a/edc-extensions/sql-vault/build.gradle.kts +++ b/edc-extensions/sql-vault/build.gradle.kts @@ -21,7 +21,7 @@ plugins { repositories { mavenCentral() } -val edcVersion = "0.15.1" +val edcVersion = "0.17.0" dependencies { implementation("org.eclipse.edc:sql-lib:${edcVersion}") diff --git a/edc-extensions/token-interceptor/build.gradle.kts b/edc-extensions/token-interceptor/build.gradle.kts index c73c2c068f..023624d5de 100644 --- a/edc-extensions/token-interceptor/build.gradle.kts +++ b/edc-extensions/token-interceptor/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(libs.edc.lib.dsp.catalog.http.api) implementation(libs.edc.lib.dsp.negotiation.http.api) implementation(libs.edc.lib.dsp.transfer.http.api) + implementation(project(":spi:dsp-spi-08")) testImplementation(libs.edc.junit) } diff --git a/edc-extensions/token-interceptor/src/main/java/org/eclipse/tractusx/edc/interceptor/OkHttpInterceptor.java b/edc-extensions/token-interceptor/src/main/java/org/eclipse/tractusx/edc/interceptor/OkHttpInterceptor.java index 9be6a7ae35..f4c611eca1 100644 --- a/edc-extensions/token-interceptor/src/main/java/org/eclipse/tractusx/edc/interceptor/OkHttpInterceptor.java +++ b/edc-extensions/token-interceptor/src/main/java/org/eclipse/tractusx/edc/interceptor/OkHttpInterceptor.java @@ -36,9 +36,9 @@ import java.nio.charset.StandardCharsets; import java.util.List; -import static org.eclipse.edc.jsonld.spi.Namespaces.DSPACE_SCHEMA; import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.CATALOG_REQUEST; import static org.eclipse.edc.protocol.dsp.catalog.http.api.CatalogApiPaths.DATASET_REQUEST; +import static org.eclipse.edc.protocol.dsp.spi.type.Dsp08Constants.DSPACE_SCHEMA; import static org.eclipse.edc.protocol.dsp.spi.type.Dsp2025Constants.V_2025_1_PATH; public class OkHttpInterceptor implements Interceptor { 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 2c8f469b4b..13637b6e58 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 @@ -23,23 +23,25 @@ import org.eclipse.edc.http.spi.EdcHttpClient; import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; 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.spi.EdcException; +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.ConfigUtil; import org.eclipse.tractusx.edc.spi.tokenrefresh.common.TokenRefreshHandler; import static org.eclipse.tractusx.edc.common.tokenrefresh.TokenRefreshHandlerExtension.NAME; - @Extension(value = NAME) public class TokenRefreshHandlerExtension implements ServiceExtension { public static final String NAME = "Token Refresh Handler Extension"; - // this setting is defined by the IdentityAndTrustExtension - private static final String PARTICIPANT_DID_PROPERTY = "edc.iam.issuer.id"; + + @Inject + private Monitor monitor; @Inject private EndpointDataReferenceCache edrStore; @Inject @@ -58,15 +60,15 @@ public String name() { @Provider public TokenRefreshHandler createTokenRefreshHander(ServiceExtensionContext context) { - return new TokenRefreshHandlerImpl(edrStore, httpClient, getOwnDid(context), context.getMonitor(), + return new TokenRefreshHandlerImpl(edrStore, httpClient, getOwnDid(), context.getMonitor(), secureTokenService, typeManager.getMapper(), participantContextSupplier); } - private String getOwnDid(ServiceExtensionContext context) { - var did = context.getConfig().getString(PARTICIPANT_DID_PROPERTY, null); - if (did == null) { - ConfigUtil.missingMandatoryProperty(context.getMonitor().withPrefix("Token Refresh Handler"), PARTICIPANT_DID_PROPERTY); - } - return did; + private String getOwnDid() { + return participantContextSupplier.get().map(ParticipantContext::getIdentity).onFailure(f -> { + var message = "This connector is not configured properly, cannot continue. Error is: %s".formatted(f.getFailureDetail()); + monitor.withPrefix(getClass().getSimpleName()).severe(message); + throw new EdcException(message); + }).getContent(); } } 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 c203286dd5..89cb1d627e 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 @@ -20,9 +20,9 @@ package org.eclipse.tractusx.edc.common.tokenrefresh; import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.FormBody; import okhttp3.HttpUrl; import okhttp3.Request; -import okhttp3.RequestBody; import org.eclipse.edc.edr.spi.store.EndpointDataReferenceCache; import org.eclipse.edc.http.spi.EdcHttpClient; import org.eclipse.edc.iam.decentralizedclaims.spi.SecureTokenService; @@ -192,17 +192,27 @@ private Result createTokenRefreshRequest(String refreshEndpoint, String if (!refreshEndpoint.endsWith("/token")) { refreshEndpoint += "/token"; } + + // TODO: This version still supports the deprecated usage of query parameter to provide the + // grant_type and refresh_token. This is due to backward compatibility to ensure interoperability + // with previous versions. This should be removed in the future, if old connectors are not used + // anymore. var url = HttpUrl.parse(refreshEndpoint) .newBuilder() .addQueryParameter("grant_type", "refresh_token") .addQueryParameter("refresh_token", refreshToken) .build(); + var body = new FormBody.Builder() + .add("grant_type", "refresh_token") + .add("refresh_token", refreshToken) + .build(); + return success(new Request.Builder() .addHeader("Authorization", bearerToken) .addHeader("Content-Type", "application/x-www-form-urlencoded") .url(url) - .post(RequestBody.create(new byte[0])) + .post(body) .build()); } 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 610494beb3..7cd18bad68 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 @@ -34,6 +34,7 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; +import okio.Buffer; import org.assertj.core.api.Assertions; import org.eclipse.edc.edr.spi.store.EndpointDataReferenceCache; import org.eclipse.edc.http.spi.EdcHttpClient; @@ -115,7 +116,8 @@ void setup() { void refresh_validateCorrectRequest() throws IOException { when(edrCache.get(anyString())).thenReturn(StoreResult.success(createEdr().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 expires = 60 * 5L; + var tokenResponse = new TokenResponse("new-access-token", "new-refresh-token", expires, expires, "bearer"); var successResponse = createResponse(tokenResponse, 200, ""); when(mockedHttpClient.execute(any())).thenReturn(successResponse); var res = tokenRefreshHandler.refreshToken("token-id"); @@ -128,7 +130,19 @@ void refresh_validateCorrectRequest() throws IOException { }); verify(mockedHttpClient).execute(argThat(r -> { var hdr = r.header("Content-Type"); - return hdr != null && hdr.equalsIgnoreCase("application/x-www-form-urlencoded"); + var body = r.body(); + Buffer sink = new Buffer(); + try { + if (body != null) { + body.writeTo(sink); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return hdr != null && + hdr.equalsIgnoreCase("application/x-www-form-urlencoded") && + body != null && + sink.readUtf8().contains("grant_type=refresh_token&refresh_token=foo-refresh-token"); })); } diff --git a/edc-extensions/provision-additional-headers/build.gradle.kts b/edc-extensions/validators/contract-definition-policies/build.gradle.kts similarity index 80% rename from edc-extensions/provision-additional-headers/build.gradle.kts rename to edc-extensions/validators/contract-definition-policies/build.gradle.kts index 8c9059eb57..2d4b3d31ec 100644 --- a/edc-extensions/provision-additional-headers/build.gradle.kts +++ b/edc-extensions/validators/contract-definition-policies/build.gradle.kts @@ -1,5 +1,5 @@ /******************************************************************************** - * Copyright (c) 2023 Contributors to the Eclipse Foundation + * Copyright (c) 2026 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -23,12 +23,9 @@ plugins { } dependencies { - implementation(project(":spi:bdrs-client-spi")) - implementation(libs.edc.spi.controlplane) - implementation(libs.edc.spi.core) - implementation(libs.edc.spi.transfer) - implementation(libs.edc.spi.dataplane.http) + implementation(libs.edc.lib.validator) + implementation(project(":edc-extensions:cx-policy")) testImplementation(libs.edc.junit) } diff --git a/edc-extensions/validators/contract-definition-policies/src/main/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/ContractDefinitionPoliciesValidatorExtension.java b/edc-extensions/validators/contract-definition-policies/src/main/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/ContractDefinitionPoliciesValidatorExtension.java new file mode 100644 index 0000000000..10598b1b21 --- /dev/null +++ b/edc-extensions/validators/contract-definition-policies/src/main/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/ContractDefinitionPoliciesValidatorExtension.java @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2026 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.validators.contractdefinitionpolicies; + +import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; +import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.validator.jsonobject.JsonObjectValidator; +import org.eclipse.edc.validator.spi.JsonObjectValidatorRegistry; + +import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ACCESSPOLICY_ID; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_CONTRACTPOLICY_ID; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_TYPE; +import static org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition.EDC_POLICY_DEFINITION_TYPE; +import static org.eclipse.tractusx.edc.policy.cx.validator.PolicyValidationConstants.ACTION_ACCESS; +import static org.eclipse.tractusx.edc.policy.cx.validator.PolicyValidationConstants.ACTION_USAGE; + +@Extension(ContractDefinitionPoliciesValidatorExtension.NAME) +public class ContractDefinitionPoliciesValidatorExtension implements ServiceExtension { + + public static final String NAME = "Contract Definition Policies Validator Extension"; + + @Inject + private JsonObjectValidatorRegistry validatorRegistry; + + @Inject + private PolicyDefinitionService policyDefinitionService; + + @Inject + private ContractDefinitionService contractDefinitionService; + + @Override + public String name() { + return NAME; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var contractDefinitionsValidator = JsonObjectValidator.newValidator() + .verify(CONTRACT_DEFINITION_ACCESSPOLICY_ID, path -> new PolicyActionMatchesExpected(path, policyDefinitionService, ACTION_ACCESS)) + .verify(CONTRACT_DEFINITION_CONTRACTPOLICY_ID, path -> new PolicyActionMatchesExpected(path, policyDefinitionService, ACTION_USAGE)) + .build(); + validatorRegistry.register(CONTRACT_DEFINITION_TYPE, contractDefinitionsValidator); + + var policyDefinitionsValidator = JsonObjectValidator.newValidator() + .verifyId(path -> new NotReferencedByContractDefinition(path, contractDefinitionService)) + .build(); + validatorRegistry.register(EDC_POLICY_DEFINITION_TYPE, policyDefinitionsValidator); + } +} diff --git a/edc-extensions/validators/contract-definition-policies/src/main/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/NotReferencedByContractDefinition.java b/edc-extensions/validators/contract-definition-policies/src/main/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/NotReferencedByContractDefinition.java new file mode 100644 index 0000000000..3b8bab5b8f --- /dev/null +++ b/edc-extensions/validators/contract-definition-policies/src/main/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/NotReferencedByContractDefinition.java @@ -0,0 +1,74 @@ +/******************************************************************************** + * Copyright (c) 2026 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.validators.contractdefinitionpolicies; + +import jakarta.json.JsonString; +import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; +import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.spi.result.ServiceResult; +import org.eclipse.edc.validator.jsonobject.JsonLdPath; +import org.eclipse.edc.validator.spi.ValidationResult; +import org.eclipse.edc.validator.spi.Validator; + +import java.util.List; +import java.util.stream.Stream; + +import static org.eclipse.edc.spi.query.Criterion.criterion; +import static org.eclipse.edc.spi.query.CriterionOperatorRegistry.EQUAL; +import static org.eclipse.edc.spi.result.ServiceResult.conflict; +import static org.eclipse.edc.spi.result.ServiceResult.success; +import static org.eclipse.edc.validator.spi.Violation.violation; + +public class NotReferencedByContractDefinition implements Validator { + + private final JsonLdPath path; + private final ContractDefinitionService contractDefinitionService; + + public NotReferencedByContractDefinition(JsonLdPath path, ContractDefinitionService contractDefinitionService) { + this.path = path; + this.contractDefinitionService = contractDefinitionService; + } + + @Override + public ValidationResult validate(JsonString id) { + var queryAccessPolicy = QuerySpec.Builder.newInstance() + .filter(criterion("accessPolicyId", EQUAL, id.getString())) + .build(); + + var queryContractPolicy = QuerySpec.Builder.newInstance() + .filter(criterion("contractPolicyId", EQUAL, id.getString())) + .build(); + + var referencedContractDefinitions = contractDefinitionService.search(queryAccessPolicy) + .compose(accessPolicyMatches -> contractDefinitionService.search(queryContractPolicy) + .compose(contractPolicyMatches -> ServiceResult.success( + Stream.concat(accessPolicyMatches.stream(), contractPolicyMatches.stream()).toList()))); + + return referencedContractDefinitions + .compose(this::isListEmpty) + .map(v -> ValidationResult.success()) + .orElse(failure -> ValidationResult.failure(violation(failure.getFailureDetail(), path.toString()))); + } + + private ServiceResult isListEmpty(List list) { + return list.isEmpty() ? success() : conflict("Policy Definition is referenced by a Contract Definition"); + } +} diff --git a/edc-extensions/validators/contract-definition-policies/src/main/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/PolicyActionMatchesExpected.java b/edc-extensions/validators/contract-definition-policies/src/main/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/PolicyActionMatchesExpected.java new file mode 100644 index 0000000000..d918bc6030 --- /dev/null +++ b/edc-extensions/validators/contract-definition-policies/src/main/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/PolicyActionMatchesExpected.java @@ -0,0 +1,88 @@ +/******************************************************************************** + * Copyright (c) 2026 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.validators.contractdefinitionpolicies; + +import jakarta.json.JsonObject; +import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; +import org.eclipse.edc.policy.model.Action; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.validator.jsonobject.JsonLdPath; +import org.eclipse.edc.validator.spi.ValidationResult; +import org.eclipse.edc.validator.spi.Validator; + +import java.util.Optional; + +import static java.lang.String.format; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE; +import static org.eclipse.edc.validator.spi.Violation.violation; + +public class PolicyActionMatchesExpected implements Validator { + + private final JsonLdPath path; + private final PolicyDefinitionService policyDefinitionService; + private final String expectedAction; + + public PolicyActionMatchesExpected( + JsonLdPath path, + PolicyDefinitionService policyDefinitionService, + String expectedAction) { + this.path = path; + this.policyDefinitionService = policyDefinitionService; + this.expectedAction = expectedAction; + } + + @Override + public ValidationResult validate(JsonObject input) { + var policyId = getPolicyId(input); + if (policyId == null) { + return ValidationResult.failure( + violation(format("Could not get value for path '%s' in '%s'", path, input), path.toString())); + } + + var policyDefinition = policyDefinitionService.findById(policyId); + if (policyDefinition == null) { + return ValidationResult.failure( + violation(format("Policy with ID '%s' does not exist", policyId), path.toString())); + } + + var hasExpectedAction = policyDefinition.getPolicy().getPermissions().stream() + .map(Permission::getAction) + .map(Action::getType) + .allMatch(expectedAction::equals); + + return hasExpectedAction + ? ValidationResult.success() + : ValidationResult.failure( + violation(format("Policy '%s' does not have the expected permission action '%s'", + policyId, expectedAction), path.toString())); + } + + private String getPolicyId(JsonObject input) { + try { + return Optional.ofNullable(input.getJsonArray(path.last())) + .filter(it -> !it.isEmpty()) + .map(it -> it.getJsonObject(0)) + .map(it -> it.getString(VALUE)) + .orElse(null); + } catch (ClassCastException e) { + return null; + } + } +} diff --git a/edc-extensions/validators/contract-definition-policies/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/validators/contract-definition-policies/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..2c73ba36df --- /dev/null +++ b/edc-extensions/validators/contract-definition-policies/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2026 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 +################################################################################# + +org.eclipse.tractusx.edc.validators.contractdefinitionpolicies.ContractDefinitionPoliciesValidatorExtension diff --git a/edc-extensions/validators/contract-definition-policies/src/test/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/NotReferencedByContractDefinitionTest.java b/edc-extensions/validators/contract-definition-policies/src/test/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/NotReferencedByContractDefinitionTest.java new file mode 100644 index 0000000000..3023114dc5 --- /dev/null +++ b/edc-extensions/validators/contract-definition-policies/src/test/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/NotReferencedByContractDefinitionTest.java @@ -0,0 +1,104 @@ +/******************************************************************************** + * Copyright (c) 2026 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.validators.contractdefinitionpolicies; + +import jakarta.json.Json; +import jakarta.json.JsonString; +import org.assertj.core.api.Assertions; +import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; +import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; +import org.eclipse.edc.spi.query.QuerySpec; +import org.eclipse.edc.spi.result.ServiceResult; +import org.eclipse.edc.validator.jsonobject.JsonLdPath; +import org.eclipse.edc.validator.spi.ValidationFailure; +import org.eclipse.edc.validator.spi.Violation; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.InstanceOfAssertFactories.list; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class NotReferencedByContractDefinitionTest { + + private final JsonLdPath path = new JsonLdPath("@id"); + private final ContractDefinitionService contractDefinitionService = mock(); + private final NotReferencedByContractDefinition validator = new NotReferencedByContractDefinition(path, contractDefinitionService); + private final JsonString policyId = Json.createValue("policy-id"); + + @Test + void shouldFail_whenSearchAccessPolicyFails() { + when(contractDefinitionService.search(any(QuerySpec.class))) + .thenReturn(ServiceResult.conflict("accessPolicy search failed")); + + var result = validator.validate(policyId); + + assertThat(result).isFailed() + .extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class)) + .isNotEmpty() + .anySatisfy(violation -> Assertions.assertThat(violation.path()).isEqualTo(path.toString())) + .anySatisfy(violation -> Assertions.assertThat(violation.message()).isEqualTo("accessPolicy search failed")); + } + + @Test + void shouldFail_whenSearchContractPolicyFails() { + when(contractDefinitionService.search(any(QuerySpec.class))) + .thenReturn(ServiceResult.success(List.of())) + .thenReturn(ServiceResult.conflict("contractPolicy search failed")); + + var result = validator.validate(policyId); + + assertThat(result).isFailed() + .extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class)) + .isNotEmpty() + .anySatisfy(violation -> Assertions.assertThat(violation.path()).isEqualTo(path.toString())) + .anySatisfy(violation -> Assertions.assertThat(violation.message()).isEqualTo("contractPolicy search failed")); + } + + @Test + void shouldFail_whenPolicyIsReferencedByContractDefinition() { + when(contractDefinitionService.search(any(QuerySpec.class))) + .thenReturn(ServiceResult.success(List.of(mock(ContractDefinition.class)))) + .thenReturn(ServiceResult.success(List.of())); + + var result = validator.validate(policyId); + + assertThat(result).isFailed() + .extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class)) + .isNotEmpty() + .anySatisfy(violation -> Assertions.assertThat(violation.path()).isEqualTo(path.toString())) + .anySatisfy(violation -> Assertions.assertThat(violation.message()) + .isEqualTo("Policy Definition is referenced by a Contract Definition")); + } + + @Test + void shouldPass_whenPolicyIsNotReferencedByContractDefinition() { + when(contractDefinitionService.search(any(QuerySpec.class))) + .thenReturn(ServiceResult.success(List.of())) + .thenReturn(ServiceResult.success(List.of())); + + var result = validator.validate(policyId); + + assertThat(result).isSucceeded(); + } +} diff --git a/edc-extensions/validators/contract-definition-policies/src/test/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/PolicyActionMatchesExpectedTest.java b/edc-extensions/validators/contract-definition-policies/src/test/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/PolicyActionMatchesExpectedTest.java new file mode 100644 index 0000000000..1c91af91ab --- /dev/null +++ b/edc-extensions/validators/contract-definition-policies/src/test/java/org/eclipse/tractusx/edc/validators/contractdefinitionpolicies/PolicyActionMatchesExpectedTest.java @@ -0,0 +1,158 @@ +/******************************************************************************** + * Copyright (c) 2026 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.validators.contractdefinitionpolicies; + +import jakarta.json.JsonObject; +import org.assertj.core.api.Assertions; +import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; +import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; +import org.eclipse.edc.policy.model.Action; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.validator.jsonobject.JsonLdPath; +import org.eclipse.edc.validator.spi.ValidationFailure; +import org.eclipse.edc.validator.spi.Violation; +import org.junit.jupiter.api.Test; + +import static jakarta.json.Json.createArrayBuilder; +import static jakarta.json.Json.createObjectBuilder; +import static org.assertj.core.api.InstanceOfAssertFactories.list; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VALUE; +import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class PolicyActionMatchesExpectedTest { + + private final JsonLdPath path = new JsonLdPath("contractPolicyId"); + private final PolicyDefinitionService policyDefinitionService = mock(); + private static final String EXPECTED_ACTION = "use"; + private final PolicyActionMatchesExpected policyActionMatchesExpected = new PolicyActionMatchesExpected( + path, + policyDefinitionService, + EXPECTED_ACTION); + + private JsonObject createInput() { + // Simplified input for testing purposes. + // Real input is a Contract Definition in expanded JsonLD + return createObjectBuilder() + .add("@id", "id") + .add("accessPolicyId", createArrayBuilder().add(createObjectBuilder().add(VALUE, "policy-id"))) + .add("contractPolicyId", createArrayBuilder().add(createObjectBuilder().add(VALUE, "policy-id"))) + .build(); + } + + private PolicyDefinition createPolicyDefinitionWithAction(String actionType) { + var action = Action.Builder.newInstance() + .type(actionType) + .build(); + var permission = Permission.Builder.newInstance() + .action(action) + .build(); + var policy = Policy.Builder.newInstance() + .permission(permission) + .build(); + + return PolicyDefinition.Builder.newInstance() + .id("policy-id") + .policy(policy) + .build(); + } + + @Test + void shouldFail_whenJsonLdPathIsNotFound() { + var input = createObjectBuilder().build(); + + var result = policyActionMatchesExpected.validate(input); + + assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class)) + .isNotEmpty() + .anySatisfy(violation -> Assertions.assertThat(violation.path()).isEqualTo(path.toString())) + .anySatisfy(violation -> Assertions.assertThat(violation.message()) + .isEqualTo("Could not get value for path 'contractPolicyId' in '{}'")); + } + + @Test + void shouldFail_whenJsonLdPathIsNotJsonArray() { + var input = createObjectBuilder() + .add("contractPolicyId", 123) + .build(); + + var result = policyActionMatchesExpected.validate(input); + + assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class)) + .isNotEmpty() + .anySatisfy(violation -> Assertions.assertThat(violation.path()).isEqualTo(path.toString())) + .anySatisfy(violation -> Assertions.assertThat(violation.message()) + .isEqualTo("Could not get value for path 'contractPolicyId' in '{\"contractPolicyId\":123}'")); + } + + @Test + void shouldFail_whenJsonLdPathIsNotJsonLdValue() { + var input = createObjectBuilder() + .add("contractPolicyId", createArrayBuilder()) + .build(); + + var result = policyActionMatchesExpected.validate(input); + + assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class)) + .isNotEmpty() + .anySatisfy(violation -> Assertions.assertThat(violation.path()).isEqualTo(path.toString())) + .anySatisfy(violation -> Assertions.assertThat(violation.message()) + .isEqualTo("Could not get value for path 'contractPolicyId' in '{\"contractPolicyId\":[]}'")); + } + + @Test + void shouldFail_whenPolicyDoesNotExist() { + when(policyDefinitionService.findById("policy-id")).thenReturn(null); + + var result = policyActionMatchesExpected.validate(createInput()); + + assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class)) + .isNotEmpty() + .anySatisfy(violation -> Assertions.assertThat(violation.path()).isEqualTo(path.toString())) + .anySatisfy(violation -> Assertions.assertThat(violation.message()) + .isEqualTo("Policy with ID 'policy-id' does not exist")); + } + + @Test + void shouldFail_whenExpectedActionDoesNotMatchActualAction() { + var policyDefinition = createPolicyDefinitionWithAction("access"); + when(policyDefinitionService.findById("policy-id")).thenReturn(policyDefinition); + + var result = policyActionMatchesExpected.validate(createInput()); + + assertThat(result).isFailed().extracting(ValidationFailure::getViolations).asInstanceOf(list(Violation.class)) + .isNotEmpty() + .anySatisfy(violation -> Assertions.assertThat(violation.path()).isEqualTo(path.toString())) + .anySatisfy(violation -> Assertions.assertThat(violation.message()) + .isEqualTo("Policy 'policy-id' does not have the expected permission action 'use'")); + } + + @Test + void shouldPass_whenExpectedActionMatchesActualAction() { + var policyDefinition = createPolicyDefinitionWithAction(EXPECTED_ACTION); + when(policyDefinitionService.findById("policy-id")).thenReturn(policyDefinition); + + var result = policyActionMatchesExpected.validate(createInput()); + + assertThat(result).isSucceeded(); + } +} diff --git a/edc-extensions/validators/empty-asset-selector/src/test/java/org/eclipse/tractusx/edc/validators/emptyassetselector/EmptyAssetSelectorValidatorTest.java b/edc-extensions/validators/empty-asset-selector/src/test/java/org/eclipse/tractusx/edc/validators/emptyassetselector/EmptyAssetSelectorValidatorTest.java index 899e626a0d..7c26bf3165 100644 --- a/edc-extensions/validators/empty-asset-selector/src/test/java/org/eclipse/tractusx/edc/validators/emptyassetselector/EmptyAssetSelectorValidatorTest.java +++ b/edc-extensions/validators/empty-asset-selector/src/test/java/org/eclipse/tractusx/edc/validators/emptyassetselector/EmptyAssetSelectorValidatorTest.java @@ -22,6 +22,7 @@ import jakarta.json.Json; import jakarta.json.JsonArrayBuilder; import org.assertj.core.api.Assertions; +import org.eclipse.edc.spi.query.CriterionOperator; import org.eclipse.edc.spi.query.CriterionOperatorRegistry; import org.eclipse.edc.validator.jsonobject.JsonObjectValidator; import org.eclipse.edc.validator.spi.ValidationFailure; @@ -46,6 +47,7 @@ class EmptyAssetSelectorValidatorTest { CriterionOperatorRegistry criterionOperatorRegistry = mock(); + CriterionOperator criterionOperator = new CriterionOperator(CriterionOperatorRegistry.IN, Iterable.class, null); private final JsonObjectValidator validator = EmptyAssetSelectorValidator.instance(criterionOperatorRegistry); @@ -54,7 +56,7 @@ void shouldPass_whenContractDefinitionIsCorrect() { var criterion = createObjectBuilder() .add(CRITERION_OPERAND_LEFT, value("operandLeft")) - .add(CRITERION_OPERATOR, value("=")) + .add(CRITERION_OPERATOR, value(CriterionOperatorRegistry.IN)) .add(CRITERION_OPERAND_RIGHT, value("operandRight")); var contractDefinition = createObjectBuilder() @@ -63,7 +65,7 @@ void shouldPass_whenContractDefinitionIsCorrect() { .add(CONTRACT_DEFINITION_ASSETS_SELECTOR, createArrayBuilder().add(criterion)) .build(); - when(criterionOperatorRegistry.isSupported("=")).thenReturn(true); + when(criterionOperatorRegistry.get(CriterionOperatorRegistry.IN)).thenReturn(criterionOperator); var result = validator.validate(contractDefinition); diff --git a/edc-tests/compatibility-tests/build.gradle.kts b/edc-tests/compatibility-tests/build.gradle.kts index 974b72c824..7dd12b49cb 100644 --- a/edc-tests/compatibility-tests/build.gradle.kts +++ b/edc-tests/compatibility-tests/build.gradle.kts @@ -23,7 +23,7 @@ plugins { } configurations.all { - exclude("org.eclipse.edc", "decentralized-claims-core") + exclude("com.networknt", "json-schema-validator") } dependencies { @@ -41,11 +41,12 @@ dependencies { testImplementation(libs.jacksonJsonP) testImplementation(libs.restAssured) testImplementation(libs.awaitility) - testImplementation(libs.wiremock) + testImplementation(libs.wiremock) { + exclude("com.networknt", "json-schema-validator") + } testImplementation(libs.testcontainers.junit) testImplementation(libs.testcontainers.postgres) testImplementation(testFixtures(libs.edc.api.management.test.fixtures)) testImplementation(testFixtures(libs.edc.sql.test.fixtures)) testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) - testImplementation("com.networknt:json-schema-validator:2.0.0") } diff --git a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/DcpHelperFunctions.java b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/DcpHelperFunctions.java index 248109d90c..3d59e0cfb3 100644 --- a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/DcpHelperFunctions.java +++ b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/DcpHelperFunctions.java @@ -22,20 +22,21 @@ import org.eclipse.edc.iam.decentralizedclaims.sts.spi.service.StsAccountService; import org.eclipse.edc.iam.did.spi.document.Service; -import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService; +import org.eclipse.edc.identityhub.spi.participantcontext.IdentityHubParticipantContextService; 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.store.CredentialStore; import org.eclipse.edc.junit.extensions.RuntimeExtension; +import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.security.Vault; import org.eclipse.tractusx.edc.tests.participant.DataspaceIssuer; -import org.eclipse.tractusx.edc.tests.participant.TractusxIatpParticipantBase; +import org.eclipse.tractusx.edc.tests.participant.TractusxDcpParticipantBase; import java.util.Base64; public class DcpHelperFunctions { public static void configureParticipantContext(DataspaceIssuer issuer, IdentityHubParticipant identityHubParticipant, RuntimeExtension identityHubRuntime) { - var participantContextService = identityHubRuntime.getService(ParticipantContextService.class); + var participantContextService = identityHubRuntime.getService(IdentityHubParticipantContextService.class); var participantKey = issuer.getKeyPairAsJwk(); var key = KeyDescriptor.Builder.newInstance() @@ -57,30 +58,31 @@ public static void configureParticipantContext(DataspaceIssuer issuer, IdentityH .active(true) .build(); - participantContextService.createParticipantContext(participantManifest); + participantContextService.createParticipantContext(participantManifest) + .orElseThrow(f -> new EdcException("Cannot create participant context: " + f.getFailureDetail())); var vault = identityHubRuntime.getService(Vault.class); vault.storeSecret(issuer.getPrivateKeyAlias(), issuer.getPrivateKeyAsString()); } - public static void configureParticipant(TractusxIatpParticipantBase participant, DataspaceIssuer issuer, IdentityHubParticipant identityHubParticipant, RuntimeExtension identityHubRuntime) { + public static void configureParticipant(TractusxDcpParticipantBase participant, DataspaceIssuer issuer, IdentityHubParticipant identityHubParticipant, RuntimeExtension identityHubRuntime) { configureParticipantContext(participant, identityHubParticipant, identityHubRuntime); var accountService = identityHubRuntime.getService(StsAccountService.class); var vault = identityHubRuntime.getService(Vault.class); var credentialStore = identityHubRuntime.getService(CredentialStore.class); - var credentials = issuer.issueCredentials(participant.getDid(), participant.getId()); + var credentials = issuer.issueCredentials(participant.getDid(), participant.getId(), participant.getParticipantContextId()); credentials.forEach(credentialStore::create); - accountService.findById(participant.getDid()) + accountService.findById(participant.getParticipantContextId()) .onSuccess(account -> vault.storeSecret(account.getSecretAlias(), "clientSecret")); } - public static void configureParticipantContext(TractusxIatpParticipantBase participant, IdentityHubParticipant identityHubParticipant, RuntimeExtension identityHubRuntime) { - var participantContextService = identityHubRuntime.getService(ParticipantContextService.class); + public static void configureParticipantContext(TractusxDcpParticipantBase participant, IdentityHubParticipant identityHubParticipant, RuntimeExtension identityHubRuntime) { + var participantContextService = identityHubRuntime.getService(IdentityHubParticipantContextService.class); var participantKey = participant.getKeyPairAsJwk(); var key = KeyDescriptor.Builder.newInstance() @@ -92,10 +94,10 @@ public static void configureParticipantContext(TractusxIatpParticipantBase parti var service = new Service(); service.setId("#credential-service"); service.setType("CredentialService"); - service.setServiceEndpoint(identityHubParticipant.getResolutionApi() + "/v1/participants/" + toBase64(participant.getDid())); + service.setServiceEndpoint(identityHubParticipant.getResolutionApi() + "/v1/participants/" + participant.getParticipantContextId()); var participantManifest = ParticipantManifest.Builder.newInstance() - .participantContextId(participant.getDid()) + .participantContextId(participant.getParticipantContextId()) .did(participant.getDid()) .key(key) .serviceEndpoint(service) diff --git a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/IdentityHubParticipant.java b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/IdentityHubParticipant.java index 84c97973b0..c2871d318f 100644 --- a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/IdentityHubParticipant.java +++ b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/IdentityHubParticipant.java @@ -60,6 +60,7 @@ public Config getConfig() { settings.put("web.http.did.path", didApi.get().getPath()); settings.put("edc.iam.did.web.use.https", "false"); settings.put("edc.api.accounts.key", "password"); + settings.put("edc.encryption.strict", "false"); return ConfigFactory.fromMap(settings); } @@ -69,7 +70,7 @@ public LazySupplier getSts() { } public URI getResolutionApi() { - return credentialsApi.get(); + return URI.create(credentialsApi.get().toString()); } public String didFor(String participantId) { @@ -77,6 +78,10 @@ public String didFor(String participantId) { return "did:web:" + URLEncoder.encode(didUri.getHost() + ":" + didUri.getPort(), StandardCharsets.UTF_8) + ":" + participantId; } + public String bpnFor(String participant) { + return "BPNL0" + participant; + } + public static class Builder { protected final IdentityHubParticipant participant; diff --git a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/RemoteParticipant.java b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/RemoteParticipant.java index 6eeb40d840..e5fe326396 100644 --- a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/RemoteParticipant.java +++ b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/RemoteParticipant.java @@ -20,27 +20,93 @@ package org.eclipse.tractusx.edc.compatibility.tests.fixtures; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import org.eclipse.edc.connector.controlplane.test.system.utils.Participant; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; -import org.eclipse.tractusx.edc.tests.participant.IatpParticipant; -import org.eclipse.tractusx.edc.tests.participant.TractusxIatpParticipantBase; +import org.eclipse.tractusx.edc.tests.participant.DcpParticipant; +import org.eclipse.tractusx.edc.tests.participant.TractusxDcpParticipantBase; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import static io.restassured.http.ContentType.JSON; +import static jakarta.json.Json.createObjectBuilder; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_ASSIGNER_ATTRIBUTE; +import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_TARGET_ATTRIBUTE; +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.DSP_2025; -public class RemoteParticipant extends IatpParticipant { +public class RemoteParticipant extends DcpParticipant { private final List datasources = List.of("asset", "contractdefinition", "contractnegotiation", "policy", "transferprocess", "bpn", "policy-monitor", "edr", "dataplane", "accesstokendata", "dataplaneinstance"); + private final Map agreementAssetIds = new ConcurrentHashMap<>(); + private static final String HTTP_PULL_TRANSFER_TYPE = "HttpData-PULL"; - public Config getConfig(IatpParticipant participant, PostgresExtension postgresql) { + @Override + public String negotiateContract(Participant provider, JsonObject offer) { + var participant = (TractusxDcpParticipantBase) provider; + var correctedOffer = createObjectBuilder(offer) + .add(ODRL_ASSIGNER_ATTRIBUTE, createObjectBuilder().add(ID, participant.getDid())) + .build(); + + var agreementId = super.negotiateContract(provider, correctedOffer); + agreementAssetIds.put(agreementId, extractAssetId(offer)); + return agreementId; + } + + @Override + public String initiateTransfer( + Participant provider, + String agreementId, + JsonObject privateProperties, + JsonObject destination, + String transferType, + JsonArray callbacks) { + + var transferRequest = createObjectBuilder() + .add(CONTEXT, createObjectBuilder() + .add("@vocab", EDC_NAMESPACE) + .build()) + .add(TYPE, "TransferRequest") + .add("counterPartyAddress", provider.getProtocolUrl()) + .add("contractId", agreementId) + .add("assetId", agreementAssetIds.remove(agreementId)) + .add("protocol", DSP_2025) + .add("transferType", transferType != null ? transferType : HTTP_PULL_TRANSFER_TYPE) + .build(); + + return baseManagementRequest() + .contentType(JSON) + .body(transferRequest) + .when() + .post("/transferprocesses") + .then() + .statusCode(200) + .extract() + .jsonPath() + .getString("'@id'"); + } + + private String extractAssetId(JsonObject offer) { + var target = offer.get(ODRL_TARGET_ATTRIBUTE); + var targetObj = (JsonObject) target; + return targetObj.getString(ID, null); + } + + public Config getConfig(DcpParticipant participant, PostgresExtension postgresql) { var postgresqlConfig = postgresql.getConfig(getName()); - Map settings = new HashMap<>() { + Map settings = new HashMap<>() { { put("edc.participant.id", id); put("edc.api.auth.key", MANAGEMENT_API_KEY); @@ -67,10 +133,12 @@ public Config getConfig(IatpParticipant participant, PostgresExtension postgresq put("testing.edc.vaults.3.value", getPublicKeyAsString()); put("edc.iam.issuer.id", getDid()); put("edc.iam.did.web.use.https", "false"); + put("tractusx.edc.participant.bpn", getBpn()); put("testing.edc.bdrs.1.key", participant.getId()); put("testing.edc.bdrs.1.value", participant.getDid()); put("edc.iam.trusted-issuer.issuer.id", trustedIssuer); put("edc.sql.schema.autocreate", "false"); + put("edc.participant.context.id", "participant-context-id"); put("web.http.public.path", dataPlanePublic.get().getPath()); put("web.http.public.port", String.valueOf(dataPlanePublic.get().getPort())); put("edc.transfer.proxy.token.signer.privatekey.alias", getPrivateKeyAlias()); @@ -89,7 +157,7 @@ private Map datasourceConfig(Config postgresqlConfig) { config.put("edc.datasource." + ds + ".name", ds); config.putAll(datasourceEnvironmentVariables(ds, postgresqlConfig)); }); - config.put("org.eclipse.tractusx.edc.postgresql.migration.schema", postgresqlConfig.getString("tx.edc.postgresql.migration.schema")); + config.put("tx.edc.postgresql.migration.schema", postgresqlConfig.getString("tx.edc.postgresql.migration.schema")); return config; } @@ -101,16 +169,25 @@ private Map datasourceEnvironmentVariables(String datasourceName ); } - public static class Builder extends TractusxIatpParticipantBase.Builder { + public static class Builder extends TractusxDcpParticipantBase.Builder { protected Builder() { super(new RemoteParticipant()); } + protected Builder(RemoteParticipant participant) { + super(participant); + } + public static Builder newInstance() { return new Builder(); } + public Builder protocol(String protocolName, String path) { + participant.protocol = new Protocol(protocolName, path); + return protocolVersionPath(path); + } + @Override public RemoteParticipant build() { super.build(); diff --git a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/RemoteParticipantExtension.java b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/RemoteParticipantExtension.java index 9c89e816a9..03ea0666a1 100644 --- a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/RemoteParticipantExtension.java +++ b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/RemoteParticipantExtension.java @@ -19,7 +19,7 @@ package org.eclipse.tractusx.edc.compatibility.tests.fixtures; -import org.eclipse.tractusx.edc.tests.participant.IatpParticipant; +import org.eclipse.tractusx.edc.tests.participant.DcpParticipant; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -29,12 +29,12 @@ public class RemoteParticipantExtension implements BeforeAllCallback, AfterAllCallback { private final RemoteParticipant participant; - private final IatpParticipant localParticipant; + private final DcpParticipant localParticipant; private final PostgresExtension postgresql; private GenericContainer connector; - public RemoteParticipantExtension(RemoteParticipant participant, IatpParticipant localParticipant, PostgresExtension postgresql) { + public RemoteParticipantExtension(RemoteParticipant participant, DcpParticipant localParticipant, PostgresExtension postgresql) { this.participant = participant; this.localParticipant = localParticipant; this.postgresql = postgresql; diff --git a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/Runtimes.java b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/Runtimes.java index bacd7d6368..2633d0c1bd 100644 --- a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/Runtimes.java +++ b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/fixtures/Runtimes.java @@ -28,7 +28,7 @@ public enum Runtimes { SNAPSHOT_CONNECTOR(":edc-tests:runtime:runtime-compatibility:snapshot:connector-snapshot"), STABLE_CONNECTOR(":edc-tests:runtime:runtime-compatibility:stable:connector-stable"), - IDENTITY_HUB(":edc-tests:runtime:iatp:runtime-memory-sts"); + IDENTITY_HUB(":edc-tests:runtime:dcp:runtime-memory-sts"); private final String[] modules; private URL[] classpathEntries; diff --git a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/transfer/TransferEndToEndTest.java b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/transfer/TransferEndToEndTest.java index 14bd4a7449..b9cba171f7 100644 --- a/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/transfer/TransferEndToEndTest.java +++ b/edc-tests/compatibility-tests/src/test/java/org/eclipse/tractusx/edc/compatibility/tests/transfer/TransferEndToEndTest.java @@ -22,7 +22,6 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import jakarta.json.JsonObject; -import org.eclipse.edc.connector.controlplane.test.system.utils.PolicyFixtures; import org.eclipse.edc.junit.extensions.RuntimeExtension; import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; import org.eclipse.edc.spi.iam.AudienceResolver; @@ -35,8 +34,8 @@ import org.eclipse.tractusx.edc.compatibility.tests.fixtures.Runtimes; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.eclipse.tractusx.edc.tests.participant.DataspaceIssuer; -import org.eclipse.tractusx.edc.tests.participant.IatpParticipant; -import org.eclipse.tractusx.edc.tests.participant.TractusxIatpParticipantBase; +import org.eclipse.tractusx.edc.tests.participant.DcpParticipant; +import org.eclipse.tractusx.edc.tests.participant.TractusxDcpParticipantBase; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeAll; @@ -48,6 +47,8 @@ import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -59,20 +60,33 @@ 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.http.ContentType.JSON; +import static jakarta.json.Json.createArrayBuilder; +import static jakarta.json.Json.createObjectBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.awaitility.Awaitility.await; import static org.eclipse.edc.connector.controlplane.test.system.utils.PolicyFixtures.noConstraintPolicy; import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates.STARTED; import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates.SUSPENDED; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_CONNECTOR_MANAGEMENT_CONTEXT; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; import static org.eclipse.tractusx.edc.compatibility.tests.fixtures.DcpHelperFunctions.configureParticipant; import static org.eclipse.tractusx.edc.compatibility.tests.fixtures.DcpHelperFunctions.configureParticipantContext; -import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.inForceDatePolicyLegacy; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.dataUsageEndDate; @CompatibilityTest public class TransferEndToEndTest { + private static final String OLDEST_STABLE_VERSION = "/v3"; + private static final String HTTP_PULL_TRANSFER_TYPE = "HttpData-PULL"; + private static final String DUMMY_DATA_RESPONSE = "data"; + private static final String SOURCE_PATH_SUFFIX = "/source"; + protected static final IdentityHubParticipant IDENTITY_HUB_PARTICIPANT = IdentityHubParticipant.Builder.newInstance() .name("identity-hub") .id("identity-hub") @@ -84,18 +98,20 @@ public class TransferEndToEndTest { protected static final RemoteParticipant REMOTE_PARTICIPANT = RemoteParticipant.Builder.newInstance() .name("remote") - .id("remote") + .id(IDENTITY_HUB_PARTICIPANT.didFor("remote")) .stsUri(IDENTITY_HUB_PARTICIPANT.getSts()) .did(IDENTITY_HUB_PARTICIPANT.didFor("remote")) + .bpn(IDENTITY_HUB_PARTICIPANT.bpnFor("remote")) + .managementVersionBasePath(OLDEST_STABLE_VERSION) .trustedIssuer(ISSUER.didUrl()) .build(); - static final IatpParticipant LOCAL_PARTICIPANT = IatpParticipant.Builder.newInstance() + static final DcpParticipant LOCAL_PARTICIPANT = DcpParticipant.Builder.newInstance() .name("local") - .id("local") + .id(IDENTITY_HUB_PARTICIPANT.bpnFor("local")) .stsUri(IDENTITY_HUB_PARTICIPANT.getSts()) .did(IDENTITY_HUB_PARTICIPANT.didFor("local")) - .bpn("local") + .bpn(IDENTITY_HUB_PARTICIPANT.bpnFor("local")) .trustedIssuer(ISSUER.didUrl()) .build(); @@ -113,7 +129,7 @@ public class TransferEndToEndTest { static final RuntimeExtension LOCAL_CONNECTOR = new RuntimePerClassExtension( Runtimes.SNAPSHOT_CONNECTOR.create("local-connector") .configurationProvider(() -> POSTGRES.getConfig(LOCAL_PARTICIPANT.getName())) - .configurationProvider(LOCAL_PARTICIPANT::iatpConfig) + .configurationProvider(LOCAL_PARTICIPANT::dcpConfig) .registerServiceMock(BdrsClient.class, new BdrsClient() { @Override public String resolveDid(String bpn) { @@ -127,8 +143,12 @@ public String resolveBpn(String did) { .findFirst().orElseThrow().getKey(); } }) - .registerServiceMock(AudienceResolver.class, message -> Result - .success(DIDS.get(message.getCounterPartyId())))); + .registerServiceMock(AudienceResolver.class, message -> { + var audience = DIDS.get(message.getCounterPartyId()); + return audience != null + ? Result.success(audience) + : Result.failure("No DID found for counter-party: " + message.getCounterPartyId()); + })); @Order(2) @RegisterExtension @@ -147,6 +167,7 @@ public String resolveBpn(String did) { @BeforeAll static void beforeAll() { + configureParticipant(LOCAL_PARTICIPANT, ISSUER, IDENTITY_HUB_PARTICIPANT, LOCAL_IDENTITY_HUB); configureParticipant(REMOTE_PARTICIPANT, ISSUER, IDENTITY_HUB_PARTICIPANT, LOCAL_IDENTITY_HUB); configureParticipantContext(ISSUER, IDENTITY_HUB_PARTICIPANT, LOCAL_IDENTITY_HUB); @@ -159,60 +180,41 @@ static void beforeAll() { @ParameterizedTest @ArgumentsSource(ParticipantsArgProvider.class) - void httpPullTransfer(TractusxIatpParticipantBase consumer, TractusxIatpParticipantBase provider, String protocol) { + void httpPullTransfer(TractusxDcpParticipantBase consumer, TractusxDcpParticipantBase provider, String protocol) { consumer.setProtocol(protocol); provider.setProtocol(protocol); - providerDataSource.stubFor(any(anyUrl()).willReturn(ok("data"))); + providerDataSource.stubFor(any(anyUrl()).willReturn(ok(DUMMY_DATA_RESPONSE))); var assetId = UUID.randomUUID().toString(); - var usagePolicy = inForceDatePolicyLegacy("gteq", "contractAgreement+0s", "lteq", "contractAgreement+5s"); + var usagePolicy = dataUsageEndDate(Instant.now().plusSeconds(300).truncatedTo(ChronoUnit.SECONDS).toString()); createResourcesOnProvider(provider, assetId, usagePolicy, httpSourceDataAddress()); - var transferProcessId = consumer.requestAssetFrom(assetId, provider) - .withTransferType("HttpData-PULL") - .execute(); + var transferProcessId = startTransferProcess(consumer, provider, assetId); consumer.awaitTransferToBeInState(transferProcessId, STARTED); - var edr = await().atMost(consumer.getTimeout()) - .until(() -> consumer.edrs().getEdr(transferProcessId), Objects::nonNull); - - // Do the transfer - var msg = UUID.randomUUID().toString(); - var data = consumer.data().pullData(edr, Map.of("message", msg)); - assertThat(data).isNotNull().isEqualTo("data"); - - // checks that the EDR is gone once the contract expires - await().atMost(consumer.getTimeout()) - .untilAsserted(() -> assertThatThrownBy(() -> consumer.edrs().getEdr(transferProcessId))); + var edr = obtainEdr(consumer, transferProcessId); - // checks that transfer fails - await().atMost(consumer.getTimeout()).untilAsserted(() -> assertThatThrownBy(() -> consumer.data().pullData(edr, Map.of("message", msg)))); - - providerDataSource.verify(getRequestedFor(urlPathEqualTo("/source"))); + pullAndAssertData(consumer, edr); + providerDataSource.verify(getRequestedFor(urlPathEqualTo(SOURCE_PATH_SUFFIX))); } @ParameterizedTest @ArgumentsSource(ParticipantsArgProvider.class) - void suspendAndResume_httpPull_dataTransfer(TractusxIatpParticipantBase consumer, TractusxIatpParticipantBase provider, String protocol) { + void suspendAndResume_httpPull_dataTransfer(TractusxDcpParticipantBase consumer, TractusxDcpParticipantBase provider, String protocol) { consumer.setProtocol(protocol); provider.setProtocol(protocol); - providerDataSource.stubFor(any(anyUrl()).willReturn(ok("data"))); + providerDataSource.stubFor(any(anyUrl()).willReturn(ok(DUMMY_DATA_RESPONSE))); var assetId = UUID.randomUUID().toString(); - createResourcesOnProvider(provider, assetId, PolicyFixtures.noConstraintPolicy(), httpSourceDataAddress()); + createResourcesOnProvider(provider, assetId, noConstraintPolicy(), httpSourceDataAddress()); - var transferProcessId = consumer.requestAssetFrom(assetId, provider) - .withTransferType("HttpData-PULL") - .execute(); + var transferProcessId = startTransferProcess(consumer, provider, assetId); consumer.awaitTransferToBeInState(transferProcessId, STARTED); - - var edr = await().atMost(consumer.getTimeout()).until(() -> consumer.edrs().getEdr(transferProcessId), Objects::nonNull); - + var edr = obtainEdr(consumer, transferProcessId); var msg = UUID.randomUUID().toString(); - var data = consumer.data().pullData(edr, Map.of("message", msg)); - assertThat(data).isNotNull().isEqualTo("data"); + pullAndAssertData(consumer, edr); - consumer.suspendTransfer(transferProcessId, "supension"); + consumer.suspendTransfer(transferProcessId, "suspension"); consumer.awaitTransferToBeInState(transferProcessId, SUSPENDED); @@ -225,20 +227,112 @@ void suspendAndResume_httpPull_dataTransfer(TractusxIatpParticipantBase consumer // check that transfer is available again consumer.awaitTransferToBeInState(transferProcessId, STARTED); - var secondEdr = await().atMost(consumer.getTimeout()).until(() -> consumer.edrs().getEdr(transferProcessId), Objects::nonNull); - var secondMessage = UUID.randomUUID().toString(); - data = consumer.data().pullData(secondEdr, Map.of("message", secondMessage)); - assertThat(data).isNotNull().isEqualTo("data"); + var secondEdr = obtainEdr(consumer, transferProcessId); + pullAndAssertData(consumer, secondEdr); - providerDataSource.verify(getRequestedFor(urlPathEqualTo("/source"))); + providerDataSource.verify(getRequestedFor(urlPathEqualTo(SOURCE_PATH_SUFFIX))); } - protected void createResourcesOnProvider(TractusxIatpParticipantBase provider, String assetId, JsonObject contractPolicy, Map dataAddressProperties) { - provider.createAsset(assetId, Map.of("description", "description"), dataAddressProperties); - var contractPolicyId = provider.createPolicyDefinition(contractPolicy); - var noConstraintPolicyId = provider.createPolicyDefinition(noConstraintPolicy()); + private void createResourcesOnProvider(TractusxDcpParticipantBase provider, String assetId, JsonObject contractPolicy, Map dataAddressProperties) { + createAssetManagementContext(provider, assetId, Map.of("description", "description"), dataAddressProperties); + var contractPolicyId = createPolicyDefinitionManagementContext(provider, contractPolicy); + var noConstraintPolicyId = createPolicyDefinitionManagementContext(provider, noConstraintPolicy()); + + createContractDefinitionManagementContext(provider, assetId, UUID.randomUUID().toString(), noConstraintPolicyId, contractPolicyId); + } + + private String startTransferProcess(TractusxDcpParticipantBase consumer, TractusxDcpParticipantBase provider, String assetId) { + return consumer.requestAssetFrom(assetId, provider) + .withTransferType(HTTP_PULL_TRANSFER_TYPE) + .execute(); + } + + private void pullAndAssertData(TractusxDcpParticipantBase consumer, JsonObject edr) { + var randomMessage = Map.of("message", UUID.randomUUID().toString()); + var data = consumer.data().pullData(edr, randomMessage); + assertThat(data).isNotNull().isEqualTo(DUMMY_DATA_RESPONSE); + } + + private JsonObject obtainEdr(TractusxDcpParticipantBase consumer, String transferProcessId) { + return await().atMost(consumer.getTimeout()) + .until(() -> consumer.edrs().getEdr(transferProcessId), Objects::nonNull); + } + + private void createAssetManagementContext(TractusxDcpParticipantBase participant, String assetId, Map properties, Map dataAddressProperties) { + var propertiesBuilder = createObjectBuilder(); + properties.forEach((key, value) -> propertiesBuilder.add(key, String.valueOf(value))); + + var dataAddressBuilder = createObjectBuilder().add(TYPE, "DataAddress"); + dataAddressProperties.forEach((key, value) -> dataAddressBuilder.add(key, String.valueOf(value))); + + var requestBody = createObjectBuilder() + .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT)) + .add(ID, assetId) + .add(TYPE, "Asset") + .add(EDC_NAMESPACE + "properties", propertiesBuilder.build()) + .add(EDC_NAMESPACE + "dataAddress", dataAddressBuilder.build()) + .build(); + + participant.baseManagementRequest() + .basePath(OLDEST_STABLE_VERSION) + .contentType(JSON) + .body(requestBody) + .when() + .post("/assets") + .then() + .log().ifValidationFails() + .statusCode(200); + } + + private String createPolicyDefinitionManagementContext(TractusxDcpParticipantBase participant, JsonObject policy) { + var requestBody = createObjectBuilder() + .add(CONTEXT, createArrayBuilder().add(EDC_CONNECTOR_MANAGEMENT_CONTEXT)) + .add(ID, UUID.randomUUID().toString()) + .add(TYPE, "PolicyDefinition") + .add(EDC_NAMESPACE + "policy", policy) + .build(); + + return participant.baseManagementRequest() + .basePath(OLDEST_STABLE_VERSION) + .contentType(JSON) + .body(requestBody) + .when() + .post("/policydefinitions") + .then() + .log().ifValidationFails() + .statusCode(200) + .extract().jsonPath().getString(ID); + } - provider.createContractDefinition(assetId, UUID.randomUUID().toString(), noConstraintPolicyId, contractPolicyId); + private void createContractDefinitionManagementContext(TractusxDcpParticipantBase participant, String assetId, String definitionId, String accessPolicyId, String contractPolicyId) { + var requestBody = createObjectBuilder() + .add(CONTEXT, createObjectBuilder() + .add("@vocab", EDC_NAMESPACE) + .build()) + .add(ID, definitionId) + .add(TYPE, "ContractDefinition") + .add("accessPolicyId", accessPolicyId) + .add("contractPolicyId", contractPolicyId) + .add("assetsSelector", createArrayBuilder() + .add(createObjectBuilder() + .add(TYPE, "Criterion") + .add("operandLeft", "https://w3id.org/edc/v0.0.1/ns/id") + .add("operator", "=") + .add("operandRight", assetId) + .build()) + .build()) + .build(); + + participant.baseManagementRequest() + .basePath(OLDEST_STABLE_VERSION) + .contentType(JSON) + .body(requestBody) + .when() + .post("/contractdefinitions") + .then() + .log().ifValidationFails() + .statusCode(200) + .extract().jsonPath().getString(ID); } private @NotNull Map httpSourceDataAddress() { @@ -254,8 +348,8 @@ private static class ParticipantsArgProvider implements ArgumentsProvider { @Override public Stream provideArguments(ExtensionContext context) { return Stream.of( - Arguments.of(REMOTE_PARTICIPANT, LOCAL_PARTICIPANT, "dataspace-protocol-http"), - Arguments.of(LOCAL_PARTICIPANT, REMOTE_PARTICIPANT, "dataspace-protocol-http") + Arguments.of(REMOTE_PARTICIPANT, LOCAL_PARTICIPANT, DSP_2025), + Arguments.of(LOCAL_PARTICIPANT, REMOTE_PARTICIPANT, DSP_2025) ); } } 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 8d5ca26dde..0aa12cf0af 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 @@ -22,11 +22,10 @@ --- fullnameOverride: tx-inmem participant: - id: "test-participant" - contextId: "test-participant-context" -iatp: - # Decentralized IDentifier id: "did:web:changeme" + bpnl: "test-participant" + contextId: "test-participant-context" +dcp: sts: div: url: "https://somewhere.div.org" 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 9f3a8acaf5..42e74382aa 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 @@ -23,11 +23,10 @@ fullnameOverride: tx-prod # EDC ControlPlane + DataPlane # ################################ participant: - id: "test-participant" - contextId: "test-participant-context" -iatp: - # Decentralized IDentifier id: "did:web:changeme" + bpnl: "test-participant" + contextId: "test-participant-context" +dcp: sts: div: url: "https://somewhere.div.org" @@ -80,14 +79,6 @@ dataplane: privatekey_alias: "key-1" verifier: publickey_alias: "key-1" -postgresql: - image: - repository: "bitnamilegacy/postgresql" - tag: "16.2.0-debian-12-r10" - jdbcUrl: jdbc:postgresql://{{ .Release.Name }}-postgresql:5432/edc - auth: - username: user - password: password vault: hashicorp: url: http://{{ .Release.Name }}-vault:8200 diff --git a/edc-tests/e2e-fixtures/build.gradle.kts b/edc-tests/e2e-fixtures/build.gradle.kts index 7ba38a3c0c..17b6d2f69c 100644 --- a/edc-tests/e2e-fixtures/build.gradle.kts +++ b/edc-tests/e2e-fixtures/build.gradle.kts @@ -34,7 +34,6 @@ dependencies { testFixturesApi(project(":core:json-ld-cx")) testFixturesApi(libs.edc.ext.jsonld) - testFixturesApi(libs.edc.core.token) testFixturesApi(libs.edc.junit) testFixturesApi(libs.edc.lib.cryptocommon) testFixturesApi(libs.edc.lib.jws2020) @@ -55,7 +54,6 @@ dependencies { testFixturesApi(testFixtures(libs.edc.api.management.test.fixtures)) - testFixturesApi(libs.edc.iam.decentralized.claims.core) testFixturesApi(libs.edc.verifiablecredentials.jwt) testFixturesApi(libs.awaitility) @@ -65,7 +63,9 @@ dependencies { testFixturesApi(libs.wiremock) testFixturesApi(libs.postgres) testFixturesApi(libs.restAssured) + testFixturesApi(libs.kafka.clients) testFixturesApi(libs.testcontainers.junit) + testFixturesApi(libs.testcontainers.kafka) testFixturesApi(libs.testcontainers.minio) testFixturesApi(libs.testcontainers.localstack) testFixturesApi(libs.testcontainers.postgres) 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 4197febd99..f209ba383e 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 @@ -85,7 +85,7 @@ public Result verifyJwtToken(String participantContextId, TokenRepre var claimToken = claimTokenResult.getContent(); var bpnlConsumer = claimToken.getStringClaim(BUSINESS_PARTNER_NUMBER_CLAIM); var didConsumer = claimToken.getStringClaim(ISSUER); - var credentials = List.of(membershipCredential(bpnlConsumer, didConsumer), dataExchangeGovernanceCredential(bpnlConsumer, didConsumer)); + var credentials = List.of(membershipCredential(bpnlConsumer, didConsumer), bpnCredential(bpnlConsumer, didConsumer), dataExchangeGovernanceCredential(bpnlConsumer, didConsumer)); var claimTokenWithVc = ClaimToken.Builder.newInstance() .claim(VC_CLAIM, credentials) @@ -148,4 +148,18 @@ private VerifiableCredential membershipCredential(String bpnlConsumer, String di .issuanceDate(Instant.now()) .build(); } + + private VerifiableCredential bpnCredential(String bpnlConsumer, String didConsumer) { + return VerifiableCredential.Builder.newInstance() + .type("VerifiableCredential") + .type("BpnCredential") + .credentialSubject(CredentialSubject.Builder.newInstance() + .id(didConsumer) + .claim("holderIdentifier", bpnlConsumer) + .claim("bpn", bpnlConsumer) + .build()) + .issuer(new Issuer("issuer", Map.of())) + .issuanceDate(Instant.now()) + .build(); + } } diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/ParticipantEdrApi.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/ParticipantEdrApi.java index bb21ab5a02..e2b0fa36af 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/ParticipantEdrApi.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/ParticipantEdrApi.java @@ -88,7 +88,7 @@ public ValidatableResponse getEdrRequest(String transferProcessId) { public ValidatableResponse getEdrWithRefresh(String transferProcessId, boolean autoRefresh) { return baseEdrRequest() .when() - .get("/v3/edrs/{id}/dataaddress?auto_refresh={auto_refresh}", transferProcessId, autoRefresh) + .get("/edrs/{id}/dataaddress?auto_refresh={auto_refresh}", transferProcessId, autoRefresh) .then() .log().ifError(); @@ -100,7 +100,7 @@ public ValidatableResponse getEdrWithRefresh(String transferProcessId, boolean a public ValidatableResponse refreshEdr(String transferProcessId) { return baseEdrRequest() .when() - .post("/v3/edrs/{id}/refresh", transferProcessId) + .post("/edrs/{id}/refresh", transferProcessId) .then() .log().ifError(); } @@ -130,7 +130,7 @@ public String negotiateEdr(TransferParticipant other, String assetId, JsonArray var response = baseEdrRequest() .when() .body(requestBody) - .post("/v3/edrs") + .post("/edrs") .then(); var body = response.extract().body().asString(); @@ -150,7 +150,7 @@ public JsonArray getEdrEntriesByContractNegotiationId(String contractNegotiation return baseEdrRequest() .when() .body(query) - .post("/v3/edrs/request") + .post("/edrs/request") .then() .statusCode(200) .extract() @@ -169,7 +169,7 @@ public JsonArray getEdrEntriesByAgreementId(String agreementId) { return baseEdrRequest() .when() .body(query) - .post("/v3/edrs/request") + .post("/edrs/request") .then() .statusCode(200) .extract() @@ -188,7 +188,7 @@ public JsonArray getEdrEntriesByAssetId(String assetId) { return baseEdrRequest() .when() .body(query) - .post("/v3/edrs/request") + .post("/edrs/request") .then() .statusCode(200) .extract() @@ -231,6 +231,6 @@ private String createQuery(String leftOp, String op, String rightOp) { } private RequestSpecification baseEdrRequest() { - return participant.baseManagementRequest().contentType(JSON); + return participant.baseManagementRequest().basePath("/v3").contentType(JSON); } } diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/TestRuntimeConfiguration.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/TestRuntimeConfiguration.java index f791872f03..b39d9118f4 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/TestRuntimeConfiguration.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/TestRuntimeConfiguration.java @@ -31,6 +31,7 @@ public class TestRuntimeConfiguration { public static final String PROVIDER_BPN = "BPNL0000PROVIDER"; public static final String PROVIDER_DID = DID_PREFIX + PROVIDER_NAME; public static final String DSP_08 = "dataspace-protocol-http"; + public static final String DSP_08_PATH = ""; public static final String DSP_2025 = "dataspace-protocol-http:2025-1"; public static final String DSP_2025_PATH = "/2025-1"; 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/FlociExtension.java similarity index 57% rename from edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/LocalstackExtension.java rename to edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/aws/FlociExtension.java index c446e1d63b..2d92f6d441 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/FlociExtension.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * Copyright (c) 2026 Cofinity-X GmbH * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -24,15 +25,19 @@ import org.eclipse.edc.aws.s3.AwsClientProviderImpl; import org.eclipse.edc.aws.s3.S3ClientRequest; import org.eclipse.edc.junit.utils.LazySupplier; +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import org.testcontainers.containers.localstack.LocalStackContainer; +import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.iam.IamAsyncClient; +import software.amazon.awssdk.services.iam.model.CreateUserRequest; +import software.amazon.awssdk.services.iam.model.EntityAlreadyExistsException; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.CreateBucketRequest; import software.amazon.awssdk.services.s3.model.ListObjectsRequest; @@ -42,62 +47,65 @@ import java.net.URI; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletionException; import static org.assertj.core.api.Assertions.assertThat; -public class LocalstackExtension implements BeforeAllCallback, AfterAllCallback { +public class FlociExtension implements BeforeAllCallback, AfterAllCallback { + private static final DockerImageName FLOCI_IMAGE = DockerImageName.parse("hectorvent/floci:latest"); private static final String S3_REGION = Region.US_WEST_2.id(); + private static final int EDGE_PORT = 4566; private static final String SYSTEM_PROPERTY_AWS_ACCESS_KEY_ID = "aws.accessKeyId"; private static final String SYSTEM_PROPERTY_AWS_SECRET_ACCESS_KEY = "aws.secretAccessKey"; - - private final String accessKeyId = "test-access-key"; - private final String secretAccessKey = UUID.randomUUID().toString(); + private static final String SYSTEM_PROPERTY_AWS_REGION = "aws.region"; + private static final String STORAGE_PATH = "/app/data"; + private static final String ROOT_USER_NAME = "root"; + + private final AwsCredentials credentials = AwsBasicCredentials.create("test-access-key", UUID.randomUUID().toString()); + @SuppressWarnings("resource") + private final GenericContainer flociContainer = new GenericContainer<>(FLOCI_IMAGE) + .withExposedPorts(EDGE_PORT) + .withEnv("FLOCI_STORAGE_MODE", "hybrid") + .withEnv("FLOCI_STORAGE_PERSISTENT_PATH", STORAGE_PATH) + .withTmpFs(Map.of(STORAGE_PATH, "rw")) + .withLogConsumer(frame -> System.out.print(frame.getUtf8String())); private final LazySupplier clientProvider = new LazySupplier<>(() -> new AwsClientProviderImpl(getConfiguration())); - private final LocalStackContainer localStackContainer = new LocalStackContainer( - DockerImageName.parse("localstack/localstack") - ).withServices(LocalStackContainer.Service.S3, LocalStackContainer.Service.IAM, LocalStackContainer.Service.STS) - .withEnv("DEFAULT_REGION", S3_REGION) - .withEnv("AWS_ACCESS_KEY_ID", accessKeyId) - .withEnv("AWS_SECRET_ACCESS_KEY", secretAccessKey) - .withExposedPorts(4566, 9000) - .withLogConsumer(frame -> System.out.print(frame.getUtf8String())); - @Override - public void beforeAll(ExtensionContext context) { - System.setProperty(SYSTEM_PROPERTY_AWS_ACCESS_KEY_ID, accessKeyId); - System.setProperty(SYSTEM_PROPERTY_AWS_SECRET_ACCESS_KEY, secretAccessKey); - localStackContainer.start(); + public void beforeAll(@NotNull ExtensionContext context) { + System.setProperty(SYSTEM_PROPERTY_AWS_ACCESS_KEY_ID, credentials.accessKeyId()); + System.setProperty(SYSTEM_PROPERTY_AWS_SECRET_ACCESS_KEY, credentials.secretAccessKey()); + System.setProperty(SYSTEM_PROPERTY_AWS_REGION, S3_REGION); + flociContainer.start(); + initializeRootUser(); } @Override - public void afterAll(ExtensionContext context) { - localStackContainer.stop(); + public void afterAll(@NotNull ExtensionContext context) { + flociContainer.stop(); } public AwsCredentials getCredentials() { - return AwsBasicCredentials.create(accessKeyId, secretAccessKey); + return credentials; } public String getEndpointOverride() { - return "http://localhost:%s/".formatted(localStackContainer.getFirstMappedPort()); + return endpointUri().toString(); } public S3Client s3Client() { return clientProvider.get().s3Client(S3ClientRequest.from(S3_REGION, getEndpointOverride())); } - public S3AsyncClient s3AsyncClient() { - return clientProvider.get().s3AsyncClient(S3ClientRequest.from(S3_REGION, getEndpointOverride())); - } - public String getS3region() { return S3_REGION; } + @SuppressWarnings("resource") public String createBucket() { var bucketName = UUID.randomUUID().toString(); var response = s3Client().createBucket(CreateBucketRequest.builder().bucket(bucketName).build()); @@ -105,11 +113,13 @@ public String createBucket() { return bucketName; } + @SuppressWarnings("resource") public void uploadObjectOnBucket(String bucketName, String key, Path filePath) { var response = s3Client().putObject(PutObjectRequest.builder().bucket(bucketName).key(key).build(), filePath); assertThat(response.sdkHttpResponse().isSuccessful()).isTrue(); } + @SuppressWarnings("resource") public List listObjects(String bucketName) { return s3Client().listObjects(ListObjectsRequest.builder().bucket(bucketName).build()) .contents().stream().map(S3Object::key).toList(); @@ -117,9 +127,28 @@ public List listObjects(String bucketName) { private AwsClientProviderConfiguration getConfiguration() { return AwsClientProviderConfiguration.Builder.newInstance() - .endpointOverride(URI.create(getEndpointOverride())) + .endpointOverride(endpointUri()) .credentialsProvider(this::getCredentials) .build(); } + private void initializeRootUser() { + try (var iamClient = IamAsyncClient.builder() + .credentialsProvider(StaticCredentialsProvider.create(credentials)) + .region(Region.AWS_GLOBAL) + .endpointOverride(endpointUri()) + .build()) { + try { + iamClient.createUser(CreateUserRequest.builder().userName(ROOT_USER_NAME).build()).join(); + } catch (CompletionException exception) { + if (!(exception.getCause() instanceof EntityAlreadyExistsException)) { + throw new RuntimeException("Failed to initialize Floci IAM root user", exception); + } + } + } + } + + private URI endpointUri() { + return URI.create("http://localhost:%s/".formatted(flociContainer.getMappedPort(EDGE_PORT))); + } } 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 3cb224da49..bdb1fe6eca 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 @@ -2,6 +2,7 @@ * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) * Copyright (c) 2025 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. * Copyright (c) 2026 Cofinity-X GmbH + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -21,7 +22,6 @@ package org.eclipse.tractusx.edc.tests.helpers; - import jakarta.json.Json; import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; @@ -38,13 +38,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.eclipse.edc.connector.controlplane.test.system.utils.PolicyFixtures.policy; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; -import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_CONSTRAINT_TYPE; import static org.eclipse.edc.jsonld.spi.PropertyAndTypeNames.ODRL_LOGICAL_CONSTRAINT_TYPE; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; +import static org.eclipse.tractusx.edc.cx.CxJsonLdExtension.CX_POLICY_2025_09_CONTEXT; 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; @@ -52,24 +51,22 @@ public class PolicyHelperFunctions { public static final String ODRL_CONTEXT = "https://w3id.org/dspace/2025/1/odrl-profile.jsonld"; - private static final String BUSINESS_PARTNER_EVALUATION_KEY = "BusinessPartnerNumber"; - - private static final String BUSINESS_PARTNER_CONSTRAINT_KEY = CX_POLICY_2025_09_NS + "BusinessPartnerGroup"; - + private static final String FRAMEWORK_AGREEMENT_KEY = "FrameworkAgreement"; public static final String FRAMEWORK_AGREEMENT_LITERAL = CX_POLICY_2025_09_NS + "FrameworkAgreement"; + private static final String USAGE_PURPOSE_KEY = "UsagePurpose"; private static final String USAGE_PURPOSE_LITERAL = CX_POLICY_2025_09_NS + "UsagePurpose"; - public static final String DATA_PROVISIONING_END_DATE_LITERAL = CX_POLICY_2025_09_NS + "DataProvisioningEndDate"; - public static final String DATA_PROVISIONING_END_DURATION_LITERAL = CX_POLICY_2025_09_NS + "DataProvisioningEndDurationDays"; - public static final String DATA_USAGE_END_DATE_LITERAL = CX_POLICY_2025_09_NS + "DataUsageEndDate"; - public static final String DATA_USAGE_END_DURATION_LITERAL = CX_POLICY_2025_09_NS + "DataUsageEndDurationDays"; + public static final String DATA_USAGE_END_DATE_KEY = "DataUsageEndDate"; + public static final String DATA_USAGE_END_DURATION_KEY = "DataUsageEndDurationDays"; - public static JsonObject bpnGroupPolicy(Operator operator, String... allowedGroups) { - return bpnGroupPolicy(operator.getOdrlRepresentation(), false, allowedGroups); - } - - public static JsonObject bpnGroupPolicyWithRightOperandAsArray(Operator operator, String... allowedGroups) { - return bpnGroupPolicy(operator.getOdrlRepresentation(), true, allowedGroups); + public static JsonObject emptyPolicy() { + return Json.createObjectBuilder() + .add(CONTEXT, Json.createArrayBuilder() + .add(ODRL_CONTEXT) + .add(CX_POLICY_2025_09_CONTEXT)) + .add(TYPE, "Set") + .add(ID, "id") + .build(); } /** @@ -85,8 +82,11 @@ public static JsonObject frameworkPolicy(String id, Map permissi public static JsonObject frameworkPolicy(Map permissions, String action) { return Json.createObjectBuilder() - .add(CONTEXT, ODRL_CONTEXT) + .add(CONTEXT, Json.createArrayBuilder() + .add(ODRL_CONTEXT) + .add(CX_POLICY_2025_09_CONTEXT)) .add(TYPE, "Set") + .add(ID, "id") .add("permission", Json.createArrayBuilder() .add(frameworkConstraint(new HashMap<>(permissions), action, Operator.EQ, false))) .build(); @@ -98,18 +98,35 @@ public static JsonObject frameworkPolicy(Map permissions, String public static JsonObject frameworkPolicy(Map permissions, String action, Operator operator) { return Json.createObjectBuilder() - .add(CONTEXT, ODRL_CONTEXT) + .add(CONTEXT, Json.createArrayBuilder() + .add(ODRL_CONTEXT) + .add(CX_POLICY_2025_09_CONTEXT)) .add(TYPE, "Set") + .add(ID, "id") .add("permission", Json.createArrayBuilder() .add(frameworkConstraint(new HashMap<>(permissions), action, operator, false))) .build(); } + public static JsonObject bpnGroupPolicy(String operator, boolean rightOperandAsArray, String... allowedGroups) { + + var groupConstraint = atomicConstraint("BusinessPartnerGroup", operator, Arrays.asList(allowedGroups), rightOperandAsArray); + + var permission = Json.createObjectBuilder() + .add("action", "access") + .add("constraint", Json.createArrayBuilder() + .add(groupConstraint) + .build()) + .build(); - public static JsonObject emptyPolicy() { return Json.createObjectBuilder() - .add(CONTEXT, ODRL_CONTEXT) + .add(CONTEXT, Json.createArrayBuilder() + .add(ODRL_CONTEXT) + .add(CX_POLICY_2025_09_CONTEXT)) .add(TYPE, "Set") + .add(ID, "id") + .add("permission", Json.createArrayBuilder() + .add(permission)) .build(); } @@ -118,38 +135,41 @@ public static JsonObject frameworkPolicy(String leftOperand, Operator operator, } public static JsonObject frameworkPolicy(String leftOperand, Operator operator, Object rightOperand, String action, boolean createRightOperandsAsArray) { - var constraint = atomicConstraint(leftOperand, operator.getOdrlRepresentation(), rightOperand, createRightOperandsAsArray); + var constraint = atomicConstraint(leftOperand, operatorValueWithoutNamespace(operator), rightOperand, createRightOperandsAsArray); var constraintsBuilder = Json.createArrayBuilder() .add(constraint); - if (!leftOperand.equals(FRAMEWORK_AGREEMENT_LITERAL) && action.contains("use")) { + if (!leftOperand.equals(FRAMEWORK_AGREEMENT_KEY) && action.contains("use")) { constraintsBuilder.add(frameworkAgreementConstraint()); } - if (!leftOperand.equals(USAGE_PURPOSE_LITERAL) && action.contains("use")) { + if (!leftOperand.equals(USAGE_PURPOSE_KEY) && action.contains("use")) { constraintsBuilder.add(usagePurposeConstraint()); } var permission = Json.createObjectBuilder() .add("action", action) - .add("constraint", Json.createObjectBuilder() - .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) - .add("and", constraintsBuilder.build()) + .add("constraint", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add("and", constraintsBuilder.build()) + .build()) .build()) .build(); return Json.createObjectBuilder() - .add(CONTEXT, ODRL_CONTEXT) + .add(CONTEXT, Json.createArrayBuilder() + .add(ODRL_CONTEXT) + .add(CX_POLICY_2025_09_CONTEXT)) .add(TYPE, "Set") + .add(ID, "id") .add("permission", Json.createArrayBuilder().add(permission)) .build(); } private static JsonObject frameworkAgreementConstraint() { return Json.createObjectBuilder() - .add(TYPE, ODRL_CONSTRAINT_TYPE) - .add("leftOperand", FRAMEWORK_AGREEMENT_LITERAL) + .add("leftOperand", FRAMEWORK_AGREEMENT_KEY) .add("operator", "eq") .add("rightOperand", "DataExchangeGovernance:1.0") .build(); @@ -157,10 +177,9 @@ private static JsonObject frameworkAgreementConstraint() { private static JsonObject usagePurposeConstraint() { return Json.createObjectBuilder() - .add(TYPE, ODRL_CONSTRAINT_TYPE) - .add("leftOperand", USAGE_PURPOSE_LITERAL) + .add("leftOperand", USAGE_PURPOSE_KEY) .add("operator", "isAnyOf") - .add("rightOperand", "cx.pcf.base:1") + .add("rightOperand", Json.createArrayBuilder().add("cx.pcf.base:1").build()) .build(); } @@ -187,127 +206,8 @@ public static JsonObject legacyFrameworkPolicy() { .build(); } - public static JsonObject inForceDatePolicyLegacy(String operatorStart, Object startDate, String operatorEnd, Object endDate) { - var constraint = Json.createObjectBuilder() - .add("@type", "LogicalConstraint") - .add("and", Json.createArrayBuilder() - .add(atomicConstraint("https://w3id.org/edc/v0.0.1/ns/inForceDate", operatorStart, startDate, false)) - .add(atomicConstraint("https://w3id.org/edc/v0.0.1/ns/inForceDate", operatorEnd, endDate, false)) - .add(atomicConstraint("https://w3id.org/catenax/policy/Membership", "eq", "active", false)) - .build()) - .build(); - - return policy(List.of(Json.createObjectBuilder() - .add("action", "use") - .add("constraint", constraint) - .build())); - } - - public static JsonObjectBuilder policyDefinitionBuilder() { - return Json.createObjectBuilder() - .add(TYPE, EDC_NAMESPACE + "PolicyDefinitionDto"); - } - - public static JsonObjectBuilder policyDefinitionBuilder(JsonObject policy) { - return policyDefinitionBuilder() - .add(EDC_NAMESPACE + "policy", policy); - } - - public static JsonObject bpnPolicy(String... bpns) { - return Json.createObjectBuilder() - .add(CONTEXT, ODRL_CONTEXT) - .add(TYPE, "Set") - .add("permission", Json.createArrayBuilder() - .add(permission(bpns))) - .build(); - } - - public static JsonObject bpnPolicy(Operator operator, String... bpns) { - JsonArrayBuilder bpnArray = Json.createArrayBuilder(); - Stream.of(bpns).forEach(bpnArray::add); - - var bpnConstraint = Json.createObjectBuilder() - .add(TYPE, ODRL_CONSTRAINT_TYPE) - .add("leftOperand", CX_POLICY_2025_09_NS + BUSINESS_PARTNER_EVALUATION_KEY) - .add("operator", operator.getOdrlRepresentation()) - .add("rightOperand", bpnArray) - .build(); - - var permission = Json.createObjectBuilder() - .add("action", CX_POLICY_2025_09_NS + "access") - .add("constraint", Json.createObjectBuilder() - .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) - .add("and", bpnConstraint) - .build()) - .build(); - return Json.createObjectBuilder() - .add(CONTEXT, ODRL_CONTEXT) - .add(TYPE, "Set") - .add("permission", Json.createArrayBuilder() - .add(permission)) - .build(); - } - - private static JsonObject bpnGroupPolicy(String operator, boolean rightOperandAsArray, String... allowedGroups) { - - var groupConstraint = atomicConstraint(BUSINESS_PARTNER_CONSTRAINT_KEY, operator, Arrays.asList(allowedGroups), rightOperandAsArray); - - var permission = Json.createObjectBuilder() - .add("action", CX_POLICY_2025_09_NS + "access") - .add("constraint", Json.createObjectBuilder() - .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) - .add("and", groupConstraint) - .build()) - .build(); - - return Json.createObjectBuilder() - .add(CONTEXT, ODRL_CONTEXT) - .add(TYPE, "Set") - .add("permission", permission) - .build(); - } - - private static JsonObject permission(String... bpns) { - - var bpnConstraints = Stream.of(bpns) - .map(bpn -> atomicConstraint(CX_POLICY_2025_09_NS + BUSINESS_PARTNER_EVALUATION_KEY, "isAnyOf", bpn, false)) - .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add); - - return Json.createObjectBuilder() - .add("action", CX_POLICY_2025_09_NS + "access") - .add("constraint", Json.createObjectBuilder() - .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) - .add("and", bpnConstraints) - .build()) - .build(); - } - - 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 (!operandMappings.containsKey(FRAMEWORK_AGREEMENT_LITERAL)) { - constraints.add(frameworkAgreementConstraint()); - } - if (!operandMappings.containsKey(USAGE_PURPOSE_LITERAL)) { - constraints.add(usagePurposeConstraint()); - } - } - - return Json.createObjectBuilder() - .add("action", action) - .add("constraint", Json.createObjectBuilder() - .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) - .add("and", constraints) - .build()) - .build(); - } - private static JsonObject atomicConstraint(String leftOperand, String operator, Object rightOperand, boolean createRightOperandsAsArray) { var builder = Json.createObjectBuilder() - .add(TYPE, ODRL_CONSTRAINT_TYPE) .add("leftOperand", leftOperand) .add("operator", operator); @@ -318,6 +218,8 @@ private static JsonObject atomicConstraint(String leftOperand, String operator, .build()); } else if (rightOperand instanceof Collection coll) { builder.add("rightOperand", coll.stream().map(Object::toString).collect(Collectors.joining(","))); + } else if (createRightOperandsAsArray) { + builder.add("rightOperand", Json.createArrayBuilder().add(rightOperand.toString()).build()); } else { builder.add("rightOperand", rightOperand.toString()); } @@ -326,99 +228,162 @@ private static JsonObject atomicConstraint(String leftOperand, String operator, public static JsonObject dataUsageEndDurationDays(Integer duration) { var constraint = Json.createObjectBuilder() - .add("@type", "LogicalConstraint") .add("and", Json.createArrayBuilder() - .add(atomicConstraint(DATA_USAGE_END_DURATION_LITERAL, "eq", duration, false)) + .add(atomicConstraint(DATA_USAGE_END_DURATION_KEY, "eq", duration, false)) .add(frameworkAgreementConstraint()) .add(usagePurposeConstraint()) .build()) .build(); - return policy(List.of(Json.createObjectBuilder() + var permission = Json.createObjectBuilder() .add("action", "use") - .add("constraint", constraint) - .build())); + .add("constraint", Json.createArrayBuilder() + .add(constraint) + .build()) + .build(); + + var contextArrayBuilder = Json.createArrayBuilder(); + contextArrayBuilder.add(ODRL_CONTEXT); + contextArrayBuilder.add(CX_POLICY_2025_09_CONTEXT); + + return Json.createObjectBuilder() + .add(CONTEXT, contextArrayBuilder) + .add(TYPE, "Set") + .add(ID, "id") + .add("permission", Json.createArrayBuilder() + .add(permission)) + .build(); + } + + public static JsonObject bpnPolicy(String... bpns) { + return bpnPolicy(Operator.IS_ANY_OF, bpns); } public static JsonObject dataUsageEndDate(String endDate) { var constraint = Json.createObjectBuilder() - .add("@type", "LogicalConstraint") .add("and", Json.createArrayBuilder() - .add(atomicConstraint(DATA_USAGE_END_DATE_LITERAL, "eq", endDate, false)) + .add(atomicConstraint(DATA_USAGE_END_DATE_KEY, "eq", endDate, false)) .add(frameworkAgreementConstraint()) .add(usagePurposeConstraint()) .build()) .build(); - return policy(List.of(Json.createObjectBuilder() + var permission = Json.createObjectBuilder() .add("action", "use") - .add("constraint", constraint) - .build())); + .add("constraint", Json.createArrayBuilder() + .add(constraint) + .build()) + .build(); + + var contextArrayBuilder = Json.createArrayBuilder(); + contextArrayBuilder.add(ODRL_CONTEXT); + contextArrayBuilder.add(CX_POLICY_2025_09_CONTEXT); + + return Json.createObjectBuilder() + .add(CONTEXT, contextArrayBuilder) + .add(TYPE, "Set") + .add(ID, "id") + .add("permission", Json.createArrayBuilder() + .add(permission)) + .build(); } - public static JsonObject dataProvisioningEndDurationDays(Integer duration) { - var requiredUsagePermissionConstraints = Json.createObjectBuilder() - .add("@type", "LogicalConstraint") - .add("and", Json.createArrayBuilder() - .add(frameworkAgreementConstraint()) - .add(usagePurposeConstraint()) - .build()) + public static JsonObjectBuilder policyDefinitionBuilder() { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "PolicyDefinitionDto"); + } + + public static JsonObjectBuilder policyDefinitionBuilder(JsonObject policy) { + return policyDefinitionBuilder() + .add(EDC_NAMESPACE + "policy", policy); + } + + public static JsonObject bpnPolicy(Operator operator, String... bpns) { + JsonArrayBuilder bpnArray = Json.createArrayBuilder(); + Stream.of(bpns).forEach(bpnArray::add); + + var bpnConstraint = Json.createObjectBuilder() + .add("leftOperand", "BusinessPartnerNumber") + .add("operator", operatorValueWithoutNamespace(operator)) + .add("rightOperand", bpnArray) .build(); - var dataProvisioningConstraint = Json.createObjectBuilder() - .add("@type", "LogicalConstraint") - .add("and", Json.createArrayBuilder() - .add(atomicConstraint(DATA_PROVISIONING_END_DURATION_LITERAL, "eq", duration, false)) + var permission = Json.createObjectBuilder() + .add("action", "access") + .add("constraint", Json.createArrayBuilder() + .add(bpnConstraint) .build()) .build(); - return Json.createObjectBuilder() - .add("@context", "http://www.w3.org/ns/odrl.jsonld") - .add("@type", "http://www.w3.org/ns/odrl/2/Set") - .add("permission", Json.createArrayBuilder( - List.of(Json.createObjectBuilder() - .add("action", "use") - .add("constraint", requiredUsagePermissionConstraints) - .build()) - )) - .add("obligation", Json.createArrayBuilder( - List.of(Json.createObjectBuilder() - .add("action", "use") - .add("constraint", dataProvisioningConstraint) - .build()) - )).build(); + .add(CONTEXT, Json.createArrayBuilder() + .add(ODRL_CONTEXT) + .add(CX_POLICY_2025_09_CONTEXT)) + .add(TYPE, "Set") + .add(ID, "id") + .add("permission", Json.createArrayBuilder() + .add(permission)) + .build(); } public static JsonObject dataProvisioningEndDate(String endDate) { var requiredUsagePermissionConstraints = Json.createObjectBuilder() - .add("@type", "LogicalConstraint") .add("and", Json.createArrayBuilder() .add(frameworkAgreementConstraint()) .add(usagePurposeConstraint()) .build()) .build(); - var dataProvisioningConstraint = Json.createObjectBuilder() - .add("@type", "LogicalConstraint") - .add("and", Json.createArrayBuilder() - .add(atomicConstraint(DATA_PROVISIONING_END_DATE_LITERAL, "eq", endDate, false)) - .build()) - .build(); + var dataProvisioningConstraint = atomicConstraint("DataProvisioningEndDate", "eq", endDate, false); return Json.createObjectBuilder() - .add("@context", "http://www.w3.org/ns/odrl.jsonld") - .add("@type", "http://www.w3.org/ns/odrl/2/Set") + .add(CONTEXT, Json.createArrayBuilder() + .add(ODRL_CONTEXT) + .add(CX_POLICY_2025_09_CONTEXT)) + .add(TYPE, "Set") + .add(ID, "id") .add("permission", Json.createArrayBuilder( List.of(Json.createObjectBuilder() .add("action", "use") - .add("constraint", requiredUsagePermissionConstraints) + .add("constraint", Json.createArrayBuilder() + .add(requiredUsagePermissionConstraints)) .build()) )) .add("obligation", Json.createArrayBuilder( List.of(Json.createObjectBuilder() .add("action", "use") - .add("constraint", dataProvisioningConstraint) + .add("constraint", Json.createArrayBuilder() + .add(dataProvisioningConstraint)) .build()) )).build(); } + + public static JsonObject frameworkConstraint(Map operandMappings, String action, Operator operator, boolean createRightOperandsAsArray) { + var constraints = operandMappings.entrySet().stream() + .map(constraint -> atomicConstraint(constraint.getKey(), operatorValueWithoutNamespace(operator), constraint.getValue(), createRightOperandsAsArray)) + .collect(Json::createArrayBuilder, JsonArrayBuilder::add, JsonArrayBuilder::add); + + if (action.contains("use")) { + if (!operandMappings.containsKey(FRAMEWORK_AGREEMENT_KEY)) { + constraints.add(frameworkAgreementConstraint()); + } + if (!operandMappings.containsKey(USAGE_PURPOSE_KEY)) { + constraints.add(usagePurposeConstraint()); + } + } + + return Json.createObjectBuilder() + .add("action", action) + .add("constraint", Json.createArrayBuilder() + .add(Json.createObjectBuilder() + .add(TYPE, ODRL_LOGICAL_CONSTRAINT_TYPE) + .add("and", constraints) + .build()) + .build()) + .build(); + } + + private static String operatorValueWithoutNamespace(Operator operator) { + var parts = operator.getOdrlRepresentation().split("/"); + return parts[parts.length - 1]; + } } diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/kafka/KafkaExtension.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/kafka/KafkaExtension.java new file mode 100644 index 0000000000..cacda7262a --- /dev/null +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/kafka/KafkaExtension.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.tests.kafka; + +import org.apache.kafka.clients.admin.Admin; +import org.apache.kafka.clients.admin.AdminClientConfig; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.testcontainers.kafka.KafkaContainer; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +public class KafkaExtension implements BeforeAllCallback, AfterAllCallback { + + private static final String IMAGE = "apache/kafka-native:4.0.0"; + + // apache/kafka-native occasionally exits during boot under CI resource pressure; retry the + // container start a few times instead of failing the whole test class in @BeforeAll. + private final KafkaContainer kafkaContainer = new KafkaContainer(IMAGE) + .withStartupAttempts(3); + + @Override + public void beforeAll(ExtensionContext context) { + kafkaContainer.start(); + } + + @Override + public void afterAll(ExtensionContext context) { + kafkaContainer.stop(); + } + + public String getBootstrapServers() { + return kafkaContainer.getBootstrapServers(); + } + + public void createTopic(String topic) { + var config = Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, getBootstrapServers()); + try (var admin = Admin.create(config)) { + admin.createTopics(List.of(new NewTopic(topic, 1, (short) 1))).all().get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + public void produce(String topic, String key, String value) { + var props = new Properties(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, getBootstrapServers()); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + try (var producer = new KafkaProducer(props)) { + try { + producer.send(new ProducerRecord<>(topic, key, value)).get(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + } + + public List> consume(String topic, Duration timeout) { + return consume(getBootstrapServers(), topic, timeout); + } + + /** + * Consumes from the given topic using the supplied bootstrap servers — e.g. the broker coordinates + * carried by an EDR — so a test can verify the consumer uses the connection details it was handed. + */ + public List> consume(String bootstrapServers, String topic, Duration timeout) { + var props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "test-" + UUID.randomUUID()); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + var collected = new ArrayList>(); + try (var consumer = new KafkaConsumer(props)) { + consumer.subscribe(Collections.singletonList(topic)); + var deadline = System.nanoTime() + timeout.toNanos(); + while (collected.isEmpty() && System.nanoTime() < deadline) { + ConsumerRecords records = consumer.poll(Duration.ofMillis(500)); + records.forEach(collected::add); + } + } + return collected; + } +} diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/DataspaceIssuer.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/DataspaceIssuer.java index f9dde21ed5..faf2b8ca15 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/DataspaceIssuer.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/DataspaceIssuer.java @@ -82,18 +82,20 @@ public String verificationId() { } - public VerifiableCredentialResource issueMembershipCredential(String did, String bpn) { + public VerifiableCredentialResource issueMembershipCredential(String did, String bpn, String participantContextId) { return issueCredential( did, bpn, "MembershipCredential", () -> CredentialSubject.Builder.newInstance() .id(did) .claim("holderIdentifier", bpn) + .claim("bpn", bpn) .build(), - membershipRawVc(did, bpn) + membershipRawVc(did, bpn), + participantContextId ); } - public VerifiableCredentialResource issueDismantlerCredential(String did, String bpn) { + public VerifiableCredentialResource issueDismantlerCredential(String did, String bpn, String participantContextId) { return issueCredential( did, bpn, "DismantlerCredential", () -> CredentialSubject.Builder.newInstance() @@ -108,11 +110,12 @@ public VerifiableCredentialResource issueDismantlerCredential(String did, String .add("activityType", "vehicleDismantle") .add("allowedVehicleBrands", Json.createArrayBuilder().add("BMW").add("Volkswagen").build()) .add("id", did) - .build()) + .build()), + participantContextId ); } - public VerifiableCredentialResource issueFrameworkCredential(String did, String bpn, String credentialType) { + public VerifiableCredentialResource issueFrameworkCredential(String did, String bpn, String credentialType, String participantContextId) { var subject = Json.createObjectBuilder() .add("type", credentialType) .add("holderIdentifier", bpn) @@ -127,7 +130,8 @@ public VerifiableCredentialResource issueFrameworkCredential(String did, String .id(did) .claim("holderIdentifier", bpn) .build(), - createVcBuilder(credentialType, subject) + createVcBuilder(credentialType, subject), + participantContextId ); } @@ -158,15 +162,38 @@ public JsonObjectBuilder membershipRawVc(String did, String bpn) { return createVcBuilder("MembershipCredential", subject); } - public List issueCredentials(String did, String bpn) { + public List issueCredentials(String did, String bpn, String participantContextId) { return List.of( - issueMembershipCredential(did, bpn), - issueDismantlerCredential(did, bpn), - issueFrameworkCredential(did, bpn, "BpnCredential"), - issueFrameworkCredential(did, bpn, "DataExchangeGovernanceCredential")); + issueMembershipCredential(did, bpn, participantContextId), + issueDismantlerCredential(did, bpn, participantContextId), + issueBpnCredential(did, bpn, participantContextId), + issueFrameworkCredential(did, bpn, "DataExchangeGovernanceCredential", participantContextId)); } - private VerifiableCredentialResource issueCredential(String did, String bpn, String type, Supplier credentialSubjectSupplier, JsonObjectBuilder vcBuilder) { + VerifiableCredentialResource issueBpnCredential(String did, String bpn, String participantContextId) { + var subject = Json.createObjectBuilder() + .add("type", "BpnCredential") + .add("holderIdentifier", bpn) + .add("bpn", bpn) + .add("id", did) + .build(); + + return issueCredential( + did, bpn, "BpnCredential", + () -> CredentialSubject.Builder.newInstance() + .id(did) + .claim("holderIdentifier", bpn) + .claim("bpn", bpn) + .build(), + createVcBuilder("BpnCredential", subject), + participantContextId + ); + } + + private VerifiableCredentialResource issueCredential(String did, String bpn, String type, + Supplier credentialSubjectSupplier, + JsonObjectBuilder vcBuilder, + String participantContextId) { var credential = VerifiableCredential.Builder.newInstance() .type(type) .credentialSubject(credentialSubjectSupplier.get()) @@ -178,7 +205,7 @@ private VerifiableCredentialResource issueCredential(String did, String bpn, Str var rawVc = createJwtVc(vcJson, did); return VerifiableCredentialResource.Builder.newInstance() .issuerId(didUrl()) - .participantContextId(did) + .participantContextId(participantContextId) .holderId(bpn) .credential(new VerifiableCredentialContainer(rawVc, CredentialFormat.VC1_0_JWT, credential)) .build(); @@ -192,8 +219,8 @@ private JsonObjectBuilder createVcBuilder(String type, JsonObject subjectSupplie .add("https://w3id.org/catenax/credentials") .add("https://w3id.org/vc/status-list/2021/v1")) .add("type", Json.createArrayBuilder() - .add("VerifiableCredential") - .add(type)) + .add(type) + .add("VerifiableCredential")) .add("credentialSubject", subjectSupplier) .add("issuer", didUrl()) .add("issuanceDate", Instant.now().toString()); diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/IatpParticipant.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/DcpParticipant.java similarity index 85% rename from edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/IatpParticipant.java rename to edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/DcpParticipant.java index a9ce4ec56e..d76245de80 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/IatpParticipant.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/DcpParticipant.java @@ -26,7 +26,7 @@ import org.eclipse.edc.iam.did.spi.document.Service; import org.eclipse.edc.iam.did.spi.document.VerificationMethod; import org.eclipse.edc.identityhub.spi.keypair.KeyPairService; -import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService; +import org.eclipse.edc.identityhub.spi.participantcontext.IdentityHubParticipantContextService; 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; @@ -38,12 +38,11 @@ import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.tractusx.edc.tests.runtimes.KeyPool; -import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Objects; -public class IatpParticipant extends TractusxIatpParticipantBase { +public class DcpParticipant extends TractusxDcpParticipantBase { protected DidDocument didDocument; public DidDocument getDidDocument() { @@ -87,23 +86,23 @@ public void configureParticipant(DataspaceIssuer issuer, RuntimeExtension runtim stsRuntimeExtension.getService(Vault.class).storeSecret(getPrivateKeyAlias(), getPrivateKeyAsString()); var participantManifest = ParticipantManifest.Builder.newInstance() - .participantContextId(getDid()) + .participantContextId(getParticipantContextId()) .did(getDid()) .build(); - var participantContextService = stsRuntimeExtension.getService(ParticipantContextService.class); + var participantContextService = stsRuntimeExtension.getService(IdentityHubParticipantContextService.class); var createParticipantContextResponse = participantContextService.createParticipantContext(participantManifest) .orElseThrow(f -> new EdcException("cannot create participant context: " + f.getFailureDetail())); runtimeExtension.getService(Vault.class).storeSecret("client_secret_alias", createParticipantContextResponse.clientSecret()); - stsRuntimeExtension.getService(KeyPairService.class).addKeyPair(getDid(), createKeyDescriptor(), true) + stsRuntimeExtension.getService(KeyPairService.class).addKeyPair(getParticipantContextId(), createKeyDescriptor(), true) .orElseThrow(f -> new EdcException("Cannot store key pair: " + f.getFailureDetail())); KeyPool.register(getFullKeyId(), getKeyPair()); var account = StsAccount.Builder.newInstance() .id(getId()) - .participantContextId(getDid()) + .participantContextId(getParticipantContextId()) .name(getName()) .clientId(getDid()) .did(getDid()) @@ -113,12 +112,7 @@ public void configureParticipant(DataspaceIssuer issuer, RuntimeExtension runtim } private List issueCredentials(DataspaceIssuer issuer) { - return List.of( - issuer.issueMembershipCredential(getDid(), getBpn()), - issuer.issueDismantlerCredential(getDid(), getBpn()), - issuer.issueFrameworkCredential(getDid(), getBpn(), "BpnCredential"), - issuer.issueFrameworkCredential(getDid(), getBpn(), "DataExchangeGovernanceCredential") - ); + return issuer.issueCredentials(getDid(), getBpn(), getParticipantContextId()); } public KeyDescriptor createKeyDescriptor() { @@ -129,13 +123,13 @@ public KeyDescriptor createKeyDescriptor() { .build(); } - public static class Builder extends TractusxIatpParticipantBase.Builder { + public static class Builder extends TractusxDcpParticipantBase.Builder { protected Builder() { - this(new IatpParticipant()); + this(new DcpParticipant()); } - protected Builder(IatpParticipant participant) { + protected Builder(DcpParticipant participant) { super(participant); } @@ -144,7 +138,7 @@ public static Builder newInstance() { } @Override - public IatpParticipant build() { + public DcpParticipant build() { super.build(); participant.didDocument = generateDidDocument(); return participant; @@ -156,7 +150,7 @@ private DidDocument generateDidDocument() { service.setId("#credential-service"); service.setType("CredentialService"); var credentialServiceBaseUri = Objects.requireNonNullElse(participant.credentialServiceUri, participant.csService); - service.setServiceEndpoint(credentialServiceBaseUri.get() + "/v1/participants/" + toBase64(participant.did)); + service.setServiceEndpoint(credentialServiceBaseUri.get() + "/v1/participants/" + participant.participantContextId); var ecKey = participant.getKeyPairAsJwk(); @@ -174,9 +168,5 @@ private DidDocument generateDidDocument() { .verificationMethod(List.of(verificationMethod)) .build(); } - - private String toBase64(String s) { - return Base64.getUrlEncoder().encodeToString(s.getBytes()); - } } } 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/TractusxDcpParticipantBase.java similarity index 71% rename from edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxIatpParticipantBase.java rename to edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/participant/TractusxDcpParticipantBase.java index 6d3a336c02..efac746704 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/TractusxDcpParticipantBase.java @@ -24,15 +24,15 @@ import org.eclipse.edc.spi.system.configuration.ConfigFactory; import java.net.URI; -import java.util.Map; +import java.util.HashMap; import java.util.Objects; import static org.eclipse.edc.util.io.Ports.getFreePort; /** - * Specialized version of {@link TractusxParticipantBase} with IATP configurations + * Specialized version of {@link TractusxParticipantBase} with DCP configurations */ -public abstract class TractusxIatpParticipantBase extends TractusxParticipantBase { +public abstract class TractusxDcpParticipantBase extends TractusxParticipantBase { protected final LazySupplier csService = new LazySupplier<>(() -> URI.create("http://localhost:" + getFreePort() + "/api/resolution")); protected LazySupplier divUri; @@ -41,24 +41,26 @@ public abstract class TractusxIatpParticipantBase extends TractusxParticipantBas protected String stsClientId; protected String trustedIssuer; - public Config iatpConfig() { - var additionalSettings = Map.of( - "edc.iam.sts.oauth.token.url", stsUri.get() + "/token", - "edc.iam.sts.oauth.client.id", getDid(), - "edc.iam.sts.oauth.client.secret.alias", "client_secret_alias", - "edc.ih.iam.id", getDid(), - "edc.ih.iam.publickey.alias", getFullKeyId(), - "edc.agent.identity.key", "client_id", - "edc.iam.trusted-issuer.issuer.id", trustedIssuer, - "edc.transfer.proxy.token.signer.privatekey.alias", getPrivateKeyAlias(), - "edc.transfer.proxy.token.verifier.publickey.alias", getFullKeyId(), - "edc.iam.did.web.use.https", "false" - ); + public Config dcpConfig() { + var additionalSettings = new HashMap() { + { + put("edc.iam.sts.oauth.token.url", stsUri.get() + "/token"); + put("edc.iam.sts.oauth.client.id", getDid()); + put("edc.iam.sts.oauth.client.secret.alias", "client_secret_alias"); + put("edc.ih.iam.id", getDid()); + put("edc.ih.iam.publickey.alias", getFullKeyId()); + put("edc.agent.identity.key", "client_id"); + put("edc.iam.trusted-issuer.issuer.id", trustedIssuer); + put("edc.transfer.proxy.token.signer.privatekey.alias", getPrivateKeyAlias()); + put("edc.transfer.proxy.token.verifier.publickey.alias", getFullKeyId()); + put("edc.iam.did.web.use.https", "false"); + } + }; return getConfig().merge(ConfigFactory.fromMap(additionalSettings)); } - public static class Builder

    > extends TractusxParticipantBase.Builder { + public static class Builder

    > extends TractusxParticipantBase.Builder { protected Builder(P participant) { super(participant); 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 7067006b2c..bd61423843 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 @@ -25,9 +25,12 @@ import jakarta.json.JsonObject; import org.eclipse.edc.connector.controlplane.test.system.utils.Participant; import org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates; +import org.eclipse.edc.jsonld.spi.JsonLd; 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.cx.CxCachedDocumentRegistry; +import org.eclipse.tractusx.edc.jsonld.TxCachedDocumentRegistry; import org.eclipse.tractusx.edc.tests.ParticipantConsumerDataPlaneApi; import org.eclipse.tractusx.edc.tests.ParticipantDataApi; import org.eclipse.tractusx.edc.tests.ParticipantEdrApi; @@ -38,6 +41,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import static io.restassured.http.ContentType.JSON; import static jakarta.json.Json.createObjectBuilder; @@ -56,6 +60,10 @@ import static org.eclipse.tractusx.edc.agreements.retirement.spi.types.AgreementsRetirementEntry.AR_ENTRY_TYPE; import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.TX_NAMESPACE; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.BPN_SUFFIX; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_08; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_08_PATH; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025_PATH; /** @@ -75,11 +83,36 @@ public abstract class TractusxParticipantBase extends IdentityParticipant { protected ParticipantConsumerDataPlaneApi dataPlane; protected String bpn; protected String did; + protected String participantContextId; public void createAsset(String id) { createAsset(id, new HashMap<>(), Map.of("type", "test-type")); } - + + /** + * Overrides the upstream variant to set an id with a random UUID + */ + @Override + public String createPolicyDefinition(JsonObject policy) { + var body = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE)) + .add(TYPE, "PolicyDefinition") + .add(ID, UUID.randomUUID().toString()) + .add("policy", policy) + .build(); + + return baseManagementRequest() + .contentType(JSON) + .body(body) + .when() + .post("/policydefinitions") + .then() + .log().ifValidationFails() + .statusCode(200) + .contentType(JSON) + .extract().jsonPath().getString("@id"); + } + @NotNull public String getBpn() { return bpn; @@ -89,7 +122,12 @@ public String getBpn() { public String getDid() { return did; } - + + @NotNull + public String getParticipantContextId() { + return participantContextId; + } + /** * Allows overriding the participant id, as for DSP 0.8 tests the provider's BPN has to be used. * @@ -128,13 +166,14 @@ public Config getConfig() { put("edc.iam.sts.oauth.token.url", "http://sts.example.com/token"); put("edc.iam.sts.oauth.client.id", "test-clientid"); put("edc.iam.sts.oauth.client.secret.alias", "test-clientid-alias"); - put("tx.edc.iam.iatp.bdrs.server.url", "http://sts.example.com"); + put("tx.edc.iam.dcp.bdrs.server.url", "http://sts.example.com"); put("edc.dataplane.api.public.baseurl", "%s/v2/data".formatted(dataPlanePublic.get())); put("edc.policy.validation.enabled", "true"); - put("edc.iam.did.web.use.https", "false"); - put("edc.participant.context.id", "general-test-id"); + put("edc.participant.context.id", participantContextId); put("tractusx.edc.participant.bpn", getBpn()); put("edc.iam.did.web.use.https", "false"); + put("edc.encryption.strict", "false"); + put("edc.policy.monitor.period", "PT5S"); } }; @@ -172,10 +211,11 @@ public void storeBusinessPartner(String bpn, String... groups) { .add(TX_NAMESPACE + "groups", Json.createArrayBuilder(Arrays.asList(groups))) .build(); baseManagementRequest() + .basePath("/v3") .contentType(JSON) .body(body) .when() - .post("/v3/business-partner-groups") + .post("/business-partner-groups") .then() .statusCode(204); } @@ -189,10 +229,11 @@ public void updateBusinessPartner(String bpn, String... groups) { .add(TX_NAMESPACE + "groups", Json.createArrayBuilder(Arrays.asList(groups))) .build(); baseManagementRequest() + .basePath("/v3") .contentType(JSON) .body(body) .when() - .put("/v3/business-partner-groups") + .put("/business-partner-groups") .then() .statusCode(204); } @@ -202,8 +243,9 @@ public void updateBusinessPartner(String bpn, String... groups) { */ public void deleteBusinessPartner(String bpn) { baseManagementRequest() + .basePath("/v3") .when() - .delete("/v3/business-partner-groups/{bpn}", bpn) + .delete("/business-partner-groups/{bpn}", bpn) .then() .statusCode(204); } @@ -215,10 +257,11 @@ public ValidatableResponse retireProviderAgreement(String agreementId) { .add(AR_ENTRY_REASON, "long-reason") .build(); return baseManagementRequest() + .basePath("/v3") .contentType(JSON) .body(body) .when() - .post("/v3/contractagreements/retirements") + .post("/contractagreements/retirements") .then(); } @@ -249,23 +292,25 @@ public ValidatableResponse getCatalog(TractusxParticipantBase provider) { .add(TYPE, "CatalogRequest") .add("counterPartyId", provider.id) .add("counterPartyAddress", provider.getProtocolUrl()) - .add("protocol", protocol); + .add("protocol", protocol.name()); return baseManagementRequest() .header("x-api-key", MANAGEMENT_API_KEY) + .basePath("/v3") .contentType(JSON) .when() .body(requestBodyBuilder.build()) - .post("/v3/catalog/request") + .post("/catalog/request") .then(); } public String getTransferProcessField(String transferProcessId, String fieldName) { return baseManagementRequest() + .basePath("/v3") .contentType(JSON) .when() - .get("/v3/transferprocesses/{id}", transferProcessId) + .get("/transferprocesses/{id}", transferProcessId) .then() .statusCode(200) .extract().body().jsonPath() @@ -274,9 +319,10 @@ public String getTransferProcessField(String transferProcessId, String fieldName public void triggerDataTransfer(String dataFlowId) { baseManagementRequest() + .basePath("v3") .contentType(JSON) .when() - .post("/v4alpha/dataflows/{id}/trigger", dataFlowId) + .post("/dataflows/{id}/trigger", dataFlowId) .then() .log().ifError() .statusCode(204); @@ -284,22 +330,43 @@ public void triggerDataTransfer(String dataFlowId) { public ValidatableResponse discoverDspParameters(JsonObject requestBody) { return baseManagementRequest() + .basePath("v3") .contentType(JSON) .body(requestBody) .when() - .post("/v4alpha/connectordiscovery/dspversionparams") + .post("/connectordiscovery/dspversionparams") .then(); } public ValidatableResponse discoverConnectorServices(JsonObject requestBody) { return baseManagementRequest() + .basePath("v3") .contentType(JSON) .body(requestBody) .when() - .post("/v4alpha/connectordiscovery/connectors") + .post("/connectordiscovery/connectors") .then(); } + // The following functions have been implemented, because these helper methods were removed upstream + // They are needed for support of DSP version v0.8 + public void setProtocol(String protocol) { + if (DSP_2025.equals(protocol)) { + this.protocol = new Protocol(DSP_2025, DSP_2025_PATH); + } else { + this.protocol = new Protocol(DSP_08, DSP_08_PATH); + } + } + + public void setJsonLd(JsonLd jsonLd) { + this.jsonLd = jsonLd; + } + + public String getBaseUrl() { + return controlPlaneProtocol.get().toString(); + } + // End of section with helper functions removed from upstream + public static class Builder

    > extends Participant.Builder { protected Builder(P participant) { super(participant); @@ -316,7 +383,7 @@ public B did(String did) { } public B protocolVersionPath(String path) { - this.participant.protocolVersionPath = path; + this.participant.protocol = new Protocol(this.participant.protocol.name(), path); return self(); } @@ -330,6 +397,8 @@ public P build() { participant.bpn = participant.name.toLowerCase() + BPN_SUFFIX; } + participant.participantContextId = UUID.randomUUID().toString(); + participant.enrichManagementRequest = requestSpecification -> requestSpecification.headers(Map.of(API_KEY_HEADER_NAME, MANAGEMENT_API_KEY)); super.timeout(ASYNC_TIMEOUT); super.build(); @@ -337,6 +406,15 @@ public P build() { this.participant.edrs = new ParticipantEdrApi(participant); this.participant.data = new ParticipantDataApi(); this.participant.dataPlane = new ParticipantConsumerDataPlaneApi(this.participant.dataPlaneProxy, Map.of("x-api-key", CONSUMER_PROXY_API_KEY)); + + TxCachedDocumentRegistry.getDocuments().forEach(result -> result + .onSuccess(c -> this.participant.jsonLd.registerCachedDocument(c.url(), c.resource())) + ); + + CxCachedDocumentRegistry.getDocuments().forEach(result -> result + .onSuccess(c -> this.participant.jsonLd.registerCachedDocument(c.url(), c.resource())) + ); + return participant; } } 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 3f762fb82f..f794ce93b1 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 @@ -26,6 +26,7 @@ import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; +import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames; import org.eclipse.edc.keys.spi.PrivateKeyResolver; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Setting; @@ -42,6 +43,8 @@ import org.junit.jupiter.api.extension.ExtensionContext; import java.security.PrivateKey; +import java.time.Instant; +import java.util.Date; import java.util.concurrent.atomic.AtomicReference; /** @@ -93,7 +96,10 @@ public void initialize(ServiceExtensionContext context) { @Override public TokenParameters.Builder decorate(TokenParameters.Builder tokenParameters) { claims.forEach(tokenParameters::claims); - return tokenParameters; + var now = Instant.now(); + return tokenParameters + .claims(JwtRegisteredClaimNames.ISSUED_AT, Date.from(now)) + .claims(JwtRegisteredClaimNames.EXPIRATION_TIME, Date.from(now.plusSeconds(300))); } }; return jwtGenerationService.generate(participantContextId, privateAlias, new KeyIdDecorator(kid), decorator); diff --git a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/PostgresExtension.java b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/PostgresExtension.java index 56cbf820cc..af3b62b589 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/PostgresExtension.java +++ b/edc-tests/e2e-fixtures/src/testFixtures/java/org/eclipse/tractusx/edc/tests/runtimes/PostgresExtension.java @@ -24,8 +24,8 @@ import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.postgresql.PostgreSQLContainer; import java.sql.DriverManager; import java.sql.SQLException; @@ -44,12 +44,12 @@ public class PostgresExtension implements BeforeAllCallback, AfterAllCallback { private static final String USER = "postgres"; private static final String PASSWORD = "password"; private static final String DB_SCHEMA_NAME = "testschema"; - private final PostgreSQLContainer postgreSqlContainer; + private final PostgreSQLContainer postgreSqlContainer; private final String[] databases; public PostgresExtension(String... databases) { this.databases = databases; - this.postgreSqlContainer = new PostgreSQLContainer<>(getPostgresTestContainerName()) + this.postgreSqlContainer = new PostgreSQLContainer(getPostgresTestContainerName()) .withUsername(USER) .withPassword(PASSWORD); } 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 4360c54e3e..67c72a975e 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 @@ -44,6 +44,7 @@ import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; 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.participant.TractusxParticipantBase.ASYNC_TIMEOUT; /** @@ -78,7 +79,7 @@ void transferData_privateBackend() { provider().createAsset(assetId, Map.of(), dataAddress); var accessPolicyId = provider().createPolicyDefinition(createAccessPolicy(consumer().getBpn())); - var contractPolicyId = provider().createPolicyDefinition(createContractPolicy(consumer().getBpn())); + var contractPolicyId = provider().createPolicyDefinition(createContractPolicy()); provider().createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var transferProcessId = consumer().requestAssetFrom(assetId, provider()).withTransferType("HttpData-PULL") .withDestination(httpDataDestination()).execute(); @@ -124,7 +125,7 @@ void transferData_privateBackend_withConsumerDataPlane() { provider().createAsset(assetId, Map.of(), dataAddress); var accessPolicyId = provider().createPolicyDefinition(createAccessPolicy(consumer().getBpn())); - var contractPolicyId = provider().createPolicyDefinition(createContractPolicy(consumer().getBpn())); + var contractPolicyId = provider().createPolicyDefinition(createContractPolicy()); provider().createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var transferProcessId = consumer().requestAssetFrom(assetId, provider()).withTransferType("HttpData-PULL") .withDestination(httpDataDestination()).execute(); @@ -170,7 +171,7 @@ protected JsonObject createAccessPolicy(String bpn) { return bpnPolicy(bpn); } - protected JsonObject createContractPolicy(String bpn) { - return bpnPolicy(bpn); + protected JsonObject createContractPolicy() { + return frameworkPolicy(Map.of(), "use"); } } 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 5af9f2628d..203f3020f1 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 @@ -80,8 +80,8 @@ void httpPushDataTransfer() { "type", "HttpData", "contentType", "application/json"); provider().createAsset(assetId, Map.of(), dataAddress); - var accessPolicyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); - var policyId = provider().createPolicyDefinition(frameworkPolicy(FRAMEWORK_AGREEMENT_LITERAL, Operator.EQ, "DataExchangeGovernance:1.0", "use")); + var accessPolicyId = provider().createPolicyDefinition(bpnPolicy(Operator.IS_ANY_OF, consumer().getBpn())); + var policyId = provider().createPolicyDefinition(frameworkPolicy("FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:1.0", "use", false)); provider().createContractDefinition(assetId, "def-1", accessPolicyId, policyId); var destination = httpDataAddress(destinationUrl); @@ -95,12 +95,12 @@ void httpPushDataTransfer() { server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_SOURCE_PATH))); server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_DESTINATION_PATH))); } - + @Test void httpPushDataTransfer_withLegacyUsagePolicy() { var sourceUrl = createMockHttpDataUrl(MOCK_BACKEND_SOURCE_PATH); var destinationUrl = createMockHttpDataUrl(MOCK_BACKEND_DESTINATION_PATH); - + var assetId = UUID.randomUUID().toString(); Map dataAddress = Map.of( "name", "transfer-test", @@ -108,17 +108,17 @@ void httpPushDataTransfer_withLegacyUsagePolicy() { "type", "HttpData", "contentType", "application/json"); provider().createAsset(assetId, Map.of(), dataAddress); - var accessPolicyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); - var policyId = provider().createPolicyDefinition(legacyFrameworkPolicy()); + var accessPolicyId = provider().createPolicyDefinition(bpnPolicy(Operator.IS_ANY_OF, consumer().getBpn())); + var policyId = provider().createPolicyDefinition(frameworkPolicy("FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:1.0", "use", false)); provider().createContractDefinition(assetId, "def-1", accessPolicyId, policyId); - + var destination = httpDataAddress(destinationUrl); var transferProcessId = consumer() .requestAssetFrom(assetId, provider()) .withDestination(destination) .withTransferType("HttpData-PUSH") .execute(); - + await().atMost(ASYNC_TIMEOUT).untilAsserted(() -> transferProcessIsInState(transferProcessId, COMPLETED)); server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_SOURCE_PATH))); server.verify(anyRequestedFor(urlPathEqualTo(MOCK_BACKEND_DESTINATION_PATH))); @@ -137,8 +137,9 @@ void httpPushNonFiniteDataTransfer() { "contentType", "application/json", "isNonFinite", "true"); provider().createAsset(assetId, Map.of(), dataAddress); - var policyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); - provider().createContractDefinition(assetId, "def-1", policyId, policyId); + var accessPolicyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); + var contractPolicyId = provider().createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + provider().createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var destination = httpDataAddress(destinationUrl); var consumerTransferProcessId = consumer() diff --git a/edc-tests/e2e-fixtures/src/testFixtures/resources/Dockerfile b/edc-tests/e2e-fixtures/src/testFixtures/resources/Dockerfile index 17b167998b..c194458f3e 100644 --- a/edc-tests/e2e-fixtures/src/testFixtures/resources/Dockerfile +++ b/edc-tests/e2e-fixtures/src/testFixtures/resources/Dockerfile @@ -1,2 +1,2 @@ -FROM postgres:18.3 +FROM postgres:18.6 USER "Dummy" diff --git a/edc-tests/e2e/bpn-event-tests/build.gradle.kts b/edc-tests/e2e/bpn-event-tests/build.gradle.kts index 3c6acb337f..ea2c40fd72 100644 --- a/edc-tests/e2e/bpn-event-tests/build.gradle.kts +++ b/edc-tests/e2e/bpn-event-tests/build.gradle.kts @@ -32,3 +32,10 @@ dependencies { edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} diff --git a/edc-tests/e2e/catalog-tests/build.gradle.kts b/edc-tests/e2e/catalog-tests/build.gradle.kts index 9414cc8a94..6221736101 100644 --- a/edc-tests/e2e/catalog-tests/build.gradle.kts +++ b/edc-tests/e2e/catalog-tests/build.gradle.kts @@ -38,3 +38,10 @@ dependencies { edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} 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 f04c0b5a8c..b4584dad50 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 @@ -20,6 +20,8 @@ package org.eclipse.tractusx.edc.tests.catalog; +import org.eclipse.edc.connector.controlplane.contract.spi.offer.store.ContractDefinitionStore; +import org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; import org.eclipse.edc.connector.controlplane.policy.spi.store.PolicyDefinitionStore; import org.eclipse.edc.jsonld.spi.JsonLd; @@ -32,6 +34,7 @@ import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Policy; import org.eclipse.edc.policy.model.PolicyType; +import org.eclipse.edc.spi.query.Criterion; import org.eclipse.tractusx.edc.tests.participant.TransferParticipant; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; import org.junit.jupiter.api.BeforeEach; @@ -40,13 +43,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.util.Map; - import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.edc.connector.controlplane.test.system.utils.PolicyFixtures.noConstraintPolicy; -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.tests.TestRuntimeConfiguration.CONSUMER_BPN; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_DID; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; @@ -58,9 +56,8 @@ import static org.eclipse.tractusx.edc.tests.helpers.CatalogHelperFunctions.getDatasetAssetId; import static org.eclipse.tractusx.edc.tests.helpers.CatalogHelperFunctions.getDatasetPolicies; import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.bpnGroupPolicy; -import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.bpnGroupPolicyWithRightOperandAsArray; 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.emptyPolicy; import static org.eclipse.tractusx.edc.tests.helpers.QueryHelperFunctions.createQuery; import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; @@ -71,8 +68,7 @@ public class CatalogTest { .name(CONSUMER_NAME) .id(CONSUMER_DID) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @@ -80,8 +76,7 @@ public class CatalogTest { .name(PROVIDER_NAME) .id(PROVIDER_DID) .bpn(PROVIDER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @RegisterExtension @@ -104,8 +99,8 @@ void setup() { void requestCatalog_fulfillsPolicy_shouldReturnOffer() { // arrange PROVIDER.createAsset("test-asset"); - var ap = PROVIDER.createPolicyDefinition(noConstraintPolicy()); - var cp = PROVIDER.createPolicyDefinition(noConstraintPolicy()); + var ap = PROVIDER.createPolicyDefinition(emptyPolicy()); + var cp = PROVIDER.createPolicyDefinition(emptyPolicy()); PROVIDER.createContractDefinition("test-asset", "test-def", ap, cp); // act @@ -124,11 +119,11 @@ void requestCatalog_fulfillsPolicy_shouldReturnOffer() { @DisplayName("Verify that the consumer receives only the offers he is permitted to (using the legacy BPN validation)") void requestCatalog_filteredByBpnLegacy_shouldReject() { var onlyConsumerPolicy = bpnPolicy(Operator.IS_ANY_OF, "BPNLAAAAAAAAAAAA", "BPNL123456ABCDEF", CONSUMER.getBpn()); - var onlyDiogenesPolicy = bpnPolicy("BPNLAAAAAAAAAABC"); + var onlyDiogenesPolicy = bpnPolicy(Operator.IS_ANY_OF, "BPNLAAAAAAAAAABC"); var onlyConsumerId = PROVIDER.createPolicyDefinition(onlyConsumerPolicy); var onlyDiogenesId = PROVIDER.createPolicyDefinition(onlyDiogenesPolicy); - var noConstraintPolicyId = PROVIDER.createPolicyDefinition(noConstraintPolicy()); + var noConstraintPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); PROVIDER.createAsset("test-asset1"); PROVIDER.createAsset("test-asset2"); @@ -150,14 +145,11 @@ void requestCatalog_filteredByBpnLegacy_shouldReject() { void requestCatalog_filteredByBpnLegacy_WithNamespace_shouldReject() { var onlyConsumerPolicy = bpnPolicy(Operator.IS_ANY_OF, "BPNLAAAAAAAAAAAA", "BPNL123456ABCDEF", CONSUMER.getBpn()); - var onlyDiogenesPolicy = frameworkPolicy( - Map.of(CX_POLICY_2025_09_NS + "BusinessPartnerNumber", "BPNLAAAAAAAAAAAB"), - CX_POLICY_2025_09_NS + "access", - Operator.IS_ANY_OF); + var onlyDiogenesPolicy = bpnPolicy(Operator.IS_ANY_OF, "BPNLAAAAAAAAAAAB"); var onlyConsumerId = PROVIDER.createPolicyDefinition(onlyConsumerPolicy); var onlyDiogenesId = PROVIDER.createPolicyDefinition(onlyDiogenesPolicy); - var noConstraintPolicyId = PROVIDER.createPolicyDefinition(noConstraintPolicy()); + var noConstraintPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); PROVIDER.createAsset("test-asset1"); PROVIDER.createAsset("test-asset2"); @@ -177,13 +169,13 @@ void requestCatalog_filteredByBpnLegacy_WithNamespace_shouldReject() { @DisplayName("Verify that the consumer receives only the offers he is permitted to (using BPN group validation)") void requestCatalog_filteredByBpnGroup_shouldReturnOffer() { var allowedGroup = "allowed-group"; - var accessPolicy = bpnGroupPolicyWithRightOperandAsArray(Operator.IS_ANY_OF, allowedGroup, "test-group"); + var accessPolicy = bpnGroupPolicy("isAnyOf", true, allowedGroup, "test-group"); PROVIDER.storeBusinessPartner(CONSUMER.getBpn(), allowedGroup); PROVIDER.createAsset("test-asset"); var accessPolicyId = PROVIDER.createPolicyDefinition(accessPolicy); - var contractPolicyId = PROVIDER.createPolicyDefinition(noConstraintPolicy()); + var contractPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); PROVIDER.createContractDefinition("test-asset", "def", accessPolicyId, contractPolicyId); var catalog = CONSUMER.getCatalogDatasets(PROVIDER); @@ -193,15 +185,14 @@ void requestCatalog_filteredByBpnGroup_shouldReturnOffer() { @Test @DisplayName("Verify that the consumer receives only the offers he is permitted to (using the new BPN validation)") void requestCatalog_filteredByBpn_shouldReject() { - - var mustBeGreekPhilosopher = bpnGroupPolicy(Operator.IS_ANY_OF, "greek_customer", "philosopher"); - var mustBeGreekMathematician = bpnGroupPolicy(Operator.IS_NONE_OF, "greek_customer", "mathematician"); + var mustBeGreekPhilosopher = bpnGroupPolicy("isAnyOf", true, "greek_customer", "philosopher"); + var mustBeGreekMathematician = bpnGroupPolicy("isNoneOf", true, "greek_customer", "mathematician"); PROVIDER.storeBusinessPartner(CONSUMER.getBpn(), "greek_customer", "philosopher"); var philosopherId = PROVIDER.createPolicyDefinition(mustBeGreekPhilosopher); var mathId = PROVIDER.createPolicyDefinition(mustBeGreekMathematician); - var noConstraintPolicyId = PROVIDER.createPolicyDefinition(noConstraintPolicy()); + var noConstraintPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); PROVIDER.createAsset("test-asset1"); PROVIDER.createAsset("test-asset2"); @@ -224,11 +215,19 @@ void requestCatalog_filteredByBpn_UsingLegacyCxPolicy_shouldReject() { var id = "philosopher-policy"; PROVIDER_RUNTIME.getService(PolicyDefinitionStore.class) .create(buildLegacyPolicyDefinition(id, "greek_customer", Operator.EQ, "philosopher")); + var contractPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); PROVIDER.createAsset("test-asset1"); PROVIDER.createAsset("test-asset2"); - PROVIDER.createContractDefinition("test-asset2", "def1", id, id); + PROVIDER_RUNTIME.getService(ContractDefinitionStore.class) + .save(ContractDefinition.Builder.newInstance() + .id("def1") + .participantContextId(PROVIDER.getParticipantContextId()) + .accessPolicyId(id) + .contractPolicyId(contractPolicyId) + .assetsSelectorCriterion(new Criterion("https://w3id.org/edc/v0.0.1/ns/id", "=", "test-asset2")) + .build()); // act var catalog = CONSUMER.getCatalogDatasets(PROVIDER); @@ -244,8 +243,8 @@ void requestCatalog_filteredByBpn_UsingLegacyCxPolicy_shouldReject() { void requestCatalog_multipleOffersForAsset() { PROVIDER.storeBusinessPartner(CONSUMER.getBpn(), "test-group"); PROVIDER.createAsset("asset-1"); - var noConstraintId = PROVIDER.createPolicyDefinition(noConstraintPolicy()); - var groupConstraintId = PROVIDER.createPolicyDefinition(bpnGroupPolicy(Operator.IS_ANY_OF, "test-group")); + var noConstraintId = PROVIDER.createPolicyDefinition(emptyPolicy()); + var groupConstraintId = PROVIDER.createPolicyDefinition(bpnGroupPolicy("isAnyOf", true, "test-group")); PROVIDER.createContractDefinition("asset-1", "def1", noConstraintId, noConstraintId); PROVIDER.createContractDefinition("asset-1", "def2", groupConstraintId, noConstraintId); @@ -261,9 +260,9 @@ void requestCatalog_multipleOffersForAsset() { @Test @DisplayName("Catalog with 1000 offers") void requestCatalog_of1000Assets_shouldContainAll() { - var policy = bpnGroupPolicy(Operator.IS_NONE_OF, "test-group1", "test-group2"); + var policy = bpnGroupPolicy("isNoneOf", true, "test-group1", "test-group2"); var policyId = PROVIDER.createPolicyDefinition(policy); - var noConstraintId = PROVIDER.createPolicyDefinition(noConstraintPolicy()); + var noConstraintId = PROVIDER.createPolicyDefinition(emptyPolicy()); PROVIDER.storeBusinessPartner(CONSUMER.getBpn(), "test-group-3"); range(0, 1000) @@ -286,7 +285,7 @@ void requestCatalog_of1000Assets_shouldContainAll() { private PolicyDefinition buildLegacyPolicyDefinition(String id, String leftExpression, Operator operator, Object rightExpression) { var action = Action.Builder.newInstance() - .type(CX_POLICY_NS + "access") + .type("access") .build(); var constraint = AtomicConstraint.Builder.newInstance() diff --git a/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/CatalogTestDspV08.java b/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/CatalogTestDspV08.java new file mode 100644 index 0000000000..c50c215710 --- /dev/null +++ b/edc-tests/e2e/catalog-tests/src/test/java/org/eclipse/tractusx/edc/tests/catalog/CatalogTestDspV08.java @@ -0,0 +1,235 @@ +/******************************************************************************** + * Copyright (c) 2026 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.tests.catalog; + +import org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; +import org.eclipse.edc.connector.controlplane.policy.spi.store.PolicyDefinitionStore; +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.policy.model.Action; +import org.eclipse.edc.policy.model.AtomicConstraint; +import org.eclipse.edc.policy.model.LiteralExpression; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.PolicyType; +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.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 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.tests.TestRuntimeConfiguration.CONSUMER_BPN; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_DID; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_08; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_08_PATH; +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; +import static org.eclipse.tractusx.edc.tests.helpers.CatalogHelperFunctions.getDatasetAssetId; +import static org.eclipse.tractusx.edc.tests.helpers.CatalogHelperFunctions.getDatasetPolicies; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.bpnPolicy; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.emptyPolicy; +import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; + +@EndToEndTest +public class CatalogTestDspV08 { + + private static final TransferParticipant CONSUMER = TransferParticipant.Builder.newInstance() + .name(CONSUMER_NAME) + .id(CONSUMER_DID) + .bpn(CONSUMER_BPN) + .protocol(DSP_08, DSP_08_PATH) + .build(); + + + private static final TransferParticipant PROVIDER = TransferParticipant.Builder.newInstance() + .name(PROVIDER_NAME) + .id(PROVIDER_DID) + .bpn(PROVIDER_BPN) + .protocol(DSP_08, DSP_08_PATH) + .build(); + + @RegisterExtension + @Order(0) + private static final PostgresExtension POSTGRES = new PostgresExtension(CONSUMER.getName(), PROVIDER.getName()); + + @RegisterExtension + private static final RuntimeExtension CONSUMER_RUNTIME = pgRuntime(CONSUMER, POSTGRES); + + @RegisterExtension + private static final RuntimeExtension PROVIDER_RUNTIME = pgRuntime(PROVIDER, POSTGRES); + + @BeforeEach + void setup() { + CONSUMER.setJsonLd(CONSUMER_RUNTIME.getService(JsonLd.class)); + } + + @Test + @DisplayName("Consumer gets catalog from the provider. No constraints.") + void requestCatalog_fulfillsPolicy_shouldReturnOffer() { + // arrange + PROVIDER.createAsset("test-asset"); + var ap = PROVIDER.createPolicyDefinition(emptyPolicy()); + var cp = PROVIDER.createPolicyDefinition(emptyPolicy()); + PROVIDER.createContractDefinition("test-asset", "test-def", ap, cp); + + // act + var catalog = CONSUMER.getCatalogDatasets(PROVIDER); + + // assert + assertThat(catalog).isNotEmpty() + .hasSize(1) + .allSatisfy(co -> { + assertThat(getDatasetAssetId(co.asJsonObject())).isEqualTo("test-asset"); + }); + + } + + @Test + @DisplayName("Verify that the consumer receives only the offers he is permitted to (using the legacy BPN validation)") + void requestCatalog_filteredByBpnLegacy_shouldReject() { + var onlyConsumerPolicy = bpnPolicy(Operator.IS_ANY_OF, "BPNLAAAAAAAAAAAA", "BPNL123456ABCDEF", CONSUMER.getBpn()); + var onlyDiogenesPolicy = bpnPolicy(Operator.IS_ANY_OF, "BPNLAAAAAAAAAABC"); + + var onlyConsumerId = PROVIDER.createPolicyDefinition(onlyConsumerPolicy); + var onlyDiogenesId = PROVIDER.createPolicyDefinition(onlyDiogenesPolicy); + var noConstraintPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); + + PROVIDER.createAsset("test-asset1"); + PROVIDER.createAsset("test-asset2"); + PROVIDER.createAsset("test-asset3"); + + PROVIDER.createContractDefinition("test-asset1", "def1", noConstraintPolicyId, noConstraintPolicyId); + PROVIDER.createContractDefinition("test-asset2", "def2", onlyConsumerId, noConstraintPolicyId); + PROVIDER.createContractDefinition("test-asset3", "def3", onlyDiogenesId, noConstraintPolicyId); + + + // act + var catalog = CONSUMER.getCatalogDatasets(PROVIDER); + assertThat(catalog).hasSize(2); + } + + + @Test + @DisplayName("Verify that the consumer receives only the offers he is permitted to (using the legacy BPN validation)") + void requestCatalog_filteredByBpnLegacy_WithNamespace_shouldReject() { + var onlyConsumerPolicy = bpnPolicy(Operator.IS_ANY_OF, "BPNLAAAAAAAAAAAA", "BPNL123456ABCDEF", CONSUMER.getBpn()); + var onlyDiogenesPolicy = bpnPolicy(Operator.IS_ANY_OF, "BPNLAAAAAAAAAABC"); + + var onlyConsumerId = PROVIDER.createPolicyDefinition(onlyConsumerPolicy); + var onlyDiogenesId = PROVIDER.createPolicyDefinition(onlyDiogenesPolicy); + var noConstraintPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); + + PROVIDER.createAsset("test-asset1"); + PROVIDER.createAsset("test-asset2"); + PROVIDER.createAsset("test-asset3"); + + PROVIDER.createContractDefinition("test-asset1", "def1", noConstraintPolicyId, noConstraintPolicyId); + PROVIDER.createContractDefinition("test-asset2", "def2", onlyConsumerId, noConstraintPolicyId); + PROVIDER.createContractDefinition("test-asset3", "def3", onlyDiogenesId, noConstraintPolicyId); + + + // act + var catalog = CONSUMER.getCatalogDatasets(PROVIDER); + assertThat(catalog).hasSize(2); + } + + @Test + @DisplayName("Verify that the consumer receives only the offers he is permitted to (using the new BPN validation)") + void requestCatalog_filteredByBpn_shouldReject() { + var mustBeGreekPhilosopher = PolicyHelperFunctions.bpnGroupPolicy("isAnyOf", true, "greek_customer", "philosopher"); + var mustBeGreekMathematician = PolicyHelperFunctions.bpnGroupPolicy("isNoneOf", true, "greek_customer", "mathematician"); + + + PROVIDER.storeBusinessPartner(CONSUMER.getBpn(), "greek_customer", "philosopher"); + var philosopherId = PROVIDER.createPolicyDefinition(mustBeGreekPhilosopher); + var mathId = PROVIDER.createPolicyDefinition(mustBeGreekMathematician); + var noConstraintPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); + + PROVIDER.createAsset("test-asset1"); + PROVIDER.createAsset("test-asset2"); + PROVIDER.createAsset("test-asset3"); + + PROVIDER.createContractDefinition("test-asset1", "def1", noConstraintPolicyId, noConstraintPolicyId); + PROVIDER.createContractDefinition("test-asset2", "def2", philosopherId, noConstraintPolicyId); + PROVIDER.createContractDefinition("test-asset3", "def3", mathId, noConstraintPolicyId); + + + // act + var catalog = CONSUMER.getCatalogDatasets(PROVIDER); + assertThat(catalog).hasSize(2); + } + + @Test + @DisplayName("Verify that the consumer receives only the offers he is permitted to (using the legacy CX policy)") + void requestCatalog_filteredByBpn_UsingLegacyCxPolicy_shouldReject() { + PROVIDER.storeBusinessPartner(CONSUMER.getBpn(), "greek_customer", "philosopher"); + var ap = "philosopher-policy"; + PROVIDER_RUNTIME.getService(PolicyDefinitionStore.class) + .create(buildLegacyPolicyDefinition(ap, "greek_customer", Operator.EQ, "philosopher")); + var cp = PROVIDER.createPolicyDefinition(emptyPolicy()); + + PROVIDER.createAsset("test-asset1"); + PROVIDER.createAsset("test-asset2"); + + PROVIDER.createContractDefinition("test-asset2", "def1", ap, cp); + + // act + var catalog = CONSUMER.getCatalogDatasets(PROVIDER); + assertThat(catalog).hasSize(1) + .allSatisfy(cd -> { + assertThat(getDatasetAssetId(cd.asJsonObject())).isEqualTo("test-asset2"); + assertThat(getDatasetPolicies(cd)).hasSize(1); + }); + } + + private PolicyDefinition buildLegacyPolicyDefinition(String id, String leftExpression, Operator operator, Object rightExpression) { + var action = Action.Builder.newInstance() + .type(CX_POLICY_2025_09_NS + "access") + .build(); + + var constraint = AtomicConstraint.Builder.newInstance() + .leftExpression(new LiteralExpression(leftExpression)) + .operator(operator) + .rightExpression(new LiteralExpression(rightExpression)) + .build(); + + var policy = Policy.Builder.newInstance() + .type(PolicyType.SET) + .permission(Permission.Builder.newInstance() + .action(action) + .constraint(constraint) + .build()) + .build(); + + return PolicyDefinition.Builder.newInstance() + .id(id) + .policy(policy) + .build(); + } +} 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 caa3c7de9e..26ab23aac8 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 @@ -130,7 +130,7 @@ void transferMultipleFile_success(Vault vault) { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(request) - .post() + .post("/start") .then() .log().ifValidationFails() .statusCode(200); @@ -167,7 +167,7 @@ void transferFile_success(Vault vault) { blobAddress(sourceContainerName, PROVIDER_AZURITE_ACCOUNT.name(), AZBLOB_PROVIDER_KEY_ALIAS, dspaceProperty(EDC_NAMESPACE + "blobName", TESTFILE_NAME)), blobAddress(destinationContainerName, CONSUMER_AZURITE_ACCOUNT.name(), AZBLOB_CONSUMER_KEY_ALIAS, dspaceProperty(EDC_NAMESPACE + "blobName", TESTFILE_NAME)) ).build()) - .post() + .post("/start") .then() .log().ifValidationFails() .statusCode(200); @@ -220,7 +220,7 @@ void transferFile_largeFile(long sizeBytes, Vault vault) throws IOException { blobAddress(sourceContainerName, PROVIDER_AZURITE_ACCOUNT.name(), AZBLOB_PROVIDER_KEY_ALIAS, dspaceProperty(EDC_NAMESPACE + "blobName", blobName)), blobAddress(destinationContainerName, CONSUMER_AZURITE_ACCOUNT.name(), AZBLOB_CONSUMER_KEY_ALIAS, dspaceProperty(EDC_NAMESPACE + "blobName", blobName)) ).build()) - .post() + .post("/start") .then() .log().ifValidationFails() .log().ifValidationFails() @@ -264,7 +264,7 @@ void transferFolder_targetFolderNotExists_shouldCreate(Vault vault) { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(request) - .post() + .post("/start") .then() .log().ifValidationFails() .statusCode(200); @@ -299,7 +299,7 @@ void transferFile_targetContainerNotExist_shouldFail(Vault vault) { blobAddress(destinationContainerName, CONSUMER_AZURITE_ACCOUNT.name(), AZBLOB_CONSUMER_KEY_ALIAS, dspaceProperty(EDC_NAMESPACE + "blobName", TESTFILE_NAME)) ).build() ) - .post() + .post("/start") .then() .log().ifValidationFails() .statusCode(200); @@ -312,7 +312,7 @@ void transferFile_targetContainerNotExist_shouldFail(Vault vault) { private JsonObjectBuilder createFlowRequestBuilder(JsonObjectBuilder sourceDataAddress, JsonObjectBuilder destinationDataAddress) { return Json.createObjectBuilder() - .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/v0.8/")) + .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/2025/1/")) .add("@type", EDC_DATA_FLOW_START_MESSAGE_TYPE) .add("@id", UUID.randomUUID().toString()) .add("processId", UUID.randomUUID().toString()) 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 5aa89193f3..643b446617 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 @@ -101,7 +101,7 @@ void transferFile_azureToS3MultipleFiles(Vault vault) { var bucketName = MINIO_CONTAINER.createBucket(); var request = Json.createObjectBuilder() - .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/v0.8/")) + .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/2025/1/")) .add("@type", EDC_DATA_FLOW_START_MESSAGE_TYPE) .add("@id", UUID.randomUUID().toString()) .add("processId", UUID.randomUUID().toString()) @@ -133,7 +133,7 @@ void transferFile_azureToS3MultipleFiles(Vault vault) { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(request) - .post() + .post("/start") .then() .log().ifError() .statusCode(200); @@ -155,7 +155,7 @@ void transferFile_azureToS3(Vault vault) { var bucketName = MINIO_CONTAINER.createBucket(); var request = Json.createObjectBuilder() - .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/v0.8/")) + .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/2025/1/")) .add("@type", EDC_DATA_FLOW_START_MESSAGE_TYPE) .add("@id", UUID.randomUUID().toString()) .add("processId", UUID.randomUUID().toString()) @@ -186,7 +186,7 @@ void transferFile_azureToS3(Vault vault) { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(request) - .post() + .post("/start") .then() .log().ifError() .statusCode(200); @@ -214,7 +214,7 @@ void transferFile_s3ToAzureMultipleFiles(Vault vault) { """.formatted(blobStoreClient.generateAccountSas(containerName))); var request = Json.createObjectBuilder() - .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/v0.8/")) + .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/2025/1/")) .add("@type", EDC_DATA_FLOW_START_MESSAGE_TYPE) .add("@id", UUID.randomUUID().toString()) .add("processId", UUID.randomUUID().toString()) @@ -245,7 +245,7 @@ void transferFile_s3ToAzureMultipleFiles(Vault vault) { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(request) - .post() + .post("/start") .then() .statusCode(200); @@ -276,7 +276,7 @@ void transferFile_s3ToAzureMultipleFiles_whenConsumerDefinesBloblName_success(Va """.formatted(blobStoreClient.generateAccountSas(containerName))); var request = Json.createObjectBuilder() - .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/v0.8/")) + .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/2025/1/")) .add("@type", EDC_DATA_FLOW_START_MESSAGE_TYPE) .add("@id", UUID.randomUUID().toString()) .add("processId", UUID.randomUUID().toString()) @@ -309,7 +309,7 @@ void transferFile_s3ToAzureMultipleFiles_whenConsumerDefinesBloblName_success(Va .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(request) - .post() + .post("/start") .then() .statusCode(200); @@ -332,7 +332,7 @@ void transferFile_s3ToAzure(Vault vault) { """.formatted(blobStoreClient.generateAccountSas(containerName))); var request = Json.createObjectBuilder() - .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/v0.8/")) + .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/2025/1/")) .add("@type", EDC_DATA_FLOW_START_MESSAGE_TYPE) .add("@id", UUID.randomUUID().toString()) .add("processId", UUID.randomUUID().toString()) @@ -364,7 +364,7 @@ void transferFile_s3ToAzure(Vault vault) { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(request) - .post() + .post("/start") .then() .statusCode(200); 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 9129f36c8d..43215cc460 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 @@ -54,7 +54,7 @@ private static Config baseConfig(LazySupplier controlApi) { put("edc.iam.sts.oauth.client.id", "test-clientid"); put("edc.iam.sts.oauth.client.secret.alias", "test-clientid-alias"); put("tx.edc.iam.sts.div.url", "http://sts.example.com"); - put("tx.edc.iam.iatp.bdrs.server.url", "http://sts.example.com"); + put("tx.edc.iam.dcp.bdrs.server.url", "http://sts.example.com"); put("edc.transfer.proxy.token.verifier.publickey.alias", "not-used-but-mandatory"); put("edc.transfer.proxy.token.signer.privatekey.alias", "not-used-but-mandatory"); } 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 0c1050ee22..8056abf4a9 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 @@ -109,7 +109,7 @@ void transferMultipleFiles() { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(createDataFlowStartMessage(sourceBucketName, destinationBucketName, additionalSourceAddressProperties, UUID.randomUUID().toString())) - .post() + .post("/start") .then() .statusCode(200); @@ -132,7 +132,7 @@ void transferFile_success() { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(createDataFlowStartMessage(sourceBucketName, destinationBucketName, additionalSourceAddressProperties, UUID.randomUUID().toString())) - .post() + .post("/start") .then() .statusCode(200); @@ -154,7 +154,7 @@ void shouldFail_whenDestinationBucketDoesNotExist() { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(createDataFlowStartMessage(sourceBucketName, "not-existent-bucket", additionalSourceAddressProperties, processId)) - .post() + .post("/start") .then() .statusCode(200); @@ -183,7 +183,7 @@ void shouldTransferOneGbFile() { .baseUri(START_DATAFLOW_URI.get().toString()) .contentType(ContentType.JSON) .body(createDataFlowStartMessage(sourceBucketName, destinationBucketName, additionalSourceAddressProperties, processId)) - .post() + .post("/start") .then() .statusCode(200) .log().ifValidationFails(); @@ -212,7 +212,7 @@ private CompletableFuture uploadLargeFile(File file, String buc private JsonObject createDataFlowStartMessage(String sourceBucketName, String destinationBucketName, List additionalSourceAddressProperties, String processId) { return Json.createObjectBuilder() - .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/v0.8/")) + .add("@context", Json.createObjectBuilder().add("@vocab", EDC_NAMESPACE).add("dspace", "https://w3id.org/dspace/2025/1/")) .add("@type", EDC_DATA_FLOW_START_MESSAGE_TYPE) .add("@id", UUID.randomUUID().toString()) .add("processId", processId) diff --git a/edc-tests/e2e/dcp-tck-tests/build.gradle.kts b/edc-tests/e2e/dcp-tck-tests/build.gradle.kts index bbd70097bc..842ae348d0 100644 --- a/edc-tests/e2e/dcp-tck-tests/build.gradle.kts +++ b/edc-tests/e2e/dcp-tck-tests/build.gradle.kts @@ -31,9 +31,9 @@ dependencies { testImplementation(libs.nimbus.jwt) testImplementation(libs.restAssured) - testImplementation(libs.dsp.tck.runtime) + testImplementation(libs.tck.runtime) testImplementation(libs.dcp.system) - testImplementation(libs.dsp.tck.core) + testImplementation(libs.tck.core) 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 42cb7b7e4c..2c8d99e3ae 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 @@ -94,7 +94,9 @@ public class DcpPresentationFlowTest { @RegisterExtension static final RuntimePerClassExtension RUNTIME = new RuntimePerClassExtension( - new EmbeddedRuntime("Connector-under-test", ":edc-tests:runtime:runtime-dcp-tck") + new EmbeddedRuntime("Connector-under-test", + ":edc-tests:runtime:runtime-dcp-tck", + ":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)) @@ -229,16 +231,17 @@ 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.participant.id", VERIFIER_DID); 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"); put("edc.iam.sts.oauth.client.secret.alias", "test-secret-alias"); - put("tx.edc.iam.iatp.bdrs.server.url", "http://sts.example.com"); + put("tx.edc.iam.dcp.bdrs.server.url", "http://sts.example.com"); + put("tx.edc.dcp.cache.enabled", "false"); //register a default scope https://github.com/eclipse-dataspacetck/dcp-tck?tab=readme-ov-file#232-required-configuration - put("tx.edc.iam.iatp.default-scopes.holderIdentifier.alias", "org.eclipse.dspace.dcp.vc.type"); - put("tx.edc.iam.iatp.default-scopes.holderIdentifier.type", "MembershipCredential"); - put("tx.edc.iam.iatp.default-scopes.holderIdentifier.operation", "read"); + put("tx.edc.iam.dcp.default-scopes.holderIdentifier.alias", "org.eclipse.dspace.dcp.vc.type"); + put("tx.edc.iam.dcp.default-scopes.holderIdentifier.type", "MembershipCredential"); + put("tx.edc.iam.dcp.default-scopes.holderIdentifier.operation", "read"); put("tractusx.edc.participant.bpn", "bpn"); } }); diff --git a/edc-tests/e2e/iatp-tests/build.gradle.kts b/edc-tests/e2e/dcp-tests/build.gradle.kts similarity index 83% rename from edc-tests/e2e/iatp-tests/build.gradle.kts rename to edc-tests/e2e/dcp-tests/build.gradle.kts index 50be7e9a7e..f9979e4d8e 100644 --- a/edc-tests/e2e/iatp-tests/build.gradle.kts +++ b/edc-tests/e2e/dcp-tests/build.gradle.kts @@ -28,12 +28,6 @@ configurations.all { } 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.spi.keypair) testImplementation(libs.edc.ih.spi) @@ -53,12 +47,19 @@ dependencies { testImplementation(libs.awaitility) testImplementation(libs.bouncyCastle.bcpkixJdk18on) - testCompileOnly(project(":edc-tests:runtime:iatp:runtime-memory-iatp-div-ih")) - testCompileOnly(project(":edc-tests:runtime:iatp:runtime-memory-iatp-ih")) - testCompileOnly(project(":edc-tests:runtime:iatp:runtime-memory-sts")) + testCompileOnly(project(":edc-tests:runtime:dcp:runtime-memory-dcp-div-ih")) + testCompileOnly(project(":edc-tests:runtime:dcp:runtime-memory-dcp-ih")) + testCompileOnly(project(":edc-tests:runtime:dcp:runtime-memory-sts")) } // do not publish edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractDcpConsumerPullTest.java similarity index 91% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractDcpConsumerPullTest.java index c998451717..034ee3aac6 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractIatpConsumerPullTest.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/AbstractDcpConsumerPullTest.java @@ -33,11 +33,12 @@ 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.policy.model.Operator; import org.eclipse.edc.spi.query.Criterion; import org.eclipse.edc.spi.query.QuerySpec; import org.eclipse.tractusx.edc.tests.participant.DataspaceIssuer; -import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.StatusList2021; -import org.eclipse.tractusx.edc.tests.transfer.iatp.harness.StsParticipant; +import org.eclipse.tractusx.edc.tests.transfer.dcp.harness.StatusList2021; +import org.eclipse.tractusx.edc.tests.transfer.dcp.harness.StsParticipant; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; @@ -69,12 +70,11 @@ import static org.awaitility.Awaitility.await; import static org.awaitility.pollinterval.FibonacciPollInterval.fibonacci; 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.helpers.PolicyHelperFunctions.frameworkPolicy; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public abstract class AbstractIatpConsumerPullTest extends ConsumerPullBaseTest { +public abstract class AbstractDcpConsumerPullTest extends ConsumerPullBaseTest { protected static final StsParticipant STS = StsParticipant.Builder.newInstance() .id("STS") @@ -104,19 +104,26 @@ void transferData_whenContractPolicyFulfilled(JsonObject contractPolicy, String var accessPolicyId = provider().createPolicyDefinition(createAccessPolicy(consumer().getBpn())); var contractPolicyId = provider().createPolicyDefinition(contractPolicy); provider().createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); - var transferProcessId = consumer().requestAssetFrom(assetId, provider()) + + var initialTransferProcessId = consumer().requestAssetFrom(assetId, provider()) .withTransferType("HttpData-PULL") .withDestination(httpDataDestination()) .execute(); + var agreementId = consumer().getTransferProcessField(initialTransferProcessId, "contractId"); + + var transferProcessId = new AtomicReference<>(initialTransferProcessId); var edr = new AtomicReference(); - // wait until transfer process completes await().pollInterval(fibonacci()) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { - var tpState = consumer().getTransferProcessState(transferProcessId); - assertThat(tpState).isNotNull().isEqualTo(TransferProcessStates.STARTED.toString()); + var current = transferProcessId.get(); + if (TransferProcessStates.TERMINATED.toString().equals(consumer().getTransferProcessState(current))) { + current = consumer().initiateTransfer(provider(), agreementId, null, httpDataDestination(), "HttpData-PULL"); + transferProcessId.set(current); + } + assertThat(consumer().getTransferProcessState(current)).isEqualTo(TransferProcessStates.STARTED.toString()); }); // wait until EDC is available on the consumer side @@ -124,7 +131,7 @@ void transferData_whenContractPolicyFulfilled(JsonObject contractPolicy, String await().pollInterval(fibonacci()) .atMost(ASYNC_TIMEOUT) .untilAsserted(() -> { - edr.set(consumer().edrs().getEdr(transferProcessId)); + edr.set(consumer().edrs().getEdr(transferProcessId.get())); assertThat(edr).isNotNull(); }); @@ -335,8 +342,8 @@ void catalogRequest_whenRequestedCredentialMissing() { } @Override - protected JsonObject createContractPolicy(String bpn) { - return frameworkPolicy(Map.of(CX_POLICY_2025_09_NS + "Membership", "active"), CX_POLICY_2025_09_NS + "access"); + protected JsonObject createContractPolicy() { + return frameworkPolicy("Membership", Operator.EQ, "active", "use", false); } protected abstract RuntimeExtension credentialStoreRuntime(); @@ -347,8 +354,8 @@ private static class ValidContractPolicyProvider implements ArgumentsProvider { @Override public Stream provideArguments(ExtensionContext extensionContext) { return Stream.of( - Arguments.of(frameworkPolicy(Map.of(CX_POLICY_2025_09_NS + "Membership", "active"), CX_POLICY_2025_09_NS + "access"), "MembershipCredential"), - Arguments.of(frameworkPolicy(Map.of(CX_POLICY_2025_09_NS + "FrameworkAgreement", "DataExchangeGovernance:1.0"), "use"), "DataExchangeGovernance use case") + Arguments.of(frameworkPolicy("Membership", Operator.EQ, "active", "use", false), "MembershipCredential"), + Arguments.of(frameworkPolicy("FrameworkAgreement", Operator.EQ, "DataExchangeGovernance:1.0", "use", false), "DataExchangeGovernance use case") ); } } diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java similarity index 82% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java index 0ee6a1e4cf..b618a9574a 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/CredentialSpoofTest.java @@ -37,10 +37,10 @@ import org.eclipse.edc.spi.result.Result; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.tractusx.edc.tests.participant.DataspaceIssuer; -import org.eclipse.tractusx.edc.tests.participant.IatpParticipant; +import org.eclipse.tractusx.edc.tests.participant.DcpParticipant; +import org.eclipse.tractusx.edc.tests.transfer.dcp.harness.StsParticipant; 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.StsParticipant; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -61,8 +61,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.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.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.emptyPolicy; +import static org.eclipse.tractusx.edc.tests.transfer.dcp.runtime.Runtimes.dcpRuntime; +import static org.eclipse.tractusx.edc.tests.transfer.dcp.runtime.Runtimes.stsRuntime; @EndToEndTest public class CredentialSpoofTest { @@ -80,19 +81,19 @@ public class CredentialSpoofTest { @RegisterExtension private static final BdrsServerExtension BDRS_SERVER_EXTENSION = new BdrsServerExtension(DATASPACE_ISSUER_PARTICIPANT.didUrl()); - private static final IatpParticipant CONSUMER = participant(CONSUMER_NAME, CONSUMER_BPN); - private static final IatpParticipant PROVIDER = participant(PROVIDER_NAME, PROVIDER_BPN); - private static final IatpParticipant MALICIOUS_ACTOR = participant("MALICIOUS", "BPNL000MALICIOUS"); + private static final DcpParticipant CONSUMER = participant(CONSUMER_NAME, CONSUMER_BPN); + private static final DcpParticipant PROVIDER = participant(PROVIDER_NAME, PROVIDER_BPN); + private static final DcpParticipant MALICIOUS_ACTOR = participant("MALICIOUS", "BPNL000MALICIOUS"); @RegisterExtension - protected static final RuntimeExtension MALICIOUS_ACTOR_RUNTIME = iatpRuntime(MALICIOUS_ACTOR.getName(), MALICIOUS_ACTOR.getKeyPair(), - () -> MALICIOUS_ACTOR.iatpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); + protected static final RuntimeExtension MALICIOUS_ACTOR_RUNTIME = dcpRuntime(MALICIOUS_ACTOR.getName(), MALICIOUS_ACTOR.getKeyPair(), + () -> MALICIOUS_ACTOR.dcpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); @RegisterExtension - protected static final RuntimeExtension CONSUMER_RUNTIME = iatpRuntime(CONSUMER.getName(), CONSUMER.getKeyPair(), - () -> CONSUMER.iatpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); + protected static final RuntimeExtension CONSUMER_RUNTIME = dcpRuntime(CONSUMER.getName(), CONSUMER.getKeyPair(), + () -> CONSUMER.dcpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); @RegisterExtension - protected static final RuntimeExtension PROVIDER_RUNTIME = iatpRuntime(PROVIDER.getName(), PROVIDER.getKeyPair(), - () -> PROVIDER.iatpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); + protected static final RuntimeExtension PROVIDER_RUNTIME = dcpRuntime(PROVIDER.getName(), PROVIDER.getKeyPair(), + () -> PROVIDER.dcpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); @RegisterExtension protected static final RuntimeExtension STS_RUNTIME = stsRuntime(STS.getName(), STS.getKeyPair(), () -> STS.stsConfig(CONSUMER, PROVIDER, MALICIOUS_ACTOR).merge(BDRS_SERVER_EXTENSION.getConfig())); @@ -103,8 +104,8 @@ public class CredentialSpoofTest { .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) + private static DcpParticipant participant(String name, String bpn) { + return DcpParticipant.Builder.newInstance().name(name).id(bpn) .stsUri(STS.stsUri()) .bpn(bpn) .stsClientId(bpn) @@ -142,13 +143,12 @@ void shouldNotImpersonateConsumer_withWrappedConsumerCredential() { var presentationService = STS_RUNTIME.getService(VerifiablePresentationService.class); - withMock((membershipCredential) -> presentationService.createPresentation(MALICIOUS_ACTOR.getDid(), List.of(membershipCredential.getVerifiableCredential()), null, PROVIDER.getDid())); + withMock((membershipCredential) -> presentationService.createPresentation(MALICIOUS_ACTOR.getParticipantContextId(), List.of(membershipCredential.getVerifiableCredential()), null, PROVIDER.getDid())); PROVIDER.createAsset(assetId, Map.of(), dataAddress); - var policy = createAccessPolicy(CONSUMER.getBpn()); - var accessPolicyId = PROVIDER.createPolicyDefinition(policy); - var contractPolicyId = PROVIDER.createPolicyDefinition(policy); + var accessPolicyId = PROVIDER.createPolicyDefinition(createAccessPolicy(CONSUMER.getBpn())); + var contractPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); PROVIDER.createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); MALICIOUS_ACTOR.getCatalog(PROVIDER) @@ -168,13 +168,12 @@ void shouldNotImpersonateConsumer_withConsumerPresentation() { var presentationService = STS_RUNTIME.getService(VerifiablePresentationService.class); - withMock((membershipCredential) -> presentationService.createPresentation(CONSUMER.getDid(), List.of(membershipCredential.getVerifiableCredential()), null, PROVIDER.getDid())); + withMock((membershipCredential) -> presentationService.createPresentation(CONSUMER.getParticipantContextId(), List.of(membershipCredential.getVerifiableCredential()), null, PROVIDER.getDid())); PROVIDER.createAsset(assetId, Map.of(), dataAddress); - var policy = createAccessPolicy(CONSUMER.getBpn()); - var accessPolicyId = PROVIDER.createPolicyDefinition(policy); - var contractPolicyId = PROVIDER.createPolicyDefinition(policy); + var accessPolicyId = PROVIDER.createPolicyDefinition(createAccessPolicy(CONSUMER.getBpn())); + var contractPolicyId = PROVIDER.createPolicyDefinition(emptyPolicy()); PROVIDER.createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); MALICIOUS_ACTOR.getCatalog(PROVIDER) diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DivConsumerPullTest.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DivConsumerPullTest.java similarity index 82% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DivConsumerPullTest.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DivConsumerPullTest.java index 353060df2b..952c5d8ece 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DivConsumerPullTest.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/DivConsumerPullTest.java @@ -25,26 +25,26 @@ 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.identityhub.spi.participantcontext.model.IdentityHubParticipantContext; 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.participantcontext.spi.store.ParticipantContextStore; import org.eclipse.edc.security.token.jwt.DefaultJwsSignerProvider; 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.DataspaceIssuer; -import org.eclipse.tractusx.edc.tests.participant.IatpParticipant; +import org.eclipse.tractusx.edc.tests.participant.DcpParticipant; import org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase; import org.eclipse.tractusx.edc.tests.runtimes.KeyPool; +import org.eclipse.tractusx.edc.tests.transfer.dcp.dispatchers.DivDispatcher; 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.DivDispatcher; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -68,13 +68,13 @@ import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025_PATH; 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.divRuntime; +import static org.eclipse.tractusx.edc.tests.transfer.dcp.runtime.Runtimes.divRuntime; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @EndToEndTest -public class DivConsumerPullTest extends AbstractIatpConsumerPullTest { +public class DivConsumerPullTest extends AbstractDcpConsumerPullTest { @RegisterExtension private static final DidServerExtension DID_SERVER = new DidServerExtension(); @@ -82,7 +82,7 @@ public class DivConsumerPullTest extends AbstractIatpConsumerPullTest { private static final DataspaceIssuer DATASPACE_ISSUER_PARTICIPANT = new DataspaceIssuer(DID_SERVER.didFor("issuer")); private static final LazySupplier DIV_URI = new LazySupplier<>(() -> URI.create("http://localhost:" + getFreePort())); - private static final IatpParticipant CONSUMER = IatpParticipant.Builder.newInstance() + private static final DcpParticipant CONSUMER = DcpParticipant.Builder.newInstance() .name(CONSUMER_NAME) .id(DID_SERVER.didFor(CONSUMER_NAME)) .stsUri(STS.stsUri()) @@ -90,10 +90,9 @@ public class DivConsumerPullTest extends AbstractIatpConsumerPullTest { .trustedIssuer(DATASPACE_ISSUER_PARTICIPANT.didUrl()) .divUri(DIV_URI) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); - private static final IatpParticipant PROVIDER = IatpParticipant.Builder.newInstance() + private static final DcpParticipant PROVIDER = DcpParticipant.Builder.newInstance() .name(PROVIDER_NAME) .id(DID_SERVER.didFor(PROVIDER_NAME)) .stsUri(STS.stsUri()) @@ -101,8 +100,7 @@ public class DivConsumerPullTest extends AbstractIatpConsumerPullTest { .trustedIssuer(DATASPACE_ISSUER_PARTICIPANT.didUrl()) .divUri(DIV_URI) .bpn(PROVIDER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @RegisterExtension @@ -110,10 +108,10 @@ public class DivConsumerPullTest extends AbstractIatpConsumerPullTest { @RegisterExtension private static final RuntimeExtension CONSUMER_RUNTIME = divRuntime(CONSUMER.getName(), CONSUMER.getKeyPair(), - () -> CONSUMER.iatpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); + () -> CONSUMER.dcpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); @RegisterExtension private static final RuntimeExtension PROVIDER_RUNTIME = divRuntime(PROVIDER.getName(), PROVIDER.getKeyPair(), - () -> PROVIDER.iatpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); + () -> PROVIDER.dcpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); private static final TypeManager MAPPER = new JacksonTypeManager(); private static WireMockServer oauthServer; @@ -135,6 +133,9 @@ static void prepare() { var generatorServices = Map.of( CONSUMER.getDid(), tokenServiceFor(consumerTokenGeneration, CONSUMER, CONSUMER_RUNTIME), PROVIDER.getDid(), tokenServiceFor(providerTokenGeneration, PROVIDER, PROVIDER_RUNTIME)); + var participantContextIds = Map.of( + CONSUMER.getDid(), CONSUMER.getParticipantContextId(), + PROVIDER.getDid(), PROVIDER.getParticipantContextId()); var stsUri = STS.stsUri().get(); @@ -143,7 +144,7 @@ static void prepare() { oauthServer.stubFor(post(urlPathEqualTo(stsUri.getPath() + "/token")).willReturn(aResponse().withStatus(200) .withBody(MAPPER.writeValueAsString(Map.of("access_token", "token"))))); - divServer = new WireMockServer(options().port(DIV_URI.get().getPort()).extensions(new DivDispatcher(generatorServices))); + divServer = new WireMockServer(options().port(DIV_URI.get().getPort()).extensions(new DivDispatcher(generatorServices, participantContextIds))); divServer.start(); divServer.stubFor(post(anyUrl()).willReturn(aResponse().withTransformers("div-dispatcher"))); @@ -156,14 +157,14 @@ static void unwind() { divServer.stop(); } - private static EmbeddedSecureTokenService tokenServiceFor(TokenGenerationService tokenGenerationService, IatpParticipant participant, + private static EmbeddedSecureTokenService tokenServiceFor(TokenGenerationService tokenGenerationService, DcpParticipant participant, RuntimeExtension runtime) { StsAccountService stsAccountService = mock(); when(stsAccountService.queryAccounts(any())).thenAnswer(i -> { var dummyId = UUID.randomUUID().toString(); var account = StsAccount.Builder.newInstance() .id(dummyId) - .participantContextId(participant.getDid()) + .participantContextId(participant.getParticipantContextId()) .clientId(participant.getDid()) .name(participant.getName()) .did(participant.getDid()) @@ -174,16 +175,21 @@ private static EmbeddedSecureTokenService tokenServiceFor(TokenGenerationService }); var participantContextStore = runtime.getService(ParticipantContextStore.class); - participantContextStore.create(ParticipantContext.Builder.newInstance() - .participantContextId(participant.getDid()) + var participantContext = IdentityHubParticipantContext.Builder.newInstance() + .participantContextId(participant.getParticipantContextId()) .did(participant.getDid()) - .apiTokenAlias(participant.getDid()).build()); + .apiTokenAlias(participant.getParticipantContextId()).build(); + var createResult = participantContextStore.create(participantContext); + if (createResult.failed()) { + participantContextStore.update(participantContext) + .orElseThrow(f -> new RuntimeException("Cannot update participant context: " + f.getFailureDetail())); + } var keyPairService = runtime.getService(KeyPairService.class); var keyDescriptor = participant.createKeyDescriptor(); KeyPool.register(participant.getFullKeyId(), participant.getKeyPair()); - keyPairService.addKeyPair(participant.getDid(), keyDescriptor, true) + keyPairService.addKeyPair(participant.getParticipantContextId(), keyDescriptor, true) .orElseThrow(f -> new RuntimeException("Cannot add key pair: " + f.getFailureDetail())); return new EmbeddedSecureTokenService( diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java similarity index 93% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java index 05e60e0368..a3ab55d204 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/IdentityExtractionTest.java @@ -28,7 +28,7 @@ import org.eclipse.edc.protocol.spi.DataspaceProfileContextRegistry; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.iam.ClaimToken; -import org.eclipse.tractusx.edc.tests.participant.IatpParticipant; +import org.eclipse.tractusx.edc.tests.participant.DcpParticipant; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -44,7 +44,7 @@ import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_08; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025; -import static org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.Runtimes.iatpRuntime; +import static org.eclipse.tractusx.edc.tests.transfer.dcp.runtime.Runtimes.dcpRuntime; /** * This test asserts that the ParticipantAgent's identity is determined by the "credentialSubject.holderIdentifier" property. @@ -54,7 +54,7 @@ public class IdentityExtractionTest { private static final LazySupplier STS_URI = new LazySupplier<>(() -> URI.create("http://localhost:" + getFreePort())); - private static final IatpParticipant CONSUMER = IatpParticipant.Builder.newInstance() + private static final DcpParticipant CONSUMER = DcpParticipant.Builder.newInstance() .name(CONSUMER_NAME) .id("did:example:" + CONSUMER_NAME) .stsUri(STS_URI) @@ -64,7 +64,7 @@ public class IdentityExtractionTest { .build(); @RegisterExtension - private static final RuntimeExtension CONSUMER_RUNTIME = iatpRuntime(CONSUMER.getName(), CONSUMER.getKeyPair(), CONSUMER::getConfig); + private static final RuntimeExtension CONSUMER_RUNTIME = dcpRuntime(CONSUMER.getName(), CONSUMER.getKeyPair(), CONSUMER::getConfig); @Test void verifyCorrectParticipantAgentId_forDsp08(DataspaceProfileContextRegistry registry) { diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java similarity index 82% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java index 47a74d4650..4bf90cb8b1 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/StsConsumerPullTest.java @@ -26,7 +26,7 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.tractusx.edc.tests.extension.VaultSeedExtension; import org.eclipse.tractusx.edc.tests.participant.DataspaceIssuer; -import org.eclipse.tractusx.edc.tests.participant.IatpParticipant; +import org.eclipse.tractusx.edc.tests.participant.DcpParticipant; 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; @@ -42,18 +42,18 @@ import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025_PATH; 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.iatpRuntime; -import static org.eclipse.tractusx.edc.tests.transfer.iatp.runtime.Runtimes.stsRuntime; +import static org.eclipse.tractusx.edc.tests.transfer.dcp.runtime.Runtimes.dcpRuntime; +import static org.eclipse.tractusx.edc.tests.transfer.dcp.runtime.Runtimes.stsRuntime; @EndToEndTest -public class StsConsumerPullTest extends AbstractIatpConsumerPullTest { +public class StsConsumerPullTest extends AbstractDcpConsumerPullTest { @RegisterExtension private static final DidServerExtension DID_SERVER = new DidServerExtension(); private static final DataspaceIssuer DATASPACE_ISSUER_PARTICIPANT = new DataspaceIssuer(DID_SERVER.didFor("issuer")); - private static final IatpParticipant CONSUMER = IatpParticipant.Builder.newInstance() + private static final DcpParticipant CONSUMER = DcpParticipant.Builder.newInstance() .name(CONSUMER_NAME) .id(DID_SERVER.didFor(CONSUMER_NAME)) .stsUri(STS.stsUri()) @@ -61,10 +61,9 @@ public class StsConsumerPullTest extends AbstractIatpConsumerPullTest { .credentialServiceUri(STS.credentialServiceUri()) .trustedIssuer(DATASPACE_ISSUER_PARTICIPANT.didUrl()) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); - private static final IatpParticipant PROVIDER = IatpParticipant.Builder.newInstance() + private static final DcpParticipant PROVIDER = DcpParticipant.Builder.newInstance() .name(PROVIDER_NAME) .id(DID_SERVER.didFor(PROVIDER_NAME)) .stsUri(STS.stsUri()) @@ -72,20 +71,19 @@ public class StsConsumerPullTest extends AbstractIatpConsumerPullTest { .credentialServiceUri(STS.credentialServiceUri()) .trustedIssuer(DATASPACE_ISSUER_PARTICIPANT.didUrl()) .bpn(PROVIDER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @RegisterExtension private static final BdrsServerExtension BDRS_SERVER_EXTENSION = new BdrsServerExtension(DATASPACE_ISSUER_PARTICIPANT.didUrl()); @RegisterExtension - private static final RuntimeExtension CONSUMER_RUNTIME = iatpRuntime(CONSUMER.getName(), CONSUMER.getKeyPair(), - () -> CONSUMER.iatpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); + private static final RuntimeExtension CONSUMER_RUNTIME = dcpRuntime(CONSUMER.getName(), CONSUMER.getKeyPair(), + () -> CONSUMER.dcpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())); @RegisterExtension - private static final RuntimeExtension PROVIDER_RUNTIME = iatpRuntime(PROVIDER.getName(), PROVIDER.getKeyPair(), - () -> PROVIDER.iatpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())) + private static final RuntimeExtension PROVIDER_RUNTIME = dcpRuntime(PROVIDER.getName(), PROVIDER.getKeyPair(), + () -> PROVIDER.dcpConfig().merge(BDRS_SERVER_EXTENSION.getConfig())) .registerSystemExtension(ServiceExtension.class, new VaultSeedExtension(Map.of("client_secret_alias", "client_secret"))); @RegisterExtension diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DivDispatcher.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/dispatchers/DivDispatcher.java similarity index 88% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DivDispatcher.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/dispatchers/DivDispatcher.java index e53881e667..a3a346e1aa 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/dispatchers/DivDispatcher.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/dispatchers/DivDispatcher.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.tests.transfer.iatp.dispatchers; +package org.eclipse.tractusx.edc.tests.transfer.dcp.dispatchers; import com.github.tomakehurst.wiremock.extension.ResponseTransformerV2; import com.github.tomakehurst.wiremock.http.HttpHeader; @@ -47,14 +47,16 @@ public class DivDispatcher implements ResponseTransformerV2 { private static final TypeManager MAPPER = new JacksonTypeManager(); private final String path; private final Map secureTokenServices; + private final Map participantContextIds; - public DivDispatcher(Map secureTokenServices) { - this("/", secureTokenServices); + public DivDispatcher(Map secureTokenServices, Map participantContextIds) { + this("/", secureTokenServices, participantContextIds); } - public DivDispatcher(String path, Map secureTokenServices) { + public DivDispatcher(String path, Map secureTokenServices, Map participantContextIds) { this.path = path; this.secureTokenServices = secureTokenServices; + this.participantContextIds = participantContextIds; } @Override @@ -97,7 +99,7 @@ private Response grantAccessHandler(Map params, Response base) { var claims = Map.of(ISSUER, issuer, SUBJECT, issuer, AUDIENCE, audience); var sts = secureTokenServices.get(issuer); - var token = sts.createToken(issuer, claims, scope) + var token = sts.createToken(participantContextId(issuer), claims, scope) .map(TokenRepresentation::getToken) .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); @@ -113,7 +115,7 @@ private Response signTokenHandler(Map params, Response base) { 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) + var token = sts.createToken(participantContextId(issuer), claims, null) .map(TokenRepresentation::getToken) .orElseThrow(f -> new RuntimeException(f.getFailureDetail())); @@ -137,4 +139,8 @@ private Response notFound(Response base) { .body("") .build(); } + + private String participantContextId(String did) { + return participantContextIds.getOrDefault(did, did); + } } diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/StatusList2021.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/harness/StatusList2021.java similarity index 97% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/StatusList2021.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/harness/StatusList2021.java index 42c5342153..39bd998a01 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/StatusList2021.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/harness/StatusList2021.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.tests.transfer.iatp.harness; +package org.eclipse.tractusx.edc.tests.transfer.dcp.harness; import jakarta.json.Json; import jakarta.json.JsonObject; 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/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/harness/StsParticipant.java similarity index 95% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/StsParticipant.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/harness/StsParticipant.java index 539e411c9f..c4029879df 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/harness/StsParticipant.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/harness/StsParticipant.java @@ -17,13 +17,13 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.tests.transfer.iatp.harness; +package org.eclipse.tractusx.edc.tests.transfer.dcp.harness; 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.IatpParticipant; +import org.eclipse.tractusx.edc.tests.participant.DcpParticipant; import org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase; import java.net.URI; @@ -45,7 +45,7 @@ public class StsParticipant extends TractusxParticipantBase { private StsParticipant() { } - public Config stsConfig(IatpParticipant... participants) { + public Config stsConfig(DcpParticipant... participants) { var additionalSettings = Map.of( "web.http.sts.port", String.valueOf(stsUri.get().getPort()), "web.http.sts.path", stsUri.get().getPath(), diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/CredentialWiper.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/runtime/CredentialWiper.java similarity index 96% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/CredentialWiper.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/runtime/CredentialWiper.java index 9f31229ff7..c96007ad1c 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/CredentialWiper.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/runtime/CredentialWiper.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.tests.transfer.iatp.runtime; +package org.eclipse.tractusx.edc.tests.transfer.dcp.runtime; import org.eclipse.edc.identityhub.spi.verifiablecredentials.store.CredentialStore; import org.eclipse.edc.spi.query.QuerySpec; 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/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/runtime/DcpParticipantRuntimeExtension.java similarity index 91% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/IatpParticipantRuntimeExtension.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/runtime/DcpParticipantRuntimeExtension.java index 4bc18735a8..fef4e0c070 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/IatpParticipantRuntimeExtension.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/runtime/DcpParticipantRuntimeExtension.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.eclipse.tractusx.edc.tests.transfer.iatp.runtime; +package org.eclipse.tractusx.edc.tests.transfer.dcp.runtime; import org.eclipse.edc.junit.extensions.EmbeddedRuntime; import org.eclipse.edc.junit.extensions.RuntimePerClassExtension; @@ -35,11 +35,11 @@ import java.security.KeyPair; import java.util.concurrent.atomic.AtomicReference; -public class IatpParticipantRuntimeExtension extends RuntimePerClassExtension implements AfterEachCallback { +public class DcpParticipantRuntimeExtension extends RuntimePerClassExtension implements AfterEachCallback { private final AtomicReference wiper = new AtomicReference<>(); - public IatpParticipantRuntimeExtension(EmbeddedRuntime runtime, KeyPair keyPair) { + public DcpParticipantRuntimeExtension(EmbeddedRuntime runtime, KeyPair keyPair) { super(runtime); registerSystemExtension(ServiceExtension.class, new ServiceExtension() { 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/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/runtime/Runtimes.java similarity index 83% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/Runtimes.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/runtime/Runtimes.java index c19ee622fe..719e302205 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/iatp/runtime/Runtimes.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/dcp/runtime/Runtimes.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.tests.transfer.iatp.runtime; +package org.eclipse.tractusx.edc.tests.transfer.dcp.runtime; import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; import org.eclipse.edc.junit.extensions.EmbeddedRuntime; @@ -36,23 +36,23 @@ public interface Runtimes { static RuntimeExtension divRuntime(String name, KeyPair keyPair, Supplier configurationProvider) { - return genericRuntime(name, ":edc-tests:runtime:iatp:runtime-memory-iatp-div-ih", keyPair, configurationProvider) + return genericRuntime(name, ":edc-tests:runtime:dcp:runtime-memory-dcp-div-ih", keyPair, configurationProvider) .registerSystemExtension(ServiceExtension.class, new VaultSeedExtension(Map.of("client_secret_alias", "client_secret"))); } - static RuntimeExtension iatpRuntime(String name, KeyPair keyPair, Supplier configurationProvider) { - return genericRuntime(name, ":edc-tests:runtime:iatp:runtime-memory-iatp-ih", keyPair, configurationProvider) + static RuntimeExtension dcpRuntime(String name, KeyPair keyPair, Supplier configurationProvider) { + return genericRuntime(name, ":edc-tests:runtime:dcp:runtime-memory-dcp-ih", keyPair, configurationProvider) .registerSystemExtension(ServiceExtension.class, new VaultSeedExtension(Map.of("client_secret_alias", "client_secret"))); } static RuntimeExtension stsRuntime(String name, KeyPair keyPair, Supplier configurationProvider) { - return new RuntimePerClassExtension(new EmbeddedRuntime(name, ":edc-tests:runtime:iatp:runtime-memory-sts").configurationProvider(configurationProvider) + return new RuntimePerClassExtension(new EmbeddedRuntime(name, ":edc-tests:runtime:dcp: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( + return new DcpParticipantRuntimeExtension( new EmbeddedRuntime(name, moduleName).configurationProvider(configurationProvider), keyPair ).registerServiceMock(DidPublicKeyResolver.class, keyId -> Result.success(KeyPool.forId(keyId).getPublic())); diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java similarity index 97% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java index bd82116d9f..94a54e12c5 100644 --- a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java +++ b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/BdrsServerExtension.java @@ -73,7 +73,7 @@ public void afterAll(ExtensionContext context) { public Config getConfig() { return ConfigFactory.fromMap(Map.of( - "tx.edc.iam.iatp.bdrs.server.url", directoryEndpoint.get().toString() + "tx.edc.iam.dcp.bdrs.server.url", directoryEndpoint.get().toString() )); } diff --git a/edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java b/edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java similarity index 100% rename from edc-tests/e2e/iatp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java rename to edc-tests/e2e/dcp-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/extension/DidServerExtension.java diff --git a/edc-tests/e2e/discovery-tests/build.gradle.kts b/edc-tests/e2e/discovery-tests/build.gradle.kts index 5884064895..3ae22cdee8 100644 --- a/edc-tests/e2e/discovery-tests/build.gradle.kts +++ b/edc-tests/e2e/discovery-tests/build.gradle.kts @@ -38,3 +38,10 @@ dependencies { edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} diff --git a/edc-tests/e2e/discovery-tests/src/test/java/org/eclipse/tractusx/edc/discovery/e2e/ConnectorDiscoveryTest.java b/edc-tests/e2e/discovery-tests/src/test/java/org/eclipse/tractusx/edc/discovery/e2e/ConnectorDiscoveryTest.java index ea323c601c..2a06d121e2 100644 --- a/edc-tests/e2e/discovery-tests/src/test/java/org/eclipse/tractusx/edc/discovery/e2e/ConnectorDiscoveryTest.java +++ b/edc-tests/e2e/discovery-tests/src/test/java/org/eclipse/tractusx/edc/discovery/e2e/ConnectorDiscoveryTest.java @@ -69,8 +69,7 @@ public class ConnectorDiscoveryTest { .name(CONSUMER_NAME) .id(CONSUMER_DID) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @@ -89,8 +88,8 @@ public class ConnectorDiscoveryTest { private static final DidDocument DIDDOCUMENT = DidDocument.Builder.newInstance() .id(LOCAL_PROVIDER_DID) .service(List.of( - new Service(LOCAL_PROVIDER_DID + "#connector", "DataService", PROVIDER_FULL_DSP.getProtocolUrl()), - new Service(LOCAL_PROVIDER_DID + "#connector2", "DataService", PROVIDER_DSP_V08.getProtocolUrl()), + new Service(LOCAL_PROVIDER_DID + "#connector", "DataService", PROVIDER_FULL_DSP.getBaseUrl()), + new Service(LOCAL_PROVIDER_DID + "#connector2", "DataService", PROVIDER_DSP_V08.getBaseUrl()), new Service(LOCAL_PROVIDER_DID + "#cs", "CredentialService", "http://dontcare"))) .build(); @@ -138,10 +137,10 @@ public void setup() throws Exception { @Test void discoveryShouldReturnDspParams_DidAsIdentifier() { var expectedProtocolString = "\"protocol\":\"dataspace-protocol-http:2025-1\""; - var expectedAddressString = "\"counterPartyAddress\":\"%s/2025-1\"".formatted(PROVIDER_FULL_DSP.getProtocolUrl()); + var expectedAddressString = "\"counterPartyAddress\":\"%s/2025-1\"".formatted(PROVIDER_FULL_DSP.getBaseUrl()); var expectedIdString = "\"counterPartyId\":\"%s\"".formatted(LOCAL_PROVIDER_DID); var expectedProtocolString2 = "\"protocol\":\"dataspace-protocol-http\""; - var expectedAddressString2 = "\"counterPartyAddress\":\"%s\"".formatted(PROVIDER_DSP_V08.getProtocolUrl()); + var expectedAddressString2 = "\"counterPartyAddress\":\"%s\"".formatted(PROVIDER_DSP_V08.getBaseUrl()); var expectedIdString2 = "\"counterPartyId\":\"%s\"".formatted(PROVIDER_DSP_V08.getBpn()); @@ -165,10 +164,10 @@ void discoveryShouldReturnDspParams_DidAsIdentifier() { @Test void discoveryShouldReturnDspParams_BpnAsIdentifier() { var expectedProtocolString = "\"protocol\":\"dataspace-protocol-http:2025-1\""; - var expectedAddressString = "\"counterPartyAddress\":\"%s/2025-1\"".formatted(PROVIDER_FULL_DSP.getProtocolUrl()); + var expectedAddressString = "\"counterPartyAddress\":\"%s/2025-1\"".formatted(PROVIDER_FULL_DSP.getBaseUrl()); var expectedIdString = "\"counterPartyId\":\"%s\"".formatted(LOCAL_PROVIDER_DID); var expectedProtocolString2 = "\"protocol\":\"dataspace-protocol-http\""; - var expectedAddressString2 = "\"counterPartyAddress\":\"%s\"".formatted(PROVIDER_DSP_V08.getProtocolUrl()); + var expectedAddressString2 = "\"counterPartyAddress\":\"%s\"".formatted(PROVIDER_DSP_V08.getBaseUrl()); var expectedIdString2 = "\"counterPartyId\":\"%s\"".formatted(PROVIDER_DSP_V08.getBpn()); var requestBody = createRequestBody(PROVIDER_FULL_DSP.getBpn(), emptyList()); diff --git a/edc-tests/e2e/discovery-tests/src/test/java/org/eclipse/tractusx/edc/discovery/e2e/ConnectorParameterDiscoveryTest.java b/edc-tests/e2e/discovery-tests/src/test/java/org/eclipse/tractusx/edc/discovery/e2e/ConnectorParameterDiscoveryTest.java index fedd3931bb..fe07cb0685 100644 --- a/edc-tests/e2e/discovery-tests/src/test/java/org/eclipse/tractusx/edc/discovery/e2e/ConnectorParameterDiscoveryTest.java +++ b/edc-tests/e2e/discovery-tests/src/test/java/org/eclipse/tractusx/edc/discovery/e2e/ConnectorParameterDiscoveryTest.java @@ -54,8 +54,7 @@ public class ConnectorParameterDiscoveryTest { .name(CONSUMER_NAME) .id(CONSUMER_DID) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @@ -107,7 +106,7 @@ private static String resolveProviderBpn(String did) { @Test void discoveryShouldReturn2025DspParams_BpnAsIdentifier() { - var requestBody = createRequestBody(PROVIDER_FULL_DSP.getBpn(), PROVIDER_FULL_DSP.getProtocolUrl()); + var requestBody = createRequestBody(PROVIDER_FULL_DSP.getBpn(), PROVIDER_FULL_DSP.getBaseUrl()); var response = CONSUMER.discoverDspParameters(requestBody); @@ -116,7 +115,7 @@ void discoveryShouldReturn2025DspParams_BpnAsIdentifier() { assertThat(body) .isNotNull() - .contains("\"counterPartyAddress\":\"" + PROVIDER_FULL_DSP.getProtocolUrl() + "/2025-1\"") + .contains("\"counterPartyAddress\":\"" + PROVIDER_FULL_DSP.getBaseUrl() + "/2025-1\"") .contains("\"counterPartyId\":\"" + PROVIDER_FULL_DSP.getDid() + "\"") .contains("\"protocol\":\"" + "dataspace-protocol-http:2025-1" + "\""); } @@ -124,7 +123,7 @@ void discoveryShouldReturn2025DspParams_BpnAsIdentifier() { @Test void discoveryShouldReturn2025DspParams_DidAsIdentifier() { - var requestBody = createRequestBody(PROVIDER_FULL_DSP.getDid(), PROVIDER_FULL_DSP.getProtocolUrl()); + var requestBody = createRequestBody(PROVIDER_FULL_DSP.getDid(), PROVIDER_FULL_DSP.getBaseUrl()); var response = CONSUMER.discoverDspParameters(requestBody); @@ -133,7 +132,7 @@ void discoveryShouldReturn2025DspParams_DidAsIdentifier() { assertThat(body) .isNotNull() - .contains("\"counterPartyAddress\":\"" + PROVIDER_FULL_DSP.getProtocolUrl() + "/2025-1\"") + .contains("\"counterPartyAddress\":\"" + PROVIDER_FULL_DSP.getBaseUrl() + "/2025-1\"") .contains("\"counterPartyId\":\"" + PROVIDER_FULL_DSP.getDid() + "\"") .contains("\"protocol\":\"" + "dataspace-protocol-http:2025-1" + "\""); } @@ -141,7 +140,7 @@ void discoveryShouldReturn2025DspParams_DidAsIdentifier() { @Test void discoveryShouldReturn08DspParams_whenDsp2025NotAvailable_BpnAsIdentifier() { - var requestBody = createRequestBody(PROVIDER_DSP_V08.getBpn(), PROVIDER_DSP_V08.getProtocolUrl()); + var requestBody = createRequestBody(PROVIDER_DSP_V08.getBpn(), PROVIDER_DSP_V08.getBaseUrl()); var response = CONSUMER.discoverDspParameters(requestBody); @@ -150,7 +149,7 @@ void discoveryShouldReturn08DspParams_whenDsp2025NotAvailable_BpnAsIdentifier() assertThat(body) .isNotNull() - .contains("\"counterPartyAddress\":\"" + PROVIDER_DSP_V08.getProtocolUrl()) + .contains("\"counterPartyAddress\":\"" + PROVIDER_DSP_V08.getBaseUrl()) .contains("\"counterPartyId\":\"" + PROVIDER_DSP_V08.getBpn() + "\"") .contains("\"protocol\":\"" + "dataspace-protocol-http" + "\""); } @@ -158,7 +157,7 @@ void discoveryShouldReturn08DspParams_whenDsp2025NotAvailable_BpnAsIdentifier() @Test void discoveryShouldReturn08DspParams_whenDsp2025NotAvailable_DidAsIdentifier() { - var requestBody = createRequestBody(PROVIDER_DSP_V08.getDid(), PROVIDER_DSP_V08.getProtocolUrl()); + var requestBody = createRequestBody(PROVIDER_DSP_V08.getDid(), PROVIDER_DSP_V08.getBaseUrl()); var response = CONSUMER.discoverDspParameters(requestBody); @@ -167,7 +166,7 @@ void discoveryShouldReturn08DspParams_whenDsp2025NotAvailable_DidAsIdentifier() assertThat(body) .isNotNull() - .contains("\"counterPartyAddress\":\"" + PROVIDER_DSP_V08.getProtocolUrl()) + .contains("\"counterPartyAddress\":\"" + PROVIDER_DSP_V08.getBaseUrl()) .contains("\"counterPartyId\":\"" + PROVIDER_DSP_V08.getBpn() + "\"") .contains("\"protocol\":\"" + "dataspace-protocol-http" + "\""); } @@ -175,7 +174,7 @@ void discoveryShouldReturn08DspParams_whenDsp2025NotAvailable_DidAsIdentifier() @Test void discoveryShouldReturn400_whenDidNotResolvable() { - var requestBody = createRequestBody(UNKNOWN_BPNL, PROVIDER_FULL_DSP.getProtocolUrl()); + var requestBody = createRequestBody(UNKNOWN_BPNL, PROVIDER_FULL_DSP.getBaseUrl()); var response = CONSUMER.discoverDspParameters(requestBody); @@ -193,7 +192,7 @@ void discoveryShouldReturn400_ifRequestHasMissingProps() { var requestBody = createObjectBuilder() .add(CONTEXT, createObjectBuilder().add("edc", EDC_NAMESPACE).add("tx", TX_NAMESPACE)) .add(TYPE, "tx:ConnectorDiscoveryRequest") - .add("edc:counterPartyAddress", PROVIDER_FULL_DSP.getProtocolUrl()) + .add("edc:counterPartyAddress", PROVIDER_FULL_DSP.getBaseUrl()) .build(); var response = CONSUMER.discoverDspParameters(requestBody); @@ -209,7 +208,7 @@ void discoveryShouldReturn400_ifRequestHasMissingProps() { @Test void discoveryShouldReturn502_ifMetadaEndpointNotReachable() { - var requestBody = createRequestBody(PROVIDER_FULL_DSP.getBpn(), PROVIDER_FULL_DSP.getProtocolUrl() + "/not-existing"); + var requestBody = createRequestBody(PROVIDER_FULL_DSP.getBpn(), PROVIDER_FULL_DSP.getBaseUrl() + "/not-existing"); var response = CONSUMER.discoverDspParameters(requestBody); @@ -222,20 +221,20 @@ void discoveryShouldReturn502_ifMetadaEndpointNotReachable() { } @Test - void discoveryShouldReturn500_whenProviderEndpointNotReachable() { + void discoveryShouldReturn502_whenProviderEndpointNotReachable() { var requestBody = createRequestBody(PROVIDER_FULL_DSP.getBpn(), "http://non-existing-provider.com"); var response = CONSUMER.discoverDspParameters(requestBody); - response.statusCode(500) + response.statusCode(502) .extract().body().asString(); } @Test void discoveryShouldReturn400_whenNoProtocolsAvailable() { - var requestBody = createRequestBody(PROVIDER_NO_PROTOCOLS.getBpn(), PROVIDER_NO_PROTOCOLS.getProtocolUrl()); + var requestBody = createRequestBody(PROVIDER_NO_PROTOCOLS.getBpn(), PROVIDER_NO_PROTOCOLS.getBaseUrl()); var response = CONSUMER.discoverDspParameters(requestBody); diff --git a/edc-tests/e2e/dsp-compatibility-tests/build.gradle.kts b/edc-tests/e2e/dsp-tck-tests/build.gradle.kts similarity index 94% rename from edc-tests/e2e/dsp-compatibility-tests/build.gradle.kts rename to edc-tests/e2e/dsp-tck-tests/build.gradle.kts index 1a75639378..d81f490f4b 100644 --- a/edc-tests/e2e/dsp-compatibility-tests/build.gradle.kts +++ b/edc-tests/e2e/dsp-tck-tests/build.gradle.kts @@ -27,8 +27,8 @@ dependencies { testImplementation(libs.edc.core.controlplane) testImplementation(libs.awaitility) testImplementation(libs.testcontainers.junit) - testImplementation(libs.dsp.tck.core) - testImplementation(libs.dsp.tck.runtime) + testImplementation(libs.tck.core) + testImplementation(libs.tck.runtime) testImplementation(libs.dsp.tck.api) testImplementation(libs.dsp.tck.system) testRuntimeOnly(libs.dsp.tck.metadata) 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-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java similarity index 79% rename from edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java rename to edc-tests/e2e/dsp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java index 58eb8db163..9b66ddb281 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-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/EdcCompatibilityPostgresTest.java @@ -19,17 +19,22 @@ package org.eclipse.tractusx.edc.tests.tck.dsp; +import org.eclipse.edc.connector.controlplane.policy.spi.store.PolicyArchive; import org.eclipse.edc.connector.controlplane.profile.DataspaceProfileContextRegistryImpl; 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.policy.model.Policy; import org.eclipse.edc.protocol.spi.DataspaceProfileContextRegistry; import org.eclipse.edc.protocol.spi.ParticipantIdExtractionFunction; import org.eclipse.edc.spi.monitor.ConsoleMonitor; +import org.eclipse.edc.spi.result.StoreResult; import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.eclipse.tractusx.edc.agreements.bpns.spi.store.AgreementsBpnsStore; +import org.eclipse.tractusx.edc.agreements.bpns.spi.types.AgreementsBpnsEntry; import org.eclipse.tractusx.edc.spi.identity.mapper.BdrsClient; import org.eclipse.tractusx.edc.tests.MockBdrsClient; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; @@ -51,6 +56,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.util.io.Ports.getFreePort; +import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.BPN_PREFIX; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @@ -66,22 +72,58 @@ public class EdcCompatibilityPostgresTest { 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 String CONNECTOR_UNDER_TEST = "participantContextId"; - + private static final String BPN = BPN_PREFIX + CONNECTOR_UNDER_TEST; + private static final String TCK_PARTICIPANT = "TCK_PARTICIPANT"; + private static final DataspaceProfileContextRegistry DATASPACE_PROFILE_CONTEXT_REGISTRY_SPY = spy(DataspaceProfileContextRegistryImpl.class); + private static final AgreementsBpnsStore AGREEMENTS_BPNS_STORE = new AgreementsBpnsStore() { + @Override + public StoreResult save(AgreementsBpnsEntry entry) { + return StoreResult.success(); + } + + @Override + public AgreementsBpnsEntry findByAgreementId(String agreementId) { + return AgreementsBpnsEntry.Builder.newInstance() + .withAgreementId(agreementId) + .withProviderBpn(BPN) + .withConsumerBpn(BPN) + .build(); + } + }; + + private static final PolicyArchive POLICY_ARCHIVE = new PolicyArchive() { + @Override + public Policy findPolicyForContract(String contractId) { + return Policy.Builder.newInstance() + .assigner(TCK_PARTICIPANT) + .assignee(TCK_PARTICIPANT) + .build(); + } + + @Override + public String getAgreementIdForContract(String contractId) { + return contractId; + } + }; + @RegisterExtension @Order(0) private static final PostgresExtension POSTGRES = new PostgresExtension(CONNECTOR_UNDER_TEST); @RegisterExtension private static final RuntimeExtension RUNTIME = new RuntimePerClassExtension(new EmbeddedRuntime(CONNECTOR_UNDER_TEST, - ":edc-tests:runtime:runtime-dsp", ":edc-extensions:single-participant-vault") + ":edc-tests:runtime:runtime-dsp", + ":edc-extensions:single-participant-vault") .registerServiceMock(BdrsClient.class, new MockBdrsClient(s -> s, s -> s)) + .registerServiceMock(AgreementsBpnsStore.class, AGREEMENTS_BPNS_STORE) + .registerServiceMock(PolicyArchive.class, POLICY_ARCHIVE) .registerServiceMock(DataspaceProfileContextRegistry.class, DATASPACE_PROFILE_CONTEXT_REGISTRY_SPY) .configurationProvider(() -> EdcCompatibilityPostgresTest.runtimeConfiguration().merge(POSTGRES.getConfig(CONNECTOR_UNDER_TEST)))); - private static final GenericContainer TCK_CONTAINER = new TckContainer<>("eclipsedataspacetck/dsp-tck-runtime:1.0.0-RC4"); - + private static final GenericContainer TCK_CONTAINER = new TckContainer<>("eclipsedataspacetck/dsp-tck-runtime:1.0.0"); + @BeforeEach void setUp() { ParticipantIdExtractionFunction function = ct -> ct.getStringClaim("client_id"); @@ -91,8 +133,8 @@ void setUp() { 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("edc.participant.id", BPN); + put("edc.participant.context.id", CONNECTOR_UNDER_TEST); put("web.http.port", "8080"); put("web.http.path", "/api"); put("web.http.control.port", String.valueOf(CONTROL_URL.getPort())); @@ -107,15 +149,15 @@ private static Config runtimeConfiguration() { put("edc.dsp.callback.address", PROTOCOL_URL.toString()); // host.docker.internal is required by the container to communicate with the host put("edc.management.context.enabled", "true"); put("edc.hostname", "host.docker.internal"); - put("edc.component.id", "DSP-compatibility-test"); + put("edc.component.id", "DSP-tck-test"); put("edc.transfer.proxy.token.signer.privatekey.alias", "private-key"); put("edc.transfer.proxy.token.verifier.publickey.alias", "public-key"); put("edc.policy.validation.enabled", "true"); put("edc.iam.issuer.id", "did:web:" + CONNECTOR_UNDER_TEST); put("edc.iam.sts.oauth.token.url", "http://sts.example.com/token"); - put("edc.iam.sts.oauth.client.id", "test-client-id"); + put("edc.iam.sts.oauth.client.id", BPN); put("edc.iam.sts.oauth.client.secret.alias", "test-clientid-alias"); - put("tx.edc.iam.iatp.bdrs.server.url", "http://sts.example.com"); + put("tx.edc.iam.dcp.bdrs.server.url", "http://sts.example.com"); put("web.http.management.auth.key", API_KEY); put("tx.edc.dpf.consumer.proxy.port", String.valueOf(DATA_PLANE_PROXY.getPort())); put("tx.edc.dpf.consumer.proxy.auth.apikey", API_KEY); @@ -126,7 +168,7 @@ private static Config runtimeConfiguration() { put("web.http.public.path", DATA_PLANE_PUBLIC.getPath()); put("web.http.public.port", String.valueOf(DATA_PLANE_PUBLIC.getPort())); put("edc.dataplane.api.public.baseurl", "%s/v2/data".formatted(DATA_PLANE_PUBLIC)); - put("tractusx.edc.participant.bpn", CONNECTOR_UNDER_TEST); + put("tractusx.edc.participant.bpn", BPN); } }); } diff --git a/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/TckContainer.java b/edc-tests/e2e/dsp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/TckContainer.java similarity index 100% rename from edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/TckContainer.java rename to edc-tests/e2e/dsp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/TckContainer.java diff --git a/edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/TckTestReporter.java b/edc-tests/e2e/dsp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/TckTestReporter.java similarity index 100% rename from edc-tests/e2e/dsp-compatibility-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/TckTestReporter.java rename to edc-tests/e2e/dsp-tck-tests/src/test/java/org/eclipse/tractusx/edc/tests/tck/dsp/TckTestReporter.java diff --git a/edc-tests/e2e/dsp-compatibility-tests/src/test/resources/docker.tck.properties b/edc-tests/e2e/dsp-tck-tests/src/test/resources/docker.tck.properties similarity index 100% rename from edc-tests/e2e/dsp-compatibility-tests/src/test/resources/docker.tck.properties rename to edc-tests/e2e/dsp-tck-tests/src/test/resources/docker.tck.properties 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 556cda0463..483614ea50 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 @@ -29,6 +29,7 @@ import com.nimbusds.jose.jwk.gen.ECKeyGenerator; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import io.restassured.http.ContentType; import org.eclipse.edc.connector.dataplane.spi.DataFlow; import org.eclipse.edc.connector.dataplane.spi.edr.EndpointDataReferenceServiceRegistry; import org.eclipse.edc.iam.did.spi.resolution.DidPublicKeyResolver; @@ -49,14 +50,14 @@ import org.junit.jupiter.api.DisplayName; 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.EmptySource; -import org.junit.jupiter.params.provider.NullSource; import java.net.URI; import java.text.ParseException; +import java.time.Instant; +import java.util.Date; import java.util.Map; +import static java.lang.String.format; import static org.apache.http.HttpHeaders.AUTHORIZATION; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; @@ -64,7 +65,6 @@ import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.TX_AUTH_NS; import static org.hamcrest.Matchers.containsString; - @EndToEndTest public class DataPlaneTokenRefreshEndToEndTest { @@ -125,9 +125,9 @@ void refresh_success() { var authToken = createAuthToken(accessToken, consumerKey); var tokenResponse = RUNTIME_CONFIG.basePublicApiRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) .header(AUTHORIZATION, "Bearer " + authToken) + .contentType(ContentType.URLENC) + .body(format("grant_type=refresh_token&refresh_token=%s", refreshToken)) .post("/token") .then() .log().ifError() @@ -137,11 +137,9 @@ void refresh_success() { assertThat(tokenResponse).isNotNull(); } - @DisplayName("Refresh token is null or empty (missing)") - @ParameterizedTest - @NullSource - @EmptySource - void refresh_invalidRefreshToken(String invalidRefreshToken) { + @DisplayName("Refresh token is empty (missing)") + @Test + void refresh_invalidRefreshToken() { // register generator and secrets prepareDataplaneRuntime(); @@ -155,9 +153,9 @@ void refresh_invalidRefreshToken(String invalidRefreshToken) { var authToken = createAuthToken(accessToken, consumerKey); RUNTIME_CONFIG.basePublicApiRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", invalidRefreshToken) .header(AUTHORIZATION, "Bearer " + authToken) + .contentType(ContentType.URLENC) + .body("grant_type=refresh_token&refresh_token=") .post("/token") .then() .log().ifError() @@ -180,9 +178,9 @@ void refresh_emptyAuthHeader() { // auth header is empty RUNTIME_CONFIG.basePublicApiRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) .header(AUTHORIZATION, "") + .contentType(ContentType.URLENC) + .body(format("grant_type=refresh_token&refresh_token=%s", refreshToken)) .post("/token") .then() .log().ifError() @@ -205,8 +203,8 @@ void refresh_missingAuthHeader() { // auth header is empty RUNTIME_CONFIG.basePublicApiRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) + .contentType(ContentType.URLENC) + .body(format("grant_type=refresh_token&refresh_token=%s", refreshToken)) .post("/token") .then() .log().ifError() @@ -229,14 +227,14 @@ void refresh_spoofedAuthToken() throws JOSEException { var authTokenWithSpoofedKey = createAuthToken(accessToken, spoofedKey); RUNTIME_CONFIG.basePublicApiRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) .header(AUTHORIZATION, "Bearer " + authTokenWithSpoofedKey) + .contentType(ContentType.URLENC) + .body(format("grant_type=refresh_token&refresh_token=%s", refreshToken)) .post("/token") .then() .log().ifValidationFails() .statusCode(401) - .body(containsString("Token verification failed")); + .body(containsString("JWT signature not valid")); } @DisplayName("The refresh token does not match the stored one") @@ -253,9 +251,9 @@ void refresh_withWrongRefreshToken() { var accessToken = edr.getStringProperty(EDC_NAMESPACE + "authorization"); RUNTIME_CONFIG.basePublicApiRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) .header(AUTHORIZATION, "Bearer " + createAuthToken(accessToken, consumerKey)) + .contentType(ContentType.URLENC) + .body(format("grant_type=refresh_token&refresh_token=%s", refreshToken)) .post("/token") .then() .log().ifValidationFails() @@ -282,13 +280,15 @@ void refresh_invalidAuthenticationToken_missingAccessToken() { .subject(CONSUMER_DID) .audience("did:web:bob") .jwtID(getJwtId(accessToken)) + .issueTime(Date.from(Instant.now())) + .expirationTime(Date.from(Instant.now().plusSeconds(60))) .build(); var authToken = createJwt(consumerKey, claims); RUNTIME_CONFIG.basePublicApiRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) .header(AUTHORIZATION, "Bearer " + authToken) + .contentType(ContentType.URLENC) + .body(format("grant_type=refresh_token&refresh_token=%s", refreshToken)) .post("/token") .then() .log().ifValidationFails() @@ -315,13 +315,15 @@ void refresh_invalidAuthenticationToken_missingAudience() { .subject(CONSUMER_DID) /* missing: .audience("did:web:bob")*/ .jwtID(getJwtId(accessToken)) + .issueTime(Date.from(Instant.now())) + .expirationTime(Date.from(Instant.now().plusSeconds(60))) .build(); var authToken = createJwt(consumerKey, claims); RUNTIME_CONFIG.basePublicApiRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) .header(AUTHORIZATION, "Bearer " + authToken) + .contentType(ContentType.URLENC) + .body(format("grant_type=refresh_token&refresh_token=%s", refreshToken)) .post("/token") .then() .log().ifValidationFails() @@ -351,14 +353,16 @@ void refresh_invalidTokenId() { .subject(CONSUMER_DID) .audience("did:web:bob") .jwtID(tokenId) + .issueTime(Date.from(Instant.now())) + .expirationTime(Date.from(Instant.now().plusSeconds(60))) .build(); var authToken = createJwt(consumerKey, claims); RUNTIME_CONFIG.basePublicApiRequest() - .queryParam("grant_type", "refresh_token") - .queryParam("refresh_token", refreshToken) .header(AUTHORIZATION, "Bearer " + authToken) + .contentType(ContentType.URLENC) + .body(format("grant_type=refresh_token&refresh_token=%s", refreshToken)) .post("/token") .then() .log().ifValidationFails() @@ -381,6 +385,8 @@ private String createAuthToken(String accessToken, ECKey signerKey) { .subject(CONSUMER_DID) .audience("did:web:bob") .jwtID(getJwtId(accessToken)) + .issueTime(Date.from(Instant.now())) + .expirationTime(Date.from(Instant.now().plusSeconds(60))) .build(); return createJwt(signerKey, claims); } 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 479ba2bd46..4829963262 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 @@ -53,7 +53,7 @@ public Config getConfig() { put("edc.iam.sts.oauth.client.id", "test-clientid"); put("edc.iam.sts.oauth.client.secret.alias", "test-clientid-alias"); put("tx.edc.iam.sts.div.url", "http://sts.example.com"); - put("tx.edc.iam.iatp.bdrs.server.url", "http://sts.example.com"); + put("tx.edc.iam.dcp.bdrs.server.url", "http://sts.example.com"); } }; diff --git a/edc-tests/e2e/edr-api-tests/build.gradle.kts b/edc-tests/e2e/edr-api-tests/build.gradle.kts index 9adf646bd5..6f1f7f182a 100644 --- a/edc-tests/e2e/edr-api-tests/build.gradle.kts +++ b/edc-tests/e2e/edr-api-tests/build.gradle.kts @@ -41,3 +41,10 @@ dependencies { edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} 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 057d296810..c068000a5f 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 @@ -56,7 +56,7 @@ 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.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.postRequestedFor; @@ -117,7 +117,9 @@ void setup() throws JOSEException { @DisplayName("Verify HTTP 200 response and body when refreshing succeeds") @Test void getEdrWithRefresh_success() { - mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")).withRequestBody(absent()) + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")) + .withFormParam("grant_type", WireMock.equalTo("refresh_token")) + .withFormParam("refresh_token", matching(".+")) .willReturn(ok(tokenResponseBody()))); storeEdr("test-id", true); @@ -126,8 +128,7 @@ void getEdrWithRefresh_success() { .extract().body().as(JsonObject.class); assertThat(edr).isNotNull(); - mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token")) - .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); + mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token"))); } @DisplayName("When multiple requests to refresh, to different edrs, verify all return non expired token") @@ -137,7 +138,9 @@ void getEdrWithRefresh_subsequentRequestReturn() throws InterruptedException { 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()) + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")) + .withFormParam("grant_type", WireMock.equalTo("refresh_token")) + .withFormParam("refresh_token", matching(".+")) .willReturn(WireMock.aResponse() .withStatus(200) .withFixedDelay(5000) @@ -184,8 +187,7 @@ void getEdrWithRefresh_subsequentRequestReturn() throws InterruptedException { latch.await(); assertThat(failed.get()).isFalse(); - mockedRefreshApi.verify(2, postRequestedFor(urlPathEqualTo("/refresh/token")) - .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); + mockedRefreshApi.verify(2, postRequestedFor(urlPathEqualTo("/refresh/token"))); } @DisplayName("Verify the refresh endpoint is not called when token not yet expired") @@ -197,8 +199,7 @@ void getEdrWithRefresh_notExpired_shouldNotCallEndpoint() { .extract().body().as(JsonObject.class); assertThat(edr).isNotNull(); - mockedRefreshApi.verify(0, postRequestedFor(urlPathEqualTo("/refresh/token")) - .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); + mockedRefreshApi.verify(0, postRequestedFor(urlPathEqualTo("/refresh/token"))); } @DisplayName("Verify the refresh endpoint is not called when auto_refresh=false") @@ -211,14 +212,15 @@ void getEdrWithRefresh_whenNotAutorefresh_shouldNotCallEndpoint() { .extract().body().as(JsonObject.class); assertThat(edr).isNotNull(); - mockedRefreshApi.verify(0, postRequestedFor(urlPathEqualTo("/refresh/token")) - .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); + mockedRefreshApi.verify(0, postRequestedFor(urlPathEqualTo("/refresh/token"))); } @DisplayName("Verify HTTP 403 response when refreshing the token is not allowed") @Test void getEdrWithRefresh_unauthorized() { - mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")).withRequestBody(absent()) + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")) + .withFormParam("grant_type", WireMock.equalTo("refresh_token")) + .withFormParam("refresh_token", matching(".+")) .willReturn(WireMock.aResponse() .withStatus(401) .withBody("unauthorized") @@ -228,13 +230,14 @@ void getEdrWithRefresh_unauthorized() { CONSUMER.edrs().getEdrWithRefresh("test-id", true) .statusCode(403); - mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token")) - .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); + mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token"))); } @Test void refreshEdr() { - mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")).withRequestBody(absent()) + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")) + .withFormParam("grant_type", WireMock.equalTo("refresh_token")) + .withFormParam("refresh_token", matching(".+")) .willReturn(ok(tokenResponseBody()))); storeEdr("test-id", true); @@ -243,8 +246,7 @@ void refreshEdr() { .extract().body().as(JsonObject.class); assertThat(edr).isNotNull(); - mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token")) - .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); + mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token"))); } @Test @@ -255,7 +257,9 @@ void refreshEdr_whenNotFound() { @Test void refreshEdr_whenNotAuthorized() { - mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")).withRequestBody(absent()) + mockedRefreshApi.stubFor(post(urlPathEqualTo("/refresh/token")) + .withFormParam("grant_type", WireMock.equalTo("refresh_token")) + .withFormParam("refresh_token", matching(".+")) .willReturn(WireMock.aResponse() .withStatus(401) .withBody("unauthorized") @@ -265,8 +269,7 @@ void refreshEdr_whenNotAuthorized() { CONSUMER.edrs().refreshEdr("test-id") .statusCode(403); - mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token")) - .withQueryParam("grant_type", WireMock.equalTo("refresh_token"))); + mockedRefreshApi.verify(1, postRequestedFor(urlPathEqualTo("/refresh/token"))); } private String tokenResponseBody() { @@ -277,7 +280,7 @@ private String tokenResponseBody() { } private String tokenResponseBody(String accessToken, String refreshToken) { - var response = new TokenResponse(accessToken, refreshToken, 300L, "bearer"); + var response = new TokenResponse(accessToken, refreshToken, null, 300L, "bearer"); try { return mapper.writeValueAsString(response); } catch (JsonProcessingException e) { 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 effefbb47c..38986cc475 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 @@ -35,7 +35,6 @@ 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.policy.model.Operator; import org.eclipse.tractusx.edc.tests.helpers.EdrNegotiationHelperFunctions; import org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions; import org.eclipse.tractusx.edc.tests.helpers.ReceivedEvent; @@ -69,6 +68,7 @@ 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.PolicyHelperFunctions.bpnGroupPolicy; 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; @@ -80,16 +80,14 @@ public class NegotiateEdrTest { .name(CONSUMER_NAME) .id(CONSUMER_DID) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); private static final TransferParticipant PROVIDER = TransferParticipant.Builder.newInstance() .name(PROVIDER_NAME) .id(PROVIDER_DID) .bpn(PROVIDER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @RegisterExtension @@ -144,8 +142,8 @@ void negotiateEdr_shouldInvokeCallbacks() { PROVIDER.createAsset(assetId, Map.of(), dataAddress); PROVIDER.storeBusinessPartner(CONSUMER.getBpn(), "test-group1", "test-group2"); - var accessPolicy = PROVIDER.createPolicyDefinition(PolicyHelperFunctions.bpnGroupPolicy(Operator.IS_NONE_OF, "forbidden-policy")); - var contractPolicy = PROVIDER.createPolicyDefinition(PolicyHelperFunctions.bpnGroupPolicy(Operator.IS_ANY_OF, "test-group1", "test-group2")); + var accessPolicy = PROVIDER.createPolicyDefinition(bpnGroupPolicy("isNoneOf", true, "forbidden-policy")); + var contractPolicy = PROVIDER.createPolicyDefinition(PolicyHelperFunctions.frameworkPolicy(Map.of(), "use")); PROVIDER.createContractDefinition(assetId, "def-1", accessPolicy, contractPolicy); diff --git a/edc-tests/e2e/end2end-transfer-cloud/build.gradle.kts b/edc-tests/e2e/end2end-transfer-cloud/build.gradle.kts index c109b4572f..a0597b1c22 100644 --- a/edc-tests/e2e/end2end-transfer-cloud/build.gradle.kts +++ b/edc-tests/e2e/end2end-transfer-cloud/build.gradle.kts @@ -41,3 +41,10 @@ dependencies { edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} 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 1a04021698..bdbdcd3f74 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 @@ -57,6 +57,7 @@ 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.bpnPolicy; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkPolicy; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; @@ -72,15 +73,13 @@ public class AzureToAzureEndToEndTest { .name(CONSUMER_NAME) .id(CONSUMER_DID) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); private static final TransferParticipant PROVIDER = TransferParticipant.Builder.newInstance() .name(PROVIDER_NAME) .id(PROVIDER_DID) .bpn(PROVIDER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); private static final int AZURITE_HOST_PORT = getFreePort(); @@ -157,8 +156,9 @@ void azureBlobPush_withDestFolder() { // create objects in EDC provider().createAsset(assetId, Map.of(), dataAddress); - var policyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); - provider().createContractDefinition(assetId, "def-1", policyId, policyId); + var accessPolicyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); + var contractPolicyId = provider().createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + provider().createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var destfolder = "destfolder"; var destination = createObjectBuilder() @@ -211,8 +211,9 @@ void azureBlobPush_withoutDestFolder() { // create objects in EDC provider().createAsset(assetId, Map.of(), dataAddress); - var policyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); - provider().createContractDefinition(assetId, "def-1", policyId, policyId); + var accessPolicyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); + var contractPolicyId = provider().createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + provider().createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var destination = createObjectBuilder() .add(TYPE, EDC_NAMESPACE + "DataAddress") @@ -262,8 +263,9 @@ void azureBlobPush_containerNotExist() { // create objects in EDC provider().createAsset(assetId, Map.of(), dataAddress); - var policyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); - provider().createContractDefinition(assetId, "def-1", policyId, policyId); + var accessPolicyId = provider().createPolicyDefinition(bpnPolicy(consumer().getBpn())); + var contractPolicyId = provider().createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + provider().createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var destinationContainerName = UUID.randomUUID().toString(); var destination = createObjectBuilder() diff --git a/edc-tests/e2e/end2end-transfer-cloud/src/test/java/org/eclipse/tractusx/edc/tests/transfer/S3ToS3EndToEndTest.java b/edc-tests/e2e/end2end-transfer-cloud/src/test/java/org/eclipse/tractusx/edc/tests/transfer/S3ToS3EndToEndTest.java index e362ccc40a..45c973be7d 100644 --- a/edc-tests/e2e/end2end-transfer-cloud/src/test/java/org/eclipse/tractusx/edc/tests/transfer/S3ToS3EndToEndTest.java +++ b/edc-tests/e2e/end2end-transfer-cloud/src/test/java/org/eclipse/tractusx/edc/tests/transfer/S3ToS3EndToEndTest.java @@ -24,7 +24,7 @@ import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.RuntimeExtension; import org.eclipse.edc.junit.testfixtures.TestUtils; -import org.eclipse.tractusx.edc.tests.aws.LocalstackExtension; +import org.eclipse.tractusx.edc.tests.aws.FlociExtension; import org.eclipse.tractusx.edc.tests.participant.TransferParticipant; import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; import org.junit.jupiter.api.BeforeEach; @@ -48,6 +48,7 @@ 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.bpnPolicy; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkPolicy; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; @@ -64,15 +65,13 @@ public class S3ToS3EndToEndTest { .name(CONSUMER_NAME) .id(CONSUMER_DID) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); private static final TransferParticipant PROVIDER = TransferParticipant.Builder.newInstance() .name(PROVIDER_NAME) .id(PROVIDER_DID) .bpn(PROVIDER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @RegisterExtension @@ -85,9 +84,10 @@ public class S3ToS3EndToEndTest { private static final RuntimeExtension CONSUMER_RUNTIME = pgRuntime(CONSUMER, POSTGRES, CONSUMER::getConfig); @RegisterExtension - private static final LocalstackExtension PROVIDER_CONTAINER = new LocalstackExtension(); + private static final FlociExtension PROVIDER_CONTAINER = new FlociExtension(); + @RegisterExtension - private static final LocalstackExtension CONSUMER_CONTAINER = new LocalstackExtension(); + private static final FlociExtension CONSUMER_CONTAINER = new FlociExtension(); @BeforeEach void setup() { @@ -117,8 +117,9 @@ void transferFile_success() { // create objects in EDC PROVIDER.createAsset(assetId, Map.of(), dataAddress); - var policyId = PROVIDER.createPolicyDefinition(bpnPolicy(CONSUMER.getBpn())); - PROVIDER.createContractDefinition(assetId, "def-1", policyId, policyId); + var accessPolicyId = PROVIDER.createPolicyDefinition(bpnPolicy(CONSUMER.getBpn())); + var contractPolicyId = PROVIDER.createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + PROVIDER.createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var destination = Json.createObjectBuilder() .add(TYPE, EDC_NAMESPACE + "DataAddress") @@ -173,8 +174,9 @@ void transferFile_withBucketCreation_success() { ); PROVIDER.createAsset(assetId, Map.of(), dataAddress); - var policyId = PROVIDER.createPolicyDefinition(bpnPolicy(CONSUMER.getBpn())); - PROVIDER.createContractDefinition(assetId, "def-1", policyId, policyId); + var accessPolicyId = PROVIDER.createPolicyDefinition(bpnPolicy(CONSUMER.getBpn())); + var contractPolicyId = PROVIDER.createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + PROVIDER.createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var destination = Json.createObjectBuilder() .add(TYPE, EDC_NAMESPACE + "DataAddress") diff --git a/samples/testing-with-mocked-connector/build.gradle.kts b/edc-tests/e2e/kafka-transfer-tests/build.gradle.kts similarity index 71% rename from samples/testing-with-mocked-connector/build.gradle.kts rename to edc-tests/e2e/kafka-transfer-tests/build.gradle.kts index c66ca63fd4..6a12e0365e 100644 --- a/samples/testing-with-mocked-connector/build.gradle.kts +++ b/edc-tests/e2e/kafka-transfer-tests/build.gradle.kts @@ -1,5 +1,5 @@ -/******************************************************************************** - * Copyright (c) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -15,7 +15,7 @@ * under the License. * * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ + */ plugins { `java-library` @@ -25,14 +25,23 @@ plugins { dependencies { testImplementation(testFixtures(project(":edc-tests:e2e-fixtures"))) - testImplementation(libs.testcontainers.junit) - testImplementation(libs.wiremock) testImplementation(libs.edc.junit) testImplementation(libs.restAssured) - testImplementation(libs.awaitility) + testImplementation(libs.wiremock) + testImplementation(libs.testcontainers.junit) + testImplementation(libs.testcontainers.kafka) + testImplementation(libs.kafka.clients) + + testCompileOnly(project(":edc-tests:runtime:runtime-postgresql")) } -// do not publish edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} diff --git a/edc-tests/e2e/kafka-transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/KafkaPullEndToEndTest.java b/edc-tests/e2e/kafka-transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/KafkaPullEndToEndTest.java new file mode 100644 index 0000000000..b02fefcdda --- /dev/null +++ b/edc-tests/e2e/kafka-transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/KafkaPullEndToEndTest.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * 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.tests.transfer; + +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import jakarta.json.Json; +import jakarta.json.JsonObject; +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.spi.security.Vault; +import org.eclipse.tractusx.edc.tests.kafka.KafkaExtension; +import org.eclipse.tractusx.edc.tests.participant.TransferParticipant; +import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; +import org.junit.jupiter.api.BeforeAll; +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 java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +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.postRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +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.connector.controlplane.transfer.spi.types.TransferProcessStates.STARTED; +import static org.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcessStates.TERMINATED; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; +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; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025; +import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025_PATH; +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; +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.participant.TractusxParticipantBase.ASYNC_TIMEOUT; +import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; + +/** + * End-to-end test for the {@code KafkaBroker-PULL} transfer type. Runs a real Kafka broker via + * Testcontainers and a WireMock-backed OAuth2 token endpoint. + */ +@EndToEndTest +public class KafkaPullEndToEndTest { + + private static final String TOPIC = "test-topic"; + private static final String CLIENT_ID = "kafka-client-id"; + private static final String CLIENT_SECRET_KEY = "kafka-client-secret"; + private static final String CLIENT_SECRET_VALUE = "kafka-client-secret-value"; + private static final String ACCESS_TOKEN = "test-access-token"; + + private static final TransferParticipant CONSUMER = TransferParticipant.Builder.newInstance() + .name(CONSUMER_NAME) + .id(CONSUMER_DID) + .bpn(CONSUMER_BPN) + .protocol(DSP_2025, DSP_2025_PATH) + .build(); + + private static final TransferParticipant PROVIDER = TransferParticipant.Builder.newInstance() + .name(PROVIDER_NAME) + .id(PROVIDER_DID) + .bpn(PROVIDER_BPN) + .protocol(DSP_2025, DSP_2025_PATH) + .build(); + + @RegisterExtension + @Order(0) + private static final PostgresExtension POSTGRES = new PostgresExtension(PROVIDER.getName(), CONSUMER.getName()); + + @RegisterExtension + private static final RuntimeExtension PROVIDER_RUNTIME = pgRuntime(PROVIDER, POSTGRES, PROVIDER::getConfig); + + @RegisterExtension + private static final RuntimeExtension CONSUMER_RUNTIME = pgRuntime(CONSUMER, POSTGRES, CONSUMER::getConfig); + + @RegisterExtension + private static final KafkaExtension KAFKA = new KafkaExtension(); + + @RegisterExtension + private static final WireMockExtension OAUTH = WireMockExtension.newInstance() + .options(wireMockConfig().dynamicPort()) + .build(); + + @BeforeAll + static void beforeAll() { + CONSUMER.setJsonLd(CONSUMER_RUNTIME.getService(JsonLd.class)); + } + + @BeforeEach + void beforeEach() { + // the provider's Kafka data plane mints (and revokes) the consumer token against this OAuth2 endpoint + OAUTH.stubFor(post(urlEqualTo("/token")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody("{\"access_token\":\"" + ACCESS_TOKEN + "\",\"expires_in\":3600}"))); + OAUTH.stubFor(post(urlEqualTo("/revoke")) + .willReturn(aResponse().withStatus(200))); + PROVIDER_RUNTIME.getService(Vault.class).storeSecret(CLIENT_SECRET_KEY, CLIENT_SECRET_VALUE); + } + + @Test + void kafkaPullTransfer_consumerReceivesMessages() { + KAFKA.createTopic(TOPIC); + KAFKA.produce(TOPIC, "k1", "hello"); + KAFKA.produce(TOPIC, "k2", "world"); + + var transferProcessId = startKafkaPullTransfer("kafka-test-asset", "def-1", kafkaSourceAddress("kafka-transfer-test", true)); + CONSUMER.waitForTransferProcess(transferProcessId, STARTED); + + // the consumer receives an EDR carrying the broker connection details and the minted token + var edr = CONSUMER.edrs().waitForEdr(transferProcessId); + assertThat(edr.getString("kafka.bootstrap.servers")).isEqualTo(KAFKA.getBootstrapServers()); + assertThat(edr.getString("topic")).isEqualTo(TOPIC); + assertThat(edr.getString("kafka.security.protocol")).isEqualTo("SASL_PLAINTEXT"); + assertThat(edr.getString("kafka.sasl.mechanism")).isEqualTo("OAUTHBEARER"); + assertThat(edr.getString("kafka.group.prefix")).isEqualTo(CONSUMER.getBpn()); + assertThat(edr.getString("token")).isEqualTo(ACCESS_TOKEN); + + // consuming via the broker coordinates and topic taken from the EDR returns the published messages + var consumed = KAFKA.consume(edr.getString("kafka.bootstrap.servers"), edr.getString("topic"), Duration.ofSeconds(10)); + assertThat(consumed).isNotEmpty(); + + // the data plane minted the access token via the OAuth2 client-credentials flow + OAUTH.verify(postRequestedFor(urlEqualTo("/token"))); + } + + @Test + void kafkaPullTransfer_withoutGroupPrefix_provisionsViaParticipantFallback() { + // the asset omits kafka.group.prefix, so the data plane must fall back to the consumer participant id + var transferProcessId = startKafkaPullTransfer("kafka-fallback-asset", "def-2", kafkaSourceAddress("kafka-fallback-test", false)); + CONSUMER.waitForTransferProcess(transferProcessId, STARTED); + + // provisioning still succeeds and the EDR carries a (non-blank) group prefix from the participant-id fallback + var edr = CONSUMER.edrs().waitForEdr(transferProcessId); + assertThat(edr.getString("kafka.group.prefix")).isNotBlank(); + OAUTH.verify(postRequestedFor(urlEqualTo("/token"))); + } + + @Test + void kafkaPullTransfer_terminationRevokesToken() { + var transferProcessId = startKafkaPullTransfer("kafka-terminate-asset", "def-3", kafkaSourceAddress("kafka-terminate-test", true)); + CONSUMER.waitForTransferProcess(transferProcessId, STARTED); + + // a token was minted (and stored under the flow id) while the transfer was active + OAUTH.verify(postRequestedFor(urlEqualTo("/token"))); + + // terminating the transfer deprovisions the Kafka flow, which revokes the OAuth2 token + CONSUMER.terminateTransfer(transferProcessId); + CONSUMER.waitForTransferProcess(transferProcessId, TERMINATED); + + await().atMost(ASYNC_TIMEOUT).untilAsserted(() -> + OAUTH.verify(postRequestedFor(urlEqualTo("/revoke")))); + } + + /** + * Registers the Kafka asset/policy/contract on the provider and starts a {@code KafkaBroker-PULL} + * transfer from the consumer, returning the transfer-process id. + */ + private String startKafkaPullTransfer(String assetId, String contractDefinitionId, Map dataAddress) { + PROVIDER.createAsset(assetId, Map.of(), dataAddress); + var accessPolicyId = PROVIDER.createPolicyDefinition(bpnPolicy(CONSUMER.getBpn())); + var contractPolicyId = PROVIDER.createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + PROVIDER.createContractDefinition(assetId, contractDefinitionId, accessPolicyId, contractPolicyId); + + return CONSUMER.requestAssetFrom(assetId, PROVIDER) + .withTransferType("KafkaBroker-PULL") + .withDestination(kafkaPullDestination()) + .execute(); + } + + /** + * Builds a {@code KafkaBroker} source {@link Map} data address. When {@code withGroupPrefix} is + * {@code false} the {@code kafka.group.prefix} property is omitted to exercise the + * participant-id fallback. + */ + private Map kafkaSourceAddress(String name, boolean withGroupPrefix) { + var dataAddress = new HashMap(); + dataAddress.put("name", name); + dataAddress.put(TYPE, "DataAddress"); + dataAddress.put("type", "KafkaBroker"); + dataAddress.put("topic", TOPIC); + dataAddress.put("kafka.bootstrap.servers", KAFKA.getBootstrapServers()); + dataAddress.put("kafka.security.protocol", "SASL_PLAINTEXT"); + dataAddress.put("kafka.sasl.mechanism", "OAUTHBEARER"); + dataAddress.put("kafka.poll.duration", "PT1S"); + dataAddress.put("tokenUrl", OAUTH.baseUrl() + "/token"); + dataAddress.put("revokeUrl", OAUTH.baseUrl() + "/revoke"); + dataAddress.put("clientId", CLIENT_ID); + dataAddress.put("clientSecretKey", CLIENT_SECRET_KEY); + if (withGroupPrefix) { + dataAddress.put("kafka.group.prefix", CONSUMER.getBpn()); + } + return dataAddress; + } + + private JsonObject kafkaPullDestination() { + return Json.createObjectBuilder() + .add(TYPE, EDC_NAMESPACE + "DataAddress") + .add(EDC_NAMESPACE + "type", "HttpData") + .add(EDC_NAMESPACE + "baseUrl", "http://placeholder") + .build(); + } +} diff --git a/edc-tests/e2e/management-tests/build.gradle.kts b/edc-tests/e2e/management-tests/build.gradle.kts index f17990b786..02dca60a84 100644 --- a/edc-tests/e2e/management-tests/build.gradle.kts +++ b/edc-tests/e2e/management-tests/build.gradle.kts @@ -33,3 +33,10 @@ dependencies { edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} diff --git a/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/auth/DelegatedAuthEndToEndTest.java b/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/auth/DelegatedAuthEndToEndTest.java index 21ea90b25e..e79089532a 100644 --- a/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/auth/DelegatedAuthEndToEndTest.java +++ b/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/auth/DelegatedAuthEndToEndTest.java @@ -64,9 +64,10 @@ void shouldDelegateAuth() { var token = KEYCLOAK.issueToken(); CONNECTOR.baseManagementRequest() + .basePath("/v3") .header("Authorization", "Bearer " + token) .contentType(ContentType.JSON) - .post("/v3/assets/request") + .post("/assets/request") .then() .log().ifValidationFails() .statusCode(200); diff --git a/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/validators/ContractDefinitionPoliciesValidatorsTest.java b/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/validators/ContractDefinitionPoliciesValidatorsTest.java new file mode 100644 index 0000000000..88a27e9e55 --- /dev/null +++ b/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/validators/ContractDefinitionPoliciesValidatorsTest.java @@ -0,0 +1,149 @@ +/******************************************************************************** + * Copyright (c) 2026 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.tests.validators; + +import jakarta.json.JsonObject; +import org.eclipse.edc.junit.annotations.EndToEndTest; +import org.eclipse.edc.junit.extensions.RuntimeExtension; +import org.eclipse.tractusx.edc.tests.participant.TransferParticipant; +import org.eclipse.tractusx.edc.tests.runtimes.PostgresExtension; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.util.Map; + +import static io.restassured.http.ContentType.JSON; +import static jakarta.json.Json.createArrayBuilder; +import static jakarta.json.Json.createObjectBuilder; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_ACCESSPOLICY_ID; +import static org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition.CONTRACT_DEFINITION_CONTRACTPOLICY_ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.CONTEXT; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.ID; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.TYPE; +import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; +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; +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.runtimes.Runtimes.pgRuntime; +import static org.hamcrest.Matchers.equalTo; + +@EndToEndTest +public class ContractDefinitionPoliciesValidatorsTest { + + private static final TransferParticipant PROVIDER = TransferParticipant.Builder.newInstance() + .name(PROVIDER_NAME) + .id(PROVIDER_DID) + .bpn(PROVIDER_BPN) + .build(); + + @RegisterExtension + @Order(0) + private static final PostgresExtension POSTGRES = new PostgresExtension(PROVIDER.getName()); + + @RegisterExtension + private static final RuntimeExtension PROVIDER_RUNTIME = pgRuntime(PROVIDER, POSTGRES); + + @Test + void shouldFail_whenAccessPolicyDefinedAsContractPolicy() { + var contractPolicyId = PROVIDER.createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + var contractDefinition = contractDefinition("contract-definition", contractPolicyId, contractPolicyId); + PROVIDER.baseManagementRequest() + .basePath("/v3") + .contentType(JSON) + .body(contractDefinition) + .when() + .post("/contractdefinitions") + .then().assertThat() + .log().ifValidationFails() + .statusCode(400) + .body("[0].message", equalTo( + "Policy '%s' does not have the expected permission action 'https://w3id.org/catenax/2025/9/policy/access'" + .formatted(contractPolicyId))); + } + + @Test + void shouldFail_whenContractPolicyDefinedAsAccessPolicy() { + var accessPolicyId = PROVIDER.createPolicyDefinition(bpnPolicy(PROVIDER_BPN)); + + var contractDefinition = contractDefinition("contract-definition", accessPolicyId, accessPolicyId); + PROVIDER.baseManagementRequest() + .basePath("/v3") + .contentType(JSON) + .body(contractDefinition) + .when() + .post("/contractdefinitions") + .then().assertThat() + .statusCode(400) + .body("[0].message", + equalTo("Policy '%s' does not have the expected permission action 'http://www.w3.org/ns/odrl/2/use'" + .formatted(accessPolicyId))); + } + + @Test + void shouldPass_whenContractDefinitionHasCorrectPolicies() { + var accessPolicyId = PROVIDER.createPolicyDefinition(bpnPolicy(PROVIDER_BPN)); + var contractPolicyId = PROVIDER.createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + + PROVIDER.createContractDefinition("assetId", "contract-definition", accessPolicyId, contractPolicyId); + } + + @Test + void shouldFail_whenUpdatingPolicyThatIsReferencedByContractDefinition() { + var accessPolicyId = PROVIDER.createPolicyDefinition(bpnPolicy(PROVIDER_BPN)); + var contractPolicyId = PROVIDER.createPolicyDefinition(frameworkPolicy(Map.of(), "use")); + PROVIDER.createContractDefinition("assetId", "contract-definition", accessPolicyId, contractPolicyId); + + var policyDefinition = createObjectBuilder() + .add(CONTEXT, createObjectBuilder().add(VOCAB, EDC_NAMESPACE)) + .add(TYPE, "PolicyDefinition") + .add(ID, contractPolicyId) + .add("policy", bpnPolicy(PROVIDER_BPN)) + .build(); + + PROVIDER.baseManagementRequest() + .basePath("/v3") + .contentType(JSON) + .body(policyDefinition) + .when() + .put("/policydefinitions/" + contractPolicyId) + .then().assertThat() + .statusCode(400) + .body("[0].message", equalTo("Policy Definition is referenced by a Contract Definition")); + } + + private JsonObject contractDefinition(String id, String accessPolicyId, String contractPolicyId) { + return createObjectBuilder() + .add(CONTEXT, createObjectBuilder()) + .add(ID, id) + .add(TYPE, EDC_NAMESPACE + "ContractDefinition") + .add(CONTRACT_DEFINITION_ACCESSPOLICY_ID, accessPolicyId) + .add(CONTRACT_DEFINITION_CONTRACTPOLICY_ID, contractPolicyId) + .add("assetsSelector", createArrayBuilder().add(createObjectBuilder() + .add("@type", "CriterionDto") + .add("operandLeft", "https://w3id.org/edc/v0.0.1/ns/id") + .add("operator", "=") + .add("operandRight", "asset"))) + .build(); + } +} diff --git a/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/validators/EmptyAssetSelectorValidatorTest.java b/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/validators/EmptyAssetSelectorValidatorTest.java index f05f275241..7f9c9dc883 100644 --- a/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/validators/EmptyAssetSelectorValidatorTest.java +++ b/edc-tests/e2e/management-tests/src/test/java/org/eclipse/tractusx/edc/tests/validators/EmptyAssetSelectorValidatorTest.java @@ -48,6 +48,8 @@ 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; +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.runtimes.Runtimes.pgRuntime; import static org.hamcrest.Matchers.contains; @@ -74,27 +76,32 @@ public class EmptyAssetSelectorValidatorTest { @Test @DisplayName("Provider gets 400 when no asset selector is used") void shouldFail_whenContractDefinitionHasNoAssetSelector() { + var accessPolicyId = PROVIDER.createPolicyDefinition(bpnPolicy(PROVIDER_BPN)); + var contractPolicyId = PROVIDER.createPolicyDefinition(frameworkPolicy(Map.of(), "use")); - var requestResponse = createContractDefinitionRequest("definitionId", "accessPolicyId", "contractPolicy", null); - - requestResponse.statusCode(400) - .body("message", contains("mandatory array '%s' is missing".formatted(CONTRACT_DEFINITION_ASSETS_SELECTOR))); - + createContractDefinitionRequest("definitionId", accessPolicyId, contractPolicyId, null) + .statusCode(400) + .body("message", contains("mandatory array '%s' is missing" + .formatted(CONTRACT_DEFINITION_ASSETS_SELECTOR))); } @Test @DisplayName("Provider gets 400 when empty asset selector is used") void shouldFail_whenContractDefinitionHasEmptyAssetSelector() { + var accessPolicyId = PROVIDER.createPolicyDefinition(bpnPolicy(PROVIDER_BPN)); + var contractPolicyId = PROVIDER.createPolicyDefinition(frameworkPolicy(Map.of(), "use")); - var requestResponse = createContractDefinitionRequest("definitionId", "accessPolicyId", "contractPolicy", createArrayBuilder().build()); - - requestResponse.statusCode(400) - .body("message", contains("array '%s' should at least contains '1' elements".formatted(CONTRACT_DEFINITION_ASSETS_SELECTOR))); + createContractDefinitionRequest("definitionId", accessPolicyId, contractPolicyId, createArrayBuilder().build()) + .statusCode(400) + .body("message", contains("array '%s' should at least contains '1' elements" + .formatted(CONTRACT_DEFINITION_ASSETS_SELECTOR))); } @Test @DisplayName("Provider gets 200 when asset selector has a valid criterion") void shouldPass_whenContractDefinitionHasCorrectAssetSelector() { + var accessPolicyId = PROVIDER.createPolicyDefinition(bpnPolicy(PROVIDER_BPN)); + var contractPolicyId = PROVIDER.createPolicyDefinition(frameworkPolicy(Map.of(), "use")); var assetSelector = Json.createArrayBuilder() .add(createObjectBuilder() .add(TYPE, "Criterion") @@ -104,9 +111,8 @@ void shouldPass_whenContractDefinitionHasCorrectAssetSelector() { .build()) .build(); - var requestResponse = createContractDefinitionRequest("definitionId", "accessPolicyId", "contractPolicy", assetSelector); - - requestResponse.statusCode(200); + createContractDefinitionRequest("definitionId", accessPolicyId, contractPolicyId, assetSelector) + .statusCode(200); } private ValidatableResponse createContractDefinitionRequest(String definitionId, String accessPolicyId, String contractPolicyId, JsonArray criterionArray) { @@ -122,10 +128,11 @@ private ValidatableResponse createContractDefinitionRequest(String definitionId, } return PROVIDER.baseManagementRequest() + .basePath("/v3") .contentType(JSON) .body(requestBody.build()) .when() - .post("/v3/contractdefinitions") + .post("/contractdefinitions") .then(); } diff --git a/edc-tests/e2e/policy-tests/build.gradle.kts b/edc-tests/e2e/policy-tests/build.gradle.kts index d51250bdba..3658450fde 100644 --- a/edc-tests/e2e/policy-tests/build.gradle.kts +++ b/edc-tests/e2e/policy-tests/build.gradle.kts @@ -36,3 +36,10 @@ dependencies { edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} 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 da68180053..d120cf1c2f 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 @@ -25,6 +25,7 @@ import io.restassured.response.Response; import jakarta.json.Json; import jakarta.json.JsonObject; +import jakarta.json.JsonValue; import org.eclipse.edc.junit.annotations.EndToEndTest; import org.eclipse.edc.junit.extensions.RuntimeExtension; import org.eclipse.edc.policy.model.Operator; @@ -41,13 +42,20 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.stream.Stream; +import static com.apicatalog.jsonld.lang.Keywords.ID; 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.edc.jsonld.spi.JsonLdKeywords.VOCAB; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2; +import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; import static org.eclipse.tractusx.edc.cx.CxJsonLdExtension.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; @@ -64,6 +72,7 @@ @EndToEndTest public class PolicyDefinitionEndToEndTest { + private static final TransferParticipant CONSUMER = TransferParticipant.Builder.newInstance() .name(CONSUMER_NAME) .id(CONSUMER_DID) @@ -88,113 +97,113 @@ public class PolicyDefinitionEndToEndTest { private static final RuntimeExtension PROVIDER_RUNTIME = pgRuntime(PROVIDER, POSTGRES); @DisplayName("Policy is accepted") - @ParameterizedTest(name = "{1}") + @ParameterizedTest(name = "{2}") @ArgumentsSource(ValidContractPolicyProvider.class) - void shouldAcceptValidPolicyDefinitions(JsonObject policy, String description) { - PROVIDER.createPolicyDefinition(policy); - } - - @DisplayName("Policy is not accepted due to missing context") - @ParameterizedTest(name = "{1}") - @ArgumentsSource(InValidNamespaceContractPolicyProvider.class) - void shouldNotAcceptInvalidNamespacePolicyDefinitions(JsonObject policy, String description) { - checkForValidationFailure(policy); + void shouldAcceptValidPolicyDefinitions(ManagementApiVersion apiVersion, JsonObject policy, String description) { + var response = createPolicyDefinition(apiVersion, policy); + assertThat(response.statusCode()).isEqualTo(200); } @DisplayName("Policy is not accepted because definition is not correct") - @ParameterizedTest(name = "{1}") - @ArgumentsSource(InValidContractPolicyProvider.class) - void shouldNotAcceptInvalidPolicyDefinitions(JsonObject policy, String description) { - checkForValidationFailure(policy); + @ParameterizedTest(name = "{2}") + @ArgumentsSource(InvalidContractPolicyProvider.class) + void shouldNotAcceptInvalidPolicyDefinitions(ManagementApiVersion apiVersion, JsonObject policy, String description) { + checkForValidationFailure(apiVersion, policy); } - private void checkForValidationFailure(JsonObject policy) { - var response = createPolicyDefinition(policy); + private void checkForValidationFailure(ManagementApiVersion apiVersion, JsonObject policy) { + var response = createPolicyDefinition(apiVersion, policy); assertThat(response.statusCode()).isEqualTo(400); assertThat(response.body().jsonPath().getString("[0].type")).isEqualTo("ValidationFailure"); } - private abstract static class BasePolicyProvider implements ArgumentsProvider { + private abstract static class ApiVersionsArgumentsProvider implements ArgumentsProvider { + List apiVersions = List.of(ManagementApiVersion.V3, ManagementApiVersion.V4); - protected final String namespace; + protected abstract Stream arguments(); - private BasePolicyProvider(String namespace) { - this.namespace = namespace; + /** + * Creates a new set of arguments, where every combination of arguments from the subclass + * is combined with management API versions v3 & v4. + * + * @param extensionContext the current extension context; never {@code null} + * @return a stream of arguments, where each combination of original arguments is paired with both management API versions (v3 & v4) + */ + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return arguments() + .flatMap(arguments -> apiVersions.stream() + .map(apiVersion -> { + var newParams = new ArrayList<>(); + newParams.add(apiVersion); + newParams.addAll(Arrays.asList(arguments.get())); + return Arguments.of(newParams.toArray()); + })); } + } + + private static class ValidContractPolicyProvider extends ApiVersionsArgumentsProvider { @Override - public Stream provideArguments(ExtensionContext extensionContext) { + public Stream arguments() { return Stream.of( - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("Membership", "active"), "use", Operator.EQ, false)), "MembershipCredential"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("FrameworkAgreement", "DataExchangeGovernance:1.0"), "use", Operator.EQ, false)), "DataExchangeGovernance use case"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", 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, + Arguments.of(policyFromRules("permission", 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, + Arguments.of(policyFromRules("permission", + frameworkConstraint(Map.of("AffiliatesBpnl", "BPNL00000000001A"), "use", Operator.IS_ANY_OF, true)), "Affiliates BPNL"), + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("DataFrequency", "cx.dataFrequency.once:1"), "use", Operator.EQ, false)), "Data Frequency"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("DataUsageEndDate", "2025-06-30T14:30:00Z"), "use", Operator.EQ, false)), "Data Usage End Date"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("DataUsageEndDefinition", "cx.dataUsageEnd.unlimited:1"), "use", Operator.EQ, false)), "Data Usage End Date Definition"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("DataUsageEndDurationDays", 3), "use", Operator.EQ, false)), "Data Usage End Duration Days"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("JurisdictionLocation", "test location"), "use", Operator.EQ, false)), "Jurisdiction Location"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("JurisdictionLocationReference", "cx.location.dataConsumer:1"), "use", Operator.EQ, false)), "Jurisdiction Location Reference"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("Liability", "cx.grossNegligence:1"), "use", Operator.EQ, false)), "Liability"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("Liability", "cx.slightNegligence:1"), "use", Operator.EQ, false)), "Liability"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("Precedence", "cx.precedence.contractReference:1"), "use", Operator.EQ, false)), "Precedence"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", 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, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("VersionChanges", "cx.versionChanges.minor:1"), "use", Operator.EQ, false)), "Version Changes"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("Warranty", "cx.warranty.none:1"), "use", Operator.EQ, false)), "Warranty"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("WarrantyDefinition", "cx.warranty.contractEndDate:1"), "use", Operator.EQ, false)), "Warranty Definition"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("WarrantyDurationMonths", 3), "use", Operator.EQ, false)), "Warranty Duration Months"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("ExclusiveUsage", "cx.exclusiveUsage.dataConsumer:1"), "use", Operator.EQ, false)), "Exclusive Usage"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("ContractReference", List.of("contractReference")), "use", Operator.IS_ALL_OF, true)), "Contract reference"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("ContractTermination", "cx.data.deletion:1"), "use", Operator.EQ, false)), "ContractTermination"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("ConfidentialInformationMeasures", "cx.confidentiality.measures:1"), "use", Operator.EQ, false)), "Confidential Information Measures"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("ConfidentialInformationSharing", List.of("cx.sharing.affiliates:1")), "use", Operator.IS_ANY_OF, true)), "Confidential Information Sharing"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", 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 BasePolicyProvider { - - private ValidContractPolicyProvider() { - super(CX_POLICY_2025_09_CONTEXT); - } - - @Override - public Stream provideArguments(ExtensionContext extensionContext) { - return Stream.concat(super.provideArguments(extensionContext), Stream.of( + Arguments.of(policyFromRules("permission", + frameworkConstraint(Map.of("BusinessPartnerNumber", List.of("BPNL00000000001A")), "access", Operator.IS_ANY_OF, true)), "Business Partner Number"), Arguments.of( emptyPolicy(), "Empty Policy"), Arguments.of( - policyWithEmptyRule("access", this.namespace), + policyWithEmptyRule("access"), "Access policy with empty permission"), Arguments.of( dataUsageEndDurationDays(1), @@ -202,74 +211,81 @@ public Stream provideArguments(ExtensionContext extensionCo Arguments.of( dataUsageEndDate(Instant.now().plus(1, ChronoUnit.SECONDS).truncatedTo(ChronoUnit.SECONDS).toString()), "Enforce data usage end date 1 second in the future") - )); - } - } - - private static class InValidNamespaceContractPolicyProvider extends BasePolicyProvider { - - private InValidNamespaceContractPolicyProvider() { - super(""); + ); } } - private static class InValidContractPolicyProvider extends BasePolicyProvider { - - private InValidContractPolicyProvider() { - super(CX_POLICY_2025_09_CONTEXT); - } + private static class InvalidContractPolicyProvider extends ApiVersionsArgumentsProvider { @Override - public Stream provideArguments(ExtensionContext extensionContext) { + public Stream arguments() { return Stream.of( - Arguments.of(policyWithEmptyRule("use", this.namespace), "Usage policy with empty permission"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyWithEmptyRule("use"), "Usage policy with empty permission"), + Arguments.of(policyFromRules("permission", 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, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("Membership", "active"), "unknown-action", Operator.EQ, false)), "Policy with unknown actions types"), - Arguments.of(policyFromRules("prohibition", namespace, + Arguments.of(policyFromRules("prohibition", frameworkConstraint(Map.of("Membership", "active"), "access", Operator.EQ, false)), "Access Policy with prohibition rule"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("UsagePurpose", "cx.core.industrycore:1"), "access", Operator.EQ, false)), "Access policy permission with not allowed constraints"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", frameworkConstraint(Map.of("BusinessPartnerNumber", "BPN0022232"), "use", Operator.EQ, false)), "Usage policy permission with not allowed constraints"), - Arguments.of(policyFromRules("prohibition", namespace, + Arguments.of(policyFromRules("prohibition", frameworkConstraint(Map.of("AffiliatesRegion", "cx.region.europe:1"), "use", Operator.EQ, false)), "Usage policy prohibition with not allowed constraints"), - Arguments.of(policyFromRules("obligation", namespace, + Arguments.of(policyFromRules("obligation", frameworkConstraint(Map.of("UsagePurpose", "cx.core.industrycore:1"), "use", Operator.EQ, false)), "Usage policy obligation with not allowed constraints"), - Arguments.of(policyFromRules("permission", namespace, + Arguments.of(policyFromRules("permission", 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 Response createPolicyDefinition(ManagementApiVersion apiVersion, JsonObject policy) { + JsonValue context; + switch (apiVersion) { + case V3 -> context = Json.createObjectBuilder() + .add(VOCAB, EDC_NAMESPACE) + .build(); + case V4 -> context = Json.createValue(EDC_CONNECTOR_MANAGEMENT_CONTEXT_V2); + default -> context = null; + } + + var requestBody = Json.createObjectBuilder() + .add(CONTEXT, context) + .add(TYPE, "PolicyDefinition") + .add(ID, UUID.randomUUID().toString()) + .add("policy", policy) + .build(); + return (Response) PROVIDER.baseManagementRequest() + .basePath("/%s".formatted(apiVersion.apiPath)) + .contentType(ContentType.JSON) + .body(requestBody) + .when() + .post("/policydefinitions", new Object[0]) + .then().extract(); } - private static JsonObject policyFromRules(String ruleType, String policyDefinition, JsonObject... rules) { + private static JsonObject policyFromRules(String ruleType, JsonObject... rules) { var rulesArrayBuilder = Json.createArrayBuilder(); for (JsonObject rule : rules) { rulesArrayBuilder.add(rule); } var contextArrayBuilder = Json.createArrayBuilder(); contextArrayBuilder.add(ODRL_CONTEXT); - if (!policyDefinition.isBlank()) { - contextArrayBuilder.add(policyDefinition); - } + contextArrayBuilder.add(CX_POLICY_2025_09_CONTEXT); return Json.createObjectBuilder() + .add(ID, "id") .add(CONTEXT, contextArrayBuilder) .add(TYPE, "Set") .add(ruleType, rulesArrayBuilder) .build(); } - private static JsonObject policyWithEmptyRule(String action, String policyContext) { + private static JsonObject policyWithEmptyRule(String action) { var rule = Json.createObjectBuilder() .add("action", action) .build(); @@ -277,12 +293,24 @@ private static JsonObject policyWithEmptyRule(String action, String policyContex rulesArrayBuilder.add(rule); var contextArrayBuilder = Json.createArrayBuilder(); contextArrayBuilder.add(ODRL_CONTEXT); - contextArrayBuilder.add(policyContext); + contextArrayBuilder.add(CX_POLICY_2025_09_CONTEXT); return Json.createObjectBuilder() + .add(ID, "id") .add(CONTEXT, contextArrayBuilder) .add(TYPE, "Set") .add("permission", rulesArrayBuilder) .build(); } + + private enum ManagementApiVersion { + V3("v3"), + V4("v4"); + + final String apiPath; + + ManagementApiVersion(String apiPath) { + this.apiPath = apiPath; + } + } } diff --git a/edc-tests/e2e/policy-tests/src/test/java/org/eclipse/tractusx/edc/tests/policy/PolicyMonitorEndToEndTest.java b/edc-tests/e2e/policy-tests/src/test/java/org/eclipse/tractusx/edc/tests/policy/PolicyMonitorEndToEndTest.java index 0dc7836061..f8ec9534f0 100644 --- a/edc-tests/e2e/policy-tests/src/test/java/org/eclipse/tractusx/edc/tests/policy/PolicyMonitorEndToEndTest.java +++ b/edc-tests/e2e/policy-tests/src/test/java/org/eclipse/tractusx/edc/tests/policy/PolicyMonitorEndToEndTest.java @@ -63,8 +63,7 @@ public class PolicyMonitorEndToEndTest { .name(CONSUMER_NAME) .id(CONSUMER_DID) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @@ -72,8 +71,7 @@ public class PolicyMonitorEndToEndTest { .name(PROVIDER_NAME) .id(PROVIDER_DID) .bpn(PROVIDER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @RegisterExtension diff --git a/edc-tests/e2e/transfer-tests/build.gradle.kts b/edc-tests/e2e/transfer-tests/build.gradle.kts index 307ad673b2..c605b6aa64 100644 --- a/edc-tests/e2e/transfer-tests/build.gradle.kts +++ b/edc-tests/e2e/transfer-tests/build.gradle.kts @@ -39,3 +39,10 @@ dependencies { edcBuild { publish.set(false) } + +configurations.all { + resolutionStrategy { + val version = libs.versions.jsonschema.get() + force("com.networknt:json-schema-validator:${version}") + } +} 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 e814d0bb1d..a3d53890f0 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 @@ -171,7 +171,7 @@ private DataAddress.Builder dataAddressBuilder() { private ValidatableResponse triggerDataTransfer(String dataFlowId) { return PARTICIPANT.baseManagementRequest() - .basePath("/v4alpha/dataflows") + .basePath("/v3/dataflows") .when() .contentType(JSON) .post("/{id}/trigger", dataFlowId) 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 761a005493..0aa42e22cb 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 @@ -26,8 +26,6 @@ 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.policy.model.Operator; -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.BeforeEach; @@ -42,7 +40,6 @@ 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.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; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; @@ -51,6 +48,8 @@ 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; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.bpnGroupPolicy; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkPolicy; 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; @@ -62,16 +61,14 @@ public class RetireAgreementTest { .name(CONSUMER_NAME) .id(CONSUMER_DID) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); private static final TransferParticipant PROVIDER = TransferParticipant.Builder.newInstance() .name(PROVIDER_NAME) .id(PROVIDER_DID) .bpn(PROVIDER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .enableEventSubscription() .build(); @@ -110,8 +107,8 @@ void retireAgreement_shouldCloseTransferProcesses() { PROVIDER.createAsset(assetId, Map.of(), dataAddress); PROVIDER.storeBusinessPartner(CONSUMER.getBpn(), "test-group1"); - var accessPolicy = PROVIDER.createPolicyDefinition(PolicyHelperFunctions.bpnGroupPolicy(Operator.IS_ANY_OF, "test-group1")); - var policy = PolicyHelperFunctions.frameworkPolicy(Map.of(), CX_POLICY_2025_09_NS + "access"); + var accessPolicy = PROVIDER.createPolicyDefinition(bpnGroupPolicy("isAnyOf", true, "test-group1")); + var policy = frameworkPolicy(Map.of(), "use"); var contractPolicy = PROVIDER.createPolicyDefinition(policy); PROVIDER.createContractDefinition(assetId, "def-1", accessPolicy, contractPolicy); 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 5083d2b746..18a2be33de 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 @@ -52,7 +52,6 @@ import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_08; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025; -import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025_PATH; 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; @@ -98,7 +97,7 @@ void transferData_withSuspendResume() { PROVIDER.createAsset(assetId, Map.of(), dataAddress); var accessPolicyId = PROVIDER.createPolicyDefinition(createAccessPolicy(CONSUMER.getBpn())); - var contractPolicyId = PROVIDER.createPolicyDefinition(createContractPolicy(CONSUMER.getBpn())); + var contractPolicyId = PROVIDER.createPolicyDefinition(createContractPolicy()); PROVIDER.createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var transferProcessId = CONSUMER.requestAssetFrom(assetId, PROVIDER) .withTransferType("HttpData-PULL") @@ -449,8 +448,8 @@ class Dsp2025to2025 extends Tests { @BeforeAll static void beforeAll() { CONSUMER.setJsonLd(CONSUMER_RUNTIME.getService(JsonLd.class)); - CONSUMER.setProtocol(DSP_2025, DSP_2025_PATH); - PROVIDER.setProtocol(DSP_2025, DSP_2025_PATH); + CONSUMER.setProtocol(DSP_2025); + PROVIDER.setProtocol(DSP_2025); } } } \ No newline at end of file diff --git a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPushEndToEndTest.java b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPushEndToEndTest.java index 0e124a856c..c08cc4948d 100644 --- a/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPushEndToEndTest.java +++ b/edc-tests/e2e/transfer-tests/src/test/java/org/eclipse/tractusx/edc/tests/transfer/TransferPushEndToEndTest.java @@ -36,7 +36,6 @@ import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.CONSUMER_NAME; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_08; import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025; -import static org.eclipse.tractusx.edc.tests.TestRuntimeConfiguration.DSP_2025_PATH; 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; @@ -133,8 +132,8 @@ public RuntimeExtension consumerRuntime() { @BeforeAll static void beforeAll() { CONSUMER.setJsonLd(CONSUMER_RUNTIME.getService(JsonLd.class)); - CONSUMER.setProtocol(DSP_2025, DSP_2025_PATH); - PROVIDER.setProtocol(DSP_2025, DSP_2025_PATH); + CONSUMER.setProtocol(DSP_2025); + PROVIDER.setProtocol(DSP_2025); } } } 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 53c7af70b0..2257c1102e 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 @@ -60,6 +60,7 @@ 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.bpnPolicy; +import static org.eclipse.tractusx.edc.tests.helpers.PolicyHelperFunctions.frameworkPolicy; import static org.eclipse.tractusx.edc.tests.participant.TractusxParticipantBase.ASYNC_TIMEOUT; import static org.eclipse.tractusx.edc.tests.runtimes.Runtimes.pgRuntime; @@ -79,15 +80,13 @@ public class TransferWithTokenRefreshTest { .name(CONSUMER_NAME) .id(CONSUMER_DID) .bpn(CONSUMER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); private static final TransferParticipant PROVIDER = TransferParticipant.Builder.newInstance() .name(PROVIDER_NAME) .id(PROVIDER_DID) .bpn(PROVIDER_BPN) - .protocol(DSP_2025) - .protocolVersionPath(DSP_2025_PATH) + .protocol(DSP_2025, DSP_2025_PATH) .build(); @RegisterExtension @@ -131,7 +130,7 @@ void transferData_withExpiredEdr_shouldReturn4xx() { PROVIDER.createAsset(assetId, Map.of(), dataAddress); var accessPolicyId = PROVIDER.createPolicyDefinition(createAccessPolicy(CONSUMER.getBpn())); - var contractPolicyId = PROVIDER.createPolicyDefinition(createContractPolicy(CONSUMER.getBpn())); + var contractPolicyId = PROVIDER.createPolicyDefinition(createContractPolicy()); PROVIDER.createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var transferProcessId = CONSUMER.requestAssetFrom(assetId, PROVIDER).withTransferType("HttpData-PULL") .withDestination(httpDataDestination()).execute(); @@ -191,7 +190,7 @@ void transferData_withAutomaticRefresh() { PROVIDER.createAsset(assetId, Map.of(), dataAddress); var accessPolicyId = PROVIDER.createPolicyDefinition(createAccessPolicy(CONSUMER.getBpn())); - var contractPolicyId = PROVIDER.createPolicyDefinition(createContractPolicy(CONSUMER.getBpn())); + var contractPolicyId = PROVIDER.createPolicyDefinition(createContractPolicy()); PROVIDER.createContractDefinition(assetId, "def-1", accessPolicyId, contractPolicyId); var transferProcessId = CONSUMER.requestAssetFrom(assetId, PROVIDER).withTransferType("HttpData-PULL") .withDestination(httpDataDestination()).execute(); @@ -252,7 +251,7 @@ protected JsonObject createAccessPolicy(String bpn) { return bpnPolicy(bpn); } - protected JsonObject createContractPolicy(String bpn) { - return bpnPolicy(bpn); + protected JsonObject createContractPolicy() { + return frameworkPolicy(Map.of(), "use"); } } diff --git a/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts b/edc-tests/runtime/dcp/dcp-extensions/build.gradle.kts similarity index 93% rename from edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts rename to edc-tests/runtime/dcp/dcp-extensions/build.gradle.kts index ea3f4a7fb4..4a53d5fa6f 100644 --- a/edc-tests/runtime/iatp/iatp-extensions/build.gradle.kts +++ b/edc-tests/runtime/dcp/dcp-extensions/build.gradle.kts @@ -25,9 +25,6 @@ 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/dcp/dcp-extensions/src/main/java/org/eclipse/tractusx/edc/dcp/CredentialsJsonLdExtension.java similarity index 85% rename from edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/CredentialsJsonLdExtension.java rename to edc-tests/runtime/dcp/dcp-extensions/src/main/java/org/eclipse/tractusx/edc/dcp/CredentialsJsonLdExtension.java index ff5f270118..1a9cf351b7 100644 --- a/edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/CredentialsJsonLdExtension.java +++ b/edc-tests/runtime/dcp/dcp-extensions/src/main/java/org/eclipse/tractusx/edc/dcp/CredentialsJsonLdExtension.java @@ -17,9 +17,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.iatp; +package org.eclipse.tractusx.edc.dcp; -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; @@ -38,18 +37,10 @@ public class CredentialsJsonLdExtension implements ServiceExtension { @Override public void initialize(ServiceExtensionContext context) { - try { jsonLd.registerCachedDocument(BUSINESS_PARTNER_DATA, Thread.currentThread().getContextClassLoader().getResource("cx-credentials-context.json").toURI()); } 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/java/org/eclipse/tractusx/edc/iatp/ih/IdentityHubExtension.java b/edc-tests/runtime/dcp/dcp-extensions/src/main/java/org/eclipse/tractusx/edc/dcp/ih/IdentityHubExtension.java similarity index 97% rename from edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/ih/IdentityHubExtension.java rename to edc-tests/runtime/dcp/dcp-extensions/src/main/java/org/eclipse/tractusx/edc/dcp/ih/IdentityHubExtension.java index 15bc0aa3eb..e40e6e92ef 100644 --- a/edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/ih/IdentityHubExtension.java +++ b/edc-tests/runtime/dcp/dcp-extensions/src/main/java/org/eclipse/tractusx/edc/dcp/ih/IdentityHubExtension.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.iatp.ih; +package org.eclipse.tractusx.edc.dcp.ih; import org.eclipse.edc.identityhub.spi.transformation.ScopeToCriterionTransformer; import org.eclipse.edc.runtime.metamodel.annotation.Extension; diff --git a/edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/ih/TxScopeToCriterionTransformer.java b/edc-tests/runtime/dcp/dcp-extensions/src/main/java/org/eclipse/tractusx/edc/dcp/ih/TxScopeToCriterionTransformer.java similarity index 93% rename from edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/ih/TxScopeToCriterionTransformer.java rename to edc-tests/runtime/dcp/dcp-extensions/src/main/java/org/eclipse/tractusx/edc/dcp/ih/TxScopeToCriterionTransformer.java index 379582cf2e..7cbc28ccff 100644 --- a/edc-tests/runtime/iatp/iatp-extensions/src/main/java/org/eclipse/tractusx/edc/iatp/ih/TxScopeToCriterionTransformer.java +++ b/edc-tests/runtime/dcp/dcp-extensions/src/main/java/org/eclipse/tractusx/edc/dcp/ih/TxScopeToCriterionTransformer.java @@ -17,7 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.edc.iatp.ih; +package org.eclipse.tractusx.edc.dcp.ih; import org.eclipse.edc.identityhub.spi.transformation.ScopeToCriterionTransformer; import org.eclipse.edc.spi.query.Criterion; @@ -41,13 +41,14 @@ public class TxScopeToCriterionTransformer implements ScopeToCriterionTransforme private final List allowedOperations = List.of("read", "*", "all"); @Override - public Result transform(String scope) { + public Result> transformScope(String scope) { var tokens = tokenize(scope); if (tokens.failed()) { return failure("Scope string cannot be converted: %s".formatted(tokens.getFailureDetail())); } var credentialType = tokens.getContent()[1]; - return success(new Criterion(TYPE_OPERAND, CONTAINS_OPERATOR, credentialType)); + + return success(List.of(new Criterion(TYPE_OPERAND, CONTAINS_OPERATOR, credentialType))); } protected Result tokenize(String scope) { diff --git a/edc-tests/runtime/iatp/iatp-extensions/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-tests/runtime/dcp/dcp-extensions/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension similarity index 89% rename from edc-tests/runtime/iatp/iatp-extensions/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension rename to edc-tests/runtime/dcp/dcp-extensions/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension index b76f4faafa..90a237eb13 100644 --- a/edc-tests/runtime/iatp/iatp-extensions/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ b/edc-tests/runtime/dcp/dcp-extensions/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -17,5 +17,5 @@ # SPDX-License-Identifier: Apache-2.0 ################################################################################# -org.eclipse.tractusx.edc.iatp.ih.IdentityHubExtension -org.eclipse.tractusx.edc.iatp.CredentialsJsonLdExtension +org.eclipse.tractusx.edc.dcp.ih.IdentityHubExtension +org.eclipse.tractusx.edc.dcp.CredentialsJsonLdExtension diff --git a/edc-tests/runtime/iatp/iatp-extensions/src/main/resources/cx-credentials-context.json b/edc-tests/runtime/dcp/dcp-extensions/src/main/resources/cx-credentials-context.json similarity index 100% rename from edc-tests/runtime/iatp/iatp-extensions/src/main/resources/cx-credentials-context.json rename to edc-tests/runtime/dcp/dcp-extensions/src/main/resources/cx-credentials-context.json diff --git a/edc-tests/runtime/iatp/runtime-memory-iatp-div-ih/README.md b/edc-tests/runtime/dcp/runtime-memory-dcp-div-ih/README.md similarity index 100% rename from edc-tests/runtime/iatp/runtime-memory-iatp-div-ih/README.md rename to edc-tests/runtime/dcp/runtime-memory-dcp-div-ih/README.md diff --git a/edc-tests/runtime/iatp/runtime-memory-iatp-div-ih/build.gradle.kts b/edc-tests/runtime/dcp/runtime-memory-dcp-div-ih/build.gradle.kts similarity index 97% rename from edc-tests/runtime/iatp/runtime-memory-iatp-div-ih/build.gradle.kts rename to edc-tests/runtime/dcp/runtime-memory-dcp-div-ih/build.gradle.kts index 31c062c85f..6934b18da0 100644 --- a/edc-tests/runtime/iatp/runtime-memory-iatp-div-ih/build.gradle.kts +++ b/edc-tests/runtime/dcp/runtime-memory-dcp-div-ih/build.gradle.kts @@ -35,7 +35,7 @@ dependencies { implementation(project(":edc-extensions:dcp:tx-dcp")) implementation(project(":edc-extensions:dcp:tx-dcp-sts-div")) - implementation(project(":edc-tests:runtime:iatp:iatp-extensions")) + implementation(project(":edc-tests:runtime:dcp:dcp-extensions")) // use basic (all in-mem) data plane implementation(project(":edc-dataplane:edc-dataplane-base")) { diff --git a/edc-tests/runtime/iatp/runtime-memory-iatp-ih/README.md b/edc-tests/runtime/dcp/runtime-memory-dcp-ih/README.md similarity index 100% rename from edc-tests/runtime/iatp/runtime-memory-iatp-ih/README.md rename to edc-tests/runtime/dcp/runtime-memory-dcp-ih/README.md diff --git a/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts b/edc-tests/runtime/dcp/runtime-memory-dcp-ih/build.gradle.kts similarity index 91% rename from edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts rename to edc-tests/runtime/dcp/runtime-memory-dcp-ih/build.gradle.kts index eaf9f7b5e6..6ba3f022e2 100644 --- a/edc-tests/runtime/iatp/runtime-memory-iatp-ih/build.gradle.kts +++ b/edc-tests/runtime/dcp/runtime-memory-dcp-ih/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { implementation(project(":edc-extensions:dcp:cx-dcp")) implementation(project(":edc-extensions:dcp:tx-dcp")) - implementation(project(":edc-tests:runtime:iatp:iatp-extensions")) + implementation(project(":edc-tests:runtime:dcp:dcp-extensions")) // use basic (all in-mem) data plane implementation(project(":edc-dataplane:edc-dataplane-base")) { @@ -42,12 +42,6 @@ dependencies { 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.decentralized.claims.transform) diff --git a/edc-tests/runtime/iatp/runtime-memory-sts/README.md b/edc-tests/runtime/dcp/runtime-memory-sts/README.md similarity index 100% rename from edc-tests/runtime/iatp/runtime-memory-sts/README.md rename to edc-tests/runtime/dcp/runtime-memory-sts/README.md diff --git a/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts b/edc-tests/runtime/dcp/runtime-memory-sts/build.gradle.kts similarity index 96% rename from edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts rename to edc-tests/runtime/dcp/runtime-memory-sts/build.gradle.kts index f4d162928b..64f08ee199 100644 --- a/edc-tests/runtime/iatp/runtime-memory-sts/build.gradle.kts +++ b/edc-tests/runtime/dcp/runtime-memory-sts/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { implementation(project(":edc-extensions:single-participant-vault")) implementation(project(":core:json-ld-core")) implementation(project(":core:json-ld-cx")) - implementation(project(":edc-tests:runtime:iatp:iatp-extensions")) + implementation(project(":edc-tests:runtime:dcp:dcp-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 deleted file mode 100644 index d65d60a3f9..0000000000 --- a/edc-tests/runtime/mock-connector/build.gradle.kts +++ /dev/null @@ -1,71 +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 - ********************************************************************************/ - -plugins { - `java-library` - id("application") - alias(libs.plugins.shadow) - id(libs.plugins.swagger.get().pluginId) -} - - -dependencies { - // compile-time dependencies - implementation(libs.edc.spi.web) - implementation(libs.edc.spi.boot) - implementation(libs.edc.spi.controlplane) - implementation(libs.edc.lib.util) - - // 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") - } - runtimeOnly(libs.edc.api.management.config) - - runtimeOnly(libs.edc.ext.http) - runtimeOnly(libs.bundles.edc.monitoring) - - // edc libs - runtimeOnly(libs.edc.ext.jsonld) - - testImplementation(libs.edc.junit) -} - -application { - mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime") -} - -edcBuild { - publish.set(false) -} - -tasks.shadowJar { - mergeServiceFiles() - duplicatesStrategy = DuplicatesStrategy.INCLUDE - archiveFileName.set("${project.name}.jar") -} - - -application { - mainClass.set("org.eclipse.edc.boot.system.runtime.BaseRuntime") -} diff --git a/edc-tests/runtime/mock-connector/notice.md b/edc-tests/runtime/mock-connector/notice.md deleted file mode 100644 index 1bfe0a6234..0000000000 --- a/edc-tests/runtime/mock-connector/notice.md +++ /dev/null @@ -1,28 +0,0 @@ -# Notice for Docker image - -A mocked EDC Management API providing an instrumentation API for configuration. - -DockerHub: - -Eclipse Tractus-X product(s) installed within the image: - -## Tractus-X EDC Control Plane - -- GitHub: -- Project home: -- Dockerfile: -- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/tractusx-edc/blob/main/LICENSE) - -## Used base image - -- [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 - images: - -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). - -As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies -with any relevant licenses for all software contained within. 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 deleted file mode 100644 index eb3c788cee..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/MockServiceExtension.java +++ /dev/null @@ -1,126 +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; - -import com.fasterxml.jackson.databind.module.SimpleModule; -import org.eclipse.edc.connector.controlplane.services.spi.asset.AssetService; -import org.eclipse.edc.connector.controlplane.services.spi.catalog.CatalogService; -import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; -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.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; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.response.StatusResult; -import org.eclipse.edc.spi.result.ServiceFailure; -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.spi.WebService; -import org.eclipse.tractusx.edc.mock.api.instrumentation.InstrumentationApiController; -import org.eclipse.tractusx.edc.mock.services.AssetServiceStub; -import org.eclipse.tractusx.edc.mock.services.ContractAgreementServiceStub; -import org.eclipse.tractusx.edc.mock.services.ContractDefinitionServiceStub; -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 java.util.Queue; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * Extension for the mock connector - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class MockServiceExtension implements ServiceExtension { - private final Queue> recordedRequests = new ConcurrentLinkedQueue<>(); - @Inject - private TypeManager typeManager; - - @Inject - private WebService webService; - - private Monitor monitor; - - @Override - public void initialize(ServiceExtensionContext context) { - monitor = context.getMonitor().withPrefix("ResponseQueue"); - webService.registerResource(new InstrumentationApiController(new ResponseQueue(recordedRequests, monitor))); - - // register custom deserializer for the ServiceFailure - var mapper = typeManager.getMapper(); - var module = new SimpleModule(); - module.addDeserializer(ServiceFailure.class, new ServiceFailureDeserializer()); - mapper.registerModule(module); - } - - @Provider - public AssetService mockAssetService(ServiceExtensionContext context) { - return new AssetServiceStub(new ResponseQueue(recordedRequests, monitor)); - } - - @Provider - public CatalogService mockCatalogService() { - return new CatalogService() { - @Override - public CompletableFuture> requestCatalog(ParticipantContext participantContext, String counterPartyId, String counterPartyAddress, String protocol, QuerySpec querySpec, String... additionalScopes) { - return null; - } - - @Override - public CompletableFuture> requestDataset(ParticipantContext participantContext, String id, String counterPartyId, String counterPartyAddress, String protocol) { - return null; - } - }; - } - - @Provider - public ContractAgreementService mockContractAgreementService() { - return new ContractAgreementServiceStub(new ResponseQueue(recordedRequests, monitor)); - } - - @Provider - public ContractDefinitionService mockContractDefService() { - return new ContractDefinitionServiceStub(new ResponseQueue(recordedRequests, monitor)); - } - - @Provider - public ContractNegotiationService mockContractNegService() { - return new ContractNegotiationServiceStub(new ResponseQueue(recordedRequests, monitor)); - } - - @Provider - public PolicyDefinitionService mockPolicyDefService() { - return new PolicyDefinitionServiceStub(new ResponseQueue(recordedRequests, monitor)); - } - - @Provider - public TransferProcessService mockTransferProcessService() { - return new TransferProcessServiceStub(new ResponseQueue(recordedRequests, monitor)); - } - -} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedRequest.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedRequest.java deleted file mode 100644 index 40ec1bd09e..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedRequest.java +++ /dev/null @@ -1,103 +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; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.eclipse.edc.spi.result.ServiceFailure; - -/** - * Represents a request, that the service stub will replay. It has an input object (can be null), an output object, some metadata - * like name and description and a {@link MatchType}. - * In addition, it can have a {@link ServiceFailure}, in which case the {@code output} object is disregarded and the failure is always returned. - * This can be used to mock a failed API call. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -@JsonDeserialize(using = RecordedResponseDeserializer.class) -public final class RecordedRequest { - private final I input; - private final O output; - private String description; - private String name; - private MatchType inputMatchType; - private ServiceFailure failure; - - private RecordedRequest(I input, O output) { - this.input = input; - this.output = output; - } - - public I getInput() { - return input; - } - - public O getOutput() { - return output; - } - - public MatchType getInputMatchType() { - return inputMatchType; - } - - public ServiceFailure getFailure() { - return failure; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public static class Builder { - private final RecordedRequest instance; - - public Builder(I input, O output) { - instance = new RecordedRequest<>(input, output); - } - - public Builder inputMatchType(MatchType input) { - instance.inputMatchType = input; - return this; - } - - public Builder name(String name) { - instance.name = name; - return this; - } - - public Builder description(String description) { - instance.description = description; - return this; - } - - public Builder failure(ServiceFailure failure) { - instance.failure = failure; - return this; - } - - public RecordedRequest build() { - return instance; - } - } -} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedResponseDeserializer.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedResponseDeserializer.java deleted file mode 100644 index 78958fcaa2..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/RecordedResponseDeserializer.java +++ /dev/null @@ -1,80 +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; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import org.eclipse.edc.spi.result.ServiceFailure; - -import java.io.IOException; -import java.util.Optional; - -/** - * Custom deserializer for a {@link RecordedRequest} object, to be able to instantiate the input and output objects according - * to their class description. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -class RecordedResponseDeserializer extends StdDeserializer> { - - public static final String INPUT_OBJECT = "input"; - public static final String OUTPUT_OBJECT = "output"; - public static final String CLASS_FIELD = "class"; - public static final String DATA_FIELD = "data"; - public static final String INPUT_MATCH_TYPE_FIELD = "match_type"; - public static final String NAME_FIELD = "name"; - public static final String DESCRIPTION_FIELD = "description"; - private static final String FAILURE_OBJECT = "failure"; - - RecordedResponseDeserializer() { - this(null); - } - - protected RecordedResponseDeserializer(Class vc) { - super(vc); - } - - @Override - public RecordedRequest deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - JsonNode node = jp.getCodec().readTree(jp); - var input = node.get(INPUT_OBJECT); - var output = node.get(OUTPUT_OBJECT); - try { - var inputClass = Class.forName(input.get(CLASS_FIELD).asText()); - var outputClass = Class.forName(output.get(CLASS_FIELD).asText()); - - var inputObj = ctxt.readTreeAsValue(input.get(DATA_FIELD), inputClass); - var matchType = Optional.ofNullable(input.get("matchType")).map(JsonNode::asText).map(MatchType::valueOf).orElse(MatchType.CLASS); - var outputObj = ctxt.readTreeAsValue(output.get(DATA_FIELD), outputClass); - - return new RecordedRequest.Builder(inputObj, outputObj) - .inputMatchType(matchType) - .failure(ctxt.readTreeAsValue(node.get(FAILURE_OBJECT), ServiceFailure.class)) - .name(Optional.ofNullable(node.get(NAME_FIELD)).map(JsonNode::asText).orElse(null)) - .description(Optional.ofNullable(node.get(DESCRIPTION_FIELD)).map(JsonNode::asText).orElse(null)) - .build(); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } -} 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 deleted file mode 100644 index 6e4db2b674..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ResponseQueue.java +++ /dev/null @@ -1,147 +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; - -import org.eclipse.edc.spi.EdcException; -import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.result.Result; -import org.eclipse.edc.spi.result.ServiceFailure; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; - -import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; -import java.util.List; -import java.util.Queue; - -/** - * Container object that maintains the queue of {@link RecordedRequest} objects and grants high-level access to it. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class ResponseQueue { - private final Queue> recordedRequests; // todo guard access with locks? - private final Monitor monitor; - - public ResponseQueue(Queue> recordedRequests, Monitor monitor) { - this.recordedRequests = recordedRequests; - this.monitor = monitor.withPrefix(getClass().getSimpleName()); - } - - public ServiceResult getNext(Class outputClass, String errorMessageTemplate) { - try { - return getNext(outputClass); - } catch (ClassCastException ex) { - var message = errorMessageTemplate.formatted(ex.getMessage()); - monitor.severe(message); // no need for the entire stack trace - return ServiceResult.badRequest(message); - } - } - - /** - * Gets the next item from the request queue and wraps it in a {@link ServiceResult}, where the generic type is a list type. - * To do that, the class of list type is expected as parameter. - * - * @param arrayElementClass The type of elements that are expected to be in the list - * @param errorMessageTemplate An error string template that should contain the '%s' placeholder to receive additional information - * @return A {@link ServiceResult} that contains the payload of the next request - */ - @SuppressWarnings("unchecked") - public ServiceResult> getNextAsList(Class arrayElementClass, String errorMessageTemplate) { - if (arrayElementClass.isArray()) { - return ServiceResult.badRequest("First parameter must be type of list elements. For example, pass Object.class if a List is expected, but '%s' was passed".formatted(arrayElementClass.getName())); - } - var r = Result.ofThrowable(() -> { - T[] serviceResult = (T[]) getNext(arrayElementClass.arrayType(), errorMessageTemplate).orElseThrow(f -> new InvalidRequestException(f.getFailureDetail())); - return Arrays.asList(serviceResult); - }); - if (r.succeeded()) { - return ServiceResult.success(r.getContent()); - } - monitor.severe(errorMessageTemplate.formatted(r.getFailureDetail())); - return ServiceResult.badRequest(r.getFailureDetail()); - } - - /** - * clear the queue - */ - public void clear() { - recordedRequests.clear(); - } - - /** - * adds a {@link RecordedRequest} - */ - public void append(RecordedRequest recordedRequest) { - recordedRequests.offer(recordedRequest); - } - - /** - * provides the contents of the queue as a list - */ - public List> toList() { - return recordedRequests.stream().toList(); //immutable - } - - /** - * Takes the next element from the queue and converts it into a {@link ServiceResult} - * - * @param outputType The desired output type. If the type description in the {@link RecordedRequest} does not match, a failure is returned. - */ - @SuppressWarnings("unchecked") - private ServiceResult getNext(Class outputType) { - monitor.debug("Get next recorded request, expect output of type %s".formatted(outputType)); - var r = recordedRequests.poll(); - - - if (r != null) { - - if (r.getFailure() != null) { - return createResult(r.getFailure()); - } - - monitor.debug("Recorded request fetched, %d remaining.".formatted(recordedRequests.size())); - var recipeOutputType = r.getOutput().getClass(); - if (!recipeOutputType.isAssignableFrom(outputType)) { - return ServiceResult.badRequest("Type mismatch: service invocation requires output type '%s', but Recipe specifies '%s'".formatted(outputType, recipeOutputType)); - } - var output = (T) r.getOutput(); - return ServiceResult.success(output); - } - var message = "Failure: no recorded request left in queue."; - monitor.debug(message); - return ServiceResult.badRequest(message); - } - - // hack to access a protected constructor of the ServiceFailure - private ServiceResult createResult(ServiceFailure failure) { - try { - var ctor = ServiceResult.class.getDeclaredConstructor(Object.class, ServiceFailure.class); - ctor.setAccessible(true); // hack hack hack! I feel dirty doing this... - return (ServiceResult) ctor.newInstance(null, failure); - } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | - InvocationTargetException e) { - throw new EdcException(e); - } - } - - //todo: add method getNextWithMatch that accepts an input and a match type -} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ServiceFailureDeserializer.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ServiceFailureDeserializer.java deleted file mode 100644 index 26755cb012..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/ServiceFailureDeserializer.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; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import org.eclipse.edc.spi.result.ServiceFailure; - -import java.io.IOException; -import java.util.Arrays; - -/** - * Custom deserializer for {@link ServiceFailure}. We need this because there is no default - * CTor, and the public constructor with args is not annotated with {@link com.fasterxml.jackson.annotation.JsonProperty}. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class ServiceFailureDeserializer extends StdDeserializer { - public static final String REASON_FIELD = "reason"; - public static final String MESSAGES_FIELD = "messages"; - - protected ServiceFailureDeserializer(Class vc) { - super(vc); - } - - public ServiceFailureDeserializer() { - this(null); - } - - @Override - public ServiceFailure deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - JsonNode node = jp.getCodec().readTree(jp); - var reason = ServiceFailure.Reason.valueOf(node.get(REASON_FIELD).asText()); - var msgs = Arrays.asList(ctxt.readTreeAsValue(node.get(MESSAGES_FIELD), String[].class)); - - return new ServiceFailure(msgs, reason); - } -} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApi.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApi.java deleted file mode 100644 index 0f74690552..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApi.java +++ /dev/null @@ -1,71 +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.api.instrumentation; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.eclipse.edc.web.spi.ApiErrorDetail; -import org.eclipse.tractusx.edc.mock.RecordedRequest; - -import java.util.List; - -/** - * Instrumentation API for the mock connector. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -@OpenAPIDefinition(info = @Info(description = "This API allows to insert ", title = "Business Partner Group API")) -@Tag(name = "Business Partner Group") -public interface InstrumentationApi { - - @Operation(description = "Adds a new RecordedRequest to the end of the queue.", - responses = { - @ApiResponse(responseCode = "204", description = "The negotiation was successfully initiated."), - @ApiResponse(responseCode = "400", description = "Request body was malformed", - content = @Content(array = @ArraySchema(schema = @Schema(implementation = ApiErrorDetail.class)))), - }) - void addNewRequest(RecordedRequest recordedRequest); - - @Operation(description = "Clears the entire request queue.", - responses = { - @ApiResponse(responseCode = "204", description = "The queue was successfully cleared.") - }) - void clearQueue(); - - @Operation(description = "Return the entire request queue.", - responses = { - @ApiResponse(responseCode = "200", description = "The list of RecordedRequest objects.", - content = @Content(array = @ArraySchema(schema = @Schema(implementation = RecordedRequest.class)))), - }) - List> getRequests(); - - @Operation(description = "Return amount of items currently in the queue.", - responses = { - @ApiResponse(responseCode = "200") - }) - int count(); -} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApiController.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApiController.java deleted file mode 100644 index eaca4abe60..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/api/instrumentation/InstrumentationApiController.java +++ /dev/null @@ -1,76 +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.api.instrumentation; - - -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import org.eclipse.tractusx.edc.mock.RecordedRequest; -import org.eclipse.tractusx.edc.mock.ResponseQueue; - -import java.util.List; - -/** - * Instrumentation controller for the mock connector. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -@Consumes({ MediaType.APPLICATION_JSON }) -@Produces({ MediaType.APPLICATION_JSON }) -@Path("/instrumentation") -public class InstrumentationApiController implements InstrumentationApi { - - private final ResponseQueue responseQueue; - - public InstrumentationApiController(ResponseQueue responseQueue) { - this.responseQueue = responseQueue; - } - - @Override - @POST - public void addNewRequest(RecordedRequest recordedRequest) { - responseQueue.append(recordedRequest); - } - - @Override - @DELETE - public void clearQueue() { - responseQueue.clear(); - } - - @Override - @GET - public List> getRequests() { - return responseQueue.toList(); - } - - @Override - @GET - @Path("/count") - public int count() { - return responseQueue.toList().size(); - } -} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AssetServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AssetServiceStub.java deleted file mode 100644 index 64a746302d..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/AssetServiceStub.java +++ /dev/null @@ -1,70 +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 org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; -import org.eclipse.edc.connector.controlplane.services.spi.asset.AssetService; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.tractusx.edc.mock.ResponseQueue; - -import java.util.List; - -/** - * Stub implementation of the {@link AssetService} for testing purposes. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class AssetServiceStub implements AssetService { - - private final ResponseQueue responseQueue; - - public AssetServiceStub(ResponseQueue responseQueue) { - this.responseQueue = responseQueue; - } - - @Override - public Asset findById(String assetId) { - return responseQueue.getNext(Asset.class, "Error finding asset by ID: %s") - .orElseThrow(InvalidRequestException::new); - } - - @Override - public ServiceResult> search(QuerySpec query) { - return responseQueue.getNextAsList(Asset.class, "Error executing asset search: %s"); - } - - @Override - public ServiceResult create(Asset asset) { - return responseQueue.getNext(Asset.class, "Error executing asset creation: %s"); - } - - @Override - public ServiceResult delete(String assetId) { - return responseQueue.getNext(Asset.class, "Error executing asset deletion: %s"); - } - - @Override - public ServiceResult update(Asset asset) { - return responseQueue.getNext(Asset.class, "Error executing asset update: %s"); - } -} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractAgreementServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractAgreementServiceStub.java deleted file mode 100644 index b03cf11d5e..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractAgreementServiceStub.java +++ /dev/null @@ -1,60 +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 org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.contract.spi.types.negotiation.ContractNegotiation; -import org.eclipse.edc.connector.controlplane.services.spi.contractagreement.ContractAgreementService; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.tractusx.edc.mock.ResponseQueue; - -import java.util.List; - -/** - * Stub implementation of the {@link ContractAgreementService} for testing purposes. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class ContractAgreementServiceStub extends AbstractServiceStub implements ContractAgreementService { - - public ContractAgreementServiceStub(ResponseQueue responseQueue) { - super(responseQueue); - } - - @Override - public ContractAgreement findById(String contractAgreementId) { - return responseQueue.getNext(ContractAgreement.class, "Error finding ContractAgreement: %s") - .orElseThrow(InvalidRequestException::new); - } - - @Override - public ServiceResult> search(QuerySpec query) { - return responseQueue.getNextAsList(ContractAgreement.class, "Error searching ContractAgreement: %s"); - } - - @Override - public ContractNegotiation findNegotiation(String contractAgreementId) { - return responseQueue.getNext(ContractNegotiation.class, "Error finding ContractNegotiation: %s") - .orElseThrow(InvalidRequestException::new); - } -} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractDefinitionServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractDefinitionServiceStub.java deleted file mode 100644 index 83c3ae19e1..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractDefinitionServiceStub.java +++ /dev/null @@ -1,70 +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 org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition; -import org.eclipse.edc.connector.controlplane.services.spi.contractdefinition.ContractDefinitionService; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.tractusx.edc.mock.ResponseQueue; - -import java.util.List; - -/** - * Stub implementation of the {@link ContractDefinitionService}. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class ContractDefinitionServiceStub extends AbstractServiceStub implements ContractDefinitionService { - - - public ContractDefinitionServiceStub(ResponseQueue responseQueue) { - super(responseQueue); - - } - - @Override - public ContractDefinition findById(String contractDefinitionId) { - return responseQueue.getNext(ContractDefinition.class, "Error finding ContractDefinition: %s") - .orElseThrow(InvalidRequestException::new); - } - - @Override - public ServiceResult> search(QuerySpec query) { - return responseQueue.getNextAsList(ContractDefinition.class, "Error searching ContractDefinition: %s"); - } - - @Override - public ServiceResult create(ContractDefinition contractDefinition) { - return responseQueue.getNext(ContractDefinition.class, "Error creating ContractDefinition: %s"); - } - - @Override - public ServiceResult update(ContractDefinition contractDefinition) { - return responseQueue.getNext(Void.class, "Error updating ContractDefinition: %s"); - } - - @Override - public ServiceResult delete(String contractDefinitionId) { - return responseQueue.getNext(ContractDefinition.class, "Error deleting ContractDefinition: %s"); - } -} 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 deleted file mode 100644 index d4009dcd67..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/ContractNegotiationServiceStub.java +++ /dev/null @@ -1,84 +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 org.eclipse.edc.connector.controlplane.contract.spi.types.agreement.ContractAgreement; -import org.eclipse.edc.connector.controlplane.contract.spi.types.command.TerminateNegotiationCommand; -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; -import org.eclipse.tractusx.edc.mock.ResponseQueue; - -import java.util.List; - -/** - * Stub implementation of the {@link ContractNegotiationService} for testing purposes. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class ContractNegotiationServiceStub extends AbstractServiceStub implements ContractNegotiationService { - public ContractNegotiationServiceStub(ResponseQueue responseQueue) { - super(responseQueue); - } - - @Override - public ContractNegotiation findbyId(String contractNegotiationId) { - return responseQueue.getNext(ContractNegotiation.class, "Error finding ContractNegotiation: %s") - .orElseThrow(InvalidRequestException::new); - } - - @Override - public ServiceResult> search(QuerySpec query) { - return responseQueue.getNextAsList(ContractNegotiation.class, "Error searching for ContractNegotiation: %s"); - } - - @Override - public String getState(String negotiationId) { - return responseQueue.getNext(String.class, "Error getting state of ContractNegotiation: %s") - .orElseThrow(InvalidRequestException::new); - } - - @Override - public ContractAgreement getForNegotiation(String negotiationId) { - return responseQueue.getNext(ContractAgreement.class, "Error getting ContractAgreement: %s") - .orElseThrow(InvalidRequestException::new); - } - - @Override - public ContractNegotiation initiateNegotiation(ParticipantContext participantContext, ContractRequest request) { - return responseQueue.getNext(ContractNegotiation.class, "Error initiating ContractNegotiation: %s") - .orElseThrow(InvalidRequestException::new); - } - - @Override - public ServiceResult terminate(TerminateNegotiationCommand command) { - return responseQueue.getNext(Void.class, "Error terminating ContractAgreement: %s"); - } - - @Override - public ServiceResult delete(String negotiationId) { - return responseQueue.getNext(Void.class, "Error deleting ContractAgreement: %s"); - } -} diff --git a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/PolicyDefinitionServiceStub.java b/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/PolicyDefinitionServiceStub.java deleted file mode 100644 index 424c48fe4b..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/PolicyDefinitionServiceStub.java +++ /dev/null @@ -1,83 +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 org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition; -import org.eclipse.edc.connector.controlplane.services.spi.policydefinition.PolicyDefinitionService; -import org.eclipse.edc.policy.engine.spi.plan.PolicyEvaluationPlan; -import org.eclipse.edc.policy.model.Policy; -import org.eclipse.edc.spi.query.QuerySpec; -import org.eclipse.edc.spi.result.ServiceResult; -import org.eclipse.edc.web.spi.exception.InvalidRequestException; -import org.eclipse.tractusx.edc.mock.ResponseQueue; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -/** - * Stub implementation of the PolicyDefinitionService. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class PolicyDefinitionServiceStub extends AbstractServiceStub implements PolicyDefinitionService { - - public PolicyDefinitionServiceStub(ResponseQueue responseQueue) { - super(responseQueue); - } - - @Override - public PolicyDefinition findById(String policyId) { - return responseQueue.getNext(PolicyDefinition.class, "Error finding PolicyDefinition by id: %s") - .orElseThrow(InvalidRequestException::new); - } - - @Override - public ServiceResult> search(QuerySpec query) { - return responseQueue.getNextAsList(PolicyDefinition.class, "Error executing PolicyDefinition search: %s"); - } - - @Override - public @NotNull ServiceResult deleteById(String policyId) { - return responseQueue.getNext(PolicyDefinition.class, "Error deleting PolicyDefinition: %s"); - } - - @Override - public @NotNull ServiceResult create(PolicyDefinition policy) { - return responseQueue.getNext(PolicyDefinition.class, "Error creating PolicyDefinition: %s"); - } - - @Override - public ServiceResult update(PolicyDefinition policy) { - return responseQueue.getNext(PolicyDefinition.class, "Error updating PolicyDefinition: %s"); - } - - @Override - public ServiceResult validate(Policy policy) { - return responseQueue.getNext(Void.class, "Error validating PolicyDefinition: %s"); - } - - @Override - public ServiceResult createEvaluationPlan(String s, Policy policy) { - return responseQueue.getNext(PolicyEvaluationPlan.class, "Error creating evaluation plan for PolicyDefinition: %s"); - } - - -} 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 deleted file mode 100644 index 2bf880c54e..0000000000 --- a/edc-tests/runtime/mock-connector/src/main/java/org/eclipse/tractusx/edc/mock/services/TransferProcessServiceStub.java +++ /dev/null @@ -1,113 +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 org.eclipse.edc.connector.controlplane.services.spi.transferprocess.TransferProcessService; -import org.eclipse.edc.connector.controlplane.transfer.spi.types.DeprovisionedResource; -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.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; -import org.eclipse.tractusx.edc.mock.ResponseQueue; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -/** - * Stub implementation of the {@link TransferProcessService} for testing purposes. - * - * @deprecated since 0.11.0 - */ -@Deprecated(since = "0.11.0") -public class TransferProcessServiceStub extends AbstractServiceStub implements TransferProcessService { - - public TransferProcessServiceStub(ResponseQueue responseQueue) { - super(responseQueue); - } - - @Override - public @Nullable TransferProcess findById(String transferProcessId) { - return responseQueue.getNext(TransferProcess.class, "Error finding TransferProcess: %s").orElseThrow(f -> new InvalidRequestException(f.getFailureDetail())); - } - - @Override - public ServiceResult> search(QuerySpec query) { - return responseQueue.getNextAsList(TransferProcess.class, "Error executing TransferProcess search: %s"); - } - - @Override - public @Nullable String getState(String transferProcessId) { - return responseQueue.getNext(String.class, "Error obtaining TransferProcess status: %s") - .orElseThrow(InvalidRequestException::new); - } - - @Override - public @NotNull ServiceResult complete(String transferProcessId) { - return responseQueue.getNext(Void.class, "Error completing TransferProcess: %s"); - } - - @Override - public @NotNull ServiceResult terminate(TerminateTransferCommand command) { - return responseQueue.getNext(Void.class, "Error terminating TransferProcess: %s"); - } - - @Override - public @NotNull ServiceResult suspend(SuspendTransferCommand command) { - return responseQueue.getNext(Void.class, "Error suspending TransferProcess: %s"); - } - - @Override - public @NotNull ServiceResult resume(ResumeTransferCommand command) { - return responseQueue.getNext(Void.class, "Error resuming TransferProcess: %s"); - } - - @Override - public @NotNull ServiceResult deprovision(String transferProcessId) { - return responseQueue.getNext(Void.class, "Error deprovisioning TransferProcess: %s"); - } - - @Override - public @NotNull ServiceResult initiateTransfer(ParticipantContext participantContext, TransferRequest request) { - return responseQueue.getNext(TransferProcess.class, "Error initiating TransferProcess: %s"); - } - - @Override - public ServiceResult notifyPrepared(NotifyPreparedCommand command) { - return responseQueue.getNext(Void.class, "Error notifying prepared on TransferProcess: %s"); - } - - @Override - public ServiceResult completeDeprovision(String transferProcessId, DeprovisionedResource resource) { - return responseQueue.getNext(Void.class, "Error completing/deprovisioning TransferProcess: %s"); - } - - @Override - public ServiceResult addProvisionedResource(String transferProcessId, ProvisionResponse response) { - return responseQueue.getNext(Void.class, "Error adding Provisioned resource to TransferProcess: %s"); - } -} diff --git a/edc-tests/runtime/mock-connector/src/test/java/org/eclipse/tractusx/edc/mock/RecordedRequestTest.java b/edc-tests/runtime/mock-connector/src/test/java/org/eclipse/tractusx/edc/mock/RecordedRequestTest.java deleted file mode 100644 index e8e2853eef..0000000000 --- a/edc-tests/runtime/mock-connector/src/test/java/org/eclipse/tractusx/edc/mock/RecordedRequestTest.java +++ /dev/null @@ -1,73 +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; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset; -import org.eclipse.edc.junit.testfixtures.TestUtils; -import org.eclipse.edc.spi.result.ServiceFailure; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class RecordedRequestTest { - - private final ObjectMapper mapper = new ObjectMapper(); - - @BeforeEach - void setup() { - var module = new SimpleModule(); - module.addDeserializer(ServiceFailure.class, new ServiceFailureDeserializer()); - mapper.registerModule(module); - } - - @Test - void verifySerDes() throws JsonProcessingException { - var json = TestUtils.getResourceFileContentAsString("asset.creation.json"); - var rr = mapper.readValue(json, RecordedRequest.class); - - assertThat(rr).isNotNull(); - assertThat(rr.getInput()).isInstanceOf(Asset.class); - assertThat(rr.getOutput()).isInstanceOf(Asset.class); - assertThat(rr.getInputMatchType()).isEqualTo(MatchType.CLASS); - assertThat(rr.getFailure()).isNull(); - assertThat(rr.getName()).isEqualTo("Asset Creation V3"); - assertThat(rr.getDescription()).isEqualTo("test description"); - } - - @Test - void verifySerDes_withFailure() throws JsonProcessingException { - var json = TestUtils.getResourceFileContentAsString("asset.failure.json"); - var rr = mapper.readValue(json, RecordedRequest.class); - - assertThat(rr).isNotNull(); - assertThat(rr.getInput()).isInstanceOf(Asset.class); - assertThat(rr.getOutput()).isNull(); - assertThat(rr.getInputMatchType()).isEqualTo(MatchType.CLASS); - assertThat(rr.getName()).isEqualTo("Some failed asset request"); - assertThat(rr.getDescription()).isEqualTo("test description"); - assertThat(rr.getFailure()).isInstanceOf(ServiceFailure.class); - assertThat(rr.getFailure().getReason()).isEqualTo(ServiceFailure.Reason.UNAUTHORIZED); - assertThat(rr.getFailure().getMessages()).containsExactlyInAnyOrder("message 1", "message 2"); - } -} \ No newline at end of file diff --git a/edc-tests/runtime/mock-connector/src/test/resources/asset.creation.json b/edc-tests/runtime/mock-connector/src/test/resources/asset.creation.json deleted file mode 100644 index e22569a120..0000000000 --- a/edc-tests/runtime/mock-connector/src/test/resources/asset.creation.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "Asset Creation V3", - "description": "test description", - "input": { - "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset", - "data": { - "id": "asset-1", - "properties": { - "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json", - "https://w3id.org/edc/v0.0.1/ns/prop1": "value1" - } - } - }, - "output": { - "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset", - "data": { - "id": "asset-1", - "properties": { - "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json", - "https://w3id.org/edc/v0.0.1/ns/prop1": "value1" - } - } - } -} \ No newline at end of file diff --git a/edc-tests/runtime/mock-connector/src/test/resources/asset.failure.json b/edc-tests/runtime/mock-connector/src/test/resources/asset.failure.json deleted file mode 100644 index 22af583514..0000000000 --- a/edc-tests/runtime/mock-connector/src/test/resources/asset.failure.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "Some failed asset request", - "description": "test description", - "input": { - "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset", - "data": { - "id": "asset-1", - "properties": { - "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json", - "https://w3id.org/edc/v0.0.1/ns/prop1": "value1" - } - } - }, - "output": { - "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset" - }, - "failure": { - "reason": "UNAUTHORIZED", - "messages": [ - "message 1", - "message 2" - ] - } -} \ No newline at end of file diff --git a/edc-tests/runtime/runtime-compatibility/stable/connector-stable/build.gradle.kts b/edc-tests/runtime/runtime-compatibility/stable/connector-stable/build.gradle.kts index 9c48852d5e..0ee6eefbb8 100644 --- a/edc-tests/runtime/runtime-compatibility/stable/connector-stable/build.gradle.kts +++ b/edc-tests/runtime/runtime-compatibility/stable/connector-stable/build.gradle.kts @@ -37,7 +37,7 @@ dependencies { exclude("org.eclipse.edc", "vault-hashicorp") } runtimeOnly(project(":edc-tests:runtime:runtime-compatibility:stable:extensions")) - runtimeOnly(stableLibs.edc.identity.trust.sts.remote.client) + runtimeOnly(stableLibs.edc.spi.decentralized.claims) runtimeOnly(stableLibs.edc.auth.oauth2.client) } @@ -55,6 +55,7 @@ tasks.shadowJar { mergeServiceFiles() duplicatesStrategy = DuplicatesStrategy.INCLUDE archiveFileName.set("${project.name}.jar") + transform(com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer()) } // configure the "dockerize" task diff --git a/edc-tests/runtime/runtime-compatibility/stable/connector-stable/src/main/docker/Dockerfile b/edc-tests/runtime/runtime-compatibility/stable/connector-stable/src/main/docker/Dockerfile index 08121bdd85..5567531bf4 100644 --- a/edc-tests/runtime/runtime-compatibility/stable/connector-stable/src/main/docker/Dockerfile +++ b/edc-tests/runtime/runtime-compatibility/stable/connector-stable/src/main/docker/Dockerfile @@ -1,5 +1,5 @@ # -buster is required to have apt available -FROM eclipse-temurin:24.0.2_12-jre-alpine +FROM eclipse-temurin:25-jre-alpine@sha256:f10d6259d0798c1e12179b6bf3b63cea0d6843f7b09c9f9c9c422c50e44379ec # Optional JVM arguments, such as memory settings ARG JVM_ARGS="" diff --git a/edc-tests/runtime/runtime-compatibility/stable/extensions/src/main/java/org/eclipse/tractusx/edc/compatibility/tests/AudienceSeedExtension.java b/edc-tests/runtime/runtime-compatibility/stable/extensions/src/main/java/org/eclipse/tractusx/edc/compatibility/tests/AudienceSeedExtension.java index ff2dda3210..f6252ca56d 100644 --- a/edc-tests/runtime/runtime-compatibility/stable/extensions/src/main/java/org/eclipse/tractusx/edc/compatibility/tests/AudienceSeedExtension.java +++ b/edc-tests/runtime/runtime-compatibility/stable/extensions/src/main/java/org/eclipse/tractusx/edc/compatibility/tests/AudienceSeedExtension.java @@ -46,13 +46,40 @@ public class AudienceSeedExtension implements ServiceExtension { @Provider public BdrsClient bdrsClient(ServiceExtensionContext context) { var dids = readDidsMapping(context); - return dids::get; + return new BdrsClient() { + @Override + public String resolveDid(String bpn) { + return dids.get(bpn); + } + + @Override + public String resolveBpn(String did) { + return dids.entrySet().stream() + .filter(e -> e.getValue().equals(did)) + .map(Map.Entry::getKey) + .findFirst() + .orElse(null); + } + }; } @Provider public AudienceResolver audienceResolver(ServiceExtensionContext context) { var dids = readDidsMapping(context); - return message -> Result.success(dids.get(message.getCounterPartyId())); + return message -> { + var counterPartyId = message.getCounterPartyId(); + + if (counterPartyId.startsWith("did:")) { + return Result.success(counterPartyId); + } + + var audience = dids.get(counterPartyId); + if (audience != null) { + return Result.success(audience); + } + + return Result.failure("No DID found for counter-party: " + counterPartyId); + }; } private Map readDidsMapping(ServiceExtensionContext context) { diff --git a/edc-tests/runtime/runtime-dcp-tck/build.gradle.kts b/edc-tests/runtime/runtime-dcp-tck/build.gradle.kts index d9709c62dd..0d0f8ba203 100644 --- a/edc-tests/runtime/runtime-dcp-tck/build.gradle.kts +++ b/edc-tests/runtime/runtime-dcp-tck/build.gradle.kts @@ -27,7 +27,6 @@ dependencies { implementation(project(":edc-controlplane:edc-controlplane-base")) { exclude(module = "cx-dcp") } - 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 456104b07c..315b49372b 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 @@ -28,8 +28,11 @@ dependencies { exclude(module = "cx-dcp") exclude(module = "tx-dcp-sts-div") exclude(module = "dataspace-protocol") - exclude(module = "dsp-08") - exclude(module = "dsp-2024") + exclude(module = "dsp-catalog-08") + exclude(module = "dsp-http-api-configuration-08") + exclude(module = "dsp-http-dispatcher-08") + exclude(module = "dsp-negotiation-08") + exclude(module = "dsp-transfer-process-08") exclude(module = "dsp-2025") exclude(module = "dsp-http-api-configuration-2025") } 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 87fb81155b..6ba211c59d 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 @@ -28,7 +28,6 @@ dependencies { exclude(module = "cx-dcp") exclude(module = "tx-dcp-sts-div") exclude(module = "dataspace-protocol") - exclude(module= "dsp-2024") exclude(module= "dsp-2025") exclude(module = "dsp-http-api-configuration-2025") } diff --git a/edc-tests/runtime/runtime-postgresql/build.gradle.kts b/edc-tests/runtime/runtime-postgresql/build.gradle.kts index 556bff33fb..bc85e02664 100644 --- a/edc-tests/runtime/runtime-postgresql/build.gradle.kts +++ b/edc-tests/runtime/runtime-postgresql/build.gradle.kts @@ -35,6 +35,10 @@ dependencies { exclude("org.eclipse.edc", "vault-hashicorp") } implementation(project(":edc-extensions:single-participant-vault")) + + // Kafka streaming is opt-in and not part of the base runtimes, so the e2e Kafka test needs it wired in here + implementation(project(":edc-extensions:dataplane:kafka:kafka-broker-extension")) + implementation(project(":edc-extensions:dataplane:kafka:validator-data-address-kafka")) } application { diff --git a/gradle.properties b/gradle.properties index 89f9507794..e4215b2eb1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,9 @@ group=org.eclipse.tractusx.edc -version=0.13.0-SNAPSHOT +version=0.13.0 # configure the build: txScmConnection=scm:git:git@github.com:eclipse-tractusx/tractusx-edc.git txWebsiteUrl=https://github.com/eclipse-tractusx/tractusx-edc.git txScmUrl=https://github.com/eclipse-tractusx/tractusx-edc.git + +# construction-x-properties +con-x-edcVersion=0.17.0 diff --git a/gradle/libs.stable.versions.toml b/gradle/libs.stable.versions.toml index c7e36ad42f..aa79129916 100644 --- a/gradle/libs.stable.versions.toml +++ b/gradle/libs.stable.versions.toml @@ -2,8 +2,8 @@ format.version = "1.1" [versions] -tractusx = "0.9.0" -edc = "0.11.1" +tractusx = "0.12.1" +edc = "0.15.1" [libraries] tx-edc-controlplane-postgresql-hashicorp-vault = { module = "org.eclipse.tractusx.edc:edc-controlplane-postgresql-hashicorp-vault", version.ref = "tractusx" } @@ -11,5 +11,5 @@ tx-edc-dataplane-postgresql-hashicorp-vault = { module = "org.eclipse.tractusx.e edc-boot-spi = { module = "org.eclipse.edc:boot-spi", version.ref = "edc" } edc-core-spi = { module = "org.eclipse.edc:core-spi", version.ref = "edc" } tx-bdrs-client-spi = { module = "org.eclipse.tractusx.edc:bdrs-client-spi", version.ref = "tractusx" } -edc-identity-trust-sts-remote-client = { module = "org.eclipse.edc:identity-trust-sts-remote-client", version.ref = "edc" } +edc-spi-decentralized-claims = { module = "org.eclipse.edc:decentralized-claims-spi", version.ref = "edc" } edc-auth-oauth2-client = { module = "org.eclipse.edc:oauth2-client", version.ref = "edc" } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 33ed321338..4781b74fdb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,32 +2,34 @@ format.version = "1.1" [versions] -edc = "0.15.1" -edc-next = "0.16.0" -edc-build = "1.1.6" -allure = "2.33.0" +edc = "0.17.0" +edc-build = "1.5.2" +allure = "2.35.4" awaitility = "4.3.0" -aws = "2.42.12" -azure-storage-blob = "12.33.2" -bouncyCastle-jdk18on = "1.83" -dcp-tck = "1.0.0-RC6" -dsp-tck = "1.0.0-RC6" -flyway = "12.1.1" -jackson = "2.21.1" +aws = "2.53.0" +azure-storage-blob = "12.35.0" +bouncyCastle-jdk18on = "1.85" +dcp-tck = "1.0.1" +dsp-tck = "1.0.0" +common-tck = "1.0.0" +flyway = "13.3.0" +jackson = "2.22.1" jakarta-json = "2.1.3" -junit = "6.0.3" -nimbus = "10.8" -okhttp = "5.3.2" -opentelemetry = "2.26.0" -opentelemetry-instrumentation = "2.25.0" -opentelemetry-log4j-appender = "2.25.0-alpha" -postgres = "42.7.10" -restAssured = "6.0.0" +jsonschema = "2.0.0" +junit = "6.1.3" +kafka = "4.3.1" +nimbus = "10.9.1" +okhttp = "5.4.0" +opentelemetry = "2.30.0" +opentelemetry-instrumentation = "2.30.0" +opentelemetry-log4j-appender = "2.30.0-alpha" +postgres = "42.7.13" +restAssured = "6.0.1" rsApi = "4.0.0" -testcontainers = "2.0.4" -testcontainers-keycloak = "4.1.1" +testcontainers = "2.0.5" +testcontainers-keycloak = "4.3.1" titanium = "1.7.0" -log4j2 = "2.25.3" +log4j2 = "2.26.1" wiremock = "3.13.2" @@ -51,7 +53,6 @@ edc-spi-http = { module = "org.eclipse.edc:http-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" } @@ -59,7 +60,6 @@ edc-spi-participant-context-single = { module = "org.eclipse.edc:participant-con 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-sts = { module = "org.eclipse.edc:sts-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" } @@ -73,10 +73,12 @@ 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-transform-controlplane = { module = "org.eclipse.edc:control-plane-transform", 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-participant-context = { module = "org.eclipse.edc:participant-context-core", version.ref = "edc" } +edc-core-participant-context-classic = { module = "org.eclipse.edc:participant-context-connector-classic-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" } @@ -88,6 +90,7 @@ edc-api-management = { module = "org.eclipse.edc:management-api", version.ref = edc-api-management-test-fixtures = { module = "org.eclipse.edc:management-api-test-fixtures", version.ref = "edc" } edc-iam-mock = { module = "org.eclipse.edc:iam-mock", version.ref = "edc" } edc-iam-decentralized-claims-core = { module = "org.eclipse.edc:decentralized-claims-core", version.ref = "edc" } +edc-iam-decentralized-claims-service = { module = "org.eclipse.edc:decentralized-claims-service", version.ref = "edc" } edc-auth-tokenbased = { module = "org.eclipse.edc:auth-tokenbased", version.ref = "edc" } edc-auth-configuration = { module = "org.eclipse.edc:auth-configuration", version.ref = "edc" } edc-auth-delegated = { module = "org.eclipse.edc:auth-delegated", version.ref = "edc" } @@ -95,9 +98,10 @@ edc-auth-oauth2-client = { module = "org.eclipse.edc:oauth2-client", version.ref edc-transaction-local = { module = "org.eclipse.edc:transaction-local", version.ref = "edc" } edc-ext-http = { module = "org.eclipse.edc:http", version.ref = "edc" } edc-ext-jsonld = { module = "org.eclipse.edc:json-ld", version.ref = "edc" } +edc-lib-jsonld = { module = "org.eclipse.edc:json-ld-lib", 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-api-management-validator-jsonschema = { module = "org.eclipse.edc:management-api-schema-validator", version.ref = "edc" } # EDC lib dependencies edc-lib-api = { module = "org.eclipse.edc:api-lib", version.ref = "edc" } @@ -115,9 +119,17 @@ edc-lib-util = { module = "org.eclipse.edc:util-lib", version.ref = "edc" } edc-lib-validator = { module = "org.eclipse.edc:validator-lib", version.ref = "edc" } 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-catalog-validation= { module = "org.eclipse.edc:dsp-catalog-validation-lib", version.ref = "edc" } +edc-lib-dsp-catalog-transform= { module = "org.eclipse.edc:dsp-catalog-transform-lib", version.ref = "edc" } +edc-lib-dsp-negotiation-validation= { module = "org.eclipse.edc:dsp-negotiation-validation-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-negotiation-transform= { module = "org.eclipse.edc:dsp-negotiation-transform-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-dsp-transfer-process-validation= { module = "org.eclipse.edc:dsp-transfer-process-validation-lib", version.ref = "edc" } +edc-lib-dsp-transfer-process-http-api= { module = "org.eclipse.edc:dsp-transfer-process-http-api-lib", version.ref = "edc" } +edc-lib-dsp-transfer-process-transform= { module = "org.eclipse.edc:dsp-transfer-process-transform-lib", version.ref = "edc" } edc-lib-dcp = { module = "org.eclipse.edc:decentralized-claims-lib", version.ref = "edc" } +edc-lib-verifiable-credentials = { module = "org.eclipse.edc:verifiable-credentials-lib", version.ref = "edc" } # implementations edc-sql-assetindex = { module = "org.eclipse.edc:asset-index-sql", version.ref = "edc" } @@ -156,6 +168,11 @@ edc-verifiablecredentials-jwt = { module = "org.eclipse.edc:jwt-verifiable-crede dcp-testcases = { module = "org.eclipse.dataspacetck.dcp:dcp-testcases", version.ref = "dcp-tck" } dcp-system = { module = "org.eclipse.dataspacetck.dcp:dcp-system", version.ref = "dcp-tck" } +# Common-TCK libraries +tck-runtime = { module = "org.eclipse.dataspacetck.common:tck-runtime", version.ref = "common-tck" } +tck-core = { module = "org.eclipse.dataspacetck.common:core", version.ref = "common-tck" } + + # DSP-TCK libraries dsp-tck-runtime = { module = "org.eclipse.dataspacetck.dsp:tck-runtime", version.ref = "dsp-tck" } dsp-tck-core = { module = "org.eclipse.dataspacetck.dsp:core", version.ref = "dsp-tck" } @@ -166,12 +183,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-next" } +tck-extension = { module = "org.eclipse.edc:tck-extension", version.ref = "edc" } # DSP libraries dsp-spi-http = { module = "org.eclipse.edc:dsp-http-spi", version.ref = "edc" } -dsp-spi-v08 = { module = "org.eclipse.edc:dsp-spi-08", version.ref = "edc" } dsp-spi-v2025 = { module = "org.eclipse.edc:dsp-spi-2025", version.ref = "edc" } +dsp-spi= { module = "org.eclipse.edc:dsp-spi", version.ref = "edc" } ## IH for testing @@ -211,6 +228,8 @@ flyway-database-postgres = { module = "org.flywaydb:flyway-database-postgresql", 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 = "jakarta.json:jakarta.json-api", version.ref = "jakarta-json" } +jsonschema = { module = "com.networknt:json-schema-validator", version.ref = "jsonschema" } +kafka-clients = { module = "org.apache.kafka:kafka-clients", version.ref = "kafka" } 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" } @@ -218,6 +237,7 @@ opentelemetry-instrumentation-annotations = { module = "io.opentelemetry.instrum postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" } restAssured = { module = "io.rest-assured:rest-assured", version.ref = "restAssured" } testcontainers-junit = { module = "org.testcontainers:testcontainers-junit-jupiter", version.ref = "testcontainers" } +testcontainers-kafka = { module = "org.testcontainers:testcontainers-kafka", version.ref = "testcontainers" } testcontainers-keycloak = { module = "com.github.dasniko:testcontainers-keycloak", version.ref = "testcontainers-keycloak" } testcontainers-minio = { module = "org.testcontainers:testcontainers-minio", version.ref = "testcontainers" } testcontainers-localstack = { module = "org.testcontainers:testcontainers-localstack", version.ref = "testcontainers" } @@ -241,6 +261,6 @@ edc-sts = [ [plugins] docker = { id = "com.bmuschko.docker-remote-api", version = "10.0.0" } -shadow = { id = "com.gradleup.shadow", version = "9.3.2" } -swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.45" } +shadow = { id = "com.gradleup.shadow", version = "9.6.1" } +swagger = { id = "io.swagger.core.v3.swagger-gradle-plugin", version = "2.2.53" } 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 d997cfc60f..eddabd2eef 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 dbc3ce4a04..69dd0d0404 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 0262dcbd52..249efbb032 100755 --- a/gradlew +++ b/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/gradlew.bat b/gradlew.bat old mode 100755 new mode 100644 index e509b2dd8f..8508ef684d --- a/gradlew.bat +++ b/gradlew.bat @@ -19,12 +19,12 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,29 +65,18 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/resources/Dockerfile b/resources/Dockerfile index 077b219a41..af6d77812c 100644 --- a/resources/Dockerfile +++ b/resources/Dockerfile @@ -19,7 +19,7 @@ # SPDX-License-Identifier: Apache-2.0 ################################################################################# -FROM eclipse-temurin:25-jre-alpine +FROM eclipse-temurin:25-jre-alpine@sha256:28db6fdf60e38945e43d840c0333aeaec66c15943070104f7586fd3c9d1665b0 RUN apk update && apk upgrade --no-cache ARG JAR diff --git a/resources/hashtag.header b/resources/hashtag.header index 15b2ffccba..f6ccc4b0d9 100644 --- a/resources/hashtag.header +++ b/resources/hashtag.header @@ -16,5 +16,6 @@ ^# under the License\.$ ^#$ ^# SPDX-License-Identifier: Apache\-2\.0$ +^# Assisted-By:.*$ ^##+$ ^$ diff --git a/resources/java.header b/resources/java.header index 8ff72fff09..d1e8dc86e9 100644 --- a/resources/java.header +++ b/resources/java.header @@ -15,6 +15,5 @@ ^ \* under the License\.$ ^ \*$ ^ \* SPDX-License-Identifier: Apache\-2\.0$ -^ \*+/$ -^$ -package .* \ No newline at end of file +^ \* Assisted-By:.*$ +^ \*+/$ \ No newline at end of file diff --git a/resources/tx-checkstyle-config.xml b/resources/tx-checkstyle-config.xml index 5ca5d0548b..361cd7b51c 100644 --- a/resources/tx-checkstyle-config.xml +++ b/resources/tx-checkstyle-config.xml @@ -46,13 +46,13 @@ - + - + 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 deleted file mode 100644 index 93756903e1..0000000000 --- a/samples/testing-with-mocked-connector/src/test/java/org/eclipse/tractusx/edc/samples/mockedc/UseMockConnectorSampleTest.java +++ /dev/null @@ -1,141 +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.samples.mockedc; - -import io.restassured.http.ContentType; -import io.restassured.specification.RequestSpecification; -import jakarta.json.JsonArray; -import org.eclipse.edc.junit.annotations.ComponentTest; -import org.eclipse.edc.junit.testfixtures.TestUtils; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import static io.restassured.RestAssured.given; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * This example demonstrates how to use the Mock-Connector as a drop-in replacement runtime for testing client code that uses EDC's - * Management API. While this is written in Java, the concepts are easily translatable into any language where test containers are - * supported. - */ -@Testcontainers -@ComponentTest -public class UseMockConnectorSampleTest { - - private static final int DEFAULT_PORT = 8080; - private static final int MANAGEMENT_PORT = 8081; - - @Container - private final GenericContainer edcContainer = new GenericContainer<>("mock-connector") - .withEnv("WEB_HTTP_PORT", String.valueOf(DEFAULT_PORT)) - .withEnv("WEB_HTTP_PATH", "/api") - .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 - void test_getAsset() { - //prime the mock - post a RecordedRequest - setupNextResponse("asset.request.json"); - - // perform the actual Asset API request. In a real test scenario, this would be the client code we're testing, i.e. the - // System-under-Test (SuT). - var assetArray = mgmtRequest() - .contentType(ContentType.JSON) - .body(""" - { - "@context": { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/" - }, - "@type": "QuerySpec" - } - """) - .post("/v3/assets/request") - .then() - .log().ifValidationFails() - .statusCode(200) - .extract().body().as(JsonArray.class); - - // assert the response - assertThat(assetArray).hasSize(1); - assertThat(assetArray.get(0).asJsonObject().get("properties")) - .hasFieldOrProperty("prop1") - .hasFieldOrProperty("id") - .hasFieldOrProperty("contenttype"); - } - - @Test - void test_apiNotAuthenticated_expect400() { - //prime the mock - post a RecordedRequest - setupNextResponse("asset.creation.failure.json"); - - // perform the actual Asset API request. In a real test scenario, this would be the client code we're testing, i.e. the - // System-under-Test (SuT). - var assetArray = mgmtRequest() - .contentType(ContentType.JSON) - .body(""" - { - "@context": { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/" - }, - "@type": "QuerySpec" - } - """) - .post("/v3/assets/request") - .then() - .log().ifValidationFails() - .statusCode(400) - .extract().body().as(JsonArray.class); - - // assert the response contains error information - assertThat(assetArray).hasSize(1); - var errorObject = assetArray.get(0).asJsonObject(); - assertThat(errorObject.get("message").toString()).contains("This user is not authorized, This is just a second error message"); - } - - private void setupNextResponse(String resourceFileName) { - var json = TestUtils.getResourceFileContentAsString(resourceFileName); - - apiRequest() - .contentType(ContentType.JSON) - .body(json) - .post("/instrumentation") - .then() - .log().ifValidationFails() - .statusCode(204); - } - - private RequestSpecification apiRequest() { - return given() - .baseUri("http://localhost:" + edcContainer.getMappedPort(DEFAULT_PORT) + "/api") - .when(); - } - - private RequestSpecification mgmtRequest() { - return given() - .baseUri("http://localhost:" + edcContainer.getMappedPort(MANAGEMENT_PORT) + "/api/management") - .when(); - } -} diff --git a/samples/testing-with-mocked-connector/src/test/resources/asset.creation.failure.json b/samples/testing-with-mocked-connector/src/test/resources/asset.creation.failure.json deleted file mode 100644 index 32d5b8459e..0000000000 --- a/samples/testing-with-mocked-connector/src/test/resources/asset.creation.failure.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "Some failed asset request", - "description": "test description", - "input": { - "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset", - "data": { - "id": "asset-1", - "properties": { - "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json", - "https://w3id.org/edc/v0.0.1/ns/prop1": "value1" - } - } - }, - "output": { - "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset" - }, - "failure": { - "reason": "UNAUTHORIZED", - "messages": [ - "This user is not authorized", - "This is just a second error message" - ] - } -} \ No newline at end of file diff --git a/samples/testing-with-mocked-connector/src/test/resources/asset.creation.json b/samples/testing-with-mocked-connector/src/test/resources/asset.creation.json deleted file mode 100644 index 1f26e3e33c..0000000000 --- a/samples/testing-with-mocked-connector/src/test/resources/asset.creation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "Asset Creation V3", - "input": { - "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset", - "data": { - "id": "asset-1", - "properties": { - "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json", - "https://w3id.org/edc/v0.0.1/ns/prop1": "value1" - } - } - }, - "output": { - "class": "org.eclipse.edc.connector.controlplane.asset.spi.domain.Asset", - "data": { - "id": "asset-1", - "properties": { - "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json", - "https://w3id.org/edc/v0.0.1/ns/prop1": "value1" - } - } - } -} \ No newline at end of file diff --git a/samples/testing-with-mocked-connector/src/test/resources/asset.request.json b/samples/testing-with-mocked-connector/src/test/resources/asset.request.json deleted file mode 100644 index 1f3dcafd88..0000000000 --- a/samples/testing-with-mocked-connector/src/test/resources/asset.request.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "Asset Query v3", - "input": { - "class": "org.eclipse.edc.spi.query.QuerySpec", - "data": { - "offset": 0, - "sortOrder": "DESC" - } - }, - "output": { - "class": "[Lorg.eclipse.edc.connector.controlplane.asset.spi.domain.Asset;", - "data": [ - { - "id": "asset-1", - "properties": { - "https://w3id.org/edc/v0.0.1/ns/contenttype": "application/json", - "https://w3id.org/edc/v0.0.1/ns/prop1": "value1" - } - } - ] - } -} \ No newline at end of file diff --git a/samples/testing-with-mocked-connector/src/test/resources/contractdef.creation.json b/samples/testing-with-mocked-connector/src/test/resources/contractdef.creation.json deleted file mode 100644 index 855ca8b1a5..0000000000 --- a/samples/testing-with-mocked-connector/src/test/resources/contractdef.creation.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "Asset Creation V3", - "input": { - "class": "org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition", - "data": { - "id": "asset-1", - "accessPolicyId": "test-policy1", - "contractPolicyId": "test-policy2", - "assetsSelector": [ - { - "operandLeft": "id", - "operator": "=", - "operandRight": "some-asset-id" - } - ] - } - }, - "output": { - "class": "org.eclipse.edc.connector.controlplane.contract.spi.types.offer.ContractDefinition", - "data": { - "id": "asset-1", - "accessPolicyId": "test-policy1", - "contractPolicyId": "test-policy2", - "assetsSelector": [ - { - "operandLeft": "id", - "operator": "=", - "operandRight": "some-asset-id" - } - ] - } - } -} \ No newline at end of file diff --git a/samples/testing-with-mocked-connector/src/test/resources/transferprocess.request.json b/samples/testing-with-mocked-connector/src/test/resources/transferprocess.request.json deleted file mode 100644 index 898eea501d..0000000000 --- a/samples/testing-with-mocked-connector/src/test/resources/transferprocess.request.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "TransferProcess Query v3", - "input": { - "class": "org.eclipse.edc.spi.query.QuerySpec", - "data": { - "offset": 0, - "sortOrder": "DESC" - } - }, - "output": { - "class": "[Lorg.eclipse.edc.connector.controlplane.transfer.spi.types.TransferProcess;", - "data": [ - { - "id": "transferprocess-1", - "type": "CONSUMER", - "assetId": "some-asset-id", - "contractId": "some-contract-id" - } - ] - } -} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 16e7eb06b3..3cf4833936 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,23 +20,16 @@ rootProject.name = "tractusx-edc" -// this is needed to have access to snapshot builds of plugins pluginManagement { repositories { gradlePluginPortal() mavenCentral() - maven { - url = uri("https://central.sonatype.com/repository/maven-snapshots/") - } } } dependencyResolutionManagement { repositories { mavenLocal() - maven { - url = uri("https://central.sonatype.com/repository/maven-snapshots/") - } mavenCentral() } versionCatalogs { @@ -55,6 +48,7 @@ include(":spi:bdrs-client-spi") include(":spi:dataflow-spi") include(":spi:dcp-spi") include(":spi:did-document-service-spi") +include(":spi:dsp-spi-08") // core modules @@ -77,7 +71,6 @@ include(":edc-extensions:migrations:control-plane-migration") include(":edc-extensions:migrations:data-plane-migration") include(":edc-extensions:tokenrefresh-handler") include(":edc-extensions:bdrs-client") -include(":edc-extensions:provision-additional-headers") include(":edc-extensions:event-subscriber") include(":edc-extensions:edr:edr-api-v2") include(":edc-extensions:edr:edr-callback") @@ -88,7 +81,19 @@ include(":edc-extensions:dcp:cx-dcp") include(":edc-extensions:dcp:tx-dcp") include(":edc-extensions:dcp:tx-dcp-sts-div") include(":edc-extensions:dcp:verifiable-presentation-cache") +include(":edc-extensions:dsp:dsp-catalog-08") +include(":edc-extensions:dsp:dsp-catalog-08:dsp-catalog-http-api-08") +include(":edc-extensions:dsp:dsp-catalog-08:dsp-catalog-transform-08") +include(":edc-extensions:dsp:dsp-http-api-configuration-08") +include(":edc-extensions:dsp:dsp-http-dispatcher-08") +include(":edc-extensions:dsp:dsp-negotiation-08") +include(":edc-extensions:dsp:dsp-negotiation-08:dsp-negotiation-http-api-08") +include(":edc-extensions:dsp:dsp-negotiation-08:dsp-negotiation-transform-08") +include(":edc-extensions:dsp:dsp-transfer-process-08") +include(":edc-extensions:dsp:dsp-transfer-process-08:dsp-transfer-process-http-api-08") +include(":edc-extensions:dsp:dsp-transfer-process-08:dsp-transfer-process-transform-08") include(":edc-extensions:data-flow-properties-provider") +include(":edc-extensions:validators:contract-definition-policies") include(":edc-extensions:validators:empty-asset-selector") include(":edc-extensions:log4j2-monitor") include(":edc-extensions:connector-discovery:connector-discovery-api") @@ -104,6 +109,7 @@ include(":edc-extensions:agreements:retirement-evaluation-core") include(":edc-extensions:agreements:retirement-evaluation-api") include(":edc-extensions:agreements:retirement-evaluation-spi") include(":edc-extensions:agreements:retirement-evaluation-store-sql") +include(":edc-extensions:agreements:retirement-evaluation-bootstrapping") // extensions - data plane include(":edc-extensions:dataplane:dataplane-proxy:edc-dataplane-proxy-consumer-api") @@ -114,6 +120,9 @@ include(":edc-extensions:dataplane:dataplane-token-refresh:token-refresh-api") include(":edc-extensions:dataplane:dataplane-proxy:dataplane-public-api-v2") include(":edc-extensions:dataplane:dataflow:dataflow-api") include(":edc-extensions:dataplane:dataflow:dataflow-service") +include(":edc-extensions:dataplane:kafka:data-address-kafka") +include(":edc-extensions:dataplane:kafka:validator-data-address-kafka") +include(":edc-extensions:dataplane:kafka:kafka-broker-extension") include(":edc-extensions:non-finite-provider-push:non-finite-provider-push-spi") include(":edc-extensions:non-finite-provider-push:non-finite-provider-push-core") @@ -133,21 +142,21 @@ include(":edc-tests:e2e:cloud-transfer-tests") include(":edc-tests:e2e:edc-dataplane-tokenrefresh-tests") include(":edc-tests:e2e:edr-api-tests") include(":edc-tests:e2e:end2end-transfer-cloud") +include(":edc-tests:e2e:kafka-transfer-tests") include(":edc-tests:e2e:management-tests") -include(":edc-tests:e2e:iatp-tests") +include(":edc-tests:e2e:dcp-tests") include(":edc-tests:e2e:policy-tests") include(":edc-tests:e2e:transfer-tests") include("edc-tests:e2e:discovery-tests") include(":edc-tests:e2e:dcp-tck-tests") -include(":edc-tests:e2e:dsp-compatibility-tests") +include(":edc-tests:e2e:dsp-tck-tests") include(":edc-tests:compatibility-tests") include(":edc-tests:runtime:dataplane-cloud") include(":edc-tests:runtime:runtime-dsp") -include(":edc-tests:runtime:iatp:iatp-extensions") -include(":edc-tests:runtime:iatp:runtime-memory-iatp-div-ih") -include(":edc-tests:runtime:iatp:runtime-memory-iatp-ih") -include(":edc-tests:runtime:iatp:runtime-memory-sts") -include(":edc-tests:runtime:mock-connector") +include(":edc-tests:runtime:dcp:dcp-extensions") +include(":edc-tests:runtime:dcp:runtime-memory-dcp-div-ih") +include(":edc-tests:runtime:dcp:runtime-memory-dcp-ih") +include(":edc-tests:runtime:dcp:runtime-memory-sts") include(":edc-tests:runtime:runtime-postgresql") include(":edc-tests:runtime:runtime-dcp-tck") include("edc-tests:runtime:runtime-discovery:runtime-discovery-base") @@ -157,8 +166,6 @@ include("edc-tests:runtime:runtime-compatibility:stable:extensions") include("edc-tests:runtime:runtime-compatibility:stable:connector-stable") include("edc-tests:runtime:runtime-compatibility:snapshot:connector-snapshot") - - // modules for controlplane artifacts include(":edc-controlplane") include(":edc-controlplane:edc-controlplane-base") @@ -174,5 +181,3 @@ include(":edc-dataplane:edc-dataplane-hashicorp-vault") include(":edc-dataplane:edc-dataplane-construct-x:con-x-dataplane-postgresql-hashicorp-vault") include(":edc-dataplane:edc-dataplane-construct-x:con-x-dataplane-postgresql-vault") -include(":samples:testing-with-mocked-connector") - diff --git a/spi/core-spi/src/main/java/org/eclipse/tractusx/edc/TxIatpConstants.java b/spi/core-spi/src/main/java/org/eclipse/tractusx/edc/TxDcpConstants.java similarity index 97% rename from spi/core-spi/src/main/java/org/eclipse/tractusx/edc/TxIatpConstants.java rename to spi/core-spi/src/main/java/org/eclipse/tractusx/edc/TxDcpConstants.java index 994ae363c0..50cf7150a1 100644 --- a/spi/core-spi/src/main/java/org/eclipse/tractusx/edc/TxIatpConstants.java +++ b/spi/core-spi/src/main/java/org/eclipse/tractusx/edc/TxDcpConstants.java @@ -23,7 +23,7 @@ import static java.lang.String.format; -public interface TxIatpConstants { +public interface TxDcpConstants { String CREDENTIAL_TYPE_NAMESPACE = "org.eclipse.tractusx.vc.type"; String MEMBERSHIP_CREDENTIAL = "MembershipCredential"; diff --git a/spi/core-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/CoreConstants.java b/spi/core-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/CoreConstants.java index 7257b1be9f..24d87e9d5d 100644 --- a/spi/core-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/CoreConstants.java +++ b/spi/core-spi/src/main/java/org/eclipse/tractusx/edc/edr/spi/CoreConstants.java @@ -42,7 +42,10 @@ public final class CoreConstants { public static final String EDR_PROPERTY_REFRESH_ENDPOINT = TX_AUTH_NS + "refreshEndpoint"; public static final String EDR_PROPERTY_REFRESH_AUDIENCE = TX_AUTH_NS + "refreshAudience"; public static final String AUDIENCE_PROPERTY = TX_AUTH_NS + "audience"; + public static final String BPN_PROPERTY = CX_CREDENTIAL_NS + "bpn"; + public static final String AGREEMENT_ID_PROPERTY = "agreementId"; public static final String EDR_PROPERTY_EXPIRES_IN = TX_AUTH_NS + "expiresIn"; + public static final String BPN_PREFIX = "BPNL"; private CoreConstants() { } diff --git a/spi/dsp-spi-08/build.gradle.kts b/spi/dsp-spi-08/build.gradle.kts new file mode 100644 index 0000000000..c9345c160b --- /dev/null +++ b/spi/dsp-spi-08/build.gradle.kts @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2025 Metaform Systems Inc. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +plugins { + `java-library` +} + +dependencies { + api(libs.dsp.spi) + implementation(libs.edc.ext.jsonld) +} \ No newline at end of file diff --git a/spi/dsp-spi-08/src/main/java/org/eclipse/edc/protocol/dsp/spi/type/Dsp08Constants.java b/spi/dsp-spi-08/src/main/java/org/eclipse/edc/protocol/dsp/spi/type/Dsp08Constants.java new file mode 100644 index 0000000000..964387122a --- /dev/null +++ b/spi/dsp-spi-08/src/main/java/org/eclipse/edc/protocol/dsp/spi/type/Dsp08Constants.java @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2025 Metaform Systems Inc. + * + * 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.edc.protocol.dsp.spi.type; + +import org.eclipse.edc.jsonld.spi.JsonLdNamespace; +import org.eclipse.edc.protocol.spi.ProtocolVersion; + +import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_CONTEXT_SEPARATOR; +import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_HTTPS_BINDING; +import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_SCOPE; +import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_TRANSFORMER_CONTEXT; + +public interface Dsp08Constants { + + String DSPACE_SCHEMA = "https://w3id.org/dspace/v0.8/"; + String V_08_VERSION = "v0.8"; + String V_08_PATH = "/"; + ProtocolVersion V_08 = new ProtocolVersion(V_08_VERSION, V_08_PATH, DSP_HTTPS_BINDING); + + String DSP_SCOPE_V_08 = DSP_SCOPE + DSP_CONTEXT_SEPARATOR + V_08_VERSION; + + String DSP_TRANSFORMER_CONTEXT_V_08 = DSP_TRANSFORMER_CONTEXT + DSP_CONTEXT_SEPARATOR + V_08_VERSION; + + JsonLdNamespace DSP_NAMESPACE_V_08 = new JsonLdNamespace(DSPACE_SCHEMA); +} diff --git a/spi/tokenrefresh-spi/src/main/java/org/eclipse/tractusx/edc/spi/tokenrefresh/dataplane/model/TokenResponse.java b/spi/tokenrefresh-spi/src/main/java/org/eclipse/tractusx/edc/spi/tokenrefresh/dataplane/model/TokenResponse.java index 148bf95d88..efff4e2d5c 100644 --- a/spi/tokenrefresh-spi/src/main/java/org/eclipse/tractusx/edc/spi/tokenrefresh/dataplane/model/TokenResponse.java +++ b/spi/tokenrefresh-spi/src/main/java/org/eclipse/tractusx/edc/spi/tokenrefresh/dataplane/model/TokenResponse.java @@ -23,6 +23,12 @@ public record TokenResponse(@JsonProperty("access_token") String accessToken, @JsonProperty("refresh_token") String refreshToken, - @JsonProperty("expires") Long expiresInSeconds, + @JsonProperty("expires") Long expiresInLegacy, // TODO: Still needed because older implementations use this non-spec-compliant value, can be removed when only 0.12.x based connectors are in the field + @JsonProperty("expires_in") Long expiresIn, @JsonProperty("token_type") String tokenType) { + + public Long expiresInSeconds() { + // Remove when the expiresInLegacy becomes obsolete, change expiresIn to expiresInSeconds + return expiresIn() != null ? expiresIn() : expiresInLegacy(); + } }