diff --git a/.github/workflows/chart-verification.yml b/.github/workflows/chart-verification.yml index 47bafb3ad..07347afea 100644 --- a/.github/workflows/chart-verification.yml +++ b/.github/workflows/chart-verification.yml @@ -156,6 +156,7 @@ jobs: context: . push: true tags: kind-registry:5000/miw:testing + file: ./miw/Dockerfile - uses: actions/setup-python@v4 with: @@ -230,6 +231,7 @@ jobs: charts/managed-identity-wallet \ -n apps \ --wait \ + --timeout 10m \ --set image.tag=testing \ --set image.repository=kind-registry:5000/miw # only run if this is not a PR -OR- if there are new versions available diff --git a/.github/workflows/dast-scan.yaml b/.github/workflows/dast-scan.yaml index 243641724..afe6a25f6 100644 --- a/.github/workflows/dast-scan.yaml +++ b/.github/workflows/dast-scan.yaml @@ -77,6 +77,7 @@ jobs: context: . push: true tags: kind-registry:5000/miw:testing + file: ./miw/Dockerfile - name: Install the chart on KinD cluster run: helm install -n apps --create-namespace --wait --set image.tag=testing --set=image.repository=kind-registry:5000/miw testing charts/managed-identity-wallet diff --git a/.github/workflows/release-miw.yml b/.github/workflows/release-miw.yml new file mode 100644 index 000000000..358984fe2 --- /dev/null +++ b/.github/workflows/release-miw.yml @@ -0,0 +1,266 @@ +# Copyright (c) 2021-2024 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 +--- + + name: Semantic Release - MIW + on: + push: + paths: + - 'miw/src/**' + - 'miw/build.gradle/**' + - 'wallet-commons/src/**' + - 'build.gradle' + - 'gradle.properties' + - 'settings.gradle' + branches: + - main + - develop + pull_request: + paths: + - 'miw/src/**' + - 'miw/build.gradle/**' + - 'wallet-commons/src/**' + - 'build.gradle' + - 'gradle.properties' + - 'settings.gradle' + branches: + - main + - develop + + env: + IMAGE_NAMESPACE: "tractusx" + IMAGE_NAME: "managed-identity-wallet" + + jobs: + + semantic_release: + name: Repository Release + runs-on: ubuntu-latest + permissions: + # see https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs + contents: write + pull-requests: write + packages: write + outputs: + next_release: ${{ steps.semantic-release.outputs.next_release }} + will_create_new_release: ${{ steps.semantic-release.outputs.will_create_new_release }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Setup Helm + uses: azure/setup-helm@v4.1.0 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + # setup helm-docs as it is needed during semantic-release + - uses: gabe565/setup-helm-docs-action@v1 + name: Setup helm-docs + if: github.event_name != 'pull_request' + with: + version: v1.11.3 + + - name: Run semantic release + id: semantic-release + if: github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com + GIT_COMMITTER_EMAIL: ${{ github.actor }}@users.noreply.github.com + run: | + npx --yes -p @semantic-release/exec -p @semantic-release/changelog -p @semantic-release/git -p @semantic-release/commit-analyzer -p @semantic-release/release-notes-generator semantic-release + + - name: Run semantic release (dry run) + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com + GIT_COMMITTER_EMAIL: ${{ github.actor }}@users.noreply.github.com + run: | + npx --yes -p @semantic-release/exec -p @semantic-release/github -p @semantic-release/changelog -p @semantic-release/git -p @semantic-release/commit-analyzer -p @semantic-release/release-notes-generator semantic-release --dry-run + + - name: Execute Gradle build + run: ./gradlew build + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: build + path: ./miw/build + if-no-files-found: error + retention-days: 1 + + - name: Upload Helm chart artifact + uses: actions/upload-artifact@v4 + with: + name: charts + path: ./charts + if-no-files-found: error + retention-days: 1 + + - name: Report semantic-release outputs + run: | + echo "::notice::${{ env.next_release }}" + echo "::notice::${{ env.will_create_new_release }}" + + - name: Upload jar to GitHub release + if: github.event_name != 'pull_request' && steps.semantic-release.outputs.will_create_new_release == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_VERSION: ${{ steps.semantic-release.outputs.next_release }} + run: | + echo "::notice::Uploading jar to GitHub release" + gh release upload "v$RELEASE_VERSION" ./miw/build/libs/miw-latest.jar + + docker: + name: Docker Release + needs: semantic_release + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: build + path: ./miw/build + + - name: Download Helm chart artifact + uses: actions/download-artifact@v4 + with: + name: charts + path: ./charts + + # Create SemVer or ref tags dependent of trigger event + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + # Automatically prepare image tags; See action docs for more examples. + # semver patter will generate tags like these for example :1 :1.2 :1.2.3 + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}},value=${{ needs.semantic_release.outputs.next_release }} + type=semver,pattern={{major}},value=${{ needs.semantic_release.outputs.next_release }} + type=semver,pattern={{major}}.{{minor}},value=${{ needs.semantic_release.outputs.next_release }} + type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} + + - name: DockerHub login + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + # Use existing DockerHub credentials present as secrets + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Push image + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + file: ./miw/Dockerfile + + # https://github.com/peter-evans/dockerhub-description + # Important step to push image description to DockerHub + - name: Update Docker Hub description + if: github.event_name != 'pull_request' + uses: peter-evans/dockerhub-description@v3 + with: + # readme-filepath defaults to toplevel README.md, Only necessary if you have a dedicated file with your 'Notice for docker images' + readme-filepath: Docker-hub-notice.md + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + + helm: + name: Helm Release + needs: semantic_release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download Helm chart artifact + uses: actions/download-artifact@v4 + with: + name: charts + path: ./charts + + - name: Install Helm + uses: azure/setup-helm@v4.1.0 + + - name: Add Helm dependency repositories + run: | + helm repo add bitnami https://charts.bitnami.com/bitnami + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Release chart + if: github.event_name != 'pull_request' && needs.semantic_release.outputs.will_create_new_release == 'true' + run: | + # Package MIW chart + helm_package_path=$(helm package -u -d helm-charts ./charts/managed-identity-wallet | grep -o 'to: .*' | cut -d' ' -f2-) + echo "HELM_PACKAGE_PATH=$helm_package_path" >> $GITHUB_ENV + + # Commit and push to gh-pages + git add helm-charts + git stash -- helm-charts + git reset --hard + git fetch origin + git checkout gh-pages + git stash pop + + # Generate helm repo index.yaml + helm repo index . --merge index.yaml --url https://${GITHUB_REPOSITORY_OWNER}.github.io/${GITHUB_REPOSITORY#*/}/ + git add index.yaml + + git commit -s -m "Release ${{ needs.semantic_release.outputs.next_release }}" + + git push origin gh-pages + + - name: Upload chart to GitHub release + if: github.event_name != 'pull_request' && needs.semantic_release.outputs.will_create_new_release == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_VERSION: ${{ needs.semantic_release.outputs.next_release }} + HELM_PACKAGE_PATH: ${{ env.HELM_PACKAGE_PATH }} + run: | + echo "::notice::Uploading chart to GitHub release" + gh release upload "v$RELEASE_VERSION" "$HELM_PACKAGE_PATH" + \ No newline at end of file diff --git a/.github/workflows/release-revocation.yml b/.github/workflows/release-revocation.yml new file mode 100644 index 000000000..3a95fa1c8 --- /dev/null +++ b/.github/workflows/release-revocation.yml @@ -0,0 +1,266 @@ +# Copyright (c) 2021-2024 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 +--- + + name: Semantic Release - Revocation Service + on: + push: + paths: + - 'revocation-service/src/**' + - 'revocation-service/build.gradle/**' + - 'wallet-commons/src/**' + - 'build.gradle' + - 'gradle.properties' + - 'settings.gradle' + branches: + - main + - develop + pull_request: + paths: + - 'revocation-service/src/**' + - 'revocation-service/build.gradle/**' + - 'wallet-commons/src/**' + - 'build.gradle' + - 'gradle.properties' + - 'settings.gradle' + branches: + - main + - develop + + env: + IMAGE_NAMESPACE: "tractusx" + IMAGE_NAME: "credential-revocation-service" + + jobs: + + semantic_release: + name: Repository Release + runs-on: ubuntu-latest + permissions: + # see https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs + contents: write + pull-requests: write + packages: write + outputs: + next_release: ${{ steps.semantic-release.outputs.next_release }} + will_create_new_release: ${{ steps.semantic-release.outputs.will_create_new_release }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Setup Helm + uses: azure/setup-helm@v4.1.0 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + # setup helm-docs as it is needed during semantic-release + - uses: gabe565/setup-helm-docs-action@v1 + name: Setup helm-docs + if: github.event_name != 'pull_request' + with: + version: v1.11.3 + + - name: Run semantic release + id: semantic-release + if: github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com + GIT_COMMITTER_EMAIL: ${{ github.actor }}@users.noreply.github.com + run: | + npx --yes -p @semantic-release/exec -p @semantic-release/changelog -p @semantic-release/git -p @semantic-release/commit-analyzer -p @semantic-release/release-notes-generator semantic-release + + - name: Run semantic release (dry run) + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com + GIT_COMMITTER_EMAIL: ${{ github.actor }}@users.noreply.github.com + run: | + npx --yes -p @semantic-release/exec -p @semantic-release/github -p @semantic-release/changelog -p @semantic-release/git -p @semantic-release/commit-analyzer -p @semantic-release/release-notes-generator semantic-release --dry-run + + - name: Execute Gradle build + run: ./gradlew build + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: build + path: ./revocation-service/build + if-no-files-found: error + retention-days: 1 + + - name: Upload Helm chart artifact + uses: actions/upload-artifact@v4 + with: + name: charts + path: ./charts + if-no-files-found: error + retention-days: 1 + + - name: Report semantic-release outputs + run: | + echo "::notice::${{ env.next_release }}" + echo "::notice::${{ env.will_create_new_release }}" + + - name: Upload jar to GitHub release + if: github.event_name != 'pull_request' && steps.semantic-release.outputs.will_create_new_release == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_VERSION: ${{ steps.semantic-release.outputs.next_release }} + run: | + echo "::notice::Uploading jar to GitHub release" + gh release upload "v$RELEASE_VERSION" ./revocation-service/build/libs/revocation-service-latest.jar + + docker: + name: Docker Release + needs: semantic_release + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: build + path: ./revocation-service/build + + - name: Download Helm chart artifact + uses: actions/download-artifact@v4 + with: + name: charts + path: ./charts + + # Create SemVer or ref tags dependent of trigger event + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + # Automatically prepare image tags; See action docs for more examples. + # semver patter will generate tags like these for example :1 :1.2 :1.2.3 + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}},value=${{ needs.semantic_release.outputs.next_release }} + type=semver,pattern={{major}},value=${{ needs.semantic_release.outputs.next_release }} + type=semver,pattern={{major}}.{{minor}},value=${{ needs.semantic_release.outputs.next_release }} + type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} + + - name: DockerHub login + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + # Use existing DockerHub credentials present as secrets + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Push image + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + file: ./revocation-service/Dockerfile + + # https://github.com/peter-evans/dockerhub-description + # Important step to push image description to DockerHub + - name: Update Docker Hub description + if: github.event_name != 'pull_request' + uses: peter-evans/dockerhub-description@v3 + with: + # readme-filepath defaults to toplevel README.md, Only necessary if you have a dedicated file with your 'Notice for docker images' + readme-filepath: Docker-hub-notice.md + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + + helm: + name: Helm Release + needs: semantic_release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download Helm chart artifact + uses: actions/download-artifact@v4 + with: + name: charts + path: ./charts + + - name: Install Helm + uses: azure/setup-helm@v4.1.0 + + - name: Add Helm dependency repositories + run: | + helm repo add bitnami https://charts.bitnami.com/bitnami + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Release chart + if: github.event_name != 'pull_request' && needs.semantic_release.outputs.will_create_new_release == 'true' + run: | + # Package Revocation-service chart,this will not work as we do not have any chart there + helm_package_path=$(helm package -u -d helm-charts ./charts/revocation-service | grep -o 'to: .*' | cut -d' ' -f2-) + echo "HELM_PACKAGE_PATH=$helm_package_path" >> $GITHUB_ENV + + # Commit and push to gh-pages + git add helm-charts + git stash -- helm-charts + git reset --hard + git fetch origin + git checkout gh-pages + git stash pop + + # Generate helm repo index.yaml + helm repo index . --merge index.yaml --url https://${GITHUB_REPOSITORY_OWNER}.github.io/${GITHUB_REPOSITORY#*/}/ + git add index.yaml + + git commit -s -m "Release ${{ needs.semantic_release.outputs.next_release }}" + + git push origin gh-pages + + - name: Upload chart to GitHub release + if: github.event_name != 'pull_request' && needs.semantic_release.outputs.will_create_new_release == 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_VERSION: ${{ needs.semantic_release.outputs.next_release }} + HELM_PACKAGE_PATH: ${{ env.HELM_PACKAGE_PATH }} + run: | + echo "::notice::Uploading chart to GitHub release" + gh release upload "v$RELEASE_VERSION" "$HELM_PACKAGE_PATH" + \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 1751418f2..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,250 +0,0 @@ -# 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 ---- - -name: Semantic Release -on: - push: - branches: - - main - - develop - pull_request: - branches: - - main - - develop - -env: - IMAGE_NAMESPACE: "tractusx" - IMAGE_NAME: "managed-identity-wallet" - -jobs: - - semantic_release: - name: Repository Release - runs-on: ubuntu-latest - permissions: - # see https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs - contents: write - pull-requests: write - packages: write - outputs: - next_release: ${{ steps.semantic-release.outputs.next_release }} - will_create_new_release: ${{ steps.semantic-release.outputs.will_create_new_release }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v2 - - - name: Setup Helm - uses: azure/setup-helm@v4.1.0 - - - name: Setup JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - # setup helm-docs as it is needed during semantic-release - - uses: gabe565/setup-helm-docs-action@v1 - name: Setup helm-docs - if: github.event_name != 'pull_request' - with: - version: v1.11.3 - - - name: Run semantic release - id: semantic-release - if: github.event_name != 'pull_request' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com - GIT_COMMITTER_EMAIL: ${{ github.actor }}@users.noreply.github.com - run: | - npx --yes -p @semantic-release/exec -p @semantic-release/changelog -p @semantic-release/git -p @semantic-release/commit-analyzer -p @semantic-release/release-notes-generator semantic-release - - - name: Run semantic release (dry run) - if: github.event_name == 'pull_request' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com - GIT_COMMITTER_EMAIL: ${{ github.actor }}@users.noreply.github.com - run: | - npx --yes -p @semantic-release/exec -p @semantic-release/github -p @semantic-release/changelog -p @semantic-release/git -p @semantic-release/commit-analyzer -p @semantic-release/release-notes-generator semantic-release --dry-run - - - name: Execute Gradle build - run: ./gradlew build - - - name: Upload build artifact - uses: actions/upload-artifact@v4 - with: - name: build - path: ./miw/build - if-no-files-found: error - retention-days: 1 - - - name: Upload Helm chart artifact - uses: actions/upload-artifact@v4 - with: - name: charts - path: ./charts - if-no-files-found: error - retention-days: 1 - - - name: Report semantic-release outputs - run: | - echo "::notice::${{ env.next_release }}" - echo "::notice::${{ env.will_create_new_release }}" - - - name: Upload jar to GitHub release - if: github.event_name != 'pull_request' && steps.semantic-release.outputs.will_create_new_release == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_VERSION: ${{ steps.semantic-release.outputs.next_release }} - run: | - echo "::notice::Uploading jar to GitHub release" - gh release upload "v$RELEASE_VERSION" ./miw/build/libs/miw-latest.jar - - docker: - name: Docker Release - needs: semantic_release - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Download build artifact - uses: actions/download-artifact@v4 - with: - name: build - path: ./miw/build - - - name: Download Helm chart artifact - uses: actions/download-artifact@v4 - with: - name: charts - path: ./charts - - # Create SemVer or ref tags dependent of trigger event - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} - # Automatically prepare image tags; See action docs for more examples. - # semver patter will generate tags like these for example :1 :1.2 :1.2.3 - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}},value=${{ needs.semantic_release.outputs.next_release }} - type=semver,pattern={{major}},value=${{ needs.semantic_release.outputs.next_release }} - type=semver,pattern={{major}}.{{minor}},value=${{ needs.semantic_release.outputs.next_release }} - type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }} - - - name: DockerHub login - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - # Use existing DockerHub credentials present as secrets - username: ${{ secrets.DOCKER_HUB_USER }} - password: ${{ secrets.DOCKER_HUB_TOKEN }} - - - name: Push image - uses: docker/build-push-action@v5 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - # https://github.com/peter-evans/dockerhub-description - # Important step to push image description to DockerHub - - name: Update Docker Hub description - if: github.event_name != 'pull_request' - uses: peter-evans/dockerhub-description@v3 - with: - # readme-filepath defaults to toplevel README.md, Only necessary if you have a dedicated file with your 'Notice for docker images' - readme-filepath: Docker-hub-notice.md - username: ${{ secrets.DOCKER_HUB_USER }} - password: ${{ secrets.DOCKER_HUB_TOKEN }} - repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} - - helm: - name: Helm Release - needs: semantic_release - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Download Helm chart artifact - uses: actions/download-artifact@v4 - with: - name: charts - path: ./charts - - - name: Install Helm - uses: azure/setup-helm@v4.1.0 - - - name: Add Helm dependency repositories - run: | - helm repo add bitnami https://charts.bitnami.com/bitnami - - - name: Configure Git - run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - - name: Release chart - if: github.event_name != 'pull_request' && needs.semantic_release.outputs.will_create_new_release == 'true' - run: | - # Package MIW chart - helm_package_path=$(helm package -u -d helm-charts ./charts/managed-identity-wallet | grep -o 'to: .*' | cut -d' ' -f2-) - echo "HELM_PACKAGE_PATH=$helm_package_path" >> $GITHUB_ENV - - # Commit and push to gh-pages - git add helm-charts - git stash -- helm-charts - git reset --hard - git fetch origin - git checkout gh-pages - git stash pop - - # Generate helm repo index.yaml - helm repo index . --merge index.yaml --url https://${GITHUB_REPOSITORY_OWNER}.github.io/${GITHUB_REPOSITORY#*/}/ - git add index.yaml - - git commit -s -m "Release ${{ needs.semantic_release.outputs.next_release }}" - - git push origin gh-pages - - - name: Upload chart to GitHub release - if: github.event_name != 'pull_request' && needs.semantic_release.outputs.will_create_new_release == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_VERSION: ${{ needs.semantic_release.outputs.next_release }} - HELM_PACKAGE_PATH: ${{ env.HELM_PACKAGE_PATH }} - run: | - echo "::notice::Uploading chart to GitHub release" - gh release upload "v$RELEASE_VERSION" "$HELM_PACKAGE_PATH" diff --git a/.github/workflows/trufflehog.yml b/.github/workflows/trufflehog.yml new file mode 100644 index 000000000..99ebba076 --- /dev/null +++ b/.github/workflows/trufflehog.yml @@ -0,0 +1,60 @@ +# +# Copyright (c) 2024 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 +# + +name: "TruffleHog" + +on: + push: + branches: ["main"] + pull_request: + # The branches below must be a subset of the branches above + branches: ["main"] + schedule: + - cron: "0 0 * * *" # Once a day + workflow_dispatch: + +permissions: + actions: read + contents: read + security-events: write + id-token: write + issues: write + +jobs: + ScanSecrets: + name: Scan secrets + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Ensure full clone for pull request workflows + + - name: TruffleHog OSS + id: trufflehog + uses: trufflesecurity/trufflehog@8a8ef8526527dd5f5d731d8e74843c121777b82d #v3.80.2 + continue-on-error: true + with: + path: ./ # Scan the entire repository + base: "${{ github.event.repository.default_branch }}" # Set base branch for comparison (pull requests) + extra_args: --filter-entropy=4 --results=verified,unknown --debug --only-verified + + - name: Scan Results Status + if: steps.trufflehog.outcome == 'failure' + run: exit 1 # Set workflow run to failure if TruffleHog finds secrets diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d65289ad..3e7c1598f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,94 @@ +# [1.0.0-develop.5](https://github.com/eclipse-tractusx/managed-identity-wallet/compare/v1.0.0-develop.4...v1.0.0-develop.5) (2024-10-18) + + +### Bug Fixes + +* chart workflows ([3d0fbf9](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/3d0fbf9d18be9f3078e9c427a8f3239ae5a67b53)) +* compilation error ([90ef524](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/90ef5241d38e678eff5064c2cd5edb0e6fdc1541)) +* copy path in docker file ([ad65e01](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/ad65e0196d40bea81d600c50170bbf31f988eaf9)) +* dependencies addded at individual project level ([60e3a5c](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/60e3a5ccf7a77a6efc6428650d9f3091e6002707)) +* docker context path ([ce29cb8](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/ce29cb8287293a88745ec2c9bbb5d938564c568a)) +* dockerfile ([234a7a0](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/234a7a05d6839409b1a62a1d15ac3a424cfad20c)) +* dockerfile and dockerfile location ([042292f](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/042292f1d4ebf6018721af1f748f3598ee619aa9)) +* failing test ([a99ca32](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/a99ca32e870a328858913f60c3bae28ca1f315dd)) +* failing test cases ([e91b6a0](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/e91b6a0330b65d09bc24b6c38086d17ee16a761c)) +* file copy path in Dockerfile ([7d76b00](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/7d76b0002615720ed2c0a1f0d45e2ca5bef642be)) +* modefied the value for the replicas ([14a67e1](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/14a67e1b982569ddf6e3becd8176fc346b5ef731)) +* more test added ([e739cdc](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/e739cdc6be1cc8c0c9d3cf2dde8b1b2ef0055b22)) +* random port added for management url ([6b118b2](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/6b118b270ff77f93ee26be8de53706ddba9de277)) +* revocation service dockerfile ([28796db](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/28796dbe1642d1255f4cfd309ce3332055abf39a)) +* sonar issues ([643493d](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/643493df5b862bbc1a86b30360706180d78aa19e)) +* sonar issues ([b1c5417](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/b1c54176cfba899fbcfed32dc1d028cc028e0a68)) +* status list changed to 2021 from bitstring ([546908b](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/546908b5e13ce4a0695fa5ade42a14a1abb88a2b)) +* status list VS as JSON-LD ([65dd812](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/65dd8124787e7897260e720d42b17e61cdb8955c)) +* test cases due to revocation client ([02ccd31](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/02ccd3112bbed9ff14f8f487a5df40eaced79eba)) +* tests ([df62fcc](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/df62fcc2e5c841a4cfd893d99fad22d90d34e792)) +* user added in dockerfile ([44b46ff](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/44b46ff81ae0730946a198fd26de199755f1df77)) +* zap scan errors ([074ab2d](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/074ab2df61f94776d23b45eebc87220fbd61a0ef)) +* zap scan errors ([c162cad](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/c162cada45cd0a885d0546214a18a32a7ac41f02)) + + +### Features + +* Helm charts for revocation service ([badb46d](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/badb46d7d90232d661665a33410c3f11d210a401)) +* intial revocation service added ([c173bd4](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/c173bd4d9902d798d75683247d13c89dda6861d2)) +* release workflow added for revocation-service ([f70b345](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/f70b3451c357dacd9b63e324efd23018723547f5)) +* revoke API, revocation support in issue VC API, wallet-commons module for common classes ([ec8bb00](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/ec8bb008746ee901acccc8eaccda3e5793aea775)) +* status list VC type set to StatusList2021 ([4429211](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/4429211d8b3bcb999b35a268f4fe588b9b28ef20)) +* test coverage verification added at root gradle level and javadoc for miw-commons ([6a7cff2](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/6a7cff2acb846fa9b664b359ec8fc179673df459)) + +# [1.0.0-develop.4](https://github.com/eclipse-tractusx/managed-identity-wallet/compare/v1.0.0-develop.3...v1.0.0-develop.4) (2024-08-09) + + +* feat(API)!: change API VC/VP default data format from Json to JWT ([233ab68](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/233ab6883012a5523ecaa4e0ad234e775c3e4577)) + + +### BREAKING CHANGES + +* All API endpoints that used Verifiable Credentials and -Presentations in JSON format per default are now working with the JWT format by default instead. + +Signed-off-by: Dominik Pinsel + +# [1.0.0-develop.3](https://github.com/eclipse-tractusx/managed-identity-wallet/compare/v1.0.0-develop.2...v1.0.0-develop.3) (2024-07-29) + + +### Features + +* remove BaseController, change Principal to Authenticationand unit test cases added ([15425be](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/15425beccd8bbb3560328d7d845766f422e6e4d8)) +* test cases added, file header updated and detail log added for security events ([a4fa6cc](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/a4fa6cc37d72e57796616fd87716fef059770e76)) + +# [1.0.0-develop.2](https://github.com/eclipse-tractusx/managed-identity-wallet/compare/v1.0.0-develop.1...v1.0.0-develop.2) (2024-07-22) + + +### Bug Fixes + +* updated spring boot and cloud version ([010ecab](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/010ecab904a0ba4e85c3e4b885fbefb2ed6057e6)) + +# [1.0.0-develop.1](https://github.com/eclipse-tractusx/managed-identity-wallet/compare/v0.6.0-develop.1...v1.0.0-develop.1) (2024-07-18) + + +* feat(identity-trust)!: update IATP protocol ([e3c5450](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/e3c5450fe2e0084f9deee16dff59e228afa40966)) + + +### BREAKING CHANGES + +* `/api/presentations/iatp` endpoint now accepts PresentationQueryMessage and returns PresentationResponseMessage objects. + +Signed-off-by: Dominik Pinsel + +# [0.6.0-develop.1](https://github.com/eclipse-tractusx/managed-identity-wallet/compare/v0.5.0...v0.6.0-develop.1) (2024-07-18) + + +### Bug Fixes + +* updated code as per review ([5961854](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/5961854ad811615453ea2afff15c1e4955ca450d)) +* updated var name ([44af067](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/44af0670f1e87b5ebedf88f66adcde89157d08fa)) + + +### Features + +* added new fields in did document ([8a17aec](https://github.com/eclipse-tractusx/managed-identity-wallet/commit/8a17aec6ddf330d730df0e9262c88abc2e297e0b)) + # [0.5.0](https://github.com/eclipse-tractusx/managed-identity-wallet/compare/v0.4.0...v0.5.0) (2024-07-05) diff --git a/README.md b/README.md index a4c2920d3..58d132616 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Managed Identity Wallets - The Managed Identity Wallets (MIW) service implements the Self-Sovereign-Identity (SSI) using `did:web`. -# Usage +This is a gradle multi-module project containing two applications: -See [INSTALL.md](INSTALL.md) +1. **miw**: This is a wallet application. Please refer [README.md](miw%2FREADME.md) for more information +2. **revocation-service**: This is verifiable credential revocation service. Please + refer [README.md](revocation-service%2FREADME.md) for more information # Committer Documentation @@ -42,361 +42,14 @@ At this point your repository will behave exactly like upstream when doing a rel # Developer Documentation -To run MIW locally, this section describes the tooling as well as the local development setup. - -There are two possible flows, which can be used for development: - -1. **local**: Run the postgresql and keycloak server inside docker. Start MIW from within your IDE (recommended for - actual development) -2. **docker**: Run everything inside docker (use to test or check behavior inside a docker environment) - -## Tooling - -Following tools the MIW development team used successfully: - -| Area | Tool | Download Link | Comment | -|----------|----------|-------------------------------------------------|--------------------------------------------------------------------------------------------------| -| IDE | IntelliJ | https://www.jetbrains.com/idea/download/ | Use[envfile plugin](https://plugins.jetbrains.com/plugin/7861-envfile) to use the **local** flow | -| Build | Gradle | https://gradle.org/install/ | | -| Runtime | Docker | https://www.docker.com/products/docker-desktop/ | | -| Database | DBeaver | https://dbeaver.io/ | | -| IAM | Keycloak | https://www.keycloak.org/ | | - -## Eclipse Dash Tool - -[Eclipse Dash Homepage](https://projects.eclipse.org/projects/technology.dash) - -The Eclipse Dash tool is used to analyze the dependencies used in the project and ensure all legal requirements are met. -We've added a gradle tasks to download the latest version of Dash locally, resolve all project dependencies and then run -the tool and update the summary in the DEPENDENCIES file. - -To run the license check: - -```bash -./gradlew dashLicenseCheck -``` - -To clean all files created by the dash tasks: - -```bash -./gradlew dashClean -``` - -This command will output all dependencies, save the to file `deps.txt`. Dash will read from the file and update the -summary in the `DEPENDENCIES` file. A committer can open and issue to resolve any problems with the dependencies. - -# Administrator Documentation - -## Manual Keycloak Configuration - -Within the development setup the Keycloak instance is initially prepared with the values -in `./dev-assets/docker-environment/keycloak`. The realm could also be manually added and configured -at http://localhost:8080 via the "Add realm" button. It can be for example named `localkeycloak`. Also add an additional -client, e.g. named `miw_private_client` with *valid redirect url* set to `http://localhost:8080/*`. The roles - -- add_wallets -- view_wallets -- update_wallets -- delete_wallets -- view_wallet -- update_wallet -- manage_app - -Roles can be added under *Clients > miw_private_client > Roles* and then assigned to the client using *Clients > -miw_private_client > Client Scopes* *> Service Account Roles > Client Roles > miw_private_client*. - -The available scopes/roles are: - -1. Role `add_wallets` to create a new wallet -2. Role `view_wallets`: - - to get a list of all wallets - - to retrieve one wallet by its identifier - - to validate a Verifiable Credential - - to validate a Verifiable Presentation - - to get all stored Verifiable Credentials -3. Role `update_wallets` for the following actions: - - to store Verifiable Credential - - to issue a Verifiable Credential - - to issue a Verifiable Presentation -4. Role `update_wallet`: - - to remove a Verifiable Credential - - to store a Verifiable Credential - - to issue a Verifiable Credential - - to issue a Verifiable Presentation -5. Role `view_wallet` requires the BPN of Caller and it can be used: - - to get the Wallet of the related BPN - - to get stored Verifiable Credentials of the related BPN - - to validate any Verifiable Credential - - to validate any Verifiable Presentation -6. Role `manage_app` used to change the log level of the application at runtime. Check Logging in the application - section for more details - -Overview by Endpoint - -| Artefact | CRUD | HTTP Verb/ Request | Endpoint | Roles | Constraints | -|-------------------------------------------|--------|--------------------|---------------------------------------|----------------------------------------------|------------------------------------------------------------| -| **Wallets** | Read | GET | /api/wallets | **view_wallets** | | -| **Wallets** | Create | POST | /api/wallets | **add_wallets** | **1 BPN : 1 WALLET**(PER ONE [1] BPN ONLY ONE [1] WALLET!) | -| **Wallets** | Create | POST | /api/wallets/{identifier}/credentials | **update_wallets**
OR**update_wallet** | | -| **Wallets** | Read | GET | /api/wallets/{identifier} | **view_wallets** OR
**view_wallet** | | -| **Verifiable Presentations - Generation** | Create | POST | /api/presentation | **update_wallets** OR
**update_wallet** | | -| **Verifiable Presentations - Validation** | Create | POST | /api/presentations/validation | **view_wallets** OR
**view_wallet** | | -| **Verifiable Credential - Holder** | Read | GET | /api/credentials | **view_wallets** OR
**view_wallet** | | -| **Verifiable Credential - Holder** | Create | POST | /api/credentials | **update_wallet** OR
**update_wallet** | | -| **Verifiable Credential - Holder** | Delete | DELETE | /api/credentials | **update_wallet** | | -| **Verfiable Credential - Validation** | Create | POST | /api/credentials/validation | **view_wallets** OR
**view_wallet** | | -| **Verfiable Credential - Issuer** | Read | GET | /api/credentials/issuer | **view_wallets** | | -| **Verfiable Credential - Issuer** | Create | POST | /api/credentials/issuer | **update_wallets** | | -| **DIDDocument** | Read | GET | /{bpn}/did.json | N/A | | -| **DIDDocument** | Read | GET | /api/didDocuments/{identifier} | N/A | | - -Additionally, a Token mapper can be created under *Clients* > *ManagedIdentityWallets* > *Mappers* > *create* -with the following configuration (using as an example `BPNL000000001`): - -| Key | Value | -|------------------------------------|-----------------| -| Name | StaticBPN | -| Mapper Type | Hardcoded claim | -| Token Claim Name | BPN | -| Claim value | BPNL000000001 | -| Claim JSON Type | String | -| Add to ID token | OFF | -| Add to access token | ON | -| Add to userinfo | OFF | -| includeInAccessTokenResponse.label | ON | - -If you receive an error message that the client secret is not valid, please go into keycloak admin and within *Clients > -Credentials* recreate the secret. - -## Development Setup - -NOTE: The MIW requires access to the internet in order to validate the JSON-LD schema of DID documents. - -### Prerequisites - -To simplify the dev environment, [Taskfile](https://taskfile.dev) is used as a task executor. You have to install it -first. - -> **IMPORTANT**: Before executing any of th tasks, you have to choose your flow (*local* or *docker*). *local* is -> default. To change that, you need to edit the variable **ENV** in the *Taskfile.yaml*. (see below) - -After that, run `task check-prereqs` to see, if any other required tool is installed or missing. If something is -missing, a link to the install docs is provided. - -Now, you have to adjust the *env* files (located in *dev-assets/env-files*). To do that, copy every file to the same -directory, but without ".dist" at the end. - -Description of the env files: - -- **env.local**: Set up everything to get ready for flow "local". You need to fill in the passwords. -- **env.docker**: Set up everything to get ready for flow "docker". You need to fill in the passwords. - -> **IMPORTANT**: ssi-lib is resolving DID documents over the network. There are two endpoints that rely on this resolution: -> - Verifiable Credentials - Validation -> - Verifiable Presentations - Validation -> -> The following parameters are set in env.local or env.docker file per default: -> ENFORCE_HTTPS_IN_DID_RESOLUTION=false -> MIW_HOST_NAME=localhost -> APPLICATION_PORT=80 -> If you intend to change them, the DID resolving may not work properly anymore! - -> **IMPORTANT**: When you are using macOS and the MIW docker container won't start up (stuck somewhere or doesn't start -> at all), you can enable the docker-desktop feature "Use Rosetta for x86/amd64 emulation on Apple Silicon" in your -> Docker settings (under "features in development"). This should fix the issue. - -Note: *SKIP_GRADLE_TASKS_PARAM* is used to pass parameters to the build process of the MIW jar. Currently, it skips the -tests and code coverage, but speeds up the build time. If you want to activate it, just comment it out -like `SKIP_GRADLE_TASKS_PARAM="" #"-x jacocoTestCoverageVerification -x test"` - -After every execution (either *local* or *docker* flow), run the matching "stop" task ( -e.g.: `task docker:start-app` -> `task docker:stop-app`) - -When you just run `task` without parameters, you will see all tasks available. - -### local - -1. Run `task docker:start-middleware` and wait until it shows "(main) Running the server in development mode. DO NOT use - this configuration in production." in the terminal -2. Run `task app:build` to build the MIW application -3. Run - [ManagedIdentityWalletsApplication.java](src/main/java/org/eclipse/tractusx/managedidentitywallets/ManagedIdentityWalletsApplication.java) - via IDE and use the local.env file to populate environment vars (e.g. EnvFile plugin for IntelliJ) -4. Run `task app:get-token` and copy the token (including "BEARER" prefix) (Mac users have the token already in their - clipboard) -5. Open API doc on http://localhost:8000 (or what port you configured in the *env.local* file) -6. Click on Authorize on swagger UI and on the dialog paste the token into the "value" input -7. Click on "Authorize" and "close" -8. MIW is up and running - -### docker - -1. Run `task docker:start-app` and wait until it shows "Started ManagedIdentityWalletsApplication in ... seconds" -2. Run `task app:get-token` and copy the token (including "BEARER" prefix) (Mac users have the token already in their - clipboard) -3. Open API doc on http://localhost:8000 (or what port you configured in the *env.local* file) -4. Click on Authorize on swagger UI and on the dialog paste the token into the "value" input -5. Click on "Authorize" and "close" -6. MIW is up and running - -### pgAdmin - -This local environment contains [pgAdmin](https://www.pgadmin.org/), which is also started ( -default: http://localhost:8888). -The default login is: - -``` -user: pg@admin.com (you can change it in the env.* files) -password: the one you set for "POSTGRES_PASSWORD" in the env.* files -``` - -#### DB connection password - -When you log in into pgAdmin, the local Postgresql server is already configured. -But you will be asked to enter the DB password on the first time you connect to the DB. -(password: POSTGRES_PASSWORD in the env.* files) - -#### Storage folder - -The storage folder of pgAdmin is mounted to `dev-assets/docker-environment/pgAdmin/storage/`. -For example, You can save DB backups there, so you can access them on your local machine. - -# End Users - -See OpenAPI documentation, which is automatically created from the source and available on each deployment at -the `/docs/api-docs/docs` endpoint (e.g. locally at http://localhost:8087/docs/api-docs/docs). An export of the JSON -document can be also found in [docs/openapi_v001.json](docs/api/openapi_v001.json). - -# Test Coverage - -Jacoco is used to generate the coverage report. The report generation and the coverage verification are automatically -executed after tests. - -The generated HTML report can be found under `jacoco-report/html/` - -To generate the report run the command: - -``` -task app:test-report -``` - -To check the coverage run the command: - -``` -task app:coverage -``` - -Currently, the minimum is 80% coverage. - -# Common issues and solutions during local setup - -## 1. Can not build with test cases - -Test cases are written using the Spring Boot integration test frameworks. These test frameworks start the Spring Boot -test context, which allows us to perform integration testing. In our tests, we utilize the Testcontainers -library (https://java.testcontainers.org/) for managing Docker containers. Specifically, we use Testcontainers to start -PostgreSQL and Keycloak Docker containers locally. - -Before running the tests, please ensure that you have Docker runtime installed and that you have the necessary -permissions to run containers. - -Alternative, you can skip test during the build with `` ./gradlew clean build -x test`` - -## 2. Database migration related issue - -We have implemented database migration using Liquibase (https://www.liquibase.org/). Liquibase allows us to manage -database schema changes effectively. - -In case you encounter any database-related issues, you can resolve them by following these steps: - -1. Delete all tables from the database. -2. Restart the application. -3. Upon restart, the application will recreate the database schema from scratch. - -This process ensures that any issues with the database schema are resolved by recreating it in a fresh state. - -# Environment Variables - -| name | description | default value | -|---------------------------------|----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| -| APPLICATION_PORT | port number of application | 8080 | -| APPLICATION_ENVIRONMENT | Environment of the application ie. local, dev, int and prod | local | -| DB_HOST | Database host | localhost | -| DB_PORT | Port of database | 5432 | -| DB_NAME | Database name | miw | -| USE_SSL | Whether SSL is enabled in database server | false | -| DB_USER_NAME | Database username | | -| DB_PASSWORD | Database password | | -| DB_POOL_SIZE | Max number of database connection acquired by application | 10 | -| KEYCLOAK_MIW_PUBLIC_CLIENT | Only needed if we want enable login with keyalock in swagger | miw_public | -| MANAGEMENT_PORT | Spring actuator port | 8090 | -| MIW_HOST_NAME | Application host name, this will be used in creation of did ie. did:web:MIW_HOST_NAME:BPN | localhost | -| ENCRYPTION_KEY | encryption key used to encrypt and decrypt private and public key of wallet | | -| AUTHORITY_WALLET_BPN | base wallet BPN number | BPNL000000000000 | -| AUTHORITY_WALLET_NAME | Base wallet name | Catena-X | -| AUTHORITY_WALLET_DID | Base wallet web did | web:did:host:BPNL000000000000 | -| VC_SCHEMA_LINK | Comma separated list of VC schema URL | https://www.w3.org/2018/credentials/v1, https://catenax-ng.github.io/product-core-schemas/businessPartnerData.json | -| VC_EXPIRY_DATE | Expiry date of VC (dd-MM-yyyy ie. 01-01-2025 expiry date will be 2024-12-31T18:30:00Z in VC) | 01-01-2025 | -| KEYCLOAK_REALM | Realm name of keycloak | miw_test | -| KEYCLOAK_CLIENT_ID | Keycloak private client id | | -| AUTH_SERVER_URL | Keycloak server url | | -| ENFORCE_HTTPS_IN_DID_RESOLUTION | Enforce https during web did resolution | true | -| APP_LOG_LEVEL | Log level of application | INFO | -| AUTHORITY_SIGNING_SERVICE_TYPE | Base wallet signing type, Currency only LOCAL is supported | Local | -| LOCAL_SIGNING_KEY_STORAGE_TYPE | Key storage type, currently only DB is supported | DB | -| | | | - -# Technical Debts and Known issue - -1. Keys are stored in database in encrypted format, need to store keys in more secure place ie. Vault -2. Policies can be validated dynamically as per request while validating VP and - VC. [Check this for more details](https://docs.walt.id/v/ssikit/concepts/verification-policies) - -# Logging in application - -Log level in application can be set using environment variable ``APP_LOG_LEVEL``. Possible values -are ``OFF, ERROR, WARN, INFO, DEBUG, TRACE`` and default value set to ``INFO`` - -## Change log level at runtime using Spring actuator - -We can use ``/actuator/loggers`` API endpoint of actuator for log related things. This end point can be accessible with -role ``manage_app``. We can add this role to authority wallet client using keycloak as below: - -![manage_app.png](docs%2Fmanage_app.png) - -1. API to get current log settings - ```bash - curl --location 'http://localhost:8090/actuator/loggers' \ - --header 'Authorization: Bearer access_token' - ``` -2. Change log level at runtime - ```bash - curl --location 'http://localhost:8090/actuator/loggers/{java package name}' \ - --header 'Content-Type: application/json' \ - --header 'Authorization: Bearer access_token' \ - --data '{"configuredLevel":"INFO"}' - ``` - i.e. - ```bash - curl --location 'http://localhost:8090/actuator/loggers/org.eclipse.tractusx.managedidentitywallets' \ - --header 'Content-Type: application/json' \ - --header 'Authorization: Bearer access_token' \ - --data '{"configuredLevel":"INFO"}' - ``` - -## Reference of external lib - -1. https://www.testcontainers.org/modules/databases/postgres/ -2. https://github.com/dasniko/testcontainers-keycloak -3. https://github.com/smartSenseSolutions/smartsense-java-commons -4. https://github.com/catenax-ng/product-lab-ssi +For end-to-end testing both the application should be running. -## Notice for Docker image +### Common gradle task -See [Docker-hub-notice.md](./Docker-hub-notice.md) +1. Build both the application -## Acknowledgments +``./gradlew clean build`` -We would like to give credit to these projects, which we use in our project. +2. Run tests -[![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release) +``./gradlew clean test`` diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..6e1e1b4a8 --- /dev/null +++ b/build.gradle @@ -0,0 +1,222 @@ +/******************************************************************************** + * Copyright (c) 2024 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 + ********************************************************************************/ + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" + } +} + +plugins { + id "de.undercouch.download" version "5.5.0" +} + +group = "${appGroup}" + +subprojects { + apply { + plugin "java" + plugin "org.springframework.boot" + plugin "io.spring.dependency-management" + plugin "jacoco" + plugin 'project-report' + + } + + java { + sourceCompatibility = JavaVersion.VERSION_17 + } + + repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://repo.danubetech.com/repository/maven-public") + } + maven { url 'https://jitpack.io' } + maven { + url = uri("https://repo.eclipse.org/content/repositories/dash-licenses/") + } + } + + dependencies { + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + + developmentOnly 'org.springframework.boot:spring-boot-devtools' + + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' + + testImplementation "org.testcontainers:junit-jupiter" + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + + } + + + tasks.named('test') { + useJUnitPlatform() + + } + bootJar { + enabled = false + } + + + test { + useJUnitPlatform() + finalizedBy jacocoTestReport + } + + jacocoTestReport { + + reports { + xml.enabled true + xml.outputLocation = file("./build/reports/xml/jacoco.xml") + + csv.enabled false + + html.enabled true + html.outputLocation = file("./build/reports/html/jacoco") + } + + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: [ + "org/eclipse/tractusx/managedidentitywallets/dto/*", + "org/eclipse/tractusx/managedidentitywallets/dao/entity/*", + "org/eclipse/tractusx/managedidentitywallets/constant/*", + "org/eclipse/tractusx/managedidentitywallets/commons/constant/*", + "org/eclipse/tractusx/managedidentitywallets/exception/*" + ]) + })) + } + } + + jacoco { + toolVersion = "${jacocoVersion}" + } + + + jacocoTestCoverageVerification { + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: [ + "org/eclipse/tractusx/managedidentitywallets/dto/*", + "org/eclipse/tractusx/managedidentitywallets/dao/entity/*", + "org/eclipse/tractusx/managedidentitywallets/constant/*", + "org/eclipse/tractusx/managedidentitywallets/commons/constant/*", + "org/eclipse/tractusx/managedidentitywallets/exception/*" + ]) + })) + } + violationRules { + rule { + limit { + minimum = 0.0 + } + } + } + } + + htmlDependencyReport { + projects = project.allprojects + } + + check.dependsOn jacocoTestCoverageVerification + + + tasks.register('dashDownload', Download) { + description = 'Download the Dash License Tool standalone jar' + group = 'License' + src 'https://repo.eclipse.org/service/local/artifact/maven/redirect?r=dash-licenses&g=org.eclipse.dash&a=org.eclipse.dash.licenses&v=LATEST' + dest rootProject.file('dash.jar') + overwrite false + } + +// This task is primarily used by CIs + tasks.register('dashClean') { + description = "Clean all files used by the 'License' group" + group = 'License' + logger.lifecycle("Removing 'dash.jar'") + rootProject.file('dash.jar').delete() + logger.lifecycle("Removing 'deps.txt'") + file('deps.txt').delete() + } + + tasks.register('dashDependencies') { dashDependencies -> + description = "Output all project dependencies as a flat list and save an intermediate file 'deps.txt'." + group = 'License' + dashDependencies.dependsOn('dashDownload') + doLast { + def deps = [] + project.configurations.each { conf -> + // resolving 'archives' or 'default' is deprecated + if (conf.canBeResolved && conf.getName() != 'archives' && conf.getName() != 'default') { + deps.addAll( + conf.incoming.resolutionResult.allDependencies + .findAll({ it instanceof ResolvedDependencyResult }) + .collect { ResolvedDependencyResult dep -> + "${dep.selected}" + } + ) + } + } + + def finalDeps = [] + for (final def d in deps) { + //skip main module dependencies + if (d.toString() == "project :miw" + || d.toString() == "project :revocation-service" + || d.toString() == "project :wallet-commons" + ) { + println(" - " + d.toString() + " -") + + } else { + finalDeps.add(d) + } + } + + def uniqueSorted = finalDeps.unique().sort() + uniqueSorted.each { logger.quiet("{}", it) } + file("deps.txt").write(uniqueSorted.join('\n')) + } + } + + tasks.register('dashLicenseCheck', JavaExec) { dashLicenseCheck -> + description = "Run the Dash License Tool and save the summary in the 'DEPENDENCIES' file" + group = 'License' + dashLicenseCheck.dependsOn('dashDownload') + dashLicenseCheck.dependsOn('dashDependencies') + doFirst { + classpath = rootProject.files('dash.jar') + // docs: https://eclipse-tractusx.github.io/docs/release/trg-7/trg-7-04 + args('-project', 'automotive.tractusx', '-summary', 'DEPENDENCIES', 'deps.txt') + } + doLast { + logger.lifecycle("Removing 'deps.txt' now.") + file('deps.txt').delete() + } + } + +} diff --git a/charts/managed-identity-wallet/.gitignore b/charts/managed-identity-wallet/.gitignore index ee3892e87..7639ceec9 100644 --- a/charts/managed-identity-wallet/.gitignore +++ b/charts/managed-identity-wallet/.gitignore @@ -1 +1,2 @@ -charts/ +charts/pgadmin4 +**/charts/*.tgz \ No newline at end of file diff --git a/charts/managed-identity-wallet/Chart.lock b/charts/managed-identity-wallet/Chart.lock index ef26fd56f..259b055b1 100644 --- a/charts/managed-identity-wallet/Chart.lock +++ b/charts/managed-identity-wallet/Chart.lock @@ -4,12 +4,12 @@ dependencies: version: 15.1.6 - name: common repository: https://charts.bitnami.com/bitnami - version: 2.13.3 + version: 2.22.0 - name: postgresql repository: https://charts.bitnami.com/bitnami version: 11.9.13 - name: pgadmin4 repository: file://charts/pgadmin4 version: 1.19.0 -digest: sha256:fb94864221b4fed31894b48ac56b72a2324da0dc1cb1d6888cc52c3490685df7 -generated: "2023-12-15T10:30:41.880265+01:00" +digest: sha256:886b90f763f2320a1601e15b06264065a764f51fc34d592c0f0a08bd76f01635 +generated: "2024-09-11T11:53:55.835418982+05:30" diff --git a/charts/managed-identity-wallet/Chart.yaml b/charts/managed-identity-wallet/Chart.yaml index 9d7913b2c..1d52d7f0b 100644 --- a/charts/managed-identity-wallet/Chart.yaml +++ b/charts/managed-identity-wallet/Chart.yaml @@ -1,21 +1,21 @@ -# /******************************************************************************** -# * 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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: v2 name: managed-identity-wallet @@ -25,8 +25,8 @@ description: | type: application -version: 0.5.0 -appVersion: 0.5.0 +version: 1.0.0-develop.5 +appVersion: 1.0.0-develop.5 home: https://github.com/eclipse-tractusx/managed-identity-wallet keywords: @@ -40,6 +40,9 @@ maintainers: - name: Dominik Pinsel email: dominik.pinsel@mercedes-benz.com url: https://github.com/DominikPinsel + - name: Rohit Solanki + email: rohit.solanki@smartsensesolutions.com + url: https://github.com/rohit-smartsensesolutions dependencies: - name: keycloak diff --git a/charts/managed-identity-wallet/README.md b/charts/managed-identity-wallet/README.md index 6983f7f27..004c59121 100644 --- a/charts/managed-identity-wallet/README.md +++ b/charts/managed-identity-wallet/README.md @@ -2,7 +2,7 @@ # managed-identity-wallet -![Version: 0.5.0](https://img.shields.io/badge/Version-0.5.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.5.0](https://img.shields.io/badge/AppVersion-0.5.0-informational?style=flat-square) +![Version: 1.0.0-develop.5](https://img.shields.io/badge/Version-1.0.0--develop.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-develop.5](https://img.shields.io/badge/AppVersion-1.0.0--develop.5-informational?style=flat-square) Managed Identity Wallet is supposed to supply a secure data source and data sink for Digital Identity Documents (DID), in order to enable Self-Sovereign Identity founding on those DIDs. And at the same it shall support an uninterrupted tracking and tracing and documenting the usage of those DIDs, e.g. within logistical supply chains. @@ -90,48 +90,47 @@ See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command document |-----|------|---------|-------------| | affinity | object | `{}` | Affinity configuration | | envs | object | `{}` | envs Parameters for the application (will be provided as environment variables) | -| extraVolumeMounts | list | `[]` | add volume mounts to the miw deployment | +| extraVolumeMounts | list | `[]` | | | extraVolumes | list | `[]` | add volumes to the miw deployment | | fullnameOverride | string | `""` | String to fully override common.names.fullname template | | image.pullPolicy | string | `"Always"` | PullPolicy | | image.repository | string | `"tractusx/managed-identity-wallet"` | Image repository | | image.tag | string | `""` | Image tag (empty one will use "appVersion" value from chart definition) | +| imagePullSecrets | list | `[]` | | +| ingress | object | `{"annotations":{},"className":"nginx","enabled":false,"hosts":[],"tls":[]}` | Ingress Configuration | | ingress.annotations | object | `{}` | Ingress annotations | | ingress.enabled | bool | `false` | Enable ingress controller resource | | ingress.hosts | list | `[]` | Ingress accepted hostnames | | ingress.tls | list | `[]` | Ingress TLS configuration | | initContainers | list | `[]` | add initContainers to the miw deployment | +| keycloak | object | `{"auth":{"adminPassword":"","adminUser":"admin"},"enabled":true,"extraEnvVars":[],"ingress":{"annotations":{},"enabled":false,"hosts":[],"tls":[]},"keycloakConfigCli":{"backoffLimit":2,"enabled":true,"existingConfigmap":"keycloak-realm-config"},"postgresql":{"auth":{"database":"miw_keycloak","password":"defaultpassword","username":"miw_keycloak"},"enabled":true,"nameOverride":"keycloak-postgresql","volumePermissions":{"enabled":true}}}` | Values for KEYCLOAK | | keycloak.auth.adminPassword | string | `""` | Keycloak admin password | | keycloak.auth.adminUser | string | `"admin"` | Keycloak admin user | | keycloak.enabled | bool | `true` | Enable to deploy Keycloak | | keycloak.extraEnvVars | list | `[]` | Extra environment variables | -| keycloak.ingress.annotations | object | `{}` | | -| keycloak.ingress.enabled | bool | `false` | | -| keycloak.ingress.hosts | list | `[]` | | -| keycloak.ingress.tls | list | `[]` | | +| keycloak.ingress.annotations | object | `{}` | Ingress annotations | +| keycloak.ingress.enabled | bool | `false` | Enable ingress controller resource | +| keycloak.ingress.hosts | list | `[]` | Ingress accepted hostnames | +| keycloak.ingress.tls | list | `[]` | Ingress TLS configuration | | keycloak.keycloakConfigCli.backoffLimit | int | `2` | Number of retries before considering a Job as failed | | keycloak.keycloakConfigCli.enabled | bool | `true` | Enable to create the miw playground realm | | keycloak.keycloakConfigCli.existingConfigmap | string | `"keycloak-realm-config"` | Existing configmap name for the realm configuration | | keycloak.postgresql.auth.database | string | `"miw_keycloak"` | Database name | -| keycloak.postgresql.auth.password | string | `""` | KeycloakPostgresql password to set (if empty one is generated) | -| keycloak.postgresql.auth.username | string | `"miw_keycloak"` | Keycloak PostgreSQL user | +| keycloak.postgresql.auth.password | string | `"defaultpassword"` | KeycloakPostgresql password to set (if empty one is generated) | +| keycloak.postgresql.auth.username | string | `"miw_keycloak"` | Postgresql admin user password | | keycloak.postgresql.enabled | bool | `true` | Enable to deploy PostgreSQL | | keycloak.postgresql.nameOverride | string | `"keycloak-postgresql"` | Name of the PostgreSQL chart to deploy. Mandatory when the MIW deploys a PostgreSQL chart, too. | -| livenessProbe | object | `{"enabled":true,"failureThreshold":3,"initialDelaySeconds":20,"periodSeconds":5,"timeoutSeconds":15}` | Kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | -| livenessProbe.enabled | bool | `true` | Enables/Disables the livenessProbe at all | -| livenessProbe.failureThreshold | int | `3` | When a probe fails, Kubernetes will try failureThreshold times before giving up. Giving up in case of liveness probe means restarting the container. | -| livenessProbe.initialDelaySeconds | int | `20` | Number of seconds after the container has started before readiness probe are initiated. | -| livenessProbe.periodSeconds | int | `5` | How often (in seconds) to perform the probe | -| livenessProbe.timeoutSeconds | int | `15` | Number of seconds after which the probe times out. | +| miw | object | `{"authorityWallet":{"bpn":"BPNL000000000000","name":""},"database":{"encryptionKey":{"secret":"","secretKey":"","value":""},"host":"{{ .Release.Name }}-postgresql","name":"miw_app","port":5432,"secret":"verifiable-credential-revocation-service","secretPasswordKey":"password","useSSL":false,"user":"miw"},"environment":"dev","host":"{{ .Release.Name }}-managed-identity-wallet:8080","keycloak":{"clientId":"miw_private_client","realm":"miw_test","url":"http://{{ .Release.Name }}-keycloak"},"livenessProbe":{"enabled":true,"failureThreshold":3,"initialDelaySeconds":20,"periodSeconds":5,"timeoutSeconds":15},"logging":{"level":"INFO"},"readinessProbe":{"enabled":true,"failureThreshold":3,"initialDelaySeconds":30,"periodSeconds":5,"successThreshold":1,"timeoutSeconds":5},"ssi":{"enforceHttpsInDidWebResolution":true,"vcExpiryDate":""}}` | Values for MIW | | miw.authorityWallet.bpn | string | `"BPNL000000000000"` | Authority Wallet BPNL | | miw.authorityWallet.name | string | `""` | Authority Wallet Name | +| miw.database.encryptionKey | object | `{"secret":"","secretKey":"","value":""}` | Password encryption configuratons | | miw.database.encryptionKey.secret | string | `""` | Existing secret for database encryption key | | miw.database.encryptionKey.secretKey | string | `""` | Existing secret key for database encryption key | | miw.database.encryptionKey.value | string | `""` | Database encryption key for confidential data. Ignored if `secret` is set. If empty a secret with 32 random alphanumeric chars is generated. | | miw.database.host | string | `"{{ .Release.Name }}-postgresql"` | Database host | | miw.database.name | string | `"miw_app"` | Database name | | miw.database.port | int | `5432` | Database port | -| miw.database.secret | string | `"{{ .Release.Name }}-postgresql"` | Existing secret name for the database password | +| miw.database.secret | string | `"verifiable-credential-revocation-service"` | Existing secret name for the database password | | miw.database.secretPasswordKey | string | `"password"` | Existing secret key for the database password | | miw.database.useSSL | bool | `false` | Set to true to enable SSL connection to the database | | miw.database.user | string | `"miw"` | Database user | @@ -140,48 +139,57 @@ See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command document | miw.keycloak.clientId | string | `"miw_private_client"` | Keycloak client id | | miw.keycloak.realm | string | `"miw_test"` | Keycloak realm | | miw.keycloak.url | string | `"http://{{ .Release.Name }}-keycloak"` | Keycloak URL | +| miw.livenessProbe | object | `{"enabled":true,"failureThreshold":3,"initialDelaySeconds":20,"periodSeconds":5,"timeoutSeconds":15}` | Kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | +| miw.livenessProbe.enabled | bool | `true` | Enables/Disables the livenessProbe at all | +| miw.livenessProbe.failureThreshold | int | `3` | When a probe fails, Kubernetes will try failureThreshold times before giving up. Giving up in case of liveness probe means restarting the container. | +| miw.livenessProbe.initialDelaySeconds | int | `20` | Number of seconds after the container has started before readiness probe are initiated. | +| miw.livenessProbe.periodSeconds | int | `5` | How often (in seconds) to perform the probe | +| miw.livenessProbe.timeoutSeconds | int | `15` | Number of seconds after which the probe times out. | | miw.logging.level | string | `"INFO"` | Log level. Should be ether ERROR, WARN, INFO, DEBUG, or TRACE. | +| miw.readinessProbe | object | `{"enabled":true,"failureThreshold":3,"initialDelaySeconds":30,"periodSeconds":5,"successThreshold":1,"timeoutSeconds":5}` | Kubernetes [readiness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | +| miw.readinessProbe.enabled | bool | `true` | Enables/Disables the readinessProbe at all | +| miw.readinessProbe.failureThreshold | int | `3` | When a probe fails, Kubernetes will try failureThreshold times before giving up. In case of readiness probe the Pod will be marked Unready. | +| miw.readinessProbe.initialDelaySeconds | int | `30` | Number of seconds after the container has started before readiness probe are initiated. | +| miw.readinessProbe.periodSeconds | int | `5` | How often (in seconds) to perform the probe | +| miw.readinessProbe.successThreshold | int | `1` | Minimum consecutive successes for the probe to be considered successful after having failed. | +| miw.readinessProbe.timeoutSeconds | int | `5` | Number of seconds after which the probe times out. | | miw.ssi.enforceHttpsInDidWebResolution | bool | `true` | Enable to use HTTPS in DID Web Resolution | | miw.ssi.vcExpiryDate | string | `""` | Verifiable Credential expiry date. Format 'dd-MM-yyyy'. If empty it is set to 31-12- | | nameOverride | string | `""` | String to partially override common.names.fullname template (will maintain the release name) | | networkPolicy.enabled | bool | `false` | If `true` network policy will be created to restrict access to managed-identity-wallet | | networkPolicy.from | list | `[{"namespaceSelector":{}}]` | Specify from rule network policy for miw (defaults to all namespaces) | | nodeSelector | object | `{"kubernetes.io/os":"linux"}` | NodeSelector configuration | +| pgadmin4 | object | `{"enabled":false,"env":{"email":"admin@miw.com","password":"very-secret-password"},"extraServerDefinitions":{"enabled":true,"servers":{}},"ingress":{"annotations":{},"enabled":false,"hosts":[],"tls":[]}}` | Values for PGADMIN For more information on how to configure the pgadmin chart see https://artifacthub.io/packages/helm/runix/pgadmin4. | | pgadmin4.enabled | bool | `false` | Enable to deploy pgAdmin | | pgadmin4.env.email | string | `"admin@miw.com"` | Preset the admin user email | | pgadmin4.env.password | string | `"very-secret-password"` | preset password (there is no auto-generated password) | | pgadmin4.extraServerDefinitions.enabled | bool | `true` | enable the predefined server for pgadmin | | pgadmin4.extraServerDefinitions.servers | object | `{}` | See [here](https://github.com/rowanruseler/helm-charts/blob/9b970b2e419c2300dfbb3f827a985157098a0287/charts/pgadmin4/values.yaml#L84) how to configure the predefined servers | -| pgadmin4.ingress.annotations | object | `{}` | | | pgadmin4.ingress.enabled | bool | `false` | Enagle pgAdmin ingress | | pgadmin4.ingress.hosts | list | `[]` | See [here](https://github.com/rowanruseler/helm-charts/blob/9b970b2e419c2300dfbb3f827a985157098a0287/charts/pgadmin4/values.yaml#L104) how to configure the ingress host(s) | | pgadmin4.ingress.tls | list | `[]` | See [here](https://github.com/rowanruseler/helm-charts/blob/9b970b2e419c2300dfbb3f827a985157098a0287/charts/pgadmin4/values.yaml#L109) how to configure tls for the ingress host(s) | | podAnnotations | object | `{}` | PodAnnotation configuration | -| podSecurityContext | object | `{}` | PodSecurityContext | +| podSecurityContext | object | `{}` | Pod security configurations | +| postgresql | object | `{"auth":{"database":"miw_app","enablePostgresUser":true,"existingSecret":"verifiable-credential-revocation-service","username":"miw"},"backup":{"cronjob":{"schedule":"* */6 * * *","storage":{"existingClaim":"","resourcePolicy":"keep","size":"8Gi"}},"enabled":false},"enabled":true,"image":{"debug":true,"tag":"16-debian-12"},"primary":{"extraVolumeMounts":[{"mountPath":"/docker-entrypoint-initdb.d/seed","name":"postgres-seed"}],"extraVolumes":[{"name":"postgres-seed","persistentVolumeClaim":{"claimName":"postgres-seed-pvc"}}],"initdb":{"password":"defaultpassword","scripts":{"init.sql":"CREATE DATABASE vcrs_app;\nCREATE USER vcrs WITH ENCRYPTED PASSWORD 'defaultpassword';\nGRANT ALL PRIVILEGES ON DATABASE vcrs_app TO vcrs;\n\\c vcrs_app\nGRANT ALL ON SCHEMA public TO vcrs;\n"},"user":"postgres"}},"volumePermissions":{"enabled":true}}` | Values for POSTGRESQL For more information on how to configure the PostgreSQL chart see https://github.com/bitnami/charts/tree/main/bitnami/postgresql. | | postgresql.auth.database | string | `"miw_app"` | Postgresql database to create | -| postgresql.auth.enablePostgresUser | bool | `false` | Enable postgresql admin user | -| postgresql.auth.password | string | `""` | Postgresql password to set (if empty one is generated) | -| postgresql.auth.postgresPassword | string | `""` | Postgresql admin user password | +| postgresql.auth.enablePostgresUser | bool | `true` | Enable postgresql admin user | +| postgresql.auth.existingSecret | string | `"verifiable-credential-revocation-service"` | Postgresql root-user and non-root user secret | | postgresql.auth.username | string | `"miw"` | Postgresql user to create | +| postgresql.backup.cronjob | object | `{"schedule":"* */6 * * *","storage":{"existingClaim":"","resourcePolicy":"keep","size":"8Gi"}}` | Cronjob Configuration | | postgresql.backup.cronjob.schedule | string | `"* */6 * * *"` | Backup schedule | | postgresql.backup.cronjob.storage.existingClaim | string | `""` | Name of an existing PVC to use | | postgresql.backup.cronjob.storage.resourcePolicy | string | `"keep"` | Set resource policy to "keep" to avoid removing PVCs during a helm delete operation | | postgresql.backup.cronjob.storage.size | string | `"8Gi"` | PVC Storage Request for the backup data volume | | postgresql.backup.enabled | bool | `false` | Enable to create a backup cronjob | | postgresql.enabled | bool | `true` | Enable to deploy Postgresql | -| readinessProbe | object | `{"enabled":true,"failureThreshold":3,"initialDelaySeconds":30,"periodSeconds":5,"successThreshold":1,"timeoutSeconds":5}` | Kubernetes [readiness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | -| readinessProbe.enabled | bool | `true` | Enables/Disables the readinessProbe at all | -| readinessProbe.failureThreshold | int | `3` | When a probe fails, Kubernetes will try failureThreshold times before giving up. In case of readiness probe the Pod will be marked Unready. | -| readinessProbe.initialDelaySeconds | int | `30` | Number of seconds after the container has started before readiness probe are initiated. | -| readinessProbe.periodSeconds | int | `5` | How often (in seconds) to perform the probe | -| readinessProbe.successThreshold | int | `1` | Minimum consecutive successes for the probe to be considered successful after having failed. | -| readinessProbe.timeoutSeconds | int | `5` | Number of seconds after which the probe times out. | +| postgresql.image.debug | bool | `true` | Debug logs | | replicaCount | int | `1` | The amount of replicas to run | | resources.limits.cpu | int | `2` | CPU resource limits | | resources.limits.memory | string | `"1Gi"` | Memory resource limits | | resources.requests.cpu | string | `"250m"` | CPU resource requests | | resources.requests.memory | string | `"500Mi"` | Memory resource requests | | secrets | object | `{}` | Parameters for the application (will be stored as secrets - so, for passwords, ...) | +| securityContext | object | `{"allowPrivilegeEscalation":false,"privileged":false,"runAsGroup":11111,"runAsNonRoot":true,"runAsUser":11111}` | Pod security parameters | | securityContext.allowPrivilegeEscalation | bool | `false` | Allow privilege escalation | | securityContext.privileged | bool | `false` | Enable privileged container | | securityContext.runAsGroup | int | `11111` | Group ID used to run the container | @@ -193,6 +201,53 @@ See [helm upgrade](https://helm.sh/docs/helm/helm_upgrade/) for command document | serviceAccount.create | bool | `true` | Enable creation of ServiceAccount | | serviceAccount.name | string | `""` | The name of the ServiceAccount to use. | | tolerations | list | `[]` | Tolerations configuration | +| vcrs | object | `{"affinity":{},"autoscaling":{"enabled":false,"maxReplicas":2,"minReplicas":1,"targetCPUUtilizationPercentage":80,"targetMemoryUtilizationPercentage":80},"configName":"verifiable-credential-revocation-service","database":{"encryptionKey":{"secret":"","secretKey":"","value":""}},"env":{"APPLICATION_LOG_LEVEL":"DEBUG","APPLICATION_NAME":"verifiable-credential-revocation-service","APPLICATION_PORT":8081,"APPLICATION_PROFILE":"local","APP_LOG_LEVEL":"INFO","AUTH_SERVER_URL":"http://{{ .Release.Name }}-keycloak","DATABASE_CONNECTION_POOL_SIZE":10,"DATABASE_HOST":"managed-identity-wallet-postgresql","DATABASE_NAME":"vcrs_app","DATABASE_PORT":5432,"DATABASE_USERNAME":"vcrs","DATABASE_USE_SSL_COMMUNICATION":false,"DOMAIN_URL":"https://977d-203-129-213-107.ngrok-free.app","ENABLE_API_DOC":true,"ENABLE_SWAGGER_UI":true,"KEYCLOAK_CLIENT_ID":"miw_private_client","KEYCLOAK_PUBLIC_CLIENT_ID":"miw_public_client","KEYCLOAK_REALM":"miw_test","MIW_URL":"https://a888-203-129-213-107.ngrok-free.app","SERVICE_SECURITY_ENABLED":true,"VC_SCHEMA_LINK":"https://www.w3.org/2018/credentials/v1, https://w3id.org/vc/status-list/2021/v1"},"fullnameOverride":"verifiable-credential-revocation-service","host":"localhost","image":{"pullPolicy":"IfNotPresent","repository":"tractusx/verifiable-credential-revocation-service","tag":"latest"},"imagePullSecrets":[],"ingress":{"annotations":{},"className":"","enabled":false,"hosts":null,"service":{"port":8081,"type":"ClusterIP"},"tls":[]},"ingressName":"verifiable-credential-revocation-service-ingress","livenessProbe":{"enabled":true,"failureThreshold":3,"initialDelaySeconds":60,"periodSeconds":5,"timeoutSeconds":30},"nameOverride":"verifiable-credential-revocation-service","nodeSelector":{},"podAnnotations":{},"podLabels":{},"podSecurityContext":{},"readinessProbe":{"enabled":true,"failureThreshold":3,"initialDelaySeconds":60,"periodSeconds":30,"timeoutSeconds":30},"replicaCount":1,"resources":{},"rollingUpdate":{"enabled":true,"rollingUpdateMaxSurge":1,"rollingUpdateMaxUnavailable":0},"secretName":"verifiable-credential-revocation-service","secrets":{"DATABASE_PASSWORD":"defaultpassword","password":"defaultpassword","postgres-password":"defaultpassword"},"securityContext":{"allowPrivilegeEscalation":false},"serviceName":"verifiable-credential-revocation-service","tolerations":[],"volumeMounts":[],"volumes":[]}` | Values for Verifiable Credential Revocation Service application | +| vcrs.configName | string | `"verifiable-credential-revocation-service"` | ConfigMap Name | +| vcrs.database.encryptionKey.secret | string | `""` | Existing secret for database encryption key | +| vcrs.database.encryptionKey.secretKey | string | `""` | Existing secret key for database encryption key | +| vcrs.database.encryptionKey.value | string | `""` | Database encryption key for confidential data. Ignored if `secret` is set. If empty a secret with 32 random alphanumeric chars is generated. | +| vcrs.env.APPLICATION_LOG_LEVEL | string | `"DEBUG"` | The application log level | +| vcrs.env.APPLICATION_NAME | string | `"verifiable-credential-revocation-service"` | The application name | +| vcrs.env.APPLICATION_PORT | int | `8081` | The application port | +| vcrs.env.APPLICATION_PROFILE | string | `"local"` | The application profile | +| vcrs.env.AUTH_SERVER_URL | string | `"http://{{ .Release.Name }}-keycloak"` | Auth URL for Keycloak | +| vcrs.env.DATABASE_CONNECTION_POOL_SIZE | int | `10` | The Database connection pool size | +| vcrs.env.DATABASE_HOST | string | `"managed-identity-wallet-postgresql"` | The Database Host | +| vcrs.env.DATABASE_NAME | string | `"vcrs_app"` | The Database Name | +| vcrs.env.DATABASE_PORT | int | `5432` | The Database Port | +| vcrs.env.DATABASE_USERNAME | string | `"vcrs"` | The Database Name | +| vcrs.env.DATABASE_USE_SSL_COMMUNICATION | bool | `false` | The Database SSL | +| vcrs.env.ENABLE_API_DOC | bool | `true` | Swagger Api Doc | +| vcrs.env.ENABLE_SWAGGER_UI | bool | `true` | Swagger UI config | +| vcrs.env.KEYCLOAK_CLIENT_ID | string | `"miw_private_client"` | ClientID Config | +| vcrs.env.KEYCLOAK_PUBLIC_CLIENT_ID | string | `"miw_public_client"` | ClientID Config | +| vcrs.env.KEYCLOAK_REALM | string | `"miw_test"` | KeyClocak Configurations | +| vcrs.env.MIW_URL | string | `"https://a888-203-129-213-107.ngrok-free.app"` | Revocation application configuration | +| vcrs.fullnameOverride | string | `"verifiable-credential-revocation-service"` | String to partially override common.names.fullname template (will maintain the release name) | +| vcrs.host | string | `"localhost"` | Revocation application configuration | +| vcrs.image.pullPolicy | string | `"IfNotPresent"` | PullPolicy | +| vcrs.image.repository | string | `"tractusx/verifiable-credential-revocation-service"` | Image repository | +| vcrs.image.tag | string | `"latest"` | Image tag (empty one will use "appVersion" value from chart definition) | +| vcrs.ingress.service.port | int | `8081` | Kubernetes Service port | +| vcrs.ingress.service.type | string | `"ClusterIP"` | Kubernetes Service type | +| vcrs.livenessProbe | object | `{"enabled":true,"failureThreshold":3,"initialDelaySeconds":60,"periodSeconds":5,"timeoutSeconds":30}` | Kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | +| vcrs.livenessProbe.enabled | bool | `true` | Enables/Disables the livenessProbe at all | +| vcrs.livenessProbe.failureThreshold | int | `3` | When a probe fails, Kubernetes will try failureThreshold times before giving up. Giving up in case of liveness probe means restarting the container. | +| vcrs.livenessProbe.initialDelaySeconds | int | `60` | Number of seconds after the container has started before readiness probe are initiated. | +| vcrs.livenessProbe.periodSeconds | int | `5` | How often (in seconds) to perform the probe | +| vcrs.livenessProbe.timeoutSeconds | int | `30` | Number of seconds after which the probe times out. | +| vcrs.nameOverride | string | `"verifiable-credential-revocation-service"` | The configmap name | +| vcrs.readinessProbe | object | `{"enabled":true,"failureThreshold":3,"initialDelaySeconds":60,"periodSeconds":30,"timeoutSeconds":30}` | Kubernetes [readiness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) | +| vcrs.readinessProbe.enabled | bool | `true` | Enables/Disables the readinessProbe at all | +| vcrs.readinessProbe.failureThreshold | int | `3` | When a probe fails, Kubernetes will try failureThreshold times before giving up. In case of readiness probe the Pod will be marked Unready. | +| vcrs.readinessProbe.initialDelaySeconds | int | `60` | Number of seconds after the container has started before readiness probe are initiated. | +| vcrs.readinessProbe.periodSeconds | int | `30` | How often (in seconds) to perform the probe | +| vcrs.readinessProbe.timeoutSeconds | int | `30` | Number of seconds after which the probe times out. | +| vcrs.secretName | string | `"verifiable-credential-revocation-service"` | The Secret name | +| vcrs.secrets.DATABASE_PASSWORD | string | `"defaultpassword"` | The Database Password | +| vcrs.secrets.password | string | `"defaultpassword"` | Postgresql password for MIW non-root User | +| vcrs.secrets.postgres-password | string | `"defaultpassword"` | Postgresql password for postgres root-user | +| vcrs.serviceName | string | `"verifiable-credential-revocation-service"` | The Service name | For more information on how to configure the Keycloak see - https://github.com/bitnami/charts/tree/main/bitnami/keycloak. @@ -263,6 +318,7 @@ when deploying the MIW in a production environment: | Name | Email | Url | | ---- | ------ | --- | | Dominik Pinsel | | | +| Rohit Solanki | | |

(back to top)

diff --git a/charts/managed-identity-wallet/templates/NOTES.txt b/charts/managed-identity-wallet/templates/NOTES.txt index ddfc099c9..320fef157 100644 --- a/charts/managed-identity-wallet/templates/NOTES.txt +++ b/charts/managed-identity-wallet/templates/NOTES.txt @@ -3,6 +3,7 @@ {{- range $host := .Values.ingress.hosts }} {{- range .paths }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + http{{ if $.Values.vcrs.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} {{- end }} {{- end }} {{- else if contains "NodePort" .Values.service.type }} @@ -17,6 +18,7 @@ {{- else if contains "ClusterIP" .Values.service.type }} export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "managed-identity-wallet.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT + Visit http://127.0.0.1:8080 (MIW) and http://127.0.0.1:8081 (VCRS) to use your application + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:8080 + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8081:8081 {{- end }} diff --git a/charts/managed-identity-wallet/templates/_helpers.tpl b/charts/managed-identity-wallet/templates/_helpers.tpl index cf153767c..6fd0b2394 100644 --- a/charts/managed-identity-wallet/templates/_helpers.tpl +++ b/charts/managed-identity-wallet/templates/_helpers.tpl @@ -24,6 +24,10 @@ Expand the name of the chart. {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} +{{- define "verifiable-credential-revocation-service.name" -}} +{{- default .Chart.Name .Values.vcrs.env.APPLICATION_NAME | trunc 63 | trimSuffix "-" }} +{{- end }} + {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). @@ -42,6 +46,19 @@ If release name contains chart name it will be used as a full name. {{- end }} {{- end }} +{{- define "verifiable-credential-revocation-service.fullname" -}} +{{- if .Values.vcrs.env.APPLICATION_NAME }} +{{- .Values.vcrs.env.APPLICATION_NAME | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.vcrs.env.APPLICATION_NAME }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + {{/* Create chart name and version as used by the chart label. */}} @@ -49,6 +66,10 @@ Create chart name and version as used by the chart label. {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} +{{- define "verifiable-credential-revocation-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + {{/* Common labels */}} @@ -61,6 +82,15 @@ app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} +{{- define "verifiable-credential-revocation-service.labels" -}} +helm.sh/chart: {{ include "verifiable-credential-revocation-service.chart" . }} +{{ include "verifiable-credential-revocation-service.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + {{/* Selector labels */}} @@ -69,6 +99,11 @@ app.kubernetes.io/name: {{ include "managed-identity-wallet.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} +{{- define "verifiable-credential-revocation-service.selectorLabels" -}} +app.kubernetes.io/name: {{ include "verifiable-credential-revocation-service.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + {{/* Create the name of the service account to use */}} diff --git a/charts/managed-identity-wallet/templates/deployment.yaml b/charts/managed-identity-wallet/templates/miw-deployment.yaml similarity index 84% rename from charts/managed-identity-wallet/templates/deployment.yaml rename to charts/managed-identity-wallet/templates/miw-deployment.yaml index 801dbf9a7..bb7401278 100644 --- a/charts/managed-identity-wallet/templates/deployment.yaml +++ b/charts/managed-identity-wallet/templates/miw-deployment.yaml @@ -1,21 +1,21 @@ -# /******************************************************************************** -# * 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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: apps/v1 kind: Deployment @@ -117,7 +117,7 @@ spec: - name: http containerPort: 8080 protocol: TCP - {{- with .Values.livenessProbe }} + {{- with .Values.miw.livenessProbe }} {{- if .enabled }} livenessProbe: httpGet: @@ -130,7 +130,7 @@ spec: timeoutSeconds: {{ .timeoutSeconds }} {{- end }} {{- end }} - {{- with .Values.readinessProbe }} + {{- with .Values.miw.readinessProbe }} {{- if .enabled }} readinessProbe: httpGet: @@ -162,4 +162,4 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} volumes: - {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- toYaml .Values.extraVolumes | nindent 8 }} \ No newline at end of file diff --git a/charts/managed-identity-wallet/templates/ingress.yaml b/charts/managed-identity-wallet/templates/miw-ingress.yaml similarity index 100% rename from charts/managed-identity-wallet/templates/ingress.yaml rename to charts/managed-identity-wallet/templates/miw-ingress.yaml diff --git a/charts/managed-identity-wallet/templates/secret.yaml b/charts/managed-identity-wallet/templates/miw-secret.yaml similarity index 100% rename from charts/managed-identity-wallet/templates/secret.yaml rename to charts/managed-identity-wallet/templates/miw-secret.yaml diff --git a/charts/managed-identity-wallet/templates/service.yaml b/charts/managed-identity-wallet/templates/miw-service.yaml similarity index 100% rename from charts/managed-identity-wallet/templates/service.yaml rename to charts/managed-identity-wallet/templates/miw-service.yaml diff --git a/charts/managed-identity-wallet/templates/networkpolicy.yaml b/charts/managed-identity-wallet/templates/networkpolicy.yaml index f989b9b71..2edaefbc3 100644 --- a/charts/managed-identity-wallet/templates/networkpolicy.yaml +++ b/charts/managed-identity-wallet/templates/networkpolicy.yaml @@ -16,6 +16,7 @@ # * # * SPDX-License-Identifier: Apache-2.0 # ********************************************************************************/ + {{- if .Values.networkPolicy.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy diff --git a/charts/managed-identity-wallet/templates/psql-pv.yaml b/charts/managed-identity-wallet/templates/psql-pv.yaml new file mode 100644 index 000000000..7db266055 --- /dev/null +++ b/charts/managed-identity-wallet/templates/psql-pv.yaml @@ -0,0 +1,30 @@ +############################################################### +# Copyright (c) 2024 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: PersistentVolumeClaim +metadata: + name: postgres-seed-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: standard \ No newline at end of file diff --git a/charts/managed-identity-wallet/templates/serviceaccount.yaml b/charts/managed-identity-wallet/templates/serviceaccount.yaml index 83a530398..f2824f59d 100644 --- a/charts/managed-identity-wallet/templates/serviceaccount.yaml +++ b/charts/managed-identity-wallet/templates/serviceaccount.yaml @@ -1,21 +1,21 @@ -# /******************************************************************************** -# * 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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 +############################################################### {{ if .Values.serviceAccount.create -}} apiVersion: v1 diff --git a/charts/managed-identity-wallet/templates/vcrs-configmap.yaml b/charts/managed-identity-wallet/templates/vcrs-configmap.yaml new file mode 100644 index 000000000..bf07ec84d --- /dev/null +++ b/charts/managed-identity-wallet/templates/vcrs-configmap.yaml @@ -0,0 +1,27 @@ +############################################################### +# Copyright (c) 2024 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 "verifiable-credential-revocation-service.fullname" . }} +data: + {{- range $key, $val := .Values.vcrs.env }} + {{ $key }}: {{ $val | quote }} + {{- end}} \ No newline at end of file diff --git a/charts/managed-identity-wallet/templates/vcrs-deployment.yaml b/charts/managed-identity-wallet/templates/vcrs-deployment.yaml new file mode 100644 index 000000000..16179582f --- /dev/null +++ b/charts/managed-identity-wallet/templates/vcrs-deployment.yaml @@ -0,0 +1,120 @@ +############################################################### +# Copyright (c) 2024 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: apps/v1 +kind: Deployment +metadata: + name: {{ include "verifiable-credential-revocation-service.fullname" . }} + labels: + {{- include "verifiable-credential-revocation-service.labels" . | nindent 4 }} +spec: + {{- if not .Values.vcrs.autoscaling.enabled }} + replicas: {{ .Values.vcrs.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "verifiable-credential-revocation-service.selectorLabels" . | nindent 6 }} + strategy: + {{- if .Values.vcrs.rollingUpdate.enabled }} + type: RollingUpdate + rollingUpdate: + maxSurge: {{ .Values.vcrs.rollingUpdate.rollingUpdateMaxSurge }} + maxUnavailable: {{ .Values.vcrs.rollingUpdate.rollingUpdateMaxUnavailable }} + {{- end }} + template: + metadata: + {{- with .Values.vcrs.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "verifiable-credential-revocation-service.labels" . | nindent 8 }} + {{- with .Values.vcrs.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.vcrs.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.vcrs.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.vcrs.securityContext | nindent 12 }} + image: "{{ .Values.vcrs.image.repository }}:{{ .Values.vcrs.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.vcrs.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.vcrs.ingress.service.port }} + protocol: TCP + {{- with .Values.vcrs.livenessProbe }} + {{- if .enabled }} + livenessProbe: + httpGet: + path: /actuator/health/liveness + port: 8081 + scheme: HTTP + failureThreshold: {{ .failureThreshold }} + initialDelaySeconds: {{ .initialDelaySeconds }} + periodSeconds: {{ .periodSeconds }} + timeoutSeconds: {{ .timeoutSeconds }} + {{- end }} + {{- end }} + {{- with .Values.vcrs.readinessProbe }} + {{- if .enabled }} + readinessProbe: + httpGet: + path: /actuator/health/readiness + port: 8081 + scheme: HTTP + failureThreshold: {{ .failureThreshold }} + initialDelaySeconds: {{ .initialDelaySeconds }} + periodSeconds: {{ .periodSeconds }} + timeoutSeconds: {{ .timeoutSeconds }} + {{- end }} + {{- end }} + resources: + {{- toYaml .Values.vcrs.resources | nindent 12 }} + envFrom: + - secretRef: + name: {{ .Values.vcrs.secretName }} + - configMapRef: + name: {{ .Values.vcrs.configName }} + {{- with .Values.vcrs.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.vcrs.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.vcrs.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.vcrs.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.vcrs.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/charts/managed-identity-wallet/templates/vcrs-hpa.yaml b/charts/managed-identity-wallet/templates/vcrs-hpa.yaml new file mode 100644 index 000000000..9c5ae5a8f --- /dev/null +++ b/charts/managed-identity-wallet/templates/vcrs-hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.vcrs.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "verifiable-credential-revocation-service.fullname" . }} + labels: + {{- include "verifiable-credential-revocation-service.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "verifiable-credential-revocation-service.fullname" . }} + minReplicas: {{ .Values.vcrs.autoscaling.minReplicas }} + maxReplicas: {{ .Values.vcrs.autoscaling.maxReplicas }} + metrics: + {{- if .Values.vcrs.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.vcrs.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.vcrs.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.vcrs.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/managed-identity-wallet/templates/vcrs-ingress.yaml b/charts/managed-identity-wallet/templates/vcrs-ingress.yaml new file mode 100644 index 000000000..b97a5eac2 --- /dev/null +++ b/charts/managed-identity-wallet/templates/vcrs-ingress.yaml @@ -0,0 +1,80 @@ +############################################################### +# Copyright (c) 2024 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 +############################################################### + +{{ if .Values.vcrs.ingress.enabled -}} +{{- $fullName := include "verifiable-credential-revocation-service.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.vcrs.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.vcrs.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.vcrs.ingress.annotations "kubernetes.io/ingress.class" .Values.vcrs.ingress.className }} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "verifiable-credential-revocation-service.labels" . | nindent 4 }} + {{- with .Values.vcrs.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.vcrs.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.vcrs.ingress.className }} + {{- end }} + {{- if .Values.vcrs.ingress.tls }} + tls: + {{- range .Values.vcrs.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ tpl . $ | quote }} + {{- end }} + secretName: "{{ $fullName }}-{{ .secretName }}" + {{- end }} + {{- end }} + rules: + {{- range .Values.vcrs.ingress.hosts }} + - host: {{ tpl .host $ | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/managed-identity-wallet/templates/vcrs-secrets.yaml b/charts/managed-identity-wallet/templates/vcrs-secrets.yaml new file mode 100644 index 000000000..82f8d7fd6 --- /dev/null +++ b/charts/managed-identity-wallet/templates/vcrs-secrets.yaml @@ -0,0 +1,27 @@ +############################################################### +# Copyright (c) 2024 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: Secret +metadata: + name: {{ include "verifiable-credential-revocation-service.fullname" . }} +type: Opaque +data: + {{- range $key, $val := .Values.vcrs.secrets }} + {{ $key }}: {{ $val | b64enc }} + {{- end}} diff --git a/charts/managed-identity-wallet/templates/vcrs-service.yaml b/charts/managed-identity-wallet/templates/vcrs-service.yaml new file mode 100644 index 000000000..15412d481 --- /dev/null +++ b/charts/managed-identity-wallet/templates/vcrs-service.yaml @@ -0,0 +1,32 @@ +############################################################### +# Copyright (c) 2024 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: Service +metadata: + name: {{ include "verifiable-credential-revocation-service.fullname" . }} +spec: + type: {{ .Values.vcrs.ingress.service.type }} + ports: + - port: {{ .Values.vcrs.ingress.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "verifiable-credential-revocation-service.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/charts/managed-identity-wallet/tests/custom-values/deployment_test.yaml b/charts/managed-identity-wallet/tests/custom-values/deployment_test.yaml index e7436abc0..cca4627ad 100644 --- a/charts/managed-identity-wallet/tests/custom-values/deployment_test.yaml +++ b/charts/managed-identity-wallet/tests/custom-values/deployment_test.yaml @@ -1,28 +1,28 @@ -# /******************************************************************************** -# * Copyright (c) 2021,2024 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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 +############################################################### suite: test custom-values deployment chart: version: 9.9.9+test appVersion: 9.9.9 templates: - - templates/deployment.yaml + - templates/miw-deployment.yaml tests: - it: should have latest image tag values: @@ -31,7 +31,6 @@ tests: - matchRegex: path: spec.template.spec.containers[0].image pattern: .:latest - - it: should have environment variables set (envs and secrets set) values: - values.yml @@ -83,7 +82,7 @@ tests: valueFrom: secretKeyRef: key: password - name: RELEASE-NAME-postgresql + name: verifiable-credential-revocation-service - name: APPLICATION_PORT value: "8080" - name: VC_EXPIRY_DATE @@ -93,4 +92,3 @@ tests: secretKeyRef: key: encryption-key name: RELEASE-NAME-managed-identity-wallet - diff --git a/charts/managed-identity-wallet/tests/custom-values/ingress_test.yaml b/charts/managed-identity-wallet/tests/custom-values/ingress_test.yaml index ba240c6f4..1887e72b1 100644 --- a/charts/managed-identity-wallet/tests/custom-values/ingress_test.yaml +++ b/charts/managed-identity-wallet/tests/custom-values/ingress_test.yaml @@ -1,28 +1,28 @@ -# /******************************************************************************** -# * 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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 +############################################################### suite: test custom-values ingress chart: version: 9.9.9+test appVersion: 9.9.9 templates: - - templates/ingress.yaml + - templates/miw-ingress.yaml values: - values.yml tests: @@ -42,7 +42,6 @@ tests: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/version: "9.9.9" app.kubernetes.io/managed-by: Helm - - it: must have rules set asserts: - isNotEmpty: @@ -57,7 +56,6 @@ tests: count: 1 - isNotEmpty: path: spec.rules[0].http.paths[0].path - - it: must have tls set asserts: - isNotEmpty: diff --git a/charts/managed-identity-wallet/tests/custom-values/secret_test.yaml b/charts/managed-identity-wallet/tests/custom-values/secret_test.yaml index 4ca3a80b8..5f99bc2c3 100644 --- a/charts/managed-identity-wallet/tests/custom-values/secret_test.yaml +++ b/charts/managed-identity-wallet/tests/custom-values/secret_test.yaml @@ -1,28 +1,28 @@ -# /******************************************************************************** -# * 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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 +############################################################### suite: test custom-values secret chart: version: 9.9.9+test appVersion: 9.9.9 templates: - - templates/secret.yaml + - templates/miw-secret.yaml values: - values.yml tests: @@ -38,20 +38,16 @@ tests: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/version: "9.9.9" app.kubernetes.io/managed-by: Helm - - it: must have type set to Opaque asserts: - equal: path: type value: Opaque - - it: must have data set asserts: - isNotEmpty: path: data - - it: must have values in data asserts: - exists: path: data.encryption-key - diff --git a/charts/managed-identity-wallet/tests/default/deployment_test.yaml b/charts/managed-identity-wallet/tests/default/deployment_test.yaml index cdc11c2e6..42b3df296 100644 --- a/charts/managed-identity-wallet/tests/default/deployment_test.yaml +++ b/charts/managed-identity-wallet/tests/default/deployment_test.yaml @@ -1,28 +1,28 @@ -# /******************************************************************************** -# * Copyright (c) 2021,2024 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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 +############################################################### suite: test default deployment chart: version: 9.9.9+test appVersion: 9.9.9 templates: - - templates/deployment.yaml + - templates/miw-deployment.yaml tests: - it: should have correct metadata asserts: @@ -40,7 +40,6 @@ tests: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/version: "9.9.9" app.kubernetes.io/managed-by: Helm - - it: should have important values set asserts: - equal: @@ -57,14 +56,12 @@ tests: name: http containerPort: 8080 protocol: TCP - - it: should have probes set asserts: - isNotEmpty: path: spec.template.spec.containers[0].livenessProbe - isNotEmpty: path: spec.template.spec.containers[0].readinessProbe - - it: should have resource limits set asserts: - isNotEmpty: @@ -81,7 +78,6 @@ tests: path: spec.template.spec.containers[0].resources.requests.cpu - isNotEmpty: path: spec.template.spec.containers[0].resources.requests.memory - - it: should have a security context asserts: - isSubset: @@ -92,7 +88,6 @@ tests: runAsGroup: 11111 runAsNonRoot: true runAsUser: 11111 - - it: should have environment variables set asserts: - isNotEmpty: @@ -142,19 +137,17 @@ tests: valueFrom: secretKeyRef: key: password - name: RELEASE-NAME-postgresql + name: verifiable-credential-revocation-service - name: APPLICATION_PORT value: "8080" - name: VC_EXPIRY_DATE value: 31-12-2024 - - it: should have empty values asserts: - notExists: path: spec.template.spec.affinity - notExists: path: spec.template.spec.tolerations - - it: should have nodeSelector value set asserts: - exists: @@ -163,7 +156,6 @@ tests: path: spec.template.spec.nodeSelector content: "kubernetes.io/os": linux - - it: should not have "imagePullSecrets" set asserts: - notExists: diff --git a/charts/managed-identity-wallet/tests/default/ingress_test.yaml b/charts/managed-identity-wallet/tests/default/ingress_test.yaml index 8217e084c..02e2735d6 100644 --- a/charts/managed-identity-wallet/tests/default/ingress_test.yaml +++ b/charts/managed-identity-wallet/tests/default/ingress_test.yaml @@ -1,28 +1,28 @@ -# /******************************************************************************** -# * 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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 +############################################################### suite: test default ingress chart: version: 9.9.9+test appVersion: 9.9.9 templates: - - templates/ingress.yaml + - templates/miw-ingress.yaml tests: - it: should not be available asserts: diff --git a/charts/managed-identity-wallet/tests/default/service_test.yaml b/charts/managed-identity-wallet/tests/default/service_test.yaml index a42879748..34f729980 100644 --- a/charts/managed-identity-wallet/tests/default/service_test.yaml +++ b/charts/managed-identity-wallet/tests/default/service_test.yaml @@ -1,28 +1,28 @@ -# /******************************************************************************** -# * 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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 +############################################################### suite: test default service chart: version: 9.9.9+test appVersion: 9.9.9 templates: - - templates/service.yaml + - templates/miw-service.yaml tests: - it: should have correct metadata asserts: @@ -40,13 +40,11 @@ tests: app.kubernetes.io/instance: RELEASE-NAME app.kubernetes.io/version: "9.9.9" app.kubernetes.io/managed-by: Helm - - it: should have type set to ClusterIP asserts: - equal: path: spec.type value: ClusterIP - - it: should have ports set asserts: - contains: diff --git a/charts/managed-identity-wallet/values.yaml b/charts/managed-identity-wallet/values.yaml index 0b87fe376..16849bd32 100644 --- a/charts/managed-identity-wallet/values.yaml +++ b/charts/managed-identity-wallet/values.yaml @@ -1,30 +1,29 @@ -# /******************************************************************************** -# * Copyright (c) 2021,2024 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 -# ********************************************************************************/ +############################################################### +# Copyright (c) 2024 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 +############################################################### +# -- Values for Managed Identity Wallet # -- The amount of replicas to run replicaCount: 1 - # -- String to partially override common.names.fullname template (will maintain the release name) nameOverride: "" # -- String to fully override common.names.fullname template fullnameOverride: "" - image: # -- Image repository repository: tractusx/managed-identity-wallet @@ -32,14 +31,11 @@ image: pullPolicy: Always # -- Image tag (empty one will use "appVersion" value from chart definition) tag: "" - - +imagePullSecrets: [] # -- Parameters for the application (will be stored as secrets - so, for passwords, ...) secrets: {} - # -- envs Parameters for the application (will be provided as environment variables) envs: {} - serviceAccount: # -- Enable creation of ServiceAccount create: true @@ -47,13 +43,12 @@ serviceAccount: annotations: {} # -- The name of the ServiceAccount to use. name: "" - service: # -- Kubernetes Service type type: ClusterIP # -- Kubernetes Service port port: 8080 - +# -- Ingress Configuration ingress: # -- Enable ingress controller resource enabled: false @@ -70,10 +65,10 @@ ingress: # - secretName: chart-example-tls # hosts: # - chart-example.local - -# -- PodSecurityContext + className: nginx +# -- Pod security configurations podSecurityContext: {} - +# -- Pod security parameters securityContext: # -- Enable privileged container privileged: false @@ -85,35 +80,8 @@ securityContext: runAsGroup: 11111 # -- Enable to run the container as a non-root user runAsNonRoot: true - -# -- Kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) -livenessProbe: - # -- Enables/Disables the livenessProbe at all - enabled: true - # -- When a probe fails, Kubernetes will try failureThreshold times before giving up. Giving up in case of liveness probe means restarting the container. - failureThreshold: 3 - # -- Number of seconds after the container has started before readiness probe are initiated. - initialDelaySeconds: 20 - # -- Number of seconds after which the probe times out. - timeoutSeconds: 15 - # -- How often (in seconds) to perform the probe - periodSeconds: 5 - -# -- Kubernetes [readiness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) -readinessProbe: - # -- Enables/Disables the readinessProbe at all - enabled: true - # -- When a probe fails, Kubernetes will try failureThreshold times before giving up. In case of readiness probe the Pod will be marked Unready. - failureThreshold: 3 - # -- Number of seconds after the container has started before readiness probe are initiated. - initialDelaySeconds: 30 - # -- How often (in seconds) to perform the probe - periodSeconds: 5 - # -- Minimum consecutive successes for the probe to be considered successful after having failed. - successThreshold: 1 - # -- Number of seconds after which the probe times out. - timeoutSeconds: 5 - + # # -- Filesystem group ID + # fsGroup: 1001 resources: requests: # -- CPU resource requests @@ -125,38 +93,27 @@ resources: cpu: 2 # -- Memory resource limits memory: 1Gi - # -- NodeSelector configuration nodeSelector: "kubernetes.io/os": linux - # -- Tolerations configuration tolerations: [] - # -- Affinity configuration affinity: {} - # -- PodAnnotation configuration podAnnotations: {} - # -- add initContainers to the miw deployment initContainers: [] - networkPolicy: # -- If `true` network policy will be created to restrict access to managed-identity-wallet enabled: false # -- Specify from rule network policy for miw (defaults to all namespaces) from: - - namespaceSelector: {} - + - namespaceSelector: {} # -- add volumes to the miw deployment extraVolumes: [] - -# -- add volume mounts to the miw deployment extraVolumeMounts: [] - -## @section Managed Identity Wallet Primary Parameters -## +# -- Values for MIW miw: ## @param miw.host Host name ## @param miw.logging.level Log level. Should be ether ERROR, WARN, INFO, DEBUG, or TRACE. @@ -190,9 +147,10 @@ miw: # -- Database name name: "miw_app" # -- Existing secret name for the database password - secret: "{{ .Release.Name }}-postgresql" + secret: "verifiable-credential-revocation-service" # -- Existing secret key for the database password secretPasswordKey: "password" + # -- Password encryption configuratons encryptionKey: # -- Database encryption key for confidential data. Ignored if `secret` is set. If empty a secret with 32 random alphanumeric chars is generated. value: "" @@ -207,32 +165,71 @@ miw: clientId: "miw_private_client" # -- Keycloak URL url: "http://{{ .Release.Name }}-keycloak" - -# For more information on how to configure the Keycloak chart see https://github.com/bitnami/charts/tree/main/bitnami/keycloak. + # -- Kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + livenessProbe: + # -- Enables/Disables the livenessProbe at all + enabled: true + # -- When a probe fails, Kubernetes will try failureThreshold times before giving up. Giving up in case of liveness probe means restarting the container. + failureThreshold: 3 + # -- Number of seconds after the container has started before readiness probe are initiated. + initialDelaySeconds: 20 + # -- Number of seconds after which the probe times out. + timeoutSeconds: 15 + # -- How often (in seconds) to perform the probe + periodSeconds: 5 + # -- Kubernetes [readiness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + readinessProbe: + # -- Enables/Disables the readinessProbe at all + enabled: true + # -- When a probe fails, Kubernetes will try failureThreshold times before giving up. In case of readiness probe the Pod will be marked Unready. + failureThreshold: 3 + # -- Number of seconds after the container has started before readiness probe are initiated. + initialDelaySeconds: 30 + # -- How often (in seconds) to perform the probe + periodSeconds: 5 + # -- Minimum consecutive successes for the probe to be considered successful after having failed. + successThreshold: 1 + # -- Number of seconds after which the probe times out. + timeoutSeconds: 5 + # For more information on how to configure the Keycloak chart see https://github.com/bitnami/charts/tree/main/bitnami/keycloak. +# -- Values for KEYCLOAK keycloak: # -- Enable to deploy Keycloak enabled: true # -- Extra environment variables extraEnvVars: [] - # - name: KEYCLOAK_HOSTNAME - # value: "{{ .Release.Name }}-keycloak" + # - name: KEYCLOAK_HOSTNAME + # value: "keycloak" postgresql: # -- Name of the PostgreSQL chart to deploy. Mandatory when the MIW deploys a PostgreSQL chart, too. nameOverride: "keycloak-postgresql" # -- Enable to deploy PostgreSQL enabled: true auth: - # -- Keycloak PostgreSQL user + # -- Postgresql admin user password username: "miw_keycloak" # -- KeycloakPostgresql password to set (if empty one is generated) - password: "" + password: "defaultpassword" # -- Database name database: "miw_keycloak" + volumePermissions: + enabled: true ingress: + # -- Enable ingress controller resource enabled: false + # -- Ingress annotations annotations: {} + # -- Ingress accepted hostnames hosts: [] + # - host: chart-example.local + # paths: + # - path: / + # pathType: Prefix + # -- Ingress TLS configuration tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local auth: # -- Keycloak admin user adminUser: "admin" @@ -245,28 +242,51 @@ keycloak: existingConfigmap: keycloak-realm-config # -- Number of retries before considering a Job as failed backoffLimit: 2 - +# -- Values for POSTGRESQL # For more information on how to configure the PostgreSQL chart see https://github.com/bitnami/charts/tree/main/bitnami/postgresql. postgresql: # -- Enable to deploy Postgresql enabled: true + image: + tag: "16-debian-12" + # -- Debug logs + debug: true auth: # -- Enable postgresql admin user - enablePostgresUser: false - # -- Postgresql admin user password - postgresPassword: "" + enablePostgresUser: true + # -- Postgresql root-user and non-root user secret + existingSecret: "verifiable-credential-revocation-service" # -- Postgresql user to create username: "miw" - # -- Postgresql password to set (if empty one is generated) - password: "" # -- Postgresql database to create database: "miw_app" + # -- Creating a new database for VCRS application (Edit the DB configurations as required in configmap) + primary: + extraVolumes: + - name: postgres-seed + persistentVolumeClaim: + claimName: postgres-seed-pvc + extraVolumeMounts: + - mountPath: /docker-entrypoint-initdb.d/seed + name: postgres-seed + initdb: + user: "postgres" + password: "defaultpassword" + scripts: + init.sql: | + CREATE DATABASE vcrs_app; + CREATE USER vcrs WITH ENCRYPTED PASSWORD 'defaultpassword'; + GRANT ALL PRIVILEGES ON DATABASE vcrs_app TO vcrs; + \c vcrs_app + GRANT ALL ON SCHEMA public TO vcrs; backup: # -- Enable to create a backup cronjob enabled: false + # -- Cronjob Configuration cronjob: # -- Backup schedule schedule: "* */6 * * *" + # Backup Storage configuration storage: # -- Name of an existing PVC to use existingClaim: "" @@ -274,9 +294,10 @@ postgresql: resourcePolicy: "keep" # -- PVC Storage Request for the backup data volume size: "8Gi" - + volumePermissions: + enabled: true +# -- Values for PGADMIN # For more information on how to configure the pgadmin chart see https://artifacthub.io/packages/helm/runix/pgadmin4. -# (Here we're using a stripped-down version of the pgadmin chart, to just ) pgadmin4: # -- Enable to deploy pgAdmin enabled: false @@ -318,3 +339,158 @@ pgadmin4: subPath: servers.json mountPath: "/pgadmin4/servers.json" readOnly: true +# -- Values for Verifiable Credential Revocation Service application +vcrs: + replicaCount: 1 + # -- Revocation application configuration + host: localhost + # -- The configmap name + nameOverride: "verifiable-credential-revocation-service" + # -- String to partially override common.names.fullname template (will maintain the release name) + fullnameOverride: "verifiable-credential-revocation-service" + # -- ConfigMap Name + configName: "verifiable-credential-revocation-service" + # -- The Service name + serviceName: "verifiable-credential-revocation-service" + # -- The Secret name + secretName: "verifiable-credential-revocation-service" + image: + # -- Image repository + repository: tractusx/verifiable-credential-revocation-service + # -- PullPolicy + pullPolicy: IfNotPresent + # -- Image tag (empty one will use "appVersion" value from chart definition) + tag: "latest" + env: + # -- The application name + APPLICATION_NAME: verifiable-credential-revocation-service + # -- The application port + APPLICATION_PORT: 8081 + # -- The application profile + APPLICATION_PROFILE: local + # -- The Database Host + DATABASE_HOST: managed-identity-wallet-postgresql + # -- The Database Port + DATABASE_PORT: 5432 + # -- The Database Name + DATABASE_NAME: vcrs_app + # -- The Database SSL + DATABASE_USE_SSL_COMMUNICATION: false + # -- The Database Name + DATABASE_USERNAME: vcrs + # -- The Database connection pool size + DATABASE_CONNECTION_POOL_SIZE: 10 + # -- Swagger UI config + ENABLE_SWAGGER_UI: true + # -- Swagger Api Doc + ENABLE_API_DOC: true + # -- The application log level + APPLICATION_LOG_LEVEL: DEBUG + # Enable application security + SERVICE_SECURITY_ENABLED: true + # -- KeyClocak Configurations + KEYCLOAK_REALM: miw_test + # -- ClientID Config + KEYCLOAK_CLIENT_ID: miw_private_client + # -- ClientID Config + KEYCLOAK_PUBLIC_CLIENT_ID: miw_public_client + # -- Auth URL for Keycloak + AUTH_SERVER_URL: "http://{{ .Release.Name }}-keycloak" + # -- Revocation application configuration + MIW_URL: https://a888-203-129-213-107.ngrok-free.app + VC_SCHEMA_LINK: https://www.w3.org/2018/credentials/v1, https://w3id.org/vc/status-list/2021/v1 + DOMAIN_URL: https://977d-203-129-213-107.ngrok-free.app + # Application logging configurations + APP_LOG_LEVEL: INFO + secrets: + # -- The Database Password + DATABASE_PASSWORD: "defaultpassword" + # -- Postgresql password for MIW non-root User + password: "defaultpassword" + # -- Postgresql password for postgres root-user + postgres-password: "defaultpassword" + podAnnotations: {} + podLabels: {} + imagePullSecrets: [] + rollingUpdate: + enabled: true + # Minimum number of pods that should be running during the update process. + rollingUpdateMaxSurge: 1 + # Maximum number of pods that can be unavailable during the update process. + rollingUpdateMaxUnavailable: 0 + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + # -- Kubernetes [liveness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + livenessProbe: + # -- Enables/Disables the livenessProbe at all + enabled: true + # -- When a probe fails, Kubernetes will try failureThreshold times before giving up. Giving up in case of liveness probe means restarting the container. + failureThreshold: 3 + # -- Number of seconds after the container has started before readiness probe are initiated. + initialDelaySeconds: 60 + # -- Number of seconds after which the probe times out. + timeoutSeconds: 30 + # -- How often (in seconds) to perform the probe + periodSeconds: 5 + # -- Kubernetes [readiness-probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) + readinessProbe: + # -- Enables/Disables the readinessProbe at all + enabled: true + # -- When a probe fails, Kubernetes will try failureThreshold times before giving up. In case of readiness probe the Pod will be marked Unready. + failureThreshold: 3 + # -- Number of seconds after the container has started before readiness probe are initiated. + initialDelaySeconds: 60 + # -- How often (in seconds) to perform the probe + periodSeconds: 30 + # -- Number of seconds after which the probe times out. + timeoutSeconds: 30 + # -- ingress configuration + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 2 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + ingressName: "verifiable-credential-revocation-service-ingress" + ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + # - host: chart-example.local + # paths: + # - path: / + # pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + service: + # -- Kubernetes Service type + type: ClusterIP + # -- Kubernetes Service port + port: 8081 + database: + encryptionKey: + # -- Database encryption key for confidential data. Ignored if `secret` is set. If empty a secret with 32 random alphanumeric chars is generated. + value: "" + # -- Existing secret for database encryption key + secret: "" + # -- Existing secret key for database encryption key + secretKey: "" + podSecurityContext: {} + securityContext: + allowPrivilegeEscalation: false + volumes: [] + # Additional volumeMounts on the output Deployment definition. + volumeMounts: [] + nodeSelector: {} + tolerations: [] + affinity: {} diff --git a/docs/api/openapi_v001.json b/docs/api/miw/openapi_v001.json similarity index 99% rename from docs/api/openapi_v001.json rename to docs/api/miw/openapi_v001.json index d29890256..e15bb2622 100644 --- a/docs/api/openapi_v001.json +++ b/docs/api/miw/openapi_v001.json @@ -1805,7 +1805,7 @@ ], "@context": [ "https://www.w3.org/2018/credentials/v1", - "https://cofinity-x.github.io/schema-registry/v1.1/DismantlerVC.json", + "https://eclipse=tractusx.github.io/schema-registry/v1.1/DismantlerVC.json", "https://w3id.org/security/suites/jws-2020/v1", "https://w3id.org/vc/status-list/2021/v1" ], diff --git a/docs/api/revocation-service/openapi_v001.json b/docs/api/revocation-service/openapi_v001.json new file mode 100644 index 000000000..0e95ff5a2 --- /dev/null +++ b/docs/api/revocation-service/openapi_v001.json @@ -0,0 +1,336 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "Reovcation Service API", + "description" : "Revocation Service API", + "contact" : { + "name" : "eclipse-tractusx", + "url" : "https://projects.eclipse.org/projects/automotive.tractusx", + "email" : "tractusx-dev@eclipse.org" + }, + "version" : "0.0.1" + }, + "servers" : [ + { + "url" : "http://localhost:8081", + "description" : "Generated server url" + } + ], + "security" : [ + { + "Authenticate using access_token" : [] + } + ], + "tags" : [ + { + "name" : "Revocation Service", + "description" : "Revocation Service API" + } + ], + "paths" : { + "/api/v1/revocations/verify" : { + "post" : { + "tags" : [ + "Revocation Service" + ], + "summary" : "Verify Revocation status", + "description" : "Verify revocation status of Credential", + "operationId" : "verifyRevocation", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CredentialStatusDto" + }, + "example" : { + "id" : "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1#12", + "statusPurpose" : "revocation", + "statusListIndex" : "12", + "statusListCredential" : "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type" : "StatusList2021" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "Status of credential", + "content" : { + "application/json" : { + "examples" : { + "if credential is revoked" : { + "description" : "if credential is revoked", + "value" : { + "status" : "revoked" + } + }, + "if credential is active" : { + "description" : "if credential is is active", + "value" : { + "status" : "active" + } + } + } + } + } + }, + "403" : { + "description" : "ForbiddenException: invalid caller" + }, + "401" : { + "description" : "UnauthorizedException: invalid token" + }, + "500" : { + "description" : "RevocationServiceException: Internal Server Error" + } + } + } + }, + "/api/v1/revocations/status-entry" : { + "post" : { + "tags" : [ + "Revocation Service" + ], + "summary" : "Create or Update a Status List Credential", + "description" : "Create the status list credential if it does not exist, else update it.", + "operationId" : "createStatusListVC", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/StatusEntryDto" + }, + "example" : { + "purpose" : "revocation", + "issuerId" : "did:web:localhost:BPNL000000000000" + } + } + }, + "required" : true + }, + "responses" : { + "403" : { + "description" : "ForbiddenException: invalid caller" + }, + "200" : { + "description" : "Status list credential created/updated successfully.", + "content" : { + "application/json" : { + "example" : { + "id" : "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1#17", + "statusPurpose" : "revocation", + "statusListIndex" : "17", + "statusListCredential" : "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type" : "StatusList2021" + } + } + } + }, + "401" : { + "description" : "UnauthorizedException: invalid token" + }, + "500" : { + "description" : "RevocationServiceException: Internal Server Error" + } + } + } + }, + "/api/v1/revocations/revoke" : { + "post" : { + "tags" : [ + "Revocation Service" + ], + "summary" : "Revoke a VerifiableCredential", + "description" : "Revoke a VerifiableCredential using the provided Credential Status", + "operationId" : "revokeCredential", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CredentialStatusDto" + }, + "example" : { + "id" : "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1#12", + "statusPurpose" : "revocation", + "statusListIndex" : "12", + "statusListCredential" : "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type" : "StatusList2021" + } + } + }, + "required" : true + }, + "responses" : { + "403" : { + "description" : "ForbiddenException: invalid caller" + }, + "409" : { + "description" : "ConflictException: Revocation service error", + "content" : { + "application/json" : { + "example" : { + "type" : "StatusList2021", + "title" : "Revocation service error", + "status" : "409", + "detail" : "Credential already revoked", + "instance" : "/api/v1/revocations/revoke", + "timestamp" : 1707133388128 + } + } + } + }, + "401" : { + "description" : "UnauthorizedException: invalid token" + }, + "200" : { + "description" : "Verifiable credential revoked successfully." + }, + "500" : { + "description" : "RevocationServiceException: Internal Server Error" + } + } + } + }, + "/api/v1/revocations/credentials/{issuerBPN}/{status}/{index}" : { + "get" : { + "tags" : [ + "Revocation Service" + ], + "summary" : "Get status list credential", + "description" : "Get status list credential using the provided issuer BPN and status purpose and status list index", + "operationId" : "getStatusListCredential", + "parameters" : [ + { + "name" : "issuerBPN", + "in" : "path", + "description" : "Issuer BPN", + "required" : true, + "schema" : { + "type" : "string" + }, + "example" : "BPNL000000000000" + }, + { + "name" : "status", + "in" : "path", + "description" : "Status Purpose ( Revocation or Suspension)", + "required" : true, + "schema" : { + "type" : "string" + }, + "example" : "revocation" + }, + { + "name" : "index", + "in" : "path", + "description" : "status list index", + "required" : true, + "schema" : { + "type" : "string" + }, + "example" : 1 + } + ], + "responses" : { + "200" : { + "description" : "Get Status list credential ", + "content" : { + "application/json" : { + "example" : { + "@context" : [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id" : "http://localhost/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type" : [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "issuer" : "did:web:localhost:BPNL000000000000", + "issuanceDate" : "2024-02-05T09:39:58Z", + "credentialSubject" : [ + { + "statusPurpose" : "revocation", + "id" : "http://localhost/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type" : "StatusList2021", + "encodedList" : "H4sIAAAAAAAA/wMAAAAAAAAAAAA=" + } + ], + "proof" : { + "proofPurpose" : "assertionMethod", + "type" : "JsonWebSignature2020", + "verificationMethod" : "did:web:localhost:BPNL000000000000#ed463e4c-b900-481a-b5d0-9ae439c434ae", + "created" : "2024-02-05T09:39:58Z", + "jws" : "eyJhbGciOiJFZERTQSJ9..swX1PLJkSlxB6JMmY4a2uUzR-uszlyLrVdNppoYSx4PTV1LzQrDb0afzp_dvTNUWEYDI57a8iPh78BDjqMjSDQ" + } + } + } + } + }, + "404" : { + "description" : "Status list credential not found" + }, + "500" : { + "description" : "RevocationServiceException: Internal Server Error" + } + } + } + } + }, + "components" : { + "schemas" : { + "CredentialStatusDto" : { + "required" : [ + "id", + "statusListCredential", + "statusListIndex", + "statusPurpose", + "type" + ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string" + }, + "statusPurpose" : { + "type" : "string" + }, + "statusListIndex" : { + "type" : "string" + }, + "statusListCredential" : { + "type" : "string" + }, + "type" : { + "type" : "string" + } + } + }, + "StatusEntryDto" : { + "required" : [ + "issuerId", + "purpose" + ], + "type" : "object", + "properties" : { + "purpose" : { + "type" : "string" + }, + "issuerId" : { + "type" : "string" + } + } + } + }, + "securitySchemes" : { + "Authenticate using access_token" : { + "type" : "apiKey", + "description" : "**Bearer (apiKey)**\nJWT Authorization header using the Bearer scheme.\nEnter **Bearer** [space] and then your token in the text input below.\nExample: Bearer 12345abcdef", + "name" : "Authorization", + "in" : "header" + } + } + } +} diff --git a/docs/arc42/images/SingleInstanceDomainView.png b/docs/arc42/miw/images/SingleInstanceDomainView.png similarity index 100% rename from docs/arc42/images/SingleInstanceDomainView.png rename to docs/arc42/miw/images/SingleInstanceDomainView.png diff --git a/docs/arc42/images/VPP-Flow.png b/docs/arc42/miw/images/VPP-Flow.png similarity index 100% rename from docs/arc42/images/VPP-Flow.png rename to docs/arc42/miw/images/VPP-Flow.png diff --git a/docs/arc42/images/VVP-Flow.puml b/docs/arc42/miw/images/VVP-Flow.puml similarity index 100% rename from docs/arc42/images/VVP-Flow.puml rename to docs/arc42/miw/images/VVP-Flow.puml diff --git a/docs/arc42/images/business_context.png b/docs/arc42/miw/images/business_context.png similarity index 100% rename from docs/arc42/images/business_context.png rename to docs/arc42/miw/images/business_context.png diff --git a/docs/arc42/miw/images/issue_revocable_vc.png b/docs/arc42/miw/images/issue_revocable_vc.png new file mode 100644 index 000000000..2174a51ed Binary files /dev/null and b/docs/arc42/miw/images/issue_revocable_vc.png differ diff --git a/docs/arc42/images/technical_context.png b/docs/arc42/miw/images/technical_context.png similarity index 100% rename from docs/arc42/images/technical_context.png rename to docs/arc42/miw/images/technical_context.png diff --git a/docs/arc42/miw/images/verify_revocable_vc.png b/docs/arc42/miw/images/verify_revocable_vc.png new file mode 100644 index 000000000..e5b58d88b Binary files /dev/null and b/docs/arc42/miw/images/verify_revocable_vc.png differ diff --git a/docs/arc42/main.md b/docs/arc42/miw/main.md similarity index 91% rename from docs/arc42/main.md rename to docs/arc42/miw/main.md index ba5f7fe2f..da8faeb31 100644 --- a/docs/arc42/main.md +++ b/docs/arc42/miw/main.md @@ -89,11 +89,11 @@ with other DID agents. ## Business Context -![](./images/business_context.png) +![](images/business_context.png) ## Technical Context -![](./images/technical_context.png) +![](images/technical_context.png) # Solution Strategy @@ -114,7 +114,7 @@ service using the following technology stack: This diagram is a zoomed out view of the MIW and the components it interacts with. -![](./images/SingleInstanceDomainView.png) +![](images/SingleInstanceDomainView.png) ## Overall System @@ -177,6 +177,72 @@ group "Create Wallet" end group ``` +### Issue revocable credential + +![issue_revocable_vc.png](images%2Fissue_revocable_vc.png) + +``` +@startuml +title issue revocable VC +actor User as User +participant "Managed identity wallet" as MIW +participant "Revocation service" as RS +group "Issue VC" +User -> MIW: "/api/credentials/issuer" with revocable=true +alt "If revocable then get issuer status list" +MIW -> RS: "/api/v1/revocations/statusEntry +alt If status list VC not created? +RS -> RS: Create StatusList credentail +RS -> MIW: Sign new status list VC +MIW -> MIW: Save status list into issuer table +RS<-- MIW: return sighed status list VC +RS -> RS: Store StatusList VC in DB +end +RS -> RS: Get Current StatusList Index for Issuer +RS -> RS: Incease statusList Index by 1 and save in DB +RS --> MIW: Return Status List +end +group "Create and Sign VC" +MIW -> MIW: Create credentail +alt "If revocable then add status list" +MIW -> MIW: Add Status List in VC +end +MIW -> MIW: Sign VC +end group +MIW --> User: Return revocable VC +end group +@enduml + +``` + +### Verify revocable credential + +![verify_revocable_vc.png](images%2Fverify_revocable_vc.png) + +``` +@startuml +actor User as User +participant "Managed Identity Wallet" as MIW +participant "Revocation service" as RS +title Verify VC with revocation status +group "Verify VC" +User -> MIW: "/api/credentials/validation?withCredentialExpiryDate=true&withRevocation=true" +alt "If withRevocation then check issuer status list" +MIW -> RS: "/api/v1/revocations/credentials/{issueId}" +RS -> RS: Get Current StatusList Index for Issuer +RS --> MIW: Return StatusList VC +end +group "Credential Validation" +MIW -> MIW: validate status list VC +MIW -> MIW: Valaidate vc index with encoded list of status list VC +MIW -> MIW: Check Credential is not expired +MIW -> MIW: Validate Credential JsonLD +MIW -> MIW: Verify Credential Signature +end group +MIW --> User: Return Valid or Invalid + Reason +@enduml +``` + ### Validate Verifiable Presentation ```plantuml diff --git a/docs/arc42/revocation-service/images/business_context.png b/docs/arc42/revocation-service/images/business_context.png new file mode 100644 index 000000000..61229f670 Binary files /dev/null and b/docs/arc42/revocation-service/images/business_context.png differ diff --git a/docs/arc42/revocation-service/images/get_status_list_entry_api.png b/docs/arc42/revocation-service/images/get_status_list_entry_api.png new file mode 100644 index 000000000..d135c4a58 Binary files /dev/null and b/docs/arc42/revocation-service/images/get_status_list_entry_api.png differ diff --git a/docs/arc42/revocation-service/images/get_status_list_vc.png b/docs/arc42/revocation-service/images/get_status_list_vc.png new file mode 100644 index 000000000..ef8d8446b Binary files /dev/null and b/docs/arc42/revocation-service/images/get_status_list_vc.png differ diff --git a/docs/arc42/revocation-service/images/issue_revocable_vc.png b/docs/arc42/revocation-service/images/issue_revocable_vc.png new file mode 100644 index 000000000..2174a51ed Binary files /dev/null and b/docs/arc42/revocation-service/images/issue_revocable_vc.png differ diff --git a/docs/arc42/revocation-service/images/revocation-tech_context.png b/docs/arc42/revocation-service/images/revocation-tech_context.png new file mode 100644 index 000000000..3c7b22b2d Binary files /dev/null and b/docs/arc42/revocation-service/images/revocation-tech_context.png differ diff --git a/docs/arc42/revocation-service/images/revoke_vc.png b/docs/arc42/revocation-service/images/revoke_vc.png new file mode 100644 index 000000000..5548249c0 Binary files /dev/null and b/docs/arc42/revocation-service/images/revoke_vc.png differ diff --git a/docs/arc42/revocation-service/images/technical_context.png b/docs/arc42/revocation-service/images/technical_context.png new file mode 100644 index 000000000..e307375b3 Binary files /dev/null and b/docs/arc42/revocation-service/images/technical_context.png differ diff --git a/docs/arc42/revocation-service/images/verify_revocable_vc.png b/docs/arc42/revocation-service/images/verify_revocable_vc.png new file mode 100644 index 000000000..e5b58d88b Binary files /dev/null and b/docs/arc42/revocation-service/images/verify_revocable_vc.png differ diff --git a/docs/arc42/revocation-service/images/verify_status.png b/docs/arc42/revocation-service/images/verify_status.png new file mode 100644 index 000000000..844250049 Binary files /dev/null and b/docs/arc42/revocation-service/images/verify_status.png differ diff --git a/docs/arc42/revocation-service/main.md b/docs/arc42/revocation-service/main.md new file mode 100644 index 000000000..8afdad6e9 --- /dev/null +++ b/docs/arc42/revocation-service/main.md @@ -0,0 +1,405 @@ +# Table of content + +[[toc]] + +# Introduction and Goals + +We have a Managed Identity Wallet(MIW) application and this application is used to issue various types of verifiable +credentials using did:web method. + +As per business needs issuer might want to revoke issued credentials issued to any holder so credential validation will +not work. + +Simply deleting credentials will not work as there might be possible that holder save credentials in other location and +present it to verifier. + +When any business partner deboarded from Catena-X + +When there a any changes in credentials or updates needed in credentials. In this case, we need to revoke the older VC +and need to reissue it. + +The core functionalities are: + +1. Issue status list credentials to all wallets (Issuer) +2. Maintain status list index for revocation +3. Verify revocation status of credential + +## Cross-cutting Concepts + +Please refer to this for more information: [Status List 2021](https://www.w3.org/TR/2023/WD-vc-status-list-20230427) + +## Requirements Overview + +The basic requirements for the Managed Identity Wallet are as follows: + +- issue status list to all issuers using REST API + +- Manage status list index for each issuer + +- Allow issuers to revoke credentials using REST API + +- Allow verifier to verify status of credential using REST API + +- Update status list credential when issuer revoke credential using REST API + +## Quality Goals + +1. Status list VC should be stored in durable storage and backup should be taken +2. The current index should be created for each issued revocable VC +3. while revocation, the correct index should be revoked +4. The application should work in case of horizontal scanning +5. Only Authorize user/client can access the revocation API +6. One status list index should be created for one VC +7. Sonar quality gate should be passed +8. No issues in veracode scanning +9. Unit test coverage should be more than 80% + +## Stakeholders + +The key stakeholders of the component are: + +- Issuer: Issuer should be able to issue revocable credentials and able to revoke issued credentials when there a need + +- Verifier: Verify status of credential(active/revoked) along with signature and expiry date verification + +# Architecture Constraints + +The architecture of the Revocation Service follows the general +principles, guidelines and boundaries from the [Tractus-X +project](https://projects.eclipse.org/projects/automotive.tractusx). You can +find more details and documentation on the [Tractus-X GitHub +page](https://github.com/eclipse-tractusx). + +# System Scope and Context + +- Issuer can issue and revoke credentials +- verifier can verify status of credentials + +## Business Context + +- Catena-X portal/Base wallet: + + - CX-Operator onboard business partner and that will create a wallet for them + + - CX-Operator can verify the revocation status of any VC issued by the base wallet + + - CX-Operator can revoke issued VC + +- Business partner wallet: + + - Any business partner can issue revocable VC to a self-wallet + + - Business partners can verify the status of any VC + + - Business partners can revoke self-issued VC + +## Technical Context + +![revocation-tech_context.png](images/revocation-tech_context.png) + +# Solution Strategy + +The Revocation service is implemented as an independent REST API +service using the following technology stack: + +- Java 17 as a base programming language +- SpringBoot as an encompassing framework +- PostgreSQL as a database backend +- Gradle as a build tool +- Dockerized setup for Kubernetes with Helm + +# Runtime View + +API documentation can be found [openapi_v001.json](..%2F..%2Fapi%2Frevocation-service%2Fopenapi_v001.json) + +In general, the API covers the following functionality: + +### 1.Get status list entry + +In this endpoint, the Issuer will call this endpoint while issuing Revocable VC. The revocation service will create a +status list vc if not created and in return will give a status list entry with an updated index. +BPN number will be the issuer id in our case. + +![get_status_list_entry_api.png](images/get_status_list_entry_api.png) + +Request Body: + +```json + { + "purpose": "revocation", + "issuerId": "BPNL000000000000" +} +``` + +Response Body: + +```json +{ + "id": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1#12", + "statusPurpose": "revocation", + "statusListIndex": "12", + "statusListCredential": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": "StatusList2021" +} + +``` + +### 2. Get the status list VC + +In this endpoint, the Verifier will provide issuerId, and the revocation service will provide the StatusList VC of that +issuer. + +**_This API should be public_** + +![get_status_list_vc.png](images/get_status_list_vc.png) + +Response: + +```json +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "http://localhost/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "issuanceDate": "2024-02-05T09:39:58Z", + "credentialSubject": [ + { + "statusPurpose": "revocation", + "id": "http://localhost/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": "StatusList2021", + "encodedList": "H4sIAAAAAAAA/wMAAAAAAAAAAAA=" + } + ], + "proof": { + "proofPurpose": "assertionMethod", + "type": "JsonWebSignature2020", + "verificationMethod": "did:web:localhost:BPNL000000000000#ed463e4c-b900-481a-b5d0-9ae439c434ae", + "created": "2024-02-05T09:39:58Z", + "jws": "eyJhbGciOiJFZERTQSJ9..swX1PLJkSlxB6JMmY4a2uUzR-uszlyLrVdNppoYSx4PTV1LzQrDb0afzp_dvTNUWEYDI57a8iPh78BDjqMjSDQ" + } +} + +``` + +### 3. Revoke VC + +In this endpoint, the Issuer will request to revoke VC with StatusList. The revocation service will verify the VC and If +the VC is valid then it will set the bit value in the encoded list and reissue the status list VC to the issuer. + +![revoke_vc.png](images/revoke_vc.png) + +Request: + +```json +{ + "id": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1#12", + "statusPurpose": "revocation", + "statusListIndex": "12", + "statusListCredential": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": "StatusList2021" +} +``` + +Response: + +``` +Verifiable credential revoked successfully. +``` + +### 4. Verify credential status + +Using this endpoint, verifier can check current status of credential whether certificate is active of revoked + +![verify_status.png](images/verify_status.png) + +Request: + +```json +{ + "id": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1#12", + "statusPurpose": "revocation", + "statusListIndex": "12", + "statusListCredential": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": "StatusList2021" +} +``` + +Response: + +```json +{ + "status": "active" +} +``` + +In the following, the most relevant operations are described as sequence +diagrams. + +### Create Wallet + +### Issue revocable credential + +![issue_revocable_vc.png](images/issue_revocable_vc.png) + +``` +@startuml +title issue revocable VC +actor User as User +participant "Managed identity wallet" as MIW +participant "Revocation service" as RS +group "Issue VC" +User -> MIW: "/api/credentials/issuer" with revocable=true +alt "If revocable then get issuer status list" +MIW -> RS: "/api/v1/revocations/statusEntry +alt If status list VC not created? +RS -> RS: Create StatusList credentail +RS -> MIW: Sign new status list VC +MIW -> MIW: Save status list into issuer table +RS<-- MIW: return sighed status list VC +RS -> RS: Store StatusList VC in DB +end +RS -> RS: Get Current StatusList Index for Issuer +RS -> RS: Incease statusList Index by 1 and save in DB +RS --> MIW: Return Status List +end +group "Create and Sign VC" +MIW -> MIW: Create credentail +alt "If revocable then add status list" +MIW -> MIW: Add Status List in VC +end +MIW -> MIW: Sign VC +end group +MIW --> User: Return revocable VC +end group +@enduml + +``` + +### Verify revocable credential + +![verify_revocable_vc.png](images/verify_revocable_vc.png) + +``` +@startuml +actor User as User +participant "Managed Identity Wallet" as MIW +participant "Revocation service" as RS +title Verify VC with revocation status +group "Verify VC" +User -> MIW: "/api/credentials/validation?withCredentialExpiryDate=true&withRevocation=true" +alt "If withRevocation then check issuer status list" +MIW -> RS: "/api/v1/revocations/credentials/{issueId}" +RS -> RS: Get Current StatusList Index for Issuer +RS --> MIW: Return StatusList VC +end +group "Credential Validation" +MIW -> MIW: validate status list VC +MIW -> MIW: Valaidate vc index with encoded list of status list VC +MIW -> MIW: Check Credential is not expired +MIW -> MIW: Validate Credential JsonLD +MIW -> MIW: Verify Credential Signature +end group +MIW --> User: Return Valid or Invalid + Reason +@enduml +``` + +# Deployment + +A description of the overall structure of components including how to +run and test it locally as well as on Kubernetes in the cloud is +available in the GitHub repository: + + +The INT/DEV deployment is done using Helm charts. The charts are located in the +`charts/` sub-directory of the repository. The charts are picked up by +[ArgoCD](https://argo-cd.readthedocs.io/en/stable/) and executed, resulting in +a INT/DEV deployment into the respective Kubernetes cluster. ArgoCD polls the +GitHub status continuously and executes the Helm charts when a new commit is +detected on one of the target branches, e.g. "main". A benefit of ArgoCD is that it +automatically detects variables from the Helm charts and displays them in the +ArgoCD UI. + +[ArgoCD INT](https://argo.int.demo.catena-x.net/) +[ArgoCD DEV](https://argo.dev.demo.catena-x.net/) + +For local setup, instruction will be added in README.md file + +# Guiding Concepts + +Please refer: https://www.w3.org/TR/2023/WD-vc-status-list-20230427 + +# Design Decisions + +Revocation service is developed at Cofinity-X and as per discussion with a product owner of MIW cofinity-x has decided to +contribute to the eclipse tractus-x + +# Quality Requirements + +The work being done on the project has been focused on creating a base +implementation of the Managed Identity Wallet Service. The current state has +compromised on some aspects to further progress the development. The [Risks and +Technical Depts](#technical-debts) section addresses those points in greater +detail. Nevertheless we've focused on Security and Deploy ability. + +The Revocation service sticks to the following Quality Gate +requirements where relevant and applicable: + +- Documentation: Architecture +- Documentation: Administrator\'s Guide +- Documentation: Interfaces +- Documentation: Source Code +- Documentation: Development Process +- Documentation: Standardization - Interoperability and Data Sovereignty +- Compliance: GDPR +- Test Results: Deployment/Installation +- Test Results: Code Quality Analysis +- Test Results: System Integration Tests +- Security & Compliance: Penetration Tests +- Security & Compliance: Threat Modeling +- Security & Compliance: Static Application Security Testing +- Security & Compliance: Dynamic Application Security Testing +- Security & Compliance: Secret scanning +- Security & Compliance: Software Composition Analysis +- Security & Compliance: Container Scan +- Security & Compliance: Infrastructure as Code + +# Technical Debts + +## Credential sensation is not supported + +- DID document only covers varification method. No service endpoints + +## SSI Library + +- No validation for JsonWebSignature2020 with RSA key +- No Security valdition only Sercurity Assessment done, no attack vectors are tested + +# Glossary + +| **Term** | **Definition** | +|-------------------------|----------------------------------------------------------------------------------------------------------------------------------| +| MIW | Managed Identity wallet application | +| VC | Verifiable Credential | +| VP | Verifiable Presentation | +| Wallet | Virtual placeholder for business partner which holds VCs | +| Base wallet | Wallet for Operating company . CX type of VC will be issued using this wallet | +| Status list credential | [https://www.w3.org/TR/vc-status-list/#statuslist2021credential](https://www.w3.org/TR/vc-status-list/#statuslist2021credential) | +| Status list entry | [https://www.w3.org/TR/vc-status-list/#statuslist2021credential](https://www.w3.org/TR/vc-status-list/#statuslist2021credential) | +| Status list index | [https://www.w3.org/TR/vc-status-list/#statuslist2021entry](https://www.w3.org/TR/vc-status-list/#statuslist2021entry) | +| Revocation verification | [https://www.w3.org/TR/vc-status-list/#validate-algorithm](https://www.w3.org/TR/vc-status-list/#validate-algorithm) | +| Encoded list | [https://www.w3.org/TR/vc-status-list/#bitstring-encoding](https://www.w3.org/TR/vc-status-list/#bitstring-encoding) | + +# NOTICE + +This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). + +- SPDX-License-Identifier: Apache-2.0 +- SPDX-FileCopyrightText: 2021,2023 Contributors to the Eclipse Foundation +- Source URL: https://github.com/eclipse-tractusx/managed-identity-wallet diff --git a/docs/ArchitectureWebDID.md b/docs/database/miw/ArchitectureWebDID.md similarity index 100% rename from docs/ArchitectureWebDID.md rename to docs/database/miw/ArchitectureWebDID.md diff --git a/docs/MIW_DB_Schema_v0.0.1.md b/docs/database/miw/MIW_DB_Schema_v0.0.1.md similarity index 100% rename from docs/MIW_DB_Schema_v0.0.1.md rename to docs/database/miw/MIW_DB_Schema_v0.0.1.md diff --git a/docs/database/revocation/revocation_DB_Schema_v0.0.1.md b/docs/database/revocation/revocation_DB_Schema_v0.0.1.md new file mode 100644 index 000000000..5d742c889 --- /dev/null +++ b/docs/database/revocation/revocation_DB_Schema_v0.0.1.md @@ -0,0 +1,24 @@ +# Database Schemas for intial draft + +### Status List Index + +- id: integer (Primary Key) +- issuer_bpn_status: varchar(27) +- current_index: varchar(16) +- status_list_credential_id: varchar(256) (Unique) + +### Status list credential + +- id: integer (Primary Key) +- issuer_bpn: varchar(16) (Unique) +- credential: text +- created_at: timestamp +- modified_at: timestamp + +# NOTICE + +This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). + +- SPDX-License-Identifier: Apache-2.0 +- SPDX-FileCopyrightText: 2021,2023 Contributors to the Eclipse Foundation +- Source URL: https://github.com/eclipse-tractusx/managed-identity-wallet diff --git a/docs/security-assessment/security-assessment-23-12.md b/docs/security-assessment/security-assessment-23-12.md index 7d7d1d7f5..3452f8629 100644 --- a/docs/security-assessment/security-assessment-23-12.md +++ b/docs/security-assessment/security-assessment-23-12.md @@ -1,39 +1,40 @@ # Security Assessment Managed Identity Wallet (MIW) -| | | -| --- | --- | -| Contact for product | [@OSchlienz](https://github.com/github)
[@borisrizov-zf](https://github.com/borisrizov-zf) | -| Security responsible | [@pablosec](https://github.com/pablosec)
[@SSIRKC](https://github.com/SSIRKC) | -| Version number of product | 23.12 | -| Dates of assessment | 2023-11-21: Re-assessment for release 23.12 | -| Status of assessment | RE-ASSESSMENT DRAFT | +| | | +|---------------------------|-------------------------------------------------------------------------------------------------| +| Contact for product | [@OSchlienz](https://github.com/github)
[@borisrizov-zf](https://github.com/borisrizov-zf) | +| Security responsible | [@pablosec](https://github.com/pablosec)
[@SSIRKC](https://github.com/SSIRKC) | +| Version number of product | 23.12 | +| Dates of assessment | 2023-11-21: Re-assessment for release 23.12 | +| Status of assessment | RE-ASSESSMENT DRAFT | ## Product Description -The Managed Identity Wallet (MIW) service implements the Self-Sovereign-Identity (SSI) readiness by providing a wallet hosting platform including a DID resolver, service endpoints and the company wallets itself. +The Managed Identity Wallet (MIW) service implements the Self-Sovereign-Identity (SSI) readiness by providing a wallet +hosting platform including a DID resolver, service endpoints and the company wallets itself. ### Important Links -* [MIW: README.md](https://github.com/eclipse-tractusx/managed-identity-wallet/blob/main/README.md) -* [SSI: Technical Debts.md](https://github.com/eclipse-tractusx/ssi-docu/blob/main/docs/architecture/cx-3-2/6.%20Technical%20Debts/Technical%20Debts.md) – partly outdated at date of security assessment - +* [MIW: README.md](https://github.com/eclipse-tractusx/managed-identity-wallet/blob/main/README.md) +* [Revocation Service: README.md](..%2F..%2Frevocation-service%2FREADME.md) +* [SSI: Technical Debts.md](https://github.com/eclipse-tractusx/ssi-docu/blob/main/docs/architecture/cx-3-2/6.%20Technical%20Debts/Technical%20Debts.md) – + partly outdated at date of security assessment ## Existing Security Controls ℹ️ Only controls added since last security assessment (2023-06) are listed below -* Role-based access control +* Role-based access control [README.md → Manual Keycloak Configuration](https://github.com/eclipse-tractusx/managed-identity-wallet/blob/main/README.md#manual-keycloak-configuration) - * MIW provides 7 roles: - * `add_wallets` - * `view_wallets` - * `view_wallet` - * `update_wallets` - * `update_wallet` - * `manage_app` + * MIW provides 7 roles: + * `add_wallets` + * `view_wallets` + * `view_wallet` + * `update_wallets` + * `update_wallet` + * `manage_app` * Logging is implemented with levels `OFF`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE` - * Currently there is no description of the events logged for the different levels - + * Currently there is no description of the events logged for the different levels ## Architecture/Data Flow Diagram (DFD) @@ -42,23 +43,30 @@ The Managed Identity Wallet (MIW) service implements the Self-Sovereign-Identity Source: https://github.com/pablosec/managed-identity-wallet/tree/main/docs ### Changes compared to last Security Assessment -[Security Assessment 2023-06, Release 3.2](https://confluence.catena-x.net/pages/viewpage.action?pageId=90482695) (Link only available to Catena-X Consortium Members) + +[Security Assessment 2023-06, Release 3.2](https://confluence.catena-x.net/pages/viewpage.action?pageId=90482695) (Link +only available to Catena-X Consortium Members) + * No architectural changes in codebase compared to last security assessment, performed in June 2023. -* Product team currently working on STS (Secure Token Service) and Presentation Flow for VC/VP, planned for rollout in version *24.05*. +* Product team currently working on STS (Secure Token Service) and Presentation Flow for VC/VP, planned for rollout in + version *24.05*. ### Features for upcoming versions -See also [SSI: Technical Debts.md](https://github.com/eclipse-tractusx/ssi-docu/blob/main/docs/architecture/cx-3-2/6.%20Technical%20Debts/Technical%20Debts.md) +See +also [SSI: Technical Debts.md](https://github.com/eclipse-tractusx/ssi-docu/blob/main/docs/architecture/cx-3-2/6.%20Technical%20Debts/Technical%20Debts.md) -* Revocation Service and substituting Summary Credentials (→ 24.05) * Use of key rotation (→ 24.05) * Switch to actually decentralized DID documents (→ will be implemented, but not scheduled at the moment) * Create credentials outside of MIW (→ will be implemented, but not scheduled at the moment) -⚠️ Multi-tenancy will not be implemented as part of the development during the Catena-X consortium phase. Security risk associated with lack of multi-tenancy is accepted. +⚠️ Multi-tenancy will not be implemented as part of the development during the Catena-X consortium phase. Security risk +associated with lack of multi-tenancy is accepted. ## Threats & Risks + The threats and risks identified during this security assessment can be found in the following issues: + * eclipse-tractusx/managed-identity-wallet#164 * eclipse-tractusx/managed-identity-wallet#165 * eclipse-tractusx/managed-identity-wallet#86 (finding was already created as issue before assessment) diff --git a/gradle.properties b/gradle.properties index 469782dd0..27dcc51aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,15 @@ -springCloudVersion=2022.0.3 +springCloudVersion=2023.0.3 testContainerVersion=1.19.3 jacocoVersion=0.8.9 -springBootVersion=3.1.6 +springBootVersion=3.3.2 springDependencyVersion=1.1.0 groupName=org.eclipse.tractusx -applicationVersion=0.5.0 -openApiVersion=2.1.0 +applicationVersion=1.0.0-develop.5 +openApiVersion=2.5.0 +lombokVersion=1.18.32 +gsonVersion=2.10.1 +ssiLibVersion=0.0.19 +wiremockVersion=3.4.2 +commonsDaoVersion=1.0.1 +appGroup=org.eclipse.tractusx.managedidentitywallets +mockInBeanVersion=boot2-v1.5.2 diff --git a/miw/DEPENDENCIES b/miw/DEPENDENCIES index 600d381ec..c1d00a950 100644 --- a/miw/DEPENDENCIES +++ b/miw/DEPENDENCIES @@ -1,20 +1,20 @@ -maven/mavencentral/ch.qos.logback/logback-classic/1.4.12, EPL-1.0 AND LGPL-2.1-only, approved, #15230 -maven/mavencentral/ch.qos.logback/logback-core/1.4.12, EPL-1.0 AND LGPL-2.1-only, approved, #15287 +maven/mavencentral/ch.qos.logback/logback-classic/1.5.6, EPL-1.0 AND LGPL-2.1-only, approved, #15279 +maven/mavencentral/ch.qos.logback/logback-core/1.5.6, EPL-1.0 AND LGPL-2.1-only, approved, #15210 maven/mavencentral/com.apicatalog/titanium-json-ld/1.3.3, Apache-2.0, approved, #8912 -maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.3, Apache-2.0, approved, #15260 -maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.3, , approved, #15194 -maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.15.3, Apache-2.0, approved, #15199 -maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.15.3, Apache-2.0, approved, #9237 -maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.3, Apache-2.0, approved, #15207 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.15.3, Apache-2.0, approved, #15281 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.3, Apache-2.0, approved, #15189 -maven/mavencentral/com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/2.15.3, Apache-2.0, approved, #11061 -maven/mavencentral/com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/2.15.3, Apache-2.0, approved, #9101 -maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.15.3, Apache-2.0, approved, #9100 -maven/mavencentral/com.fasterxml.jackson.module/jackson-module-parameter-names/2.15.3, Apache-2.0, approved, #15219 -maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.15.3, Apache-2.0, approved, #7929 -maven/mavencentral/com.fasterxml.woodstox/woodstox-core/6.5.1, Apache-2.0, approved, #7950 -maven/mavencentral/com.fasterxml/classmate/1.5.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.17.2, Apache-2.0, approved, #13672 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.17.2, Apache-2.0 AND MIT, approved, #13665 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.17.2, Apache-2.0, approved, #13671 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.17.2, Apache-2.0, approved, #13666 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.17.2, Apache-2.0, approved, #13669 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.17.2, Apache-2.0, approved, #15117 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.17.2, Apache-2.0, approved, #14160 +maven/mavencentral/com.fasterxml.jackson.jaxrs/jackson-jaxrs-base/2.17.2, Apache-2.0, approved, #13663 +maven/mavencentral/com.fasterxml.jackson.jaxrs/jackson-jaxrs-json-provider/2.17.2, Apache-2.0, approved, #13670 +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-jaxb-annotations/2.17.2, Apache-2.0, approved, #13664 +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-parameter-names/2.17.2, Apache-2.0, approved, #15122 +maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.17.2, Apache-2.0, approved, #14162 +maven/mavencentral/com.fasterxml.woodstox/woodstox-core/6.7.0, Apache-2.0, approved, #15476 +maven/mavencentral/com.fasterxml/classmate/1.7.0, Apache-2.0, approved, clearlydefined maven/mavencentral/com.github.ben-manes.caffeine/caffeine/3.1.8, Apache-2.0, approved, clearlydefined maven/mavencentral/com.github.curious-odd-man/rgxgen/1.4, Apache-2.0, approved, clearlydefined maven/mavencentral/com.github.dasniko/testcontainers-keycloak/2.5.0, Apache-2.0, approved, #9175 @@ -34,17 +34,18 @@ maven/mavencentral/com.google.errorprone/error_prone_annotations/2.21.1, Apache- maven/mavencentral/com.google.protobuf/protobuf-java/3.19.6, BSD-3-Clause, approved, clearlydefined maven/mavencentral/com.h2database/h2/2.2.220, (EPL-1.0 OR MPL-2.0) AND (LGPL-3.0-or-later OR EPL-1.0 OR MPL-2.0), approved, #9322 maven/mavencentral/com.ibm.async/asyncutil/0.1.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/com.jayway.jsonpath/json-path/2.8.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.37.1, Apache-2.0, approved, #11701 -maven/mavencentral/com.opencsv/opencsv/5.7.1, Apache-2.0, approved, clearlydefined -maven/mavencentral/com.smartsensesolutions/commons-dao/0.0.5, Apache-2.0, approved, #9176 +maven/mavencentral/com.jayway.jsonpath/json-path/2.9.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.37.3, Apache-2.0, approved, #11701 +maven/mavencentral/com.opencsv/opencsv/5.9, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.smartsensesolutions/commons-dao/1.0.1, Apache-2.0, approved, clearlydefined maven/mavencentral/com.sun.activation/jakarta.activation/1.2.1, EPL-2.0 OR BSD-3-Clause OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jaf maven/mavencentral/com.sun.istack/istack-commons-runtime/4.1.2, BSD-3-Clause, approved, #15290 maven/mavencentral/com.sun.mail/jakarta.mail/1.6.5, EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, ee4j.mail +maven/mavencentral/com.teketik/mock-in-bean/boot2-v1.5.2, Apache-2.0, approved, clearlydefined maven/mavencentral/com.vaadin.external.google/android-json/0.0.20131108.vaadin1, Apache-2.0, approved, CQ21310 -maven/mavencentral/com.zaxxer/HikariCP/5.0.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.zaxxer/HikariCP/5.1.0, Apache-2.0, approved, clearlydefined maven/mavencentral/commons-beanutils/commons-beanutils/1.9.4, Apache-2.0, approved, CQ12654 -maven/mavencentral/commons-codec/commons-codec/1.15, Apache-2.0 AND BSD-3-Clause AND LicenseRef-Public-Domain, approved, CQ22641 +maven/mavencentral/commons-codec/commons-codec/1.16.1, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #9157 maven/mavencentral/commons-collections/commons-collections/3.2.2, Apache-2.0, approved, #15185 maven/mavencentral/commons-digester/commons-digester/2.1, Apache-2.0, approved, clearlydefined maven/mavencentral/commons-fileupload/commons-fileupload/1.5, Apache-2.0, approved, #7109 @@ -53,11 +54,12 @@ maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ maven/mavencentral/commons-validator/commons-validator/1.7, Apache-2.0, approved, clearlydefined maven/mavencentral/io.github.openfeign.form/feign-form-spring/3.8.0, Apache-2.0, approved, clearlydefined maven/mavencentral/io.github.openfeign.form/feign-form/3.8.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.github.openfeign/feign-core/12.3, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.github.openfeign/feign-slf4j/12.3, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.micrometer/micrometer-commons/1.11.6, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #9243 -maven/mavencentral/io.micrometer/micrometer-core/1.11.6, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #9238 -maven/mavencentral/io.micrometer/micrometer-observation/1.11.6, Apache-2.0, approved, #9242 +maven/mavencentral/io.github.openfeign/feign-core/13.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.github.openfeign/feign-slf4j/13.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.micrometer/micrometer-commons/1.13.2, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #14826 +maven/mavencentral/io.micrometer/micrometer-core/1.13.2, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #14827 +maven/mavencentral/io.micrometer/micrometer-jakarta9/1.13.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.micrometer/micrometer-observation/1.13.2, Apache-2.0, approved, #14829 maven/mavencentral/io.quarkus/quarkus-junit4-mock/2.13.7.Final, Apache-2.0, approved, clearlydefined maven/mavencentral/io.setl/rdf-urdna/1.2, Apache-2.0, approved, clearlydefined maven/mavencentral/io.smallrye.common/smallrye-common-annotation/1.6.0, Apache-2.0, approved, clearlydefined @@ -68,63 +70,61 @@ maven/mavencentral/io.smallrye.common/smallrye-common-function/1.6.0, Apache-2.0 maven/mavencentral/io.smallrye.config/smallrye-config-common/2.3.0, Apache-2.0, approved, clearlydefined maven/mavencentral/io.smallrye.config/smallrye-config-core/2.3.0, Apache-2.0, approved, clearlydefined maven/mavencentral/io.smallrye.config/smallrye-config/2.3.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.smallrye/jandex/3.0.5, Apache-2.0, approved, clearlydefined -maven/mavencentral/io.swagger.core.v3/swagger-annotations-jakarta/2.2.9, Apache-2.0, approved, #5947 -maven/mavencentral/io.swagger.core.v3/swagger-core-jakarta/2.2.9, Apache-2.0, approved, #5929 -maven/mavencentral/io.swagger.core.v3/swagger-models-jakarta/2.2.9, Apache-2.0, approved, #5919 -maven/mavencentral/jakarta.activation/jakarta.activation-api/2.1.2, EPL-2.0 OR BSD-3-Clause OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jaf +maven/mavencentral/io.smallrye/jandex/3.1.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.swagger.core.v3/swagger-annotations-jakarta/2.2.21, Apache-2.0, approved, #5947 +maven/mavencentral/io.swagger.core.v3/swagger-core-jakarta/2.2.21, Apache-2.0, approved, #5929 +maven/mavencentral/io.swagger.core.v3/swagger-models-jakarta/2.2.21, 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/2.1.1, 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.persistence/jakarta.persistence-api/3.1.0, EPL-2.0 OR BSD-3-Clause, approved, ee4j.jpa maven/mavencentral/jakarta.transaction/jakarta.transaction-api/2.0.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jta maven/mavencentral/jakarta.validation/jakarta.validation-api/3.0.2, Apache-2.0, approved, ee4j.validation -maven/mavencentral/jakarta.xml.bind/jakarta.xml.bind-api/4.0.1, BSD-3-Clause, approved, ee4j.jaxb -maven/mavencentral/javax.activation/javax.activation-api/1.2.0, (CDDL-1.1 OR GPL-2.0 WITH Classpath-exception-2.0) AND Apache-2.0, approved, CQ18740 +maven/mavencentral/jakarta.xml.bind/jakarta.xml.bind-api/4.0.2, BSD-3-Clause, approved, ee4j.jaxb maven/mavencentral/javax.xml.bind/jaxb-api/2.3.1, CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, CQ16911 maven/mavencentral/junit/junit/4.13.2, EPL-2.0, approved, CQ23636 -maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.14.10, Apache-2.0, approved, #7164 -maven/mavencentral/net.bytebuddy/byte-buddy/1.14.10, Apache-2.0 AND BSD-3-Clause, approved, #7163 +maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.14.18, Apache-2.0, approved, #7164 +maven/mavencentral/net.bytebuddy/byte-buddy/1.14.18, Apache-2.0 AND BSD-3-Clause, approved, #7163 maven/mavencentral/net.i2p.crypto/eddsa/0.3.0, CC0-1.0, approved, CQ22537 maven/mavencentral/net.java.dev.jna/jna/5.13.0, Apache-2.0 AND LGPL-2.1-or-later, approved, #15196 -maven/mavencentral/net.minidev/accessors-smart/2.4.11, Apache-2.0, approved, #7515 -maven/mavencentral/net.minidev/json-smart/2.4.11, Apache-2.0, approved, #3288 -maven/mavencentral/org.antlr/antlr4-runtime/4.10.1, BSD-3-Clause AND LicenseRef-Public-domain AND MIT AND LicenseRef-Unicode-TOU, approved, #7065 +maven/mavencentral/net.minidev/accessors-smart/2.5.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/net.minidev/json-smart/2.5.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.antlr/antlr4-runtime/4.13.0, BSD-3-Clause, approved, #10767 maven/mavencentral/org.apache.commons/commons-collections4/4.4, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.commons/commons-compress/1.24.0, Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND LicenseRef-Public-Domain, approved, #10368 -maven/mavencentral/org.apache.commons/commons-lang3/3.12.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.apache.commons/commons-text/1.10.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.apache.commons/commons-lang3/3.14.0, Apache-2.0, approved, #11677 +maven/mavencentral/org.apache.commons/commons-text/1.11.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.httpcomponents/httpclient/4.5.13, Apache-2.0, approved, #15248 maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.16, Apache-2.0, approved, CQ23528 maven/mavencentral/org.apache.james/apache-mime4j-core/0.8.3, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.james/apache-mime4j-dom/0.8.3, Apache-2.0, approved, #2340 maven/mavencentral/org.apache.james/apache-mime4j-storage/0.8.3, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.apache.logging.log4j/log4j-api/2.20.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.apache.logging.log4j/log4j-to-slf4j/2.20.0, Apache-2.0, approved, #8799 -maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.16, Apache-2.0 AND (EPL-2.0 OR (GPL-2.0 WITH Classpath-exception-2.0)) AND CDDL-1.0 AND (CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)) AND EPL-2.0, approved, #15195 -maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-el/10.1.16, Apache-2.0, approved, #6997 -maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.16, Apache-2.0, approved, #7920 +maven/mavencentral/org.apache.logging.log4j/log4j-api/2.23.1, Apache-2.0, approved, #13368 +maven/mavencentral/org.apache.logging.log4j/log4j-to-slf4j/2.23.1, Apache-2.0, approved, #15121 +maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.26, Apache-2.0 AND (EPL-2.0 OR (GPL-2.0 WITH Classpath-exception-2.0)) AND CDDL-1.0 AND (CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)) AND EPL-2.0, approved, #15195 +maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-el/10.1.26, Apache-2.0, approved, #6997 +maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.26, Apache-2.0, approved, #7920 maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.aspectj/aspectjweaver/1.9.20.1, Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND BSD-3-Clause AND Apache-1.1, approved, #7695 -maven/mavencentral/org.assertj/assertj-core/3.24.2, Apache-2.0, approved, #6161 -maven/mavencentral/org.bouncycastle/bcpkix-jdk15on/1.69, MIT, approved, clearlydefined -maven/mavencentral/org.bouncycastle/bcprov-jdk15on/1.69, MIT, approved, clearlydefined -maven/mavencentral/org.bouncycastle/bcprov-jdk18on/1.77, MIT AND CC0-1.0, approved, #11595 -maven/mavencentral/org.bouncycastle/bcutil-jdk15on/1.69, MIT, approved, clearlydefined +maven/mavencentral/org.aspectj/aspectjweaver/1.9.22.1, Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND BSD-3-Clause AND Apache-1.1, approved, #15252 +maven/mavencentral/org.assertj/assertj-core/3.25.3, Apache-2.0, approved, #12585 +maven/mavencentral/org.awaitility/awaitility/4.2.1, Apache-2.0, approved, #14178 +maven/mavencentral/org.bouncycastle/bcprov-jdk18on/1.78, MIT AND CC0-1.0, approved, #14433 maven/mavencentral/org.checkerframework/checker-qual/3.37.0, MIT, approved, clearlydefined -maven/mavencentral/org.codehaus.woodstox/stax2-api/4.2.1, BSD-2-Clause, approved, #2670 -maven/mavencentral/org.eclipse.angus/angus-activation/2.0.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus +maven/mavencentral/org.checkerframework/checker-qual/3.42.0, MIT, approved, clearlydefined +maven/mavencentral/org.codehaus.woodstox/stax2-api/4.2.2, BSD-2-Clause, approved, #2670 +maven/mavencentral/org.eclipse.angus/angus-activation/2.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus maven/mavencentral/org.eclipse.microprofile.config/microprofile-config-api/2.0, Apache-2.0, approved, technology.microprofile maven/mavencentral/org.eclipse.parsson/parsson/1.1.5, EPL-2.0, approved, ee4j.parsson maven/mavencentral/org.eclipse.tractusx.ssi/cx-ssi-lib/0.0.19, Apache-2.0, approved, automotive.tractusx -maven/mavencentral/org.glassfish.jaxb/jaxb-core/4.0.4, BSD-3-Clause, approved, ee4j.jaxb-impl -maven/mavencentral/org.glassfish.jaxb/jaxb-runtime/4.0.4, BSD-3-Clause, approved, ee4j.jaxb-impl -maven/mavencentral/org.glassfish.jaxb/txw2/4.0.4, BSD-3-Clause, approved, ee4j.jaxb-impl +maven/mavencentral/org.glassfish.jaxb/jaxb-core/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl +maven/mavencentral/org.glassfish.jaxb/jaxb-runtime/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl +maven/mavencentral/org.glassfish.jaxb/txw2/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl maven/mavencentral/org.hamcrest/hamcrest-core/2.2, BSD-3-Clause, approved, clearlydefined maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-3-Clause, approved, clearlydefined -maven/mavencentral/org.hdrhistogram/HdrHistogram/2.1.12, CC0-1.0, approved, #15259 +maven/mavencentral/org.hdrhistogram/HdrHistogram/2.2.2, BSD-2-Clause AND CC0-1.0 AND CC0-1.0, approved, #14828 maven/mavencentral/org.hibernate.common/hibernate-commons-annotations/6.0.6.Final, LGPL-2.1-only, approved, #6962 -maven/mavencentral/org.hibernate.orm/hibernate-core/6.2.13.Final, LGPL-2.1-only AND Apache-2.0 AND MIT AND CC-PDDC AND (EPL-2.0 OR BSD-3-Clause), approved, #9121 +maven/mavencentral/org.hibernate.orm/hibernate-core/6.5.2.Final, LGPL-2.1-only AND (EPL-2.0 OR BSD-3-Clause) AND LGPL-2.1-or-later AND MIT, approved, #15118 maven/mavencentral/org.hibernate.validator/hibernate-validator/8.0.1.Final, Apache-2.0, approved, clearlydefined maven/mavencentral/org.jacoco/org.jacoco.agent/0.8.9, EPL-2.0, approved, CQ23285 maven/mavencentral/org.jacoco/org.jacoco.ant/0.8.9, EPL-2.0, approved, #1068 @@ -146,91 +146,90 @@ maven/mavencentral/org.jboss.spec.javax.ws.rs/jboss-jaxrs-api_2.1_spec/2.0.1.Fin maven/mavencentral/org.jboss.spec.javax.xml.bind/jboss-jaxb-api_2.3_spec/2.0.0.Final, BSD-3-Clause, approved, #2122 maven/mavencentral/org.jetbrains/annotations/17.0.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.json/json/20230227, LicenseRef-Public-domain, approved, #9174 -maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.9.3, EPL-2.0, approved, #3133 -maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.9.3, EPL-2.0, approved, #3125 -maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.9.3, EPL-2.0, approved, #3134 -maven/mavencentral/org.junit.jupiter/junit-jupiter/5.9.3, EPL-2.0, approved, #6972 -maven/mavencentral/org.junit.platform/junit-platform-commons/1.9.3, EPL-2.0, approved, #3130 -maven/mavencentral/org.junit.platform/junit-platform-engine/1.9.3, EPL-2.0, approved, #3128 -maven/mavencentral/org.junit/junit-bom/5.9.3, EPL-2.0, approved, #4711 +maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.10.3, EPL-2.0, approved, #9714 +maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.10.3, EPL-2.0, approved, #9711 +maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.10.3, EPL-2.0, approved, #15250 +maven/mavencentral/org.junit.jupiter/junit-jupiter/5.10.3, EPL-2.0, approved, #15197 +maven/mavencentral/org.junit.platform/junit-platform-commons/1.10.3, EPL-2.0, approved, #9715 +maven/mavencentral/org.junit.platform/junit-platform-engine/1.10.3, EPL-2.0, approved, #9709 +maven/mavencentral/org.junit/junit-bom/5.10.3, EPL-2.0, approved, #9844 maven/mavencentral/org.keycloak/keycloak-admin-client/21.0.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.keycloak/keycloak-common/21.0.0, Apache-2.0 AND LicenseRef-scancode-public-domain-disclaimer, approved, #7287 maven/mavencentral/org.keycloak/keycloak-core/21.0.0, Apache-2.0, approved, #7293 maven/mavencentral/org.latencyutils/LatencyUtils/2.0.3, CC0-1.0, approved, #15280 -maven/mavencentral/org.liquibase/liquibase-core/4.20.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.mockito/mockito-core/4.8.1, MIT, approved, clearlydefined +maven/mavencentral/org.liquibase/liquibase-core/4.27.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.mockito/mockito-core/5.11.0, MIT AND (Apache-2.0 AND MIT) AND Apache-2.0, approved, #13505 maven/mavencentral/org.mockito/mockito-inline/5.2.0, MIT, approved, clearlydefined -maven/mavencentral/org.mockito/mockito-junit-jupiter/4.8.1, MIT, approved, clearlydefined -maven/mavencentral/org.objenesis/objenesis/3.2, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.opentest4j/opentest4j/1.2.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.mockito/mockito-junit-jupiter/5.11.0, MIT, approved, #13504 +maven/mavencentral/org.objenesis/objenesis/3.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.opentest4j/opentest4j/1.3.0, Apache-2.0, approved, #9713 maven/mavencentral/org.ow2.asm/asm-commons/9.5, BSD-3-Clause, approved, #7553 maven/mavencentral/org.ow2.asm/asm-tree/9.5, BSD-3-Clause, approved, #7555 -maven/mavencentral/org.ow2.asm/asm/9.3, BSD-3-Clause, approved, clearlydefined maven/mavencentral/org.ow2.asm/asm/9.5, BSD-3-Clause, approved, #7554 -maven/mavencentral/org.postgresql/postgresql/42.6.0, BSD-2-Clause AND Apache-2.0, approved, #9159 -maven/mavencentral/org.projectlombok/lombok/1.18.28, MIT, approved, #15192 -maven/mavencentral/org.projectlombok/lombok/1.18.30, MIT, approved, #15192 +maven/mavencentral/org.ow2.asm/asm/9.6, BSD-3-Clause, approved, #10776 +maven/mavencentral/org.postgresql/postgresql/42.7.3, BSD-2-Clause AND Apache-2.0, approved, #11681 +maven/mavencentral/org.projectlombok/lombok/1.18.34, MIT, approved, #15192 maven/mavencentral/org.reactivestreams/reactive-streams/1.0.4, CC0-1.0, approved, CQ16332 maven/mavencentral/org.rnorth.duct-tape/duct-tape/1.0.8, MIT, approved, clearlydefined -maven/mavencentral/org.skyscreamer/jsonassert/1.5.1, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.slf4j/jul-to-slf4j/2.0.9, MIT, approved, #7698 -maven/mavencentral/org.slf4j/slf4j-api/2.0.9, MIT, approved, #5915 -maven/mavencentral/org.springdoc/springdoc-openapi-starter-common/2.1.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-api/2.1.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-ui/2.1.0, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.springframework.boot/spring-boot-actuator-autoconfigure/3.1.6, Apache-2.0, approved, #9348 -maven/mavencentral/org.springframework.boot/spring-boot-actuator/3.1.6, Apache-2.0, approved, #9342 -maven/mavencentral/org.springframework.boot/spring-boot-autoconfigure/3.1.6, Apache-2.0, approved, #9341 -maven/mavencentral/org.springframework.boot/spring-boot-devtools/3.1.5, Apache-2.0, approved, clearlydefined -maven/mavencentral/org.springframework.boot/spring-boot-starter-actuator/3.1.6, Apache-2.0, approved, #9344 -maven/mavencentral/org.springframework.boot/spring-boot-starter-aop/3.1.6, Apache-2.0, approved, #9338 -maven/mavencentral/org.springframework.boot/spring-boot-starter-data-jpa/3.1.6, Apache-2.0, approved, #9733 -maven/mavencentral/org.springframework.boot/spring-boot-starter-jdbc/3.1.6, Apache-2.0, approved, #9737 -maven/mavencentral/org.springframework.boot/spring-boot-starter-json/3.1.6, Apache-2.0, approved, #9336 -maven/mavencentral/org.springframework.boot/spring-boot-starter-logging/3.1.6, Apache-2.0, approved, #9343 -maven/mavencentral/org.springframework.boot/spring-boot-starter-security/3.1.6, Apache-2.0, approved, #9337 -maven/mavencentral/org.springframework.boot/spring-boot-starter-test/3.1.6, Apache-2.0, approved, #9353 -maven/mavencentral/org.springframework.boot/spring-boot-starter-tomcat/3.1.6, Apache-2.0, approved, #9351 -maven/mavencentral/org.springframework.boot/spring-boot-starter-validation/3.1.6, Apache-2.0, approved, #9335 -maven/mavencentral/org.springframework.boot/spring-boot-starter-web/3.1.6, Apache-2.0, approved, #9347 -maven/mavencentral/org.springframework.boot/spring-boot-starter/3.1.6, Apache-2.0, approved, #9349 -maven/mavencentral/org.springframework.boot/spring-boot-test-autoconfigure/3.1.6, Apache-2.0, approved, #9339 -maven/mavencentral/org.springframework.boot/spring-boot-test/3.1.6, Apache-2.0, approved, #9346 -maven/mavencentral/org.springframework.boot/spring-boot/3.1.6, Apache-2.0, approved, #9352 -maven/mavencentral/org.springframework.cloud/spring-cloud-commons/4.0.3, Apache-2.0, approved, #7292 -maven/mavencentral/org.springframework.cloud/spring-cloud-context/4.0.3, Apache-2.0, approved, #7306 -maven/mavencentral/org.springframework.cloud/spring-cloud-openfeign-core/4.0.3, Apache-2.0, approved, #7305 -maven/mavencentral/org.springframework.cloud/spring-cloud-starter-openfeign/4.0.3, Apache-2.0, approved, #7302 -maven/mavencentral/org.springframework.cloud/spring-cloud-starter/4.0.3, Apache-2.0, approved, #7299 -maven/mavencentral/org.springframework.data/spring-data-commons/3.1.6, Apache-2.0, approved, #8805 -maven/mavencentral/org.springframework.data/spring-data-jpa/3.1.6, Apache-2.0, approved, #9120 -maven/mavencentral/org.springframework.security/spring-security-config/6.1.5, Apache-2.0, approved, #9736 -maven/mavencentral/org.springframework.security/spring-security-core/6.1.5, Apache-2.0, approved, #9801 -maven/mavencentral/org.springframework.security/spring-security-crypto/6.1.5, Apache-2.0 AND ISC, approved, #9735 -maven/mavencentral/org.springframework.security/spring-security-oauth2-core/6.1.5, Apache-2.0, approved, #9741 -maven/mavencentral/org.springframework.security/spring-security-oauth2-jose/6.1.5, Apache-2.0, approved, #9345 -maven/mavencentral/org.springframework.security/spring-security-oauth2-resource-server/6.1.5, Apache-2.0, approved, #8798 -maven/mavencentral/org.springframework.security/spring-security-rsa/1.0.11.RELEASE, Apache-2.0, approved, CQ20647 -maven/mavencentral/org.springframework.security/spring-security-web/6.1.5, Apache-2.0, approved, #9800 -maven/mavencentral/org.springframework/spring-aop/6.0.14, Apache-2.0, approved, #5940 -maven/mavencentral/org.springframework/spring-aspects/6.0.14, Apache-2.0, approved, #5930 -maven/mavencentral/org.springframework/spring-beans/6.0.14, Apache-2.0, approved, #5937 -maven/mavencentral/org.springframework/spring-context/6.0.14, Apache-2.0, approved, #5936 -maven/mavencentral/org.springframework/spring-core/6.0.14, Apache-2.0 AND BSD-3-Clause, approved, #5948 -maven/mavencentral/org.springframework/spring-expression/6.0.14, Apache-2.0, approved, #3284 -maven/mavencentral/org.springframework/spring-jcl/6.0.14, Apache-2.0, approved, #3283 -maven/mavencentral/org.springframework/spring-jdbc/6.0.14, Apache-2.0, approved, #5924 -maven/mavencentral/org.springframework/spring-orm/6.0.14, Apache-2.0, approved, #5925 -maven/mavencentral/org.springframework/spring-test/6.0.14, Apache-2.0, approved, #7003 -maven/mavencentral/org.springframework/spring-tx/6.0.14, Apache-2.0, approved, #5926 -maven/mavencentral/org.springframework/spring-web/6.0.14, Apache-2.0, approved, #5942 -maven/mavencentral/org.springframework/spring-webmvc/6.0.14, Apache-2.0, approved, #5944 +maven/mavencentral/org.skyscreamer/jsonassert/1.5.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.slf4j/jul-to-slf4j/2.0.13, MIT, approved, #7698 +maven/mavencentral/org.slf4j/slf4j-api/2.0.13, MIT, approved, #5915 +maven/mavencentral/org.springdoc/springdoc-openapi-starter-common/2.5.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-api/2.5.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-ui/2.5.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-actuator-autoconfigure/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-actuator/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-autoconfigure/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-devtools/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-actuator/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-aop/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-data-jpa/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-jdbc/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-json/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-logging/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-security/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-test/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-tomcat/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-validation/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-web/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-test-autoconfigure/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-test/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.cloud/spring-cloud-commons/4.1.4, Apache-2.0, approved, #13495 +maven/mavencentral/org.springframework.cloud/spring-cloud-context/4.1.4, Apache-2.0, approved, #13494 +maven/mavencentral/org.springframework.cloud/spring-cloud-openfeign-core/4.1.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.cloud/spring-cloud-starter-openfeign/4.1.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.cloud/spring-cloud-starter/4.1.4, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.data/spring-data-commons/3.3.2, Apache-2.0, approved, #15116 +maven/mavencentral/org.springframework.data/spring-data-jpa/3.3.2, Apache-2.0, approved, #15120 +maven/mavencentral/org.springframework.security/spring-security-config/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-core/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-crypto/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-oauth2-core/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-oauth2-jose/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-oauth2-resource-server/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-rsa/1.1.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-web/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework/spring-aop/6.1.11, Apache-2.0, approved, #15221 +maven/mavencentral/org.springframework/spring-aspects/6.1.11, Apache-2.0, approved, #15193 +maven/mavencentral/org.springframework/spring-beans/6.1.11, Apache-2.0, approved, #15213 +maven/mavencentral/org.springframework/spring-context/6.1.11, Apache-2.0, approved, #15261 +maven/mavencentral/org.springframework/spring-core/6.1.11, Apache-2.0 AND BSD-3-Clause, approved, #15206 +maven/mavencentral/org.springframework/spring-expression/6.1.11, Apache-2.0, approved, #15264 +maven/mavencentral/org.springframework/spring-jcl/6.1.11, Apache-2.0, approved, #15266 +maven/mavencentral/org.springframework/spring-jdbc/6.1.11, Apache-2.0, approved, #15191 +maven/mavencentral/org.springframework/spring-orm/6.1.11, Apache-2.0, approved, #15278 +maven/mavencentral/org.springframework/spring-test/6.1.11, Apache-2.0, approved, #15265 +maven/mavencentral/org.springframework/spring-tx/6.1.11, Apache-2.0, approved, #15229 +maven/mavencentral/org.springframework/spring-web/6.1.11, Apache-2.0, approved, #15188 +maven/mavencentral/org.springframework/spring-webmvc/6.1.11, Apache-2.0, approved, #15182 maven/mavencentral/org.testcontainers/database-commons/1.19.3, Apache-2.0, approved, #10345 maven/mavencentral/org.testcontainers/jdbc/1.19.3, Apache-2.0, approved, #10348 maven/mavencentral/org.testcontainers/junit-jupiter/1.19.3, MIT, approved, #10344 maven/mavencentral/org.testcontainers/postgresql/1.19.3, MIT, approved, #10350 maven/mavencentral/org.testcontainers/testcontainers/1.19.3, Apache-2.0 AND MIT, approved, #10347 -maven/mavencentral/org.webjars/swagger-ui/4.18.2, Apache-2.0, approved, #15184 +maven/mavencentral/org.webjars/swagger-ui/5.13.0, Apache-2.0, approved, #14547 maven/mavencentral/org.wiremock/wiremock-standalone/3.4.2, MIT AND Apache-2.0, approved, #14889 maven/mavencentral/org.xmlunit/xmlunit-core/2.9.1, Apache-2.0, approved, #6272 -maven/mavencentral/org.yaml/snakeyaml/2.0, 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, #7275 +maven/mavencentral/org.yaml/snakeyaml/2.2, 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, #10232 diff --git a/Dockerfile b/miw/Dockerfile similarity index 88% rename from Dockerfile rename to miw/Dockerfile index 59c6d84da..4fa495b21 100644 --- a/Dockerfile +++ b/miw/Dockerfile @@ -1,5 +1,5 @@ # /******************************************************************************** -# * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation +# * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation # * # * See the NOTICE file(s) distributed with this work for additional # * information regarding copyright ownership. @@ -27,7 +27,7 @@ RUN apk add curl USER miw -COPY LICENSE NOTICE.md miw/DEPENDENCIES SECURITY.md miw/build/libs/miw-latest.jar /app/ +COPY ./LICENSE ./NOTICE.md ./miw/DEPENDENCIES ./SECURITY.md ./miw/build/libs/miw-latest.jar /app/ WORKDIR /app diff --git a/miw/README.md b/miw/README.md new file mode 100644 index 000000000..d5a56ec1e --- /dev/null +++ b/miw/README.md @@ -0,0 +1,375 @@ +# Managed Identity Wallets + +The Managed Identity Wallets (MIW) service implements the Self-Sovereign-Identity (SSI) using `did:web`. + +# Usage + +See [INSTALL.md](INSTALL.md) + +# Developer Documentation + +To run MIW locally, this section describes the tooling as well as the local development setup. + +There are two possible flows, which can be used for development: + +1. **local**: Run the postgresql and keycloak server inside docker. Start MIW from within your IDE (recommended for + actual development) +2. **docker**: Run everything inside docker (use to test or check behavior inside a docker environment) + +## Tooling + +Following tools the MIW development team used successfully: + +| Area | Tool | Download Link | Comment | +|----------|----------|-------------------------------------------------|--------------------------------------------------------------------------------------------------| +| IDE | IntelliJ | https://www.jetbrains.com/idea/download/ | Use[envfile plugin](https://plugins.jetbrains.com/plugin/7861-envfile) to use the **local** flow | +| Build | Gradle | https://gradle.org/install/ | | +| Runtime | Docker | https://www.docker.com/products/docker-desktop/ | | +| Database | DBeaver | https://dbeaver.io/ | | +| IAM | Keycloak | https://www.keycloak.org/ | | + +## Eclipse Dash Tool + +[Eclipse Dash Homepage](https://projects.eclipse.org/projects/technology.dash) + +The Eclipse Dash tool is used to analyze the dependencies used in the project and ensure all legal requirements are met. +We've added a gradle tasks to download the latest version of Dash locally, resolve all project dependencies and then run +the tool and update the summary in the DEPENDENCIES file. + +To run the license check: + +```bash +./gradlew dashLicenseCheck +``` + +To clean all files created by the dash tasks: + +```bash +./gradlew dashClean +``` + +This command will output all dependencies, save the to file `deps.txt`. Dash will read from the file and update the +summary in the `DEPENDENCIES` file. A committer can open and issue to resolve any problems with the dependencies. + +# Administrator Documentation + +## Manual Keycloak Configuration + +Within the development setup the Keycloak instance is initially prepared with the values +in `./dev-assets/docker-environment/keycloak`. The realm could also be manually added and configured +at http://localhost:8080 via the "Add realm" button. It can be for example named `localkeycloak`. Also add an additional +client, e.g. named `miw_private_client` with *valid redirect url* set to `http://localhost:8080/*`. The roles + +- add_wallets +- view_wallets +- update_wallets +- delete_wallets +- view_wallet +- update_wallet +- manage_app + +Roles can be added under *Clients > miw_private_client > Roles* and then assigned to the client using *Clients > +miw_private_client > Client Scopes* *> Service Account Roles > Client Roles > miw_private_client*. + +The available scopes/roles are: + +1. Role `add_wallets` to create a new wallet +2. Role `view_wallets`: + - to get a list of all wallets + - to retrieve one wallet by its identifier + - to validate a Verifiable Credential + - to validate a Verifiable Presentation + - to get all stored Verifiable Credentials +3. Role `update_wallets` for the following actions: + - to store Verifiable Credential + - to issue a Verifiable Credential + - to issue a Verifiable Presentation +4. Role `update_wallet`: + - to remove a Verifiable Credential + - to store a Verifiable Credential + - to issue a Verifiable Credential + - to issue a Verifiable Presentation +5. Role `view_wallet` requires the BPN of Caller and it can be used: + - to get the Wallet of the related BPN + - to get stored Verifiable Credentials of the related BPN + - to validate any Verifiable Credential + - to validate any Verifiable Presentation +6. Role `manage_app` used to change the log level of the application at runtime. Check Logging in the application + section for more details + +Overview by Endpoint + +| Artefact | CRUD | HTTP Verb/ Request | Endpoint | Roles | Constraints | +|-------------------------------------------|--------|--------------------|---------------------------------------|----------------------------------------------|------------------------------------------------------------| +| **Wallets** | Read | GET | /api/wallets | **view_wallets** | | +| **Wallets** | Create | POST | /api/wallets | **add_wallets** | **1 BPN : 1 WALLET**(PER ONE [1] BPN ONLY ONE [1] WALLET!) | +| **Wallets** | Create | POST | /api/wallets/{identifier}/credentials | **update_wallets**
OR**update_wallet** | | +| **Wallets** | Read | GET | /api/wallets/{identifier} | **view_wallets** OR
**view_wallet** | | +| **Verifiable Presentations - Generation** | Create | POST | /api/presentation | **update_wallets** OR
**update_wallet** | | +| **Verifiable Presentations - Validation** | Create | POST | /api/presentations/validation | **view_wallets** OR
**view_wallet** | | +| **Verifiable Credential - Holder** | Read | GET | /api/credentials | **view_wallets** OR
**view_wallet** | | +| **Verifiable Credential - Holder** | Create | POST | /api/credentials | **update_wallet** OR
**update_wallet** | | +| **Verifiable Credential - Holder** | Delete | DELETE | /api/credentials | **update_wallet** | | +| **Verfiable Credential - Validation** | Create | POST | /api/credentials/validation | **view_wallets** OR
**view_wallet** | | +| **Verfiable Credential - Issuer** | Read | GET | /api/credentials/issuer | **view_wallets** | | +| **Verfiable Credential - Issuer** | Create | POST | /api/credentials/issuer | **update_wallets** | | +| **Verfiable Credential - Issuer** | Create | POST | /api/credentials/issuer/membership | **update_wallets** | | +| **Verfiable Credential - Issuer** | Create | POST | /api/credentials/issuer/framework | **update_wallets** | | +| **Verfiable Credential - Issuer** | Create | POST | /api/credentials/issuer/distmantler | **update_wallets** | | +| **DIDDocument** | Read | GET | /{bpn}/did.json | N/A | | +| **DIDDocument** | Read | GET | /api/didDocuments/{identifier} | N/A | | + +Additionally, a Token mapper can be created under *Clients* > *ManagedIdentityWallets* > *Mappers* > *create* +with the following configuration (using as an example `BPNL000000001`): + +| Key | Value | +|------------------------------------|-----------------| +| Name | StaticBPN | +| Mapper Type | Hardcoded claim | +| Token Claim Name | BPN | +| Claim value | BPNL000000001 | +| Claim JSON Type | String | +| Add to ID token | OFF | +| Add to access token | ON | +| Add to userinfo | OFF | +| includeInAccessTokenResponse.label | ON | + +If you receive an error message that the client secret is not valid, please go into keycloak admin and within *Clients > +Credentials* recreate the secret. + +## Development Setup + +NOTE: The MIW requires access to the internet in order to validate the JSON-LD schema of DID documents. + +### Prerequisites + +To simplify the dev environment, [Taskfile](https://taskfile.dev) is used as a task executor. You have to install it +first. + +> **IMPORTANT**: Before executing any of th tasks, you have to choose your flow (*local* or *docker*). *local* is +> default. To change that, you need to edit the variable **ENV** in the *Taskfile.yaml*. (see below) + +After that, run `task check-prereqs` to see, if any other required tool is installed or missing. If something is +missing, a link to the install docs is provided. + +Now, you have to adjust the *env* files (located in *dev-assets/env-files*). To do that, copy every file to the same +directory, but without ".dist" at the end. + +Description of the env files: + +- **env.local**: Set up everything to get ready for flow "local". You need to fill in the passwords. +- **env.docker**: Set up everything to get ready for flow "docker". You need to fill in the passwords. + +> **IMPORTANT**: ssi-lib is resolving DID documents over the network. There are two endpoints that rely on this +> resolution: +> - Verifiable Credentials - Validation +> - Verifiable Presentations - Validation +> +> The following parameters are set in env.local or env.docker file per default: +> ENFORCE_HTTPS_IN_DID_RESOLUTION=false +> MIW_HOST_NAME=localhost +> APPLICATION_PORT=80 +> If you intend to change them, the DID resolving may not work properly anymore! + +> **IMPORTANT**: When you are using macOS and the MIW docker container won't start up (stuck somewhere or doesn't start +> at all), you can enable the docker-desktop feature "Use Rosetta for x86/amd64 emulation on Apple Silicon" in your +> Docker settings (under "features in development"). This should fix the issue. + +Note: *SKIP_GRADLE_TASKS_PARAM* is used to pass parameters to the build process of the MIW jar. Currently, it skips the +tests and code coverage, but speeds up the build time. If you want to activate it, just comment it out +like `SKIP_GRADLE_TASKS_PARAM="" #"-x jacocoTestCoverageVerification -x test"` + +After every execution (either *local* or *docker* flow), run the matching "stop" task ( +e.g.: `task docker:start-app` -> `task docker:stop-app`) + +When you just run `task` without parameters, you will see all tasks available. + +### local + +1. Run `task docker:start-middleware` and wait until it shows "(main) Running the server in development mode. DO NOT use + this configuration in production." in the terminal +2. Run `task app:build` to build the MIW application +3. Run + [ManagedIdentityWalletsApplication.java](src/main/java/org/eclipse/tractusx/managedidentitywallets/ManagedIdentityWalletsApplication.java) + via IDE and use the local.env file to populate environment vars (e.g. EnvFile plugin for IntelliJ) +4. Run `task app:get-token` and copy the token (including "BEARER" prefix) (Mac users have the token already in their + clipboard) +5. Open API doc on http://localhost:8000 (or what port you configured in the *env.local* file) +6. Click on Authorize on swagger UI and on the dialog paste the token into the "value" input +7. Click on "Authorize" and "close" +8. MIW is up and running + +### docker + +1. Run `task docker:start-app` and wait until it shows "Started ManagedIdentityWalletsApplication in ... seconds" +2. Run `task app:get-token` and copy the token (including "BEARER" prefix) (Mac users have the token already in their + clipboard) +3. Open API doc on http://localhost:8000 (or what port you configured in the *env.local* file) +4. Click on Authorize on swagger UI and on the dialog paste the token into the "value" input +5. Click on "Authorize" and "close" +6. MIW is up and running + +### pgAdmin + +This local environment contains [pgAdmin](https://www.pgadmin.org/), which is also started ( +default: http://localhost:8888). +The default login is: + +``` +user: pg@admin.com (you can change it in the env.* files) +password: the one you set for "POSTGRES_PASSWORD" in the env.* files +``` + +#### DB connection password + +When you log in into pgAdmin, the local Postgresql server is already configured. +But you will be asked to enter the DB password on the first time you connect to the DB. +(password: POSTGRES_PASSWORD in the env.* files) + +#### Storage folder + +The storage folder of pgAdmin is mounted to `dev-assets/docker-environment/pgAdmin/storage/`. +For example, You can save DB backups there, so you can access them on your local machine. + +# End Users + +See OpenAPI documentation, which is automatically created from the source and available on each deployment at +the `/docs/api-docs/docs` endpoint (e.g. locally at http://localhost:8087/docs/api-docs/docs). An export of the JSON +document can be also found in [docs/openapi_v001.json](docs/api/openapi_v001.json). + +# Test Coverage + +Jacoco is used to generate the coverage report. The report generation and the coverage verification are automatically +executed after tests. + +The generated HTML report can be found under `jacoco-report/html/` + +To generate the report run the command: + +``` +task app:test-report +``` + +To check the coverage run the command: + +``` +task app:coverage +``` + +Currently, the minimum is 80% coverage. + +# Common issues and solutions during local setup + +## 1. Can not build with test cases + +Test cases are written using the Spring Boot integration test frameworks. These test frameworks start the Spring Boot +test context, which allows us to perform integration testing. In our tests, we utilize the Testcontainers +library (https://java.testcontainers.org/) for managing Docker containers. Specifically, we use Testcontainers to start +PostgreSQL and Keycloak Docker containers locally. + +Before running the tests, please ensure that you have Docker runtime installed and that you have the necessary +permissions to run containers. + +Alternative, you can skip test during the build with `` ./gradlew clean build -x test`` + +## 2. Database migration related issue + +We have implemented database migration using Liquibase (https://www.liquibase.org/). Liquibase allows us to manage +database schema changes effectively. + +In case you encounter any database-related issues, you can resolve them by following these steps: + +1. Delete all tables from the database. +2. Restart the application. +3. Upon restart, the application will recreate the database schema from scratch. + +This process ensures that any issues with the database schema are resolved by recreating it in a fresh state. + +# Environment Variables + +| name | description | default value | +|---------------------------------|----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| APPLICATION_PORT | port number of application | 8080 | +| APPLICATION_ENVIRONMENT | Environment of the application ie. local, dev, int and prod | local | +| DB_HOST | Database host | localhost | +| DB_PORT | Port of database | 5432 | +| DB_NAME | Database name | miw | +| USE_SSL | Whether SSL is enabled in database server | false | +| DB_USER_NAME | Database username | | +| DB_PASSWORD | Database password | | +| DB_POOL_SIZE | Max number of database connection acquired by application | 10 | +| KEYCLOAK_MIW_PUBLIC_CLIENT | Only needed if we want enable login with keyalock in swagger | miw_public | +| MANAGEMENT_PORT | Spring actuator port | 8090 | +| MIW_HOST_NAME | Application host name, this will be used in creation of did ie. did:web:MIW_HOST_NAME:BPN | localhost | +| ENCRYPTION_KEY | encryption key used to encrypt and decrypt private and public key of wallet | | +| AUTHORITY_WALLET_BPN | base wallet BPN number | BPNL000000000000 | +| AUTHORITY_WALLET_NAME | Base wallet name | Catena-X | +| AUTHORITY_WALLET_DID | Base wallet web did | web:did:host:BPNL000000000000 | +| VC_SCHEMA_LINK | Comma separated list of VC schema URL | https://www.w3.org/2018/credentials/v1, https://catenax-ng.github.io/product-core-schemas/businessPartnerData.json | +| VC_EXPIRY_DATE | Expiry date of VC (dd-MM-yyyy ie. 01-01-2025 expiry date will be 2024-12-31T18:30:00Z in VC) | 01-01-2025 | +| KEYCLOAK_REALM | Realm name of keycloak | miw_test | +| KEYCLOAK_CLIENT_ID | Keycloak private client id | | +| AUTH_SERVER_URL | Keycloak server url | | +| SUPPORTED_FRAMEWORK_VC_TYPES | Supported framework VC, provide values ie type1=value1,type2=value2 | cx-behavior-twin=Behavior Twin,cx-pcf=PCF,cx-quality=Quality,cx-resiliency=Resiliency,cx-sustainability=Sustainability,cx-traceability=ID_3.0_Trace | +| ENFORCE_HTTPS_IN_DID_RESOLUTION | Enforce https during web did resolution | true | +| CONTRACT_TEMPLATES_URL | Contract templates URL used in summary VC | https://public.catena-x.org/contracts/ | +| APP_LOG_LEVEL | Log level of application | INFO | +| AUTHORITY_SIGNING_SERVICE_TYPE | Base wallet signing type, Currency only LOCAL is supported | Local | +| LOCAL_SIGNING_KEY_STORAGE_TYPE | Key storage type, currently only DB is supported | DB | +| STATUS_LIST_2021_CONTEXT_URL | Context URI for status list 2021 | https://w3id.org/vc/status-list/2021/v1 | +| | | | + +# Technical Debts and Known issue + +1. Keys are stored in database in encrypted format, need to store keys in more secure place ie. Vault +2. Policies can be validated dynamically as per request while validating VP and + VC. [Check this for more details](https://docs.walt.id/v/ssikit/concepts/verification-policies) + +# Logging in application + +Log level in application can be set using environment variable ``APP_LOG_LEVEL``. Possible values +are ``OFF, ERROR, WARN, INFO, DEBUG, TRACE`` and default value set to ``INFO`` + +## Change log level at runtime using Spring actuator + +We can use ``/actuator/loggers`` API endpoint of actuator for log related things. This end point can be accessible with +role ``manage_app``. We can add this role to authority wallet client using keycloak as below: + +![manage_app.png](docs%2Fmanage_app.png) + +1. API to get current log settings + ```bash + curl --location 'http://localhost:8090/actuator/loggers' \ + --header 'Authorization: Bearer access_token' + ``` +2. Change log level at runtime + ```bash + curl --location 'http://localhost:8090/actuator/loggers/{java package name}' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer access_token' \ + --data '{"configuredLevel":"INFO"}' + ``` + i.e. + ```bash + curl --location 'http://localhost:8090/actuator/loggers/org.eclipse.tractusx.managedidentitywallets' \ + --header 'Content-Type: application/json' \ + --header 'Authorization: Bearer access_token' \ + --data '{"configuredLevel":"INFO"}' + ``` + +## Reference of external lib + +1. https://www.testcontainers.org/modules/databases/postgres/ +2. https://github.com/dasniko/testcontainers-keycloak +3. https://github.com/smartSenseSolutions/smartsense-java-commons +4. https://github.com/catenax-ng/product-lab-ssi + +## Notice for Docker image + +See [Docker-hub-notice.md](./Docker-hub-notice.md) + +## Acknowledgments + +We would like to give credit to these projects, which we use in our project. + +[![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release) diff --git a/miw/build.gradle b/miw/build.gradle index 9312d3728..5b3c6c454 100644 --- a/miw/build.gradle +++ b/miw/build.gradle @@ -19,13 +19,6 @@ plugins { id 'java' - id 'org.springframework.boot' version "${springBootVersion}" - id 'io.spring.dependency-management' version "${springDependencyVersion}" - id "jacoco" - id 'project-report' - - // used to download the 'dash.jar' for license checks - // docs: https://github.com/michel-kraemer/gradle-download-task id "de.undercouch.download" version "5.5.0" } @@ -43,47 +36,12 @@ configurations { compileOnly.extendsFrom(annotaionProcessor) } -// this needs to be done, as we're using the org.springframework.boot plugin -// with native Gradle bom resolution -// docs: https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/htmlsingle/#managing-dependencies.gradle-bom-support.customizing -// docs: https://docs.gradle.org/7.6/dsl/org.gradle.api.artifacts.ResolutionStrategy.html -configurations.configureEach { - resolutionStrategy.eachDependency { DependencyResolveDetails details -> - if (details.requested.group == 'ch.qos.logback') { - details.useVersion '1.4.12' - } - // avoid a license issue - if (details.requested.name == 'spring-boot-devtools') { - details.useVersion '3.1.5' - } - } -} - -repositories { - // delegate is RepositoryHandler - // docs: https://docs.gradle.org/7.6/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html - mavenLocal() - mavenCentral() - maven { - url = uri("https://repo.danubetech.com/repository/maven-public") - } - maven { url 'https://jitpack.io' } - maven { - // Used to resolve Dash License Tool - // Dash has a maven plugin, BUT is not resolvable through mavenCentral() - url = uri("https://repo.eclipse.org/content/repositories/dash-licenses/") - } -} -ext { +dependencies { -} + //project deps + implementation project(":wallet-commons") -// comes from gradle directly -dependencies { - // 'implementation', 'testImplementation', 'runtimeOnly', 'compileOnly', 'annotationProcessor' and - // 'testAnnotationProcessor' configuration come from the java-plugin - // docs: https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_plugin_and_dependency_management implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' @@ -94,25 +52,13 @@ dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' implementation "org.springdoc:springdoc-openapi-starter-common:${openApiVersion}" implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:${openApiVersion}" - implementation group: 'com.smartsensesolutions', name: 'commons-dao', version: '0.0.5' + implementation "com.smartsensesolutions:commons-dao:${commonsDaoVersion}" implementation 'org.liquibase:liquibase-core' - implementation 'org.eclipse.tractusx.ssi:cx-ssi-lib:0.0.19' - - //Added explicitly to mitigate CVE 2022-1471 - implementation group: 'org.yaml', name: 'snakeyaml', version: '2.0' - - //Added explicitly to mitigate CVE 2023-24998 - implementation group: 'commons-fileupload', name: 'commons-fileupload', version: '1.5' + implementation "org.eclipse.tractusx.ssi:cx-ssi-lib:${ssiLibVersion}" runtimeOnly 'org.postgresql:postgresql' - compileOnly 'org.projectlombok:lombok' - // custom 'developmentOnly' config - // https://docs.spring.io/spring-boot/docs/2.0.6.RELEASE/reference/html/using-boot-devtools.html#using-boot-devtools - developmentOnly 'org.springframework.boot:spring-boot-devtools' - annotationProcessor 'org.projectlombok:lombok' - testAnnotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.wiremock:wiremock-standalone:3.4.2' - testImplementation 'org.projectlombok:lombok:1.18.28' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation "org.testcontainers:testcontainers" testImplementation 'com.h2database:h2:2.2.220' @@ -122,79 +68,9 @@ dependencies { testImplementation group: 'org.mockito', name: 'mockito-inline', version: '5.2.0' testImplementation group: 'org.json', name: 'json', version: '20230227' testImplementation group: 'com.github.curious-odd-man', name: 'rgxgen', version: '1.4' - -} - -// uses the 'download' plugin -// docs: https://plugins.gradle.org/plugin/de.undercouch.download -tasks.register('dashDownload', Download) { - description = 'Download the Dash License Tool standalone jar' - group = 'License' - src 'https://repo.eclipse.org/service/local/artifact/maven/redirect?r=dash-licenses&g=org.eclipse.dash&a=org.eclipse.dash.licenses&v=LATEST' - dest rootProject.file('dash.jar') - // will not replace an existing file. If you know you need a new version - // then manually delete the file yourself, or run `dashClean` - overwrite false -} - -// This task is primarily used by CIs -tasks.register('dashClean') { - description = "Clean all files used by the 'License' group" - group = 'License' - logger.lifecycle("Removing 'dash.jar'") - rootProject.file('dash.jar').delete() - logger.lifecycle("Removing 'deps.txt'") - file('deps.txt').delete() -} - -// Usage: in the root of the project: `./gradlew -q dashDependencies` -// The `-q` option is important if you want to use the output in a pipe. -tasks.register('dashDependencies') { dashDependencies -> - description = "Output all project dependencies as a flat list and save an intermediate file 'deps.txt'." - group = 'License' - dashDependencies.dependsOn('dashDownload') - doLast { - def deps = [] - project.configurations.each { conf -> - // resolving 'archives' or 'default' is deprecated - if (conf.canBeResolved && conf.getName() != 'archives' && conf.getName() != 'default') { - deps.addAll(conf.incoming.resolutionResult.allDependencies - // the 'allDependencies' method return a 'DependencyResult' - // we're only interested in the 'ResolvedDependencyResult' sub-interface - // docs: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/result/ResolutionResult.html#allDependencies-groovy.lang.Closure- - // docs: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/result/DependencyResult.html - // docs: https://docs.gradle.org/current/javadoc/org/gradle/api/artifacts/result/ResolvedDependencyResult.html - .findAll({ it instanceof ResolvedDependencyResult }) - .collect { ResolvedDependencyResult dep -> - "${dep.selected}" - }) - } - } - - def uniqueSorted = deps.unique().sort() - uniqueSorted.each { logger.quiet("{}", it) } - file("deps.txt").write(uniqueSorted.join('\n')) - } -} - -tasks.register('dashLicenseCheck', JavaExec) { dashLicenseCheck -> - description = "Run the Dash License Tool and save the summary in the 'DEPENDENCIES' file" - group = 'License' - dashLicenseCheck.dependsOn('dashDownload') - dashLicenseCheck.dependsOn('dashDependencies') - doFirst { - classpath = rootProject.files('dash.jar') - // docs: https://eclipse-tractusx.github.io/docs/release/trg-7/trg-7-04 - args('-project', 'automotive.tractusx', '-summary', 'DEPENDENCIES', 'deps.txt') - } - doLast { - logger.lifecycle("Removing 'deps.txt' now.") - file('deps.txt').delete() - } + testImplementation "com.teketik:mock-in-bean:${mockInBeanVersion}" } -// 'dependencyManagement' comes from the 'io.spring.dependency-management' plugin -// docs: https://docs.spring.io/dependency-management-plugin/docs/current/reference/html/ dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" @@ -202,17 +78,13 @@ dependencyManagement { } } -// 'build' task comes from the 'java' plugin -// docs: https://docs.gradle.org/current/userguide/java_plugin.html build { archivesBaseName = "miw" version = "latest" } -// 'bootJar' comes from the 'org.springframework.boot' plugin -// 'bootJar' is a subclass of the 'jar' task type -// docs: https://docs.gradle.org/current/dsl/org.gradle.jvm.tasks.Jar.html#org.gradle.jvm.tasks.Jar bootJar { + enabled = true metaInf { from 'DEPENDENCIES' from '../SECURITY.md' @@ -220,74 +92,3 @@ bootJar { from '../LICENSE' } } - -// 'test' comes from the 'java' plugin -// docs: https://docs.gradle.org/current/userguide/java_plugin.html -test { - useJUnitPlatform() - finalizedBy jacocoTestReport -} - -// standard gradle class -// docs: https://docs.gradle.org/current/dsl/org.gradle.api.reporting.dependencies.HtmlDependencyReportTask.html -htmlDependencyReport { - projects = project.allprojects -} - -// 'jacocoTestReport' is provided by the 'jacoco' plugin -// docs: https://docs.gradle.org/current/userguide/jacoco_plugin.html -jacocoTestReport { - - reports { - xml.enabled true - xml.outputLocation = file("./build/reports/xml/jacoco.xml") - - csv.enabled false - - html.enabled true - html.outputLocation = file("./build/reports/html/jacoco") - } - - afterEvaluate { - classDirectories.setFrom(files(classDirectories.files.collect { - fileTree(dir: it, exclude: [ - "org/eclipse/tractusx/managedidentitywallets/dto/*", - "org/eclipse/tractusx/managedidentitywallets/dao/entity/*", - "org/eclipse/tractusx/managedidentitywallets/constant/*", - "org/eclipse/tractusx/managedidentitywallets/exception/*" - ]) - })) - } -} - -// 'jacoco' is provided by the 'jacoco' plugin -// docs: https://docs.gradle.org/current/userguide/jacoco_plugin.html#sec:configuring_the_jacoco_plugin -jacoco { - toolVersion = "${jacocoVersion}" -} - - -// 'jacocoTestCoverageVerification' is provided by the 'jacoco' plugin -// docs: https://docs.gradle.org/current/userguide/jacoco_plugin.html#sec:jacoco_report_violation_rules -jacocoTestCoverageVerification { - afterEvaluate { - classDirectories.setFrom(files(classDirectories.files.collect { - fileTree(dir: it, exclude: [ - "org/eclipse/tractusx/managedidentitywallets/dto/*", - "org/eclipse/tractusx/managedidentitywallets/dao/entity/*", - "org/eclipse/tractusx/managedidentitywallets/constant/*", - "org/eclipse/tractusx/managedidentitywallets/exception/*" - ]) - })) - } - violationRules { - rule { - limit { - // disabled for now - minimum = 0.00 - } - } - } -} - -check.dependsOn jacocoTestCoverageVerification diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/ManagedIdentityWalletsApplication.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/ManagedIdentityWalletsApplication.java index f67053adc..9fc948c8e 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/ManagedIdentityWalletsApplication.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/ManagedIdentityWalletsApplication.java @@ -24,6 +24,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.transaction.annotation.EnableTransactionManagement; /** @@ -32,6 +33,7 @@ @SpringBootApplication @ConfigurationPropertiesScan @EnableTransactionManagement +@EnableFeignClients public class ManagedIdentityWalletsApplication { /** diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/IssuersCredentialControllerApiDocs.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/IssuersCredentialControllerApiDocs.java index 6f8571efe..771ccda3f 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/IssuersCredentialControllerApiDocs.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/IssuersCredentialControllerApiDocs.java @@ -222,7 +222,6 @@ public class IssuersCredentialControllerApiDocs { @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) - @Tag(name = API_TAG_VERIFIABLE_CREDENTIAL_VALIDATION) @ApiResponses(value = { @ApiResponse(responseCode = "401", description = "The request could not be completed due to a failed authorization.", content = { @Content(examples = {}) }), @@ -615,11 +614,12 @@ public class IssuersCredentialControllerApiDocs { + "Setting this parameter to false will result in the VC being created as JSON-LD " + "Defaults to false if not specified.", examples = { - @ExampleObject(name = "Create VC as JWT", value = "true"), - @ExampleObject(name = "Do not create VC as JWT", value = "false") - }) + @ExampleObject(name = "Create VC as JWT", value = "true"), + @ExampleObject(name = "Do not create VC as JWT", value = "false") + }) @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface AsJwtParam { } + } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/RevocationAPIDoc.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/RevocationAPIDoc.java new file mode 100644 index 000000000..d631047f0 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/RevocationAPIDoc.java @@ -0,0 +1,158 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.apidocs; + + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public class RevocationAPIDoc { + + @Parameter(description = "Specifies whether the VC (Verifiable Credential) should revocable. The default value will be true") + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface Revocable { + + } + + @Tag(name = "Verifiable Credential - Revoke") + @ApiResponse(responseCode = "201", description = "Issuer credential", content = { + @Content(examples = { + @ExampleObject(name = "Success response", value = """ + { + "message": "Credential has been revoked" + } + """) + }) + }) + @ApiResponse(responseCode = "404", description = "Wallet not found with credential issuer", content = { @Content(examples = { + @ExampleObject(name = "Wallet not found with credential issuer", value = """ + { + "type": "about:blank", + "title": "Wallet not found for identifier web:did:localhost:BPN", + "status": 404, + "detail": "Error Details", + "instance": "API endpoint", + "properties": { + "timestamp": 1689762476720 + } + } + """) + }) }) + @ApiResponse(responseCode = "500", description = "Any other internal server error", content = { @Content(examples = { + @ExampleObject(name = "Internal server error", value = """ + { + "type": "about:blank", + "title": "Error Title", + "status": 500, + "detail": "Error Details", + "instance": "API endpoint", + "properties": { + "timestamp": 1689762476720 + } + } + """) + }) }) + @ApiResponse(responseCode = "409", description = "Credential is already revoked", content = { @Content(examples = { + @ExampleObject(name = "Credential is already revoked", value = """ + { + "type": "about:blank", + "title": "Revocation service error", + "status": 409, + "detail": "RevocationProblem: Credential already revoked", + "instance": "/api/credentials/revoke", + "properties": { + "timestamp": "1704438069232", + "type": "about:blank", + "title": "Revocation service error", + "status": "409", + "detail": "Credential already revoked", + "instance": "/api/v1/revocations/revoke" + } + } + """) + }) }) + @ApiResponse(responseCode = "403", description = "The request could not be completed due to a forbidden access", content = { @Content(examples = {}) }) + @io.swagger.v3.oas.annotations.parameters.RequestBody(content = { + @Content(examples = @ExampleObject(""" + { + "issuanceDate": "2023-12-26T10:58:02Z", + "credentialSubject": + [ + { + "holderIdentifier": "BPNL000000000002", + "id": "did:web:localhost:BPNL000000000002", + "type": "SummaryCredential", + "items": + [ + "BpnCredential" + ], + "contractTemplate": "https://public.catena-x.org/contracts/" + } + ], + "id": "did:web:localhost:BPNL000000000000#6b680abe-8869-435f-9d83-ad5ac336b8da", + "proof": + { + "proofPurpose": "assertionMethod", + "type": "JsonWebSignature2020", + "verificationMethod": "did:web:localhost:BPNL000000000000#a8233b68-f41e-4f14-8dff-fe16a63e0b19", + "created": "2023-12-26T10:58:02Z", + "jws": "eyJhbGciOiJFZERTQSJ9..uFqnCMbcOJneZDl7mCg8PeUjhWdUN53C8dB1E3EoWx7_hVgxsU8L7WkRYxvxIEa_DddViOoKs8E95ymYK081Aw" + }, + "type": + [ + "VerifiableCredential", + "SummaryCredential" + ], + "@context": + [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T18:30:00Z", + "credentialStatus": + { + "id": "did:web:localhost:BPNL000000000000#1", + "statusPurpose": "revocation", + "statusListIndex": "1", + "statusListCredential": "https://7337-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials?issuerId=did:web:localhost:BPNL000000000000", + "type": "StatusList2021" + } + } + """)) + }) + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface RevokeCredentialDoc { + + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ApplicationConfig.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ApplicationConfig.java index 9cefda803..22ac481fa 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ApplicationConfig.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ApplicationConfig.java @@ -25,7 +25,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.smartsensesolutions.java.commons.specification.SpecificationUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.text.StringEscapeUtils; import org.eclipse.tractusx.managedidentitywallets.domain.SigningServiceType; @@ -81,11 +80,6 @@ public ObjectMapper objectMapper() { return objectMapper; } - @Bean - public SpecificationUtil specificationUtil() { - return new SpecificationUtil<>(); - } - @Override public void addViewControllers(ViewControllerRegistry registry) { String redirectUri = properties.getPath(); @@ -117,7 +111,7 @@ public Map availableKeyStorages(List available = new EnumMap<>(SigningServiceType.class); storages.forEach( s -> { - if(s instanceof LocalSigningService local){ + if (s instanceof LocalSigningService local) { local.setKeyProvider(localSigningKeyProvider); } available.put(s.getSupportedServiceType(), s); diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ExceptionHandling.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ExceptionHandling.java index 95b00550c..3e73396ad 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ExceptionHandling.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ExceptionHandling.java @@ -26,10 +26,10 @@ import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; import org.eclipse.tractusx.managedidentitywallets.exception.CredentialNotFoundProblem; import org.eclipse.tractusx.managedidentitywallets.exception.DuplicateWalletProblem; -import org.eclipse.tractusx.managedidentitywallets.exception.ForbiddenException; import org.eclipse.tractusx.managedidentitywallets.exception.MissingVcTypesException; import org.eclipse.tractusx.managedidentitywallets.exception.PermissionViolationException; import org.eclipse.tractusx.managedidentitywallets.exception.WalletNotFoundProblem; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/RevocationSettings.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/RevocationSettings.java new file mode 100644 index 000000000..78c418f1d --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/RevocationSettings.java @@ -0,0 +1,30 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.net.URI; + +@ConfigurationProperties(prefix = "miw.revocation") +public record RevocationSettings(URI url, URI statusList2021Context) { +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/CustomAuthenticationEntryPoint.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/CustomAuthenticationEntryPoint.java new file mode 100644 index 000000000..488184b93 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/CustomAuthenticationEntryPoint.java @@ -0,0 +1,103 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.config.security; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Setter; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.server.resource.BearerTokenError; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.util.StringUtils; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * The type Custom authentication entry point. + */ +@Setter +public final class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private String realmName; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { + HttpStatus status = HttpStatus.UNAUTHORIZED; + Map parameters = new LinkedHashMap<>(); + + if (this.realmName != null) { + parameters.put("realm", this.realmName); + } + + if (authException instanceof OAuth2AuthenticationException) { + OAuth2Error error = ((OAuth2AuthenticationException) authException).getError(); + parameters.put("error", error.getErrorCode()); + if (StringUtils.hasText(error.getDescription())) { + parameters.put("error_description", error.getDescription()); + } + + if (StringUtils.hasText(error.getUri())) { + parameters.put("error_uri", error.getUri()); + } + + if (error instanceof BearerTokenError bearerTokenError) { + if (StringUtils.hasText(bearerTokenError.getScope())) { + parameters.put("scope", bearerTokenError.getScope()); + } + + status = ((BearerTokenError) error).getHttpStatus(); + } + } + + if (authException.getMessage().contains(StringPool.BPN_NOT_FOUND)) { + status = HttpStatus.FORBIDDEN; + } + + String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters); + response.addHeader(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate); + response.setStatus(status.value()); + } + + private static String computeWWWAuthenticateHeaderValue(Map parameters) { + StringBuilder wwwAuthenticate = new StringBuilder(); + wwwAuthenticate.append("Bearer"); + if (!parameters.isEmpty()) { + wwwAuthenticate.append(" "); + int i = 0; + for (Map.Entry entry : parameters.entrySet()) { + wwwAuthenticate.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\""); + if (i != parameters.size() - 1) { + wwwAuthenticate.append(", "); + } + i++; + } + } + return wwwAuthenticate.toString(); + } + +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java index 61ca5acd0..6099e12af 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java @@ -28,6 +28,7 @@ import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; import org.eclipse.tractusx.managedidentitywallets.dto.ValidationResult; import org.eclipse.tractusx.managedidentitywallets.service.STSTokenValidationService; @@ -39,7 +40,6 @@ import java.util.ArrayList; import java.util.List; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COMA_SEPARATOR; public class PresentationIatpFilter extends GenericFilterBean { @@ -66,7 +66,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha if (!result.isValid()) { List errorValues = new ArrayList<>(); result.getErrors().forEach(c -> errorValues.add(c.name())); - String content = String.join(COMA_SEPARATOR, errorValues); + String content = String.join(StringPool.COMA_SEPARATOR, errorValues); httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); httpServletResponse.setContentLength(content.length()); diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java index 59bce9fad..708a1f37f 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java @@ -21,11 +21,13 @@ package org.eclipse.tractusx.managedidentitywallets.config.security; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.managedidentitywallets.constant.ApplicationRole; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.ApplicationRole; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; import org.eclipse.tractusx.managedidentitywallets.service.STSTokenValidationService; +import org.eclipse.tractusx.managedidentitywallets.utils.BpnValidator; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; @@ -39,12 +41,22 @@ import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtDecoders; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.NegatedRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; + +import java.util.List; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; +import static org.springframework.http.HttpMethod.PUT; /** * The type Security config. @@ -53,13 +65,16 @@ @EnableWebSecurity @EnableMethodSecurity(securedEnabled = true) @Configuration -@AllArgsConstructor +@RequiredArgsConstructor public class SecurityConfig { private final STSTokenValidationService validationService; private final SecurityConfigProperties securityConfigProperties; + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") + private String issuerUri; + /** * Filter chain security filter chain. * @@ -81,7 +96,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/ui/swagger-ui/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/actuator/health/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/token", POST.name())).permitAll() - .requestMatchers(new AntPathRequestMatcher("/api/presentations/iatp", GET.name())).permitAll() + .requestMatchers(new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP, POST.name())).permitAll() + .requestMatchers(new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP_WORKAROUND, POST.name())).permitAll() .requestMatchers(new AntPathRequestMatcher("/actuator/loggers/**")).hasRole(ApplicationRole.ROLE_MANAGE_APP) //did document resolve APIs @@ -100,6 +116,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { //VP - Validation .requestMatchers(new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_VALIDATION, POST.name())).hasAnyRole(ApplicationRole.ROLE_VIEW_WALLETS, ApplicationRole.ROLE_VIEW_WALLET) //validate VP + //VC - revoke + .requestMatchers(new AntPathRequestMatcher(RestURI.CREDENTIALS_REVOKE, PUT.name())).hasAnyRole(ApplicationRole.ROLE_UPDATE_WALLET, ApplicationRole.ROLE_UPDATE_WALLETS) //revoke credentials + //VC - Holder .requestMatchers(new AntPathRequestMatcher(RestURI.CREDENTIALS, GET.name())).hasAnyRole(ApplicationRole.ROLE_VIEW_WALLET, ApplicationRole.ROLE_VIEW_WALLETS) //get credentials .requestMatchers(new AntPathRequestMatcher(RestURI.CREDENTIALS, POST.name())).hasAnyRole(ApplicationRole.ROLE_UPDATE_WALLET, ApplicationRole.ROLE_UPDATE_WALLETS) //issue credential @@ -114,8 +133,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { //error .requestMatchers(new AntPathRequestMatcher("/error")).permitAll() ).oauth2ResourceServer(resourceServer -> resourceServer.jwt(jwt -> - jwt.jwtAuthenticationConverter(new CustomAuthenticationConverter(securityConfigProperties.clientId())))) - .addFilterAfter(new PresentationIatpFilter(validationService), BasicAuthenticationFilter.class); + jwt.jwtAuthenticationConverter(new CustomAuthenticationConverter(securityConfigProperties.clientId()))) + .authenticationEntryPoint(new CustomAuthenticationEntryPoint())) + .securityMatcher(new NegatedRequestMatcher(new OrRequestMatcher( + List.of( + new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP), + new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP_WORKAROUND))))); return http.build(); } @@ -137,7 +160,20 @@ public WebSecurityCustomizer securityCustomizer() { */ @Bean public AuthenticationEventPublisher authenticationEventPublisher - (ApplicationEventPublisher applicationEventPublisher) { + (ApplicationEventPublisher applicationEventPublisher) { return new DefaultAuthenticationEventPublisher(applicationEventPublisher); } + + @Bean + JwtDecoder jwtDecoder() { + NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri); + OAuth2TokenValidator bpnValidator = bpnValidator(); + OAuth2TokenValidator withBpn = new DelegatingOAuth2TokenValidator<>(bpnValidator); + jwtDecoder.setJwtValidator(withBpn); + return jwtDecoder; + } + + OAuth2TokenValidator bpnValidator() { + return new BpnValidator(); + } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityEvents.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityEvents.java index 841bd3fdf..90dc1f86e 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityEvents.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityEvents.java @@ -38,7 +38,11 @@ public void onFailure(AbstractAuthenticationFailureEvent failures) { @EventListener public void onFailure(AuthorizationDeniedEvent failure) { - log.warn("Failed Authorization: Missing 'Authorization' header."); + if (failure.getAuthorizationDecision() != null) { + log.warn("Failed Authorization: {}",failure.getAuthorizationDecision().toString()); + } else { + log.warn("Failed Authorization: Missing 'Authorization' header."); + } } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java index 764a0af4d..27c8ad276 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java @@ -62,6 +62,8 @@ private RestURI() { */ public static final String CREDENTIALS_VALIDATION = "/api/credentials/validation"; + public static final String CREDENTIALS_REVOKE = "/api/credentials/revoke"; + /** * The constant ISSUERS_CREDENTIALS. */ @@ -80,4 +82,10 @@ private RestURI() { */ public static final String API_PRESENTATIONS_IATP = "/api/presentations/iatp"; + /** + * The constant API_PRESENTATIONS_IATP_WORKAROUND. THe EDC assumes (hard coded) that the presentation query endpoint is at /presentations/query. + * To mitigate this issue the MIW has to provide the same endpoint (without documentation), besides the correct one. + */ + public static final String API_PRESENTATIONS_IATP_WORKAROUND = "/presentations/query"; + } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/DidDocumentController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/DidDocumentController.java index 6869d4532..7c3a8ad8a 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/DidDocumentController.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/DidDocumentController.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -45,7 +45,7 @@ @RequiredArgsConstructor @Tag(name = "DIDDocument") @Slf4j -public class DidDocumentController extends BaseController { +public class DidDocumentController { private final DidDocumentService service; /** diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/HoldersCredentialController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/HoldersCredentialController.java index 9d55124df..62f97247f 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/HoldersCredentialController.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/HoldersCredentialController.java @@ -32,22 +32,26 @@ import org.eclipse.tractusx.managedidentitywallets.apidocs.HoldersCredentialControllerApiDocs.GetCredentialsApiDocs; import org.eclipse.tractusx.managedidentitywallets.apidocs.HoldersCredentialControllerApiDocs.IssueCredentialApiDoc; import org.eclipse.tractusx.managedidentitywallets.apidocs.IssuersCredentialControllerApiDocs.AsJwtParam; +import org.eclipse.tractusx.managedidentitywallets.apidocs.RevocationAPIDoc; import org.eclipse.tractusx.managedidentitywallets.command.GetCredentialsCommand; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.dto.CredentialsResponse; import org.eclipse.tractusx.managedidentitywallets.service.HoldersCredentialService; import org.springframework.data.domain.PageImpl; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.security.Principal; import java.util.List; import java.util.Map; @@ -58,7 +62,7 @@ @RequiredArgsConstructor @Slf4j @Tag(name = "Verifiable Credential - Holder") -public class HoldersCredentialController extends BaseController { +public class HoldersCredentialController { private final HoldersCredentialService holdersCredentialService; @@ -71,31 +75,30 @@ public class HoldersCredentialController extends BaseController { * @param type the type * @param sortColumn the sort column * @param sortTpe the sort tpe - * @param principal the principal + * @param authentication the authentication * @return the credentials - */ + */ @GetCredentialsApiDocs @GetMapping(path = RestURI.CREDENTIALS, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> getCredentials(@Parameter(name = "credentialId", description = "Credential Id", examples = {@ExampleObject(name = "Credential Id", value = "did:web:localhost:BPNL000000000000#12528899-160a-48bd-ba15-f396c3959ae9")}) @RequestParam(required = false) String credentialId, - @Parameter(name = "issuerIdentifier", description = "Issuer identifier(did of BPN)", examples = {@ExampleObject(name = "bpn", value = "BPNL000000000000", description = "bpn"), @ExampleObject(description = "did", name = "did", value = "did:web:localhost:BPNL000000000000")}) @RequestParam(required = false) String issuerIdentifier, - @Parameter(name = "type", description = "Type of VC", examples = {@ExampleObject(name = "SummaryCredential", value = "SummaryCredential", description = "SummaryCredential"), @ExampleObject(description = "BpnCredential", name = "BpnCredential", value = "BpnCredential")}) @RequestParam(required = false) List type, - @Parameter(name = "sortColumn", description = "Sort column name", - examples = { - @ExampleObject(value = "createdAt", name = "creation date"), - @ExampleObject(value = "issuerDid", name = "Issuer did"), - @ExampleObject(value = "type", name = "Credential type"), - @ExampleObject(value = "credentialId", name = "Credential id"), - @ExampleObject(value = "selfIssued", name = "Self issued credential"), - @ExampleObject(value = "stored", name = "Stored credential") - } - ) @RequestParam(required = false, defaultValue = "createdAt") String sortColumn, - @Parameter(name = "sortTpe", description = "Sort order", examples = {@ExampleObject(value = "desc", name = "Descending order"), @ExampleObject(value = "asc", name = "Ascending order")}) @RequestParam(required = false, defaultValue = "desc") String sortTpe, - @Min(0) @Max(Integer.MAX_VALUE) @Parameter(description = "Page number, Page number start with zero") @RequestParam(required = false, defaultValue = "0") int pageNumber, - @Min(0) @Max(Integer.MAX_VALUE) @Parameter(description = "Number of records per page") @RequestParam(required = false, defaultValue = Integer.MAX_VALUE + "") int size, - @AsJwtParam @RequestParam(name = StringPool.AS_JWT, defaultValue = "false") boolean asJwt, - - Principal principal) { - log.debug("Received request to get credentials. BPN: {}", getBPNFromToken(principal)); + public ResponseEntity> getCredentials(@Parameter(name = "credentialId", description = "Credential Id", examples = { @ExampleObject(name = "Credential Id", value = "did:web:localhost:BPNL000000000000#12528899-160a-48bd-ba15-f396c3959ae9") }) @RequestParam(required = false) String credentialId, + @Parameter(name = "issuerIdentifier", description = "Issuer identifier(did of BPN)", examples = { @ExampleObject(name = "bpn", value = "BPNL000000000000", description = "bpn"), @ExampleObject(description = "did", name = "did", value = "did:web:localhost:BPNL000000000000") }) @RequestParam(required = false) String issuerIdentifier, + @Parameter(name = "type", description = "Type of VC", examples = { @ExampleObject(name = "SummaryCredential", value = "SummaryCredential", description = "SummaryCredential"), @ExampleObject(description = "BpnCredential", name = "BpnCredential", value = "BpnCredential") }) @RequestParam(required = false) List type, + @Parameter(name = "sortColumn", description = "Sort column name", + examples = { + @ExampleObject(value = "createdAt", name = "creation date"), + @ExampleObject(value = "issuerDid", name = "Issuer did"), + @ExampleObject(value = "type", name = "Credential type"), + @ExampleObject(value = "credentialId", name = "Credential id"), + @ExampleObject(value = "selfIssued", name = "Self issued credential"), + @ExampleObject(value = "stored", name = "Stored credential") + } + ) @RequestParam(required = false, defaultValue = "createdAt") String sortColumn, + @Parameter(name = "sortTpe", description = "Sort order", examples = { @ExampleObject(value = "desc", name = "Descending order"), @ExampleObject(value = "asc", name = "Ascending order") }) @RequestParam(required = false, defaultValue = "desc") String sortTpe, + @Min(0) @Max(Integer.MAX_VALUE) @Parameter(description = "Page number, Page number start with zero") @RequestParam(required = false, defaultValue = "0") int pageNumber, + @Min(0) @Max(Integer.MAX_VALUE) @Parameter(description = "Number of records per page") @RequestParam(required = false, defaultValue = Integer.MAX_VALUE + "") int size, + @AsJwtParam @RequestParam(name = StringPool.AS_JWT, defaultValue = "true") boolean asJwt, + Authentication authentication) { + log.debug("Received request to get credentials. BPN: {}", TokenParsingUtils.getBPNFromToken(authentication)); final GetCredentialsCommand command; command = GetCredentialsCommand.builder() .credentialId(credentialId) @@ -106,7 +109,7 @@ public ResponseEntity> getCredentials(@Parameter(n .pageNumber(pageNumber) .size(size) .asJwt(asJwt) - .callerBPN(getBPNFromToken(principal)) + .callerBPN(TokenParsingUtils.getBPNFromToken(authentication)) .build(); return ResponseEntity.status(HttpStatus.OK).body(holdersCredentialService.getCredentials(command)); } @@ -115,17 +118,20 @@ public ResponseEntity> getCredentials(@Parameter(n /** * Issue credential response entity. * - * @param data the data - * @param principal the principal + * @param data the data + * @param authentication the authentication * @return the response entity */ @IssueCredentialApiDoc @PostMapping(path = RestURI.CREDENTIALS, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity issueCredential(@RequestBody Map data, Principal principal, - @AsJwtParam @RequestParam(name = "asJwt", defaultValue = "false") boolean asJwt + + public ResponseEntity issueCredential(@RequestBody Map data, Authentication authentication, + @AsJwtParam @RequestParam(name = "asJwt", defaultValue = "true") boolean asJwt, + @RevocationAPIDoc.Revocable @RequestParam(name = StringPool.REVOCABLE, defaultValue = "true") boolean revocable, + @Parameter(hidden = true) @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token ) { - log.debug("Received request to issue credential. BPN: {}", getBPNFromToken(principal)); - return ResponseEntity.status(HttpStatus.CREATED).body(holdersCredentialService.issueCredential(data, getBPNFromToken(principal), asJwt)); + log.debug("Received request to issue credential. BPN: {}", TokenParsingUtils.getBPNFromToken(authentication)); + return ResponseEntity.status(HttpStatus.CREATED).body(holdersCredentialService.issueCredential(data, TokenParsingUtils.getBPNFromToken(authentication), asJwt, revocable, token)); } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/IssuersCredentialController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/IssuersCredentialController.java index 5d3ca437f..1c3ac8b22 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/IssuersCredentialController.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/IssuersCredentialController.java @@ -31,23 +31,27 @@ import org.eclipse.tractusx.managedidentitywallets.apidocs.IssuersCredentialControllerApiDocs.GetCredentialsApiDocs; import org.eclipse.tractusx.managedidentitywallets.apidocs.IssuersCredentialControllerApiDocs.IssueVerifiableCredentialUsingBaseWalletApiDocs; import org.eclipse.tractusx.managedidentitywallets.apidocs.IssuersCredentialControllerApiDocs.ValidateVerifiableCredentialApiDocs; +import org.eclipse.tractusx.managedidentitywallets.apidocs.RevocationAPIDoc; import org.eclipse.tractusx.managedidentitywallets.command.GetCredentialsCommand; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.dto.CredentialVerificationRequest; import org.eclipse.tractusx.managedidentitywallets.dto.CredentialsResponse; import org.eclipse.tractusx.managedidentitywallets.service.IssuersCredentialService; import org.springframework.data.domain.PageImpl; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.security.Principal; import java.util.List; import java.util.Map; @@ -57,16 +61,7 @@ @RestController @RequiredArgsConstructor @Slf4j -public class IssuersCredentialController extends BaseController { - - /** - * The constant API_TAG_VERIFIABLE_CREDENTIAL_ISSUER. - */ - public static final String API_TAG_VERIFIABLE_CREDENTIAL_ISSUER = "Verifiable Credential - Issuer"; - /** - * The constant API_TAG_VERIFIABLE_CREDENTIAL_VALIDATION. - */ - public static final String API_TAG_VERIFIABLE_CREDENTIAL_VALIDATION = "Verifiable Credential - Validation"; +public class IssuersCredentialController { private final IssuersCredentialService issuersCredentialService; @@ -81,28 +76,28 @@ public class IssuersCredentialController extends BaseController { * @param size the size * @param sortColumn the sort column * @param sortTpe the sort tpe - * @param principal the principal + * @param authentication the authentication * @return the credentials */ @GetCredentialsApiDocs @GetMapping(path = RestURI.ISSUERS_CREDENTIALS, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> getCredentials(@Parameter(name = "credentialId", description = "Credential Id", examples = {@ExampleObject(name = "Credential Id", value = "did:web:localhost:BPNL000000000000#12528899-160a-48bd-ba15-f396c3959ae9")}) @RequestParam(required = false) String credentialId, - @Parameter(name = "holderIdentifier", description = "Holder identifier(did of BPN)", examples = {@ExampleObject(name = "bpn", value = "BPNL000000000001", description = "bpn"), @ExampleObject(description = "did", name = "did", value = "did:web:localhost:BPNL000000000001")}) @RequestParam(required = false) String holderIdentifier, - @Parameter(name = "type", description = "Type of VC", examples = {@ExampleObject(name = "SummaryCredential", value = "SummaryCredential", description = "SummaryCredential"), @ExampleObject(description = "BpnCredential", name = "BpnCredential", value = "BpnCredential")}) @RequestParam(required = false) List type, - @Min(0) @Max(Integer.MAX_VALUE) @Parameter(description = "Page number, Page number start with zero") @RequestParam(required = false, defaultValue = "0") int pageNumber, - @Min(0) @Max(Integer.MAX_VALUE) @Parameter(description = "Number of records per page") @RequestParam(required = false, defaultValue = Integer.MAX_VALUE + "") int size, - @Parameter(name = "sortColumn", description = "Sort column name", - examples = { - @ExampleObject(value = "createdAt", name = "creation date"), - @ExampleObject(value = "holderDid", name = "Holder did"), - @ExampleObject(value = "type", name = "Credential type"), - @ExampleObject(value = "credentialId", name = "Credential id") - } - ) @RequestParam(required = false, defaultValue = "createdAt") String sortColumn, + public ResponseEntity> getCredentials(@Parameter(name = "credentialId", description = "Credential Id", examples = { @ExampleObject(name = "Credential Id", value = "did:web:localhost:BPNL000000000000#12528899-160a-48bd-ba15-f396c3959ae9") }) @RequestParam(required = false) String credentialId, + @Parameter(name = "holderIdentifier", description = "Holder identifier(did of BPN)", examples = { @ExampleObject(name = "bpn", value = "BPNL000000000001", description = "bpn"), @ExampleObject(description = "did", name = "did", value = "did:web:localhost:BPNL000000000001") }) @RequestParam(required = false) String holderIdentifier, + @Parameter(name = "type", description = "Type of VC", examples = { @ExampleObject(name = "SummaryCredential", value = "SummaryCredential", description = "SummaryCredential"), @ExampleObject(description = "BpnCredential", name = "BpnCredential", value = "BpnCredential") }) @RequestParam(required = false) List type, + @Min(0) @Max(Integer.MAX_VALUE) @Parameter(description = "Page number, Page number start with zero") @RequestParam(required = false, defaultValue = "0") int pageNumber, + @Min(0) @Max(Integer.MAX_VALUE) @Parameter(description = "Number of records per page") @RequestParam(required = false, defaultValue = Integer.MAX_VALUE + "") int size, + @Parameter(name = "sortColumn", description = "Sort column name", + examples = { + @ExampleObject(value = "createdAt", name = "creation date"), + @ExampleObject(value = "holderDid", name = "Holder did"), + @ExampleObject(value = "type", name = "Credential type"), + @ExampleObject(value = "credentialId", name = "Credential id") + } + ) @RequestParam(required = false, defaultValue = "createdAt") String sortColumn, @Parameter(name = "sortTpe", description = "Sort order", examples = { @ExampleObject(value = "desc", name = "Descending order"), @ExampleObject(value = "asc", name = "Ascending order") }) @RequestParam(required = false, defaultValue = "desc") String sortTpe, - @AsJwtParam @RequestParam(name = StringPool.AS_JWT, defaultValue = "false") boolean asJwt, - Principal principal) { - log.debug("Received request to get credentials. BPN: {}", getBPNFromToken(principal)); + @AsJwtParam @RequestParam(name = StringPool.AS_JWT, defaultValue = "true") boolean asJwt, + Authentication authentication) { + log.debug("Received request to get credentials. BPN: {}", TokenParsingUtils.getBPNFromToken(authentication)); final GetCredentialsCommand command; command = GetCredentialsCommand.builder() .credentialId(credentialId) @@ -113,7 +108,7 @@ public ResponseEntity> getCredentials(@Parameter(n .pageNumber(pageNumber) .size(size) .asJwt(asJwt) - .callerBPN(getBPNFromToken(principal)) + .callerBPN(TokenParsingUtils.getBPNFromToken(authentication)) .build(); return ResponseEntity.status(HttpStatus.OK).body(issuersCredentialService.getCredentials(command)); } @@ -122,31 +117,36 @@ public ResponseEntity> getCredentials(@Parameter(n /** * Credentials validation response entity. * - * @param credentialVerificationRequest the request - * @param withCredentialExpiryDate the with credential expiry date + * @param credentialVerificationRequest the request + * @param withCredentialExpiryDate the with credential expiry date * @return the response entity */ @PostMapping(path = RestURI.CREDENTIALS_VALIDATION, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @ValidateVerifiableCredentialApiDocs public ResponseEntity> credentialsValidation(@RequestBody CredentialVerificationRequest credentialVerificationRequest, - @Parameter(description = "Check expiry of VC") @RequestParam(name = "withCredentialExpiryDate", defaultValue = "false", required = false) boolean withCredentialExpiryDate) { + @Parameter(description = "Check expiry of VC") @RequestParam(name = "withCredentialExpiryDate", defaultValue = "false", required = false) boolean withCredentialExpiryDate, + @Parameter(hidden = true) @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token) { log.debug("Received request to validate verifiable credentials"); - return ResponseEntity.status(HttpStatus.OK).body(issuersCredentialService.credentialsValidation(credentialVerificationRequest, withCredentialExpiryDate)); + return ResponseEntity.status(HttpStatus.OK).body(issuersCredentialService.credentialsValidation(credentialVerificationRequest, withCredentialExpiryDate, token)); } /** * Issue credential response entity. * - * @param holderDid the holder did - * @param data the data - * @param principal the principal + * @param holderDid the holder did + * @param data the data + * @param authentication the authentication * @return the response entity */ @PostMapping(path = RestURI.ISSUERS_CREDENTIALS, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @IssueVerifiableCredentialUsingBaseWalletApiDocs - public ResponseEntity issueCredentialUsingBaseWallet(@Parameter(description = "Holder DID", examples = {@ExampleObject(description = "did", name = "did", value = "did:web:localhost:BPNL000000000000")}) @RequestParam(name = "holderDid") String holderDid, @RequestBody Map data, Principal principal, - @AsJwtParam @RequestParam(name = StringPool.AS_JWT, defaultValue = "false") boolean asJwt) { - log.debug("Received request to issue verifiable credential. BPN: {}", getBPNFromToken(principal)); - return ResponseEntity.status(HttpStatus.CREATED).body(issuersCredentialService.issueCredentialUsingBaseWallet(holderDid, data, asJwt, getBPNFromToken(principal))); + + + public ResponseEntity issueCredentialUsingBaseWallet(@Parameter(description = "Holder DID", examples = { @ExampleObject(description = "did", name = "did", value = "did:web:localhost:BPNL000000000000") }) @RequestParam(name = "holderDid") String holderDid, @RequestBody Map data, Authentication authentication, + @AsJwtParam @RequestParam(name = StringPool.AS_JWT, defaultValue = "true") boolean asJwt, + @RevocationAPIDoc.Revocable @RequestParam(name = StringPool.REVOCABLE, defaultValue = "true") boolean revocable, + @Parameter(hidden = true) @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token) { + log.debug("Received request to issue verifiable credential. BPN: {}", TokenParsingUtils.getBPNFromToken(authentication)); + return ResponseEntity.status(HttpStatus.CREATED).body(issuersCredentialService.issueCredentialUsingBaseWallet(holderDid, data, asJwt, revocable, TokenParsingUtils.getBPNFromToken(authentication), token)); } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java index 1a2f7d878..ebde65f60 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java @@ -23,27 +23,35 @@ import com.nimbusds.jwt.SignedJWT; import io.swagger.v3.oas.annotations.Parameter; +import liquibase.util.StringUtil; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.managedidentitywallets.apidocs.PresentationControllerApiDocs.GetVerifiablePresentationIATPApiDocs; import org.eclipse.tractusx.managedidentitywallets.apidocs.PresentationControllerApiDocs.PostVerifiablePresentationApiDocs; import org.eclipse.tractusx.managedidentitywallets.apidocs.PresentationControllerApiDocs.PostVerifiablePresentationValidationApiDocs; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; +import org.eclipse.tractusx.managedidentitywallets.dto.PresentationResponseMessage; +import org.eclipse.tractusx.managedidentitywallets.reader.TractusXPresentationRequestReader; import org.eclipse.tractusx.managedidentitywallets.service.PresentationService; +import org.eclipse.tractusx.managedidentitywallets.service.STSTokenValidationService; +import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.security.Principal; +import java.io.InputStream; +import java.util.List; import java.util.Map; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getAccessToken; +import static org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils.getAccessToken; /** * The type Presentation controller. @@ -51,27 +59,31 @@ @RestController @RequiredArgsConstructor @Slf4j -public class PresentationController extends BaseController { +public class PresentationController { private final PresentationService presentationService; + private final TractusXPresentationRequestReader presentationRequestReader; + + private final STSTokenValidationService validationService; + /** * Create presentation response entity. * - * @param data the data - * @param audience the audience - * @param asJwt the as jwt - * @param principal the principal + * @param data the data + * @param audience the audience + * @param asJwt the as jwt + * @param authentication the authentication * @return the response entity */ @PostVerifiablePresentationApiDocs @PostMapping(path = RestURI.API_PRESENTATIONS, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> createPresentation(@RequestBody Map data, @RequestParam(name = "audience", required = false) String audience, - @RequestParam(name = "asJwt", required = false, defaultValue = "false") boolean asJwt, Principal principal + @RequestParam(name = "asJwt", required = false, defaultValue = "true") boolean asJwt, Authentication authentication ) { - log.debug("Received request to create presentation. BPN: {}", getBPNFromToken(principal)); - return ResponseEntity.status(HttpStatus.CREATED).body(presentationService.createPresentation(data, asJwt, audience, getBPNFromToken(principal))); + log.debug("Received request to create presentation. BPN: {}", TokenParsingUtils.getBPNFromToken(authentication)); + return ResponseEntity.status(HttpStatus.CREATED).body(presentationService.createPresentation(data, asJwt, audience, TokenParsingUtils.getBPNFromToken(authentication))); } /** @@ -87,7 +99,7 @@ public ResponseEntity> createPresentation(@RequestBody Map> validatePresentation(@RequestBody Map data, @Parameter(description = "Audience to validate in VP (Only supported in case of JWT formatted VP)") @RequestParam(name = "audience", required = false) String audience, - @Parameter(description = "Pass true in case of VP is in JWT format") @RequestParam(name = "asJwt", required = false, defaultValue = "false") boolean asJwt, + @Parameter(description = "Pass true in case of VP is in JWT format") @RequestParam(name = "asJwt", required = false, defaultValue = "true") boolean asJwt, @Parameter(description = "Check expiry of VC(Only supported in case of JWT formatted VP)") @RequestParam(name = "withCredentialExpiryDate", required = false, defaultValue = "false") boolean withCredentialExpiryDate ) { log.debug("Received request to validate presentation"); @@ -97,17 +109,59 @@ public ResponseEntity> validatePresentation(@RequestBody Map /** * Create presentation response entity for VC types provided in STS token. * - * @param stsToken the STS token with required scopes - * @param asJwt as JWT VP response + * @param stsToken the STS token with required scopes + * @param asJwt as JWT VP response * @return the VP response entity */ - @GetMapping(path = RestURI.API_PRESENTATIONS_IATP, produces = { MediaType.APPLICATION_JSON_VALUE }) + + @PostMapping(path = { RestURI.API_PRESENTATIONS_IATP, RestURI.API_PRESENTATIONS_IATP_WORKAROUND }, produces = { MediaType.APPLICATION_JSON_VALUE }) @GetVerifiablePresentationIATPApiDocs - public ResponseEntity> createPresentation(@Parameter(hidden = true) @RequestHeader(name = "Authorization") String stsToken, - @RequestParam(name = "asJwt", required = false, defaultValue = "false") boolean asJwt) { - SignedJWT accessToken = getAccessToken(stsToken); - Map vp = presentationService.createVpWithRequiredScopes(accessToken, asJwt); - return ResponseEntity.ok(vp); + @SneakyThrows + public ResponseEntity createPresentation( + /* As filters are disabled for this endpoint set required to false and handle missing token manually */ + @Parameter(hidden = true) @RequestHeader(name = "Authorization", required = false) String stsToken, + @RequestParam(name = "asJwt", required = false, defaultValue = "true") boolean asJwt, + InputStream is) { + try { + + if (stsToken == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + if (stsToken.startsWith("Bearer ")) { + stsToken = stsToken.substring("Bearer ".length()); + } + + var validationResult = validationService.validateToken(stsToken); + if (!validationResult.isValid()) { + log.atDebug().log("Unauthorized request. Errors: '%s'".formatted( + StringUtil.join(validationResult.getErrors().stream() + .map(Enum::name) + .toList(), + ", "))); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + // requested scopes are ignored for now + final List requestedScopes = presentationRequestReader.readVerifiableCredentialScopes(is); + + SignedJWT accessToken = getAccessToken(stsToken); + + if(asJwt) { + Map map = presentationService.createVpWithRequiredScopes(accessToken, true); + String verifiablePresentation = (String) map.get("vp"); + PresentationResponseMessage message = new PresentationResponseMessage(verifiablePresentation); + return ResponseEntity.ok(message); + } else { + Map map = presentationService.createVpWithRequiredScopes(accessToken, false); + VerifiablePresentation verifiablePresentation = new VerifiablePresentation((Map) map.get("vp")); + PresentationResponseMessage message = new PresentationResponseMessage(verifiablePresentation); + return ResponseEntity.ok(message); + } + + } catch (TractusXPresentationRequestReader.InvalidPresentationQueryMessageResource e) { + return ResponseEntity.badRequest().build(); + } } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/RevocationController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/RevocationController.java new file mode 100644 index 000000000..5227f9e26 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/RevocationController.java @@ -0,0 +1,76 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.controller; + + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.managedidentitywallets.apidocs.IssuersCredentialControllerApiDocs; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils; +import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; +import org.eclipse.tractusx.managedidentitywallets.dto.CredentialVerificationRequest; +import org.eclipse.tractusx.managedidentitywallets.service.revocation.RevocationService; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * The type Revocation controller. + */ +@RestController +@Slf4j +@RequiredArgsConstructor +@Tag(name = "Verifiable Credential - Revoke") +public class RevocationController { + + + private final RevocationService revocationService; + + + /** + * Revoke credential . + * + * @param credentialVerificationRequest the credential verification request + * @param token the token + * @param principal the principal + * @return the response entity + */ + @PutMapping(path = RestURI.CREDENTIALS_REVOKE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @IssuersCredentialControllerApiDocs.ValidateVerifiableCredentialApiDocs + public ResponseEntity> revokeCredential(@RequestBody CredentialVerificationRequest credentialVerificationRequest, + @Parameter(hidden = true) @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token, Authentication authentication) { + revocationService.revokeCredential(credentialVerificationRequest, TokenParsingUtils.getBPNFromToken(authentication), token); + return ResponseEntity.status(HttpStatus.OK).body(Map.of("message", "Credential has been revoked")); + + } + +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/SecureTokenController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/SecureTokenController.java index 3cfd78657..641378a3a 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/SecureTokenController.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/SecureTokenController.java @@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.tractusx.managedidentitywallets.apidocs.SecureTokenControllerApiDoc; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; import org.eclipse.tractusx.managedidentitywallets.domain.BusinessPartnerNumber; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/WalletController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/WalletController.java index fb41bdf03..128b29daa 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/WalletController.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/WalletController.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -25,7 +25,6 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; - import org.eclipse.tractusx.managedidentitywallets.apidocs.DidDocumentControllerApiDocs.DidOrBpnParameterDoc; import org.eclipse.tractusx.managedidentitywallets.apidocs.WalletControllerApiDocs.CreateWalletApiDoc; import org.eclipse.tractusx.managedidentitywallets.apidocs.WalletControllerApiDocs.PageNumberParameterDoc; @@ -35,6 +34,7 @@ import org.eclipse.tractusx.managedidentitywallets.apidocs.WalletControllerApiDocs.SortColumnParameterDoc; import org.eclipse.tractusx.managedidentitywallets.apidocs.WalletControllerApiDocs.SortTypeParameterDoc; import org.eclipse.tractusx.managedidentitywallets.apidocs.WalletControllerApiDocs.StoreVerifiableCredentialApiDoc; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dto.CreateWalletRequest; @@ -43,9 +43,14 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; -import java.security.Principal; import java.util.Map; /** @@ -55,7 +60,7 @@ @RequiredArgsConstructor @Slf4j @Tag(name = "Wallets") -public class WalletController extends BaseController { +public class WalletController { private final WalletService service; @@ -67,9 +72,9 @@ public class WalletController extends BaseController { */ @CreateWalletApiDoc @PostMapping(path = RestURI.WALLETS, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity createWallet(@Valid @RequestBody CreateWalletRequest request, Principal principal) { - log.debug("Received request to create wallet with BPN {}. authorized by BPN: {}", request.getBusinessPartnerNumber(), getBPNFromToken(principal)); - return ResponseEntity.status(HttpStatus.CREATED).body(service.createWallet(request, getBPNFromToken(principal))); + public ResponseEntity createWallet(@Valid @RequestBody CreateWalletRequest request, Authentication authentication) { + log.debug("Received request to create wallet with BPN {}. authorized by BPN: {}", request.getBusinessPartnerNumber(), TokenParsingUtils.getBPNFromToken(authentication)); + return ResponseEntity.status(HttpStatus.CREATED).body(service.createWallet(request, TokenParsingUtils.getBPNFromToken(authentication))); } /** @@ -82,9 +87,9 @@ public ResponseEntity createWallet(@Valid @RequestBody CreateWalletReque @StoreVerifiableCredentialApiDoc @PostMapping(path = RestURI.API_WALLETS_IDENTIFIER_CREDENTIALS, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity> storeCredential(@RequestBody Map data, - @DidOrBpnParameterDoc @PathVariable(name = "identifier") String identifier, Principal principal) { - log.debug("Received request to store credential in wallet with identifier {}. authorized by BPN: {}", identifier, getBPNFromToken(principal)); - return ResponseEntity.status(HttpStatus.CREATED).body(service.storeCredential(data, identifier, getBPNFromToken(principal))); + @DidOrBpnParameterDoc @PathVariable(name = "identifier") String identifier, Authentication authentication) { + log.debug("Received request to store credential in wallet with identifier {}. authorized by BPN: {}", identifier, TokenParsingUtils.getBPNFromToken(authentication)); + return ResponseEntity.status(HttpStatus.CREATED).body(service.storeCredential(data, identifier, TokenParsingUtils.getBPNFromToken(authentication))); } /** @@ -96,11 +101,11 @@ public ResponseEntity> storeCredential(@RequestBody Map getWalletByIdentifier( @DidOrBpnParameterDoc @PathVariable(name = "identifier") String identifier, + public ResponseEntity getWalletByIdentifier(@DidOrBpnParameterDoc @PathVariable(name = "identifier") String identifier, @RequestParam(name = "withCredentials", defaultValue = "false") boolean withCredentials, - Principal principal) { - log.debug("Received request to retrieve wallet with identifier {}. authorized by BPN: {}", identifier, getBPNFromToken(principal)); - return ResponseEntity.status(HttpStatus.OK).body(service.getWalletByIdentifier(identifier, withCredentials, getBPNFromToken(principal))); + Authentication authentication) { + log.debug("Received request to retrieve wallet with identifier {}. authorized by BPN: {}", identifier, TokenParsingUtils.getBPNFromToken(authentication)); + return ResponseEntity.status(HttpStatus.OK).body(service.getWalletByIdentifier(identifier, withCredentials, TokenParsingUtils.getBPNFromToken(authentication))); } /** diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/entity/MIWBaseEntity.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/entity/MIWBaseEntity.java index 2a8a58640..850bda66b 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/entity/MIWBaseEntity.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/entity/MIWBaseEntity.java @@ -22,7 +22,7 @@ package org.eclipse.tractusx.managedidentitywallets.dao.entity; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.smartsensesolutions.java.commons.base.entity.BaseEntity; +import com.smartsensesolutions.commons.dao.base.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.MappedSuperclass; import jakarta.persistence.Temporal; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/HoldersCredentialRepository.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/HoldersCredentialRepository.java index f3459d9eb..8e9c97020 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/HoldersCredentialRepository.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/HoldersCredentialRepository.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.managedidentitywallets.dao.repository; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseRepository; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.springframework.data.jpa.repository.Query; @@ -58,24 +58,4 @@ public interface HoldersCredentialRepository extends BaseRepository getByHolderDidAndType(String holderDid, String type); - - List getByHolderDidAndIssuerDidAndTypeAndStored(String holderDid, String issuerDid, String type, boolean stored); - - /** - * Exists by holder did and type boolean. - * - * @param holderDid the holder did - * @param type the type - * @return the boolean - */ - boolean existsByHolderDidAndType(String holderDid, String type); - - /** - * Exists by holder did and credential id boolean. - * - * @param holderDid the holder did - * @param credentialId the credential id - * @return the boolean - */ - boolean existsByHolderDidAndCredentialId(String holderDid, String credentialId); } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/IssuersCredentialRepository.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/IssuersCredentialRepository.java index 8b512b253..92b307887 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/IssuersCredentialRepository.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/IssuersCredentialRepository.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.managedidentitywallets.dao.repository; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseRepository; import org.eclipse.tractusx.managedidentitywallets.dao.entity.IssuersCredential; import java.util.List; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/JtiRepository.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/JtiRepository.java index 69c83a4f9..0079fee1a 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/JtiRepository.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/JtiRepository.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.managedidentitywallets.dao.repository; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseRepository; import org.eclipse.tractusx.managedidentitywallets.dao.entity.JtiRecord; import org.springframework.stereotype.Repository; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/WalletKeyRepository.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/WalletKeyRepository.java index 54196d73b..4c403bf0e 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/WalletKeyRepository.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/WalletKeyRepository.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.managedidentitywallets.dao.repository; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseRepository; import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; import org.springframework.stereotype.Repository; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/WalletRepository.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/WalletRepository.java index 4020d0044..d23529e60 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/WalletRepository.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dao/repository/WalletRepository.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.managedidentitywallets.dao.repository; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseRepository; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.springframework.stereotype.Repository; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/domain/CredentialCreationConfig.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/domain/CredentialCreationConfig.java index f75ae464b..0661ae1c1 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/domain/CredentialCreationConfig.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/domain/CredentialCreationConfig.java @@ -25,7 +25,7 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; import org.eclipse.tractusx.ssi.lib.model.did.DidDocument; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialStatus; @@ -65,6 +65,8 @@ public class CredentialCreationConfig { private boolean selfIssued; + private boolean revocable; + // this will be used by the DB-Impl of storage to retrieve privateKey @NonNull private String keyName; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/domain/PresentationCreationConfig.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/domain/PresentationCreationConfig.java index 0ff581e0a..b2479f4a7 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/domain/PresentationCreationConfig.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/domain/PresentationCreationConfig.java @@ -24,7 +24,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NonNull; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; import org.eclipse.tractusx.ssi.lib.model.did.Did; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CreateWalletRequest.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CreateWalletRequest.java index 4d5b253d6..b2038989e 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CreateWalletRequest.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CreateWalletRequest.java @@ -29,7 +29,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.domain.SigningServiceType; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CredentialVerificationRequest.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CredentialVerificationRequest.java index c4f9dd4a2..73d15872a 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CredentialVerificationRequest.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CredentialVerificationRequest.java @@ -21,11 +21,11 @@ package org.eclipse.tractusx.managedidentitywallets.dto; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import java.util.LinkedHashMap; import java.util.Map; - + public class CredentialVerificationRequest extends LinkedHashMap { diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CredentialsResponse.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CredentialsResponse.java index d90d7e445..d1dc365ae 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CredentialsResponse.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/CredentialsResponse.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.managedidentitywallets.dto; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import java.util.LinkedHashMap; import java.util.Map; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseMessage.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseMessage.java new file mode 100644 index 000000000..33326fbc9 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseMessage.java @@ -0,0 +1,60 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; + +import java.util.List; + +/** + * Class to represent the response message of a presentation request. + * Defined in JsonLD Tractus-X context.json. + *

+ * As `presentationSubmission` a not well-defined, we will just skip the property for HTTP responses. Defining all types as 'Json' make the whole idea of using Json-Linked-Data a waste of time, but ok. + *

+ * The `presentation` property is only specified as 'Json'. For this implementation we will assume these are Presentations from ether the Verifiable Credential Data Model v1.1 or Verifiable Credential Data Model v2.0. + *
+ * At the same time other applications require the Verifiable Presentation to be a Json Web Token. As this protocol is not able to define a good data type, and implementations of this protocol even require different types, object is the correct data type here... + */ +@Getter +public class PresentationResponseMessage { + + + public PresentationResponseMessage(Object verifiablePresentation) { + this(List.of(verifiablePresentation)); + } + + public PresentationResponseMessage(List verifiablePresentations) { + this.verifiablePresentations = verifiablePresentations; + } + + @JsonProperty("@context") + private List contexts = List.of("https://w3id.org/tractusx-trust/v0.8"); + + @JsonProperty("@type") + private List types = List.of("PresentationResponseMessage"); + + @JsonProperty("presentation") + private List verifiablePresentations; +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/StatusListRequest.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/StatusListRequest.java new file mode 100644 index 000000000..2c6c2e3f8 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/StatusListRequest.java @@ -0,0 +1,37 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.dto; + + +import lombok.*; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class StatusListRequest { + + private String issuerId; + + private String purpose; +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/ValidationResult.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/ValidationResult.java index 8929fada7..97700284d 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/ValidationResult.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/ValidationResult.java @@ -26,7 +26,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.TokenValidationErrors; import java.util.List; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/RevocationException.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/RevocationException.java new file mode 100644 index 000000000..9b11bbf87 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/RevocationException.java @@ -0,0 +1,53 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.exception; + +import lombok.Getter; + +import java.util.Map; + +/** + * The type Revocation problem. + */ +@Getter +public class RevocationException extends RuntimeException { + + + private final int status; + + private final String message; + + private final Map details; + + /** + * Instantiates a new Revocation problem. + * + * @param status the status + * @param message the message + */ + public RevocationException(int status, String message, Map details) { + super(message); + this.status = status; + this.message = message; + this.details = details; + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXJsonLdReader.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXJsonLdReader.java new file mode 100644 index 000000000..075c046ab --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXJsonLdReader.java @@ -0,0 +1,79 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.reader; + +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.JsonLdOptions; +import com.apicatalog.jsonld.document.JsonDocument; +import com.apicatalog.jsonld.processor.ExpansionProcessor; +import jakarta.json.JsonArray; +import lombok.NonNull; +import org.eclipse.tractusx.managedidentitywallets.utils.ResourceUtil; +import org.eclipse.tractusx.ssi.lib.model.RemoteDocumentLoader; + +import java.io.InputStream; +import java.net.URI; +import java.util.Map; + +public class TractusXJsonLdReader { + + private static final String TRACTUS_X_CONTEXT_RESOURCE = "jsonld/IdentityMinusTrust.json"; + private static final URI TRACTUS_X_CONTEXT = URI.create("https://w3id.org/tractusx-trust/v0.8"); + private static final URI IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_CONTEXT = URI.create("https://identity.foundation/presentation-exchange/submission/v1"); + private static final String IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_RESOURCE = "jsonld/identity.foundation.presentation-exchange.submission.v1.json"; + + + private final RemoteDocumentLoader documentLoader = RemoteDocumentLoader.DOCUMENT_LOADER; + + public TractusXJsonLdReader() { + + documentLoader.setEnableLocalCache(true); + + if (!documentLoader.getLocalCache().containsKey(TRACTUS_X_CONTEXT)) { + cacheOfflineResource(TRACTUS_X_CONTEXT_RESOURCE, TRACTUS_X_CONTEXT); + } + if (!documentLoader.getLocalCache().containsKey(IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_CONTEXT)) { + cacheOfflineResource(IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_RESOURCE, IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_CONTEXT); + } + } + + public JsonArray expand(@NonNull final InputStream documentStream) throws JsonLdError { + + final JsonLdOptions jsonLdOptions = new JsonLdOptions(); + jsonLdOptions.setDocumentLoader(documentLoader); + + final JsonDocument document = JsonDocument.of(com.apicatalog.jsonld.http.media.MediaType.JSON_LD, documentStream); + return ExpansionProcessor.expand(document, jsonLdOptions, false); + } + + private void cacheOfflineResource(final String resource, final URI context) { + try { + final InputStream resourceStream = ResourceUtil.getResourceStream(resource); + final JsonDocument identityMinusTrustDocument; + identityMinusTrustDocument = JsonDocument.of(com.apicatalog.jsonld.http.media.MediaType.JSON_LD, resourceStream); + documentLoader.getLocalCache().put(context, identityMinusTrustDocument); + } catch (JsonLdError e) { + // If this ever fails, it is a programming error. Loading of the embedded context resource is checked by Unit Tests. + throw new RuntimeException("Could not parse Tractus-X JsonL-d context from resource. This should never happen. Resource: '%s'".formatted(resource), e); + } + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXPresentationRequestReader.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXPresentationRequestReader.java new file mode 100644 index 000000000..502bdeb3b --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXPresentationRequestReader.java @@ -0,0 +1,85 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.reader; + +import com.apicatalog.jsonld.JsonLdError; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.InputStream; +import java.util.List; + +@Slf4j +@Component +public class TractusXPresentationRequestReader extends TractusXJsonLdReader { + + private static final String JSON_LD_TYPE = "@type"; + private static final String JSON_LD_VALUE = "@value"; + private static final String TRACTUS_X_PRESENTATION_QUERY_MESSAGE_TYPE = "https://w3id.org/tractusx-trust/v0.8/PresentationQueryMessage"; + private static final String TRACTUS_X_SCOPE_TYPE = "https://w3id.org/tractusx-trust/v0.8/scope"; + + public List readVerifiableCredentialScopes(InputStream is) throws InvalidPresentationQueryMessageResource { + try { + + final JsonArray jsonArray = expand(is); + + if (jsonArray.size() != 1) { + log.atDebug().addArgument(jsonArray::toString).log("Expanded JSON-LD: {}"); + throw new InvalidPresentationQueryMessageResource("Expected a single JSON object. Found %d".formatted(jsonArray.size())); + } + + var jsonObject = jsonArray.getJsonObject(0); + + final JsonArray typeArray = jsonObject.getJsonArray(JSON_LD_TYPE); + final List types = typeArray.getValuesAs(JsonString.class).stream().map(JsonString::getString).toList(); + if (!types.contains(TRACTUS_X_PRESENTATION_QUERY_MESSAGE_TYPE)) { + log.atDebug().addArgument(jsonArray::toString).log("Expanded JSON-LD: {}"); + throw new InvalidPresentationQueryMessageResource("Unexpected type. Expected %s".formatted(TRACTUS_X_PRESENTATION_QUERY_MESSAGE_TYPE)); + } + + final JsonArray scopes = jsonObject.getJsonArray(TRACTUS_X_SCOPE_TYPE); + return scopes.getValuesAs(JsonObject.class) + .stream() + .map(o -> o.getJsonString(JSON_LD_VALUE)) + .map(JsonString::getString) + .toList(); + + } catch (JsonLdError e) { + throw new InvalidPresentationQueryMessageResource(e); + } + } + + public static class InvalidPresentationQueryMessageResource extends Exception { + public InvalidPresentationQueryMessageResource(String message) { + super(message); + } + + public InvalidPresentationQueryMessageResource(Throwable cause) { + super(cause); + } + } + +} + diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/RevocationClient.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/RevocationClient.java new file mode 100644 index 000000000..6d27e8afd --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/RevocationClient.java @@ -0,0 +1,95 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation; + +import io.swagger.v3.oas.annotations.Parameter; +import org.eclipse.tractusx.managedidentitywallets.dto.StatusListRequest; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialStatus; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; + +import java.util.Map; + +/** + * The interface Revocation client. + */ +@FeignClient(value = "RevocationService", url = "${miw.revocation.url}", configuration = RevocationClientConfig.class) +public interface RevocationClient { + + /** + * Gets status list credential. + * + * @param issuerBpn the issuer BPN + * @param status the status + * @param index the index + * @param token the token + * @return the status list credential + */ + @GetMapping(path = "/api/v1/revocations/credentials/{issuerBpn}/{status}/{index}", produces = MediaType.APPLICATION_JSON_VALUE) + VerifiableCredential getStatusListCredential(@PathVariable(name = "issuerBpn") String issuerBpn, + @PathVariable(name = "status") String status, + @PathVariable(name = "index") String index, + @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token); + + + /** + * Gets status list entry. + * + * @param statusListRequest the status list request + * @param token the token + * @return the status list entry + */ + @PostMapping(path = "/api/v1/revocations/status-entry", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + Map getStatusListEntry(@RequestBody StatusListRequest statusListRequest, + @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token); + + + /** + * Revoke credential. + * + * @param verifiableCredentialStatus the verifiable credential status + * @param token the token + */ + @PostMapping(path = "/api/v1/revocations/revoke", consumes = MediaType.APPLICATION_JSON_VALUE) + void revokeCredential(@RequestBody VerifiableCredentialStatus verifiableCredentialStatus, + @Parameter(hidden = true) @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token); + + + /** + * Verify credential status map. + * + * @param verifiableCredentialStatus the verifiable credential status + * @param token the token + * @return the map + */ + @PostMapping(path = "/api/v1/revocations/verify", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + Map verifyCredentialStatus(@RequestBody VerifiableCredentialStatus verifiableCredentialStatus, + @Parameter(hidden = true) @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token); + +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/RevocationClientConfig.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/RevocationClientConfig.java new file mode 100644 index 000000000..b440bc8a8 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/RevocationClientConfig.java @@ -0,0 +1,43 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation; + +import feign.codec.ErrorDecoder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * The type Revocation client config. + */ +@Configuration +public class RevocationClientConfig { + + /** + * Error decoder error decoder. + * + * @return the error decoder + */ + @Bean + public ErrorDecoder errorDecoder() { + return new RevocationErrorDecoder(); + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/RevocationErrorDecoder.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/RevocationErrorDecoder.java new file mode 100644 index 000000000..572f1a22d --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/RevocationErrorDecoder.java @@ -0,0 +1,62 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.Response; +import feign.codec.ErrorDecoder; +import lombok.SneakyThrows; +import org.eclipse.tractusx.managedidentitywallets.exception.RevocationException; +import org.springframework.http.HttpStatus; +import org.springframework.util.StreamUtils; + +import java.nio.charset.Charset; +import java.util.Map; + +/** + * The type Revocation error decoder. + */ +public class RevocationErrorDecoder implements ErrorDecoder { + @SneakyThrows + @Override + public Exception decode(String methodKey, Response response) { + Response.Body responseBody = response.body(); + HttpStatus responseStatus = HttpStatus.valueOf(response.status()); + if (responseBody != null && response.body() != null) { + String data = StreamUtils.copyToString(response.body().asInputStream(), Charset.defaultCharset()); + if (responseStatus.value() == HttpStatus.CONFLICT.value()) { + ObjectMapper objectMapper = new ObjectMapper(); + Map map = objectMapper.readValue(data, Map.class); + if (map.containsKey("detail")) { + throw new RevocationException(responseStatus.value(), map.get("detail").toString(), map); + } else { + throw new RevocationException(responseStatus.value(), data, map); + } + } else { + throw new RevocationException(responseStatus.value(), data, Map.of()); + + } + } else { + throw new RevocationException(responseStatus.value(), "Error in revocation service", Map.of()); + } + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/CommonService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/CommonService.java index 1acf32154..8e6228c37 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/CommonService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/CommonService.java @@ -23,12 +23,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; import org.eclipse.tractusx.managedidentitywallets.exception.WalletNotFoundProblem; import org.eclipse.tractusx.managedidentitywallets.utils.CommonUtils; -import org.eclipse.tractusx.managedidentitywallets.utils.Validate; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.springframework.stereotype.Service; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/DidDocumentResolverService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/DidDocumentResolverService.java index 3ffcccf86..eccb6b11c 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/DidDocumentResolverService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/DidDocumentResolverService.java @@ -33,7 +33,7 @@ @Service public class DidDocumentResolverService { - final static HttpClient httpClient = HttpClient.newHttpClient(); + private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient(); @Getter private final DidWebResolver didDocumentResolverRegistry; @@ -47,10 +47,10 @@ public DidDocumentResolverService(MIWSettings miwSettings) { final DidWebParser didParser = new DidWebParser(); didDocumentResolverRegistry = - new DidWebResolver(httpClient, didParser, enforceHttps); + new DidWebResolver(HTTP_CLIENT, didParser, enforceHttps); compositeDidResolver = new CompositeDidResolver( - new DidWebResolver(httpClient, didParser, enforceHttps) + new DidWebResolver(HTTP_CLIENT, didParser, enforceHttps) ); } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/HoldersCredentialService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/HoldersCredentialService.java index a69ad4659..5de2569d6 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/HoldersCredentialService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/HoldersCredentialService.java @@ -21,20 +21,21 @@ package org.eclipse.tractusx.managedidentitywallets.service; -import com.smartsensesolutions.java.commons.FilterRequest; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; -import com.smartsensesolutions.java.commons.base.service.BaseService; -import com.smartsensesolutions.java.commons.criteria.CriteriaOperator; -import com.smartsensesolutions.java.commons.operator.Operator; -import com.smartsensesolutions.java.commons.sort.Sort; -import com.smartsensesolutions.java.commons.sort.SortType; -import com.smartsensesolutions.java.commons.specification.SpecificationUtil; +import com.smartsensesolutions.commons.dao.base.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseService; +import com.smartsensesolutions.commons.dao.filter.FilterRequest; +import com.smartsensesolutions.commons.dao.filter.sort.Sort; +import com.smartsensesolutions.commons.dao.filter.sort.SortType; +import com.smartsensesolutions.commons.dao.operator.Operator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.text.StringEscapeUtils; import org.eclipse.tractusx.managedidentitywallets.command.GetCredentialsCommand; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; +import org.eclipse.tractusx.managedidentitywallets.config.RevocationSettings; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.repository.HoldersCredentialRepository; @@ -42,19 +43,19 @@ import org.eclipse.tractusx.managedidentitywallets.domain.SigningServiceType; import org.eclipse.tractusx.managedidentitywallets.domain.VerifiableEncoding; import org.eclipse.tractusx.managedidentitywallets.dto.CredentialsResponse; -import org.eclipse.tractusx.managedidentitywallets.exception.CredentialNotFoundProblem; -import org.eclipse.tractusx.managedidentitywallets.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.service.revocation.RevocationService; import org.eclipse.tractusx.managedidentitywallets.signing.SignerResult; import org.eclipse.tractusx.managedidentitywallets.signing.SigningService; import org.eclipse.tractusx.managedidentitywallets.utils.CommonUtils; -import org.eclipse.tractusx.managedidentitywallets.utils.Validate; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialStatusList2021Entry; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import java.net.URI; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -77,21 +78,18 @@ public class HoldersCredentialService extends BaseService credentialSpecificationUtil; - private final Map availableSigningServices; + private final RevocationService revocationService; + + private final RevocationSettings revocationSettings; + @Override protected BaseRepository getRepository() { return holdersCredentialRepository; } - @Override - protected SpecificationUtil getSpecificationUtil() { - return credentialSpecificationUtil; - } - /** * Gets credentials. @@ -116,21 +114,17 @@ public PageImpl getCredentials(GetCredentialsCommand comman if (StringUtils.hasText(command.getCredentialId())) { filterRequest.appendCriteria(StringPool.CREDENTIAL_ID, Operator.EQUALS, command.getCredentialId()); } - FilterRequest request = new FilterRequest(); if (!CollectionUtils.isEmpty(command.getType())) { - request.setPage(filterRequest.getPage()); - request.setSize(filterRequest.getSize()); - request.setCriteriaOperator(CriteriaOperator.OR); for (String str : command.getType()) { - request.appendCriteria(StringPool.TYPE, Operator.CONTAIN, str); + filterRequest.appendOrCriteria(StringPool.TYPE, Operator.CONTAIN, str); } } Sort sort = new Sort(); sort.setColumn(command.getSortColumn()); sort.setSortType(SortType.valueOf(command.getSortType().toUpperCase())); - filterRequest.setSort(sort); - Page filter = filter(filterRequest, request, CriteriaOperator.AND); + filterRequest.setSort(List.of(sort)); + Page filter = filter(filterRequest); List list = new ArrayList<>(filter.getContent().size()); @@ -172,7 +166,7 @@ public PageImpl getCredentials(GetCredentialsCommand comman * @param asJwt the as jwt * @return the verifiable credential */ - public CredentialsResponse issueCredential(Map data, String callerBpn, boolean asJwt) { + public CredentialsResponse issueCredential(Map data, String callerBpn, boolean asJwt, boolean revocable, String token) { VerifiableCredential verifiableCredential = new VerifiableCredential(data); Wallet issuerWallet = commonService.getWalletByIdentifier(verifiableCredential.getIssuer().toString()); @@ -185,7 +179,7 @@ public CredentialsResponse issueCredential(Map data, String call expiryDate = Date.from(verifiableCredential.getExpirationDate()); } - CredentialCreationConfig holdersCredentialCreationConfig = CredentialCreationConfig.builder() + CredentialCreationConfig.CredentialCreationConfigBuilder builder = CredentialCreationConfig.builder() .encoding(VerifiableEncoding.JSON_LD) .subject(verifiableCredential.getCredentialSubject().get(0)) .types(verifiableCredential.getTypes()) @@ -194,12 +188,27 @@ public CredentialsResponse issueCredential(Map data, String call .contexts(verifiableCredential.getContext()) .expiryDate(expiryDate) .selfIssued(true) + .revocable(revocable) .keyName(issuerWallet.getBpn()) - .algorithm(SupportedAlgorithms.valueOf(issuerWallet.getAlgorithm())) - .build(); + .algorithm(SupportedAlgorithms.valueOf(issuerWallet.getAlgorithm())); + + if (revocable) { + //get credential status in case of revocation + VerifiableCredentialStatusList2021Entry statusListEntry = revocationService.getStatusListEntry(issuerWallet.getBpn(), token); + builder.verifiableCredentialStatus(statusListEntry); + + //add revocation context if missing + List uris = verifiableCredential.getContext(); + if (!uris.contains(revocationSettings.statusList2021Context())) { + uris.add(revocationSettings.statusList2021Context()); + builder.contexts(uris); + } - // Create Credential + } + + CredentialCreationConfig holdersCredentialCreationConfig = builder.build(); + // Create Credential SignerResult signerResult = availableSigningServices.get(issuerWallet.getSigningServiceType()).createCredential(holdersCredentialCreationConfig); VerifiableCredential vc = (VerifiableCredential) signerResult.getJsonLd(); HoldersCredential credential = CommonUtils.convertVerifiableCredential(vc, holdersCredentialCreationConfig); @@ -224,8 +233,4 @@ public CredentialsResponse issueCredential(Map data, String call return cr; } - - private void isCredentialExistWithId(String holderDid, String credentialId) { - Validate.isFalse(holdersCredentialRepository.existsByHolderDidAndCredentialId(holderDid, credentialId)).launch(new CredentialNotFoundProblem("Credential ID: " + credentialId + " is not exists ")); - } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IdpAuthorization.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IdpAuthorization.java index 956a938cb..da0e819f9 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IdpAuthorization.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IdpAuthorization.java @@ -22,11 +22,12 @@ package org.eclipse.tractusx.managedidentitywallets.service; import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.config.security.SecurityConfigProperties; import org.eclipse.tractusx.managedidentitywallets.domain.IdpTokenResponse; import org.eclipse.tractusx.managedidentitywallets.dto.SecureTokenRequest; -import org.eclipse.tractusx.managedidentitywallets.exception.UnsupportedGrantTypeException; import org.eclipse.tractusx.managedidentitywallets.exception.InvalidIdpTokenResponseException; +import org.eclipse.tractusx.managedidentitywallets.exception.UnsupportedGrantTypeException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpHeaders; @@ -37,7 +38,6 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.CLIENT_CREDENTIALS; import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.CLIENT_ID; import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.CLIENT_SECRET; import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.GRANT_TYPE; @@ -64,11 +64,11 @@ public IdpAuthorization(final SecurityConfigProperties properties, final RestTem public IdpTokenResponse fromSecureTokenRequest(SecureTokenRequest secureTokenRequest) throws UnsupportedGrantTypeException, InvalidIdpTokenResponseException { // we're ignoring the input, but the protocol requires us to check. - if (!secureTokenRequest.getGrantType().equals(CLIENT_CREDENTIALS)) { + if (!secureTokenRequest.getGrantType().equals(StringPool.CLIENT_CREDENTIALS)) { throw new UnsupportedGrantTypeException("The provided 'grant_type' is not valid. Use 'client_credentials'."); } MultiValueMap tokenRequest = new LinkedMultiValueMap<>(); - tokenRequest.add(GRANT_TYPE, CLIENT_CREDENTIALS); + tokenRequest.add(GRANT_TYPE, StringPool.CLIENT_CREDENTIALS); tokenRequest.add(SCOPE, OPENID); tokenRequest.add(CLIENT_ID, secureTokenRequest.getClientId()); tokenRequest.add(CLIENT_SECRET, secureTokenRequest.getClientSecret()); diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialService.java index 8d6fb6d2b..6ed7a757f 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialService.java @@ -23,22 +23,24 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jwt.SignedJWT; -import com.smartsensesolutions.java.commons.FilterRequest; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; -import com.smartsensesolutions.java.commons.base.service.BaseService; -import com.smartsensesolutions.java.commons.criteria.CriteriaOperator; -import com.smartsensesolutions.java.commons.operator.Operator; -import com.smartsensesolutions.java.commons.sort.Sort; -import com.smartsensesolutions.java.commons.sort.SortType; -import com.smartsensesolutions.java.commons.specification.SpecificationUtil; +import com.smartsensesolutions.commons.dao.base.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseService; +import com.smartsensesolutions.commons.dao.filter.FilterRequest; +import com.smartsensesolutions.commons.dao.filter.sort.Sort; +import com.smartsensesolutions.commons.dao.filter.sort.SortType; +import com.smartsensesolutions.commons.dao.operator.Operator; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.text.StringEscapeUtils; import org.eclipse.tractusx.managedidentitywallets.command.GetCredentialsCommand; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.CredentialStatus; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.config.RevocationSettings; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.IssuersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; @@ -49,11 +51,10 @@ import org.eclipse.tractusx.managedidentitywallets.domain.VerifiableEncoding; import org.eclipse.tractusx.managedidentitywallets.dto.CredentialVerificationRequest; import org.eclipse.tractusx.managedidentitywallets.dto.CredentialsResponse; -import org.eclipse.tractusx.managedidentitywallets.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.service.revocation.RevocationService; import org.eclipse.tractusx.managedidentitywallets.signing.SignerResult; import org.eclipse.tractusx.managedidentitywallets.signing.SigningService; import org.eclipse.tractusx.managedidentitywallets.utils.CommonUtils; -import org.eclipse.tractusx.managedidentitywallets.utils.Validate; import org.eclipse.tractusx.ssi.lib.did.resolver.DidResolver; import org.eclipse.tractusx.ssi.lib.did.web.DidWebResolver; import org.eclipse.tractusx.ssi.lib.did.web.util.DidWebParser; @@ -61,6 +62,7 @@ import org.eclipse.tractusx.ssi.lib.jwt.SignedJwtValidator; import org.eclipse.tractusx.ssi.lib.jwt.SignedJwtVerifier; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialStatusList2021Entry; import org.eclipse.tractusx.ssi.lib.proof.LinkedDataProofValidation; import org.eclipse.tractusx.ssi.lib.serialization.SerializeUtil; import org.springframework.beans.factory.annotation.Autowired; @@ -74,12 +76,15 @@ import org.springframework.util.StringUtils; import java.io.IOException; +import java.net.URI; import java.net.http.HttpClient; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.TreeMap; /** @@ -98,8 +103,6 @@ public class IssuersCredentialService extends BaseService credentialSpecificationUtil; - private final HoldersCredentialRepository holdersCredentialRepository; private final CommonService commonService; @@ -108,17 +111,16 @@ public class IssuersCredentialService extends BaseService availableSigningServices; + private final RevocationService revocationService; + + private final RevocationSettings revocationSettings; + @Override protected BaseRepository getRepository() { return issuersCredentialRepository; } - @Override - protected SpecificationUtil getSpecificationUtil() { - return credentialSpecificationUtil; - } - /** * Gets credentials. @@ -143,21 +145,17 @@ public PageImpl getCredentials(GetCredentialsCommand comman if (StringUtils.hasText(command.getCredentialId())) { filterRequest.appendCriteria(StringPool.CREDENTIAL_ID, Operator.EQUALS, command.getCredentialId()); } - FilterRequest request = new FilterRequest(); if (!CollectionUtils.isEmpty(command.getType())) { - request.setPage(filterRequest.getPage()); - request.setSize(filterRequest.getSize()); - request.setCriteriaOperator(CriteriaOperator.OR); for (String str : command.getType()) { - request.appendCriteria(StringPool.TYPE, Operator.CONTAIN, str); + filterRequest.appendOrCriteria(StringPool.TYPE, Operator.CONTAIN, str); } } Sort sort = new Sort(); sort.setColumn(command.getSortColumn()); sort.setSortType(SortType.valueOf(command.getSortType().toUpperCase())); - filterRequest.setSort(sort); - Page filter = filter(filterRequest, request, CriteriaOperator.AND); + filterRequest.setSort(List.of(sort)); + Page filter = super.filter(filterRequest); List list = new ArrayList<>(filter.getContent().size()); @@ -190,18 +188,19 @@ public PageImpl getCredentials(GetCredentialsCommand comman } - /** * Issue credential using base wallet * * @param holderDid the holder did * @param data the data * @param asJwt the as jwt + * @param revocable the revocable * @param callerBpn the caller bpn + * @param token the token * @return the verifiable credential */ @Transactional(isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRED) - public CredentialsResponse issueCredentialUsingBaseWallet(String holderDid, Map data, boolean asJwt, String callerBpn) { + public CredentialsResponse issueCredentialUsingBaseWallet(String holderDid, Map data, boolean asJwt, boolean revocable, String callerBpn, String token) { //Fetch Holder Wallet Wallet holderWallet = commonService.getWalletByIdentifier(holderDid); @@ -213,7 +212,8 @@ public CredentialsResponse issueCredentialUsingBaseWallet(String holderDid, Map< boolean isSelfIssued = isSelfIssued(holderWallet.getBpn()); - CredentialCreationConfig holdersCredentialCreationConfig = CredentialCreationConfig.builder() + + CredentialCreationConfig.CredentialCreationConfigBuilder builder = CredentialCreationConfig.builder() .encoding(VerifiableEncoding.JSON_LD) .subject(verifiableCredential.getCredentialSubject().get(0)) .types(verifiableCredential.getTypes()) @@ -223,9 +223,24 @@ public CredentialsResponse issueCredentialUsingBaseWallet(String holderDid, Map< .contexts(verifiableCredential.getContext()) .expiryDate(Date.from(verifiableCredential.getExpirationDate())) .selfIssued(isSelfIssued) - .algorithm(SupportedAlgorithms.valueOf(issuerWallet.getAlgorithm())) - .build(); + .revocable(revocable) + .algorithm(SupportedAlgorithms.valueOf(issuerWallet.getAlgorithm())); + + if (revocable) { + //get credential status in case of revocation + VerifiableCredentialStatusList2021Entry statusListEntry = revocationService.getStatusListEntry(issuerWallet.getBpn(), token); + builder.verifiableCredentialStatus(statusListEntry); + + //add revocation context if missing + List uris = miwSettings.vcContexts(); + if (!uris.contains(revocationSettings.statusList2021Context())) { + uris.add(revocationSettings.statusList2021Context()); + builder.contexts(uris); + } + } + + CredentialCreationConfig holdersCredentialCreationConfig = builder.build(); // Create Credential SignerResult result = availableSigningServices.get(issuerWallet.getSigningServiceType()).createCredential(holdersCredentialCreationConfig); @@ -258,7 +273,7 @@ public CredentialsResponse issueCredentialUsingBaseWallet(String holderDid, Map< } - private JWTVerificationResult verifyVCAsJWT(String jwt, DidResolver didResolver, boolean withCredentialsValidation, boolean withCredentialExpiryDate) throws IOException, ParseException { + private JWTVerificationResult verifyVCAsJWT(String jwt, DidResolver didResolver, boolean withCredentialsValidation, boolean withCredentialExpiryDate, String token) throws IOException, ParseException { SignedJWT signedJWT = SignedJWT.parse(jwt); Map claims = objectMapper.readValue(signedJWT.getPayload().toBytes(), Map.class); String vcClaim = objectMapper.writeValueAsString(claims.get("vc")); @@ -266,7 +281,10 @@ private JWTVerificationResult verifyVCAsJWT(String jwt, DidResolver didResolver, VerifiableCredential verifiableCredential = new VerifiableCredential(map); //took this approach to avoid issues in sonarQube - return new JWTVerificationResult(validateSignature(withCredentialsValidation, signedJWT, didResolver) && validateJWTExpiryDate(withCredentialExpiryDate, signedJWT), verifiableCredential); + return new JWTVerificationResult(validateSignature(withCredentialsValidation, signedJWT, didResolver) + && validateJWTExpiryDate(withCredentialExpiryDate, signedJWT) + && !checkRevocationStatus(token, verifiableCredential, new HashMap<>()) + , verifiableCredential); } @@ -310,10 +328,11 @@ private boolean validateJWTExpiryDate(boolean withExpiryDate, SignedJWT signedJW * * @param verificationRequest the verification request * @param withCredentialExpiryDate the with credential expiry date + * @param token the token * @return the map */ - public Map credentialsValidation(CredentialVerificationRequest verificationRequest, boolean withCredentialExpiryDate) { - return credentialsValidation(verificationRequest, true, withCredentialExpiryDate); + public Map credentialsValidation(CredentialVerificationRequest verificationRequest, boolean withCredentialExpiryDate, String token) { + return credentialsValidation(verificationRequest, true, withCredentialExpiryDate, token); } /** @@ -322,10 +341,12 @@ public Map credentialsValidation(CredentialVerificationRequest v * @param verificationRequest the verification request * @param withCredentialsValidation the with credentials validation * @param withCredentialExpiryDate the with credential expiry date + * @param token the token * @return the map */ @SneakyThrows - public Map credentialsValidation(CredentialVerificationRequest verificationRequest, boolean withCredentialsValidation, boolean withCredentialExpiryDate) { + public Map credentialsValidation(CredentialVerificationRequest verificationRequest, boolean withCredentialsValidation, + boolean withCredentialExpiryDate, String token) { HttpClient httpClient = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.ALWAYS) .build(); @@ -337,31 +358,41 @@ public Map credentialsValidation(CredentialVerificationRequest v VerifiableCredential verifiableCredential; boolean dateValidation = true; + boolean revoked = false; if (verificationRequest.containsKey(StringPool.VC_JWT_KEY)) { - JWTVerificationResult result = verifyVCAsJWT((String) verificationRequest.get(StringPool.VC_JWT_KEY), didResolver, withCredentialsValidation, withCredentialExpiryDate); + JWTVerificationResult result = verifyVCAsJWT((String) verificationRequest.get(StringPool.VC_JWT_KEY), didResolver, withCredentialsValidation, withCredentialExpiryDate, token); valid = result.valid; } else { - verifiableCredential = new VerifiableCredential(verificationRequest); LinkedDataProofValidation proofValidation = LinkedDataProofValidation.newInstance(didResolver); - - if (withCredentialsValidation) { valid = proofValidation.verify(verifiableCredential); } else { valid = true; } + revoked = checkRevocationStatus(token, verifiableCredential, response); + dateValidation = CommonService.validateExpiry(withCredentialExpiryDate, verifiableCredential, response); } - response.put(StringPool.VALID, valid && dateValidation); + response.put(StringPool.VALID, valid && dateValidation && !revoked); response.put(StringPool.VC, verificationRequest); return response; } + private boolean checkRevocationStatus(String token, VerifiableCredential verifiableCredential, Map response) { + //check revocation + if (verifiableCredential.getVerifiableCredentialStatus() != null) { + CredentialStatus credentialStatus = revocationService.checkRevocation(verifiableCredential, token); + response.put(StringPool.CREDENTIAL_STATUS, credentialStatus.getName()); + return !Objects.equals(credentialStatus.getName(), CredentialStatus.ACTIVE.getName()); + } + return false; + } + private void validateAccess(String callerBpn, Wallet issuerWallet) { //validate BPN access, VC must be issued by base wallet diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/JwtPresentationES256KService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/JwtPresentationES256KService.java index 1d057d74b..5126108e6 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/JwtPresentationES256KService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/JwtPresentationES256KService.java @@ -35,15 +35,18 @@ import com.nimbusds.jwt.SignedJWT; import lombok.extern.slf4j.Slf4j; import org.apache.commons.text.StringEscapeUtils; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.exception.SignatureFailureException; import org.eclipse.tractusx.managedidentitywallets.exception.UnsupportedAlgorithmException; +import org.eclipse.tractusx.ssi.lib.model.JsonLdObject; import org.eclipse.tractusx.ssi.lib.model.did.Did; import org.eclipse.tractusx.ssi.lib.model.did.DidDocument; import org.eclipse.tractusx.ssi.lib.model.did.DidDocumentBuilder; import org.eclipse.tractusx.ssi.lib.model.did.JWKVerificationMethod; import org.eclipse.tractusx.ssi.lib.model.did.VerificationMethod; +import org.eclipse.tractusx.ssi.lib.model.verifiable.Verifiable; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentationBuilder; @@ -130,7 +133,22 @@ public DidDocument buildDidDocument(String bpn, Did did, List ids = new ArrayList<>(); + jwkVerificationMethods.forEach((verificationMethod) -> { + ids.add(verificationMethod.getId()); + }); + didDocument.put(StringPool.ASSERTION_METHOD, ids); + //add service + Map tokenServiceData = Map.of(Verifiable.ID, did.toUri() + "#" + StringPool.SECURITY_TOKEN_SERVICE, Verifiable.TYPE, StringPool.SECURITY_TOKEN_SERVICE, + StringPool.SERVICE_ENDPOINT, StringPool.HTTPS_SCHEME + miwSettings.host() + "/api/token"); + org.eclipse.tractusx.ssi.lib.model.did.Service tokenService = new org.eclipse.tractusx.ssi.lib.model.did.Service(tokenServiceData); + Map credentialServiceData = Map.of(Verifiable.ID, did.toUri() + "#" + StringPool.CREDENTIAL_SERVICE, Verifiable.TYPE, StringPool.CREDENTIAL_SERVICE, + StringPool.SERVICE_ENDPOINT, StringPool.HTTPS_SCHEME + miwSettings.host()); + org.eclipse.tractusx.ssi.lib.model.did.Service credentialService = new org.eclipse.tractusx.ssi.lib.model.did.Service(credentialServiceData); + didDocument.put(StringPool.SERVICE, List.of(tokenService, credentialService)); + didDocument = DidDocument.fromJson(didDocument.toJson()); log.debug("did document created for bpn ->{}", StringEscapeUtils.escapeJava(bpn)); return didDocument; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/LocalSecureTokenService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/LocalSecureTokenService.java index d6fccfb60..b6f68b4f1 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/LocalSecureTokenService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/LocalSecureTokenService.java @@ -43,7 +43,7 @@ import java.util.Set; import java.util.UUID; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getJtiAccessToken; +import static org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils.getJtiAccessToken; @Slf4j @RequiredArgsConstructor diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java index a121b84a3..51944ed17 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java @@ -24,15 +24,16 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; -import com.smartsensesolutions.java.commons.base.service.BaseService; -import com.smartsensesolutions.java.commons.specification.SpecificationUtil; +import com.smartsensesolutions.commons.dao.base.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseService; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.JtiRecord; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; @@ -41,12 +42,10 @@ import org.eclipse.tractusx.managedidentitywallets.domain.PresentationCreationConfig; import org.eclipse.tractusx.managedidentitywallets.domain.SigningServiceType; import org.eclipse.tractusx.managedidentitywallets.domain.VerifiableEncoding; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.exception.MissingVcTypesException; import org.eclipse.tractusx.managedidentitywallets.exception.PermissionViolationException; import org.eclipse.tractusx.managedidentitywallets.signing.SignerResult; import org.eclipse.tractusx.managedidentitywallets.signing.SigningService; -import org.eclipse.tractusx.managedidentitywallets.utils.Validate; import org.eclipse.tractusx.ssi.lib.did.resolver.DidResolver; import org.eclipse.tractusx.ssi.lib.exception.json.InvalidJsonLdException; import org.eclipse.tractusx.ssi.lib.exception.proof.JwtExpiredException; @@ -70,13 +69,9 @@ import java.util.Objects; import java.util.UUID; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.BLANK_SEPARATOR; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COMA_SEPARATOR; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.UNDERSCORE; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getClaimsSet; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getScope; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getStringClaim; +import static org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils.getClaimsSet; +import static org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils.getScope; +import static org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils.getStringClaim; import static org.springframework.security.oauth2.jwt.JwtClaimNames.JTI; /** @@ -89,8 +84,6 @@ public class PresentationService extends BaseService { private final HoldersCredentialRepository holdersCredentialRepository; - private final SpecificationUtil credentialSpecificationUtil; - private final CommonService commonService; @@ -109,10 +102,6 @@ protected BaseRepository getRepository() { return holdersCredentialRepository; } - @Override - protected SpecificationUtil getSpecificationUtil() { - return credentialSpecificationUtil; - } /** * Create presentation map. @@ -278,12 +267,7 @@ private boolean validateCredential(VerifiableCredential credential) { final DidResolver resolver = didDocumentResolverService.getCompositeDidResolver(); final LinkedDataProofValidation linkedDataProofValidation = LinkedDataProofValidation.newInstance(resolver); final boolean isValid = linkedDataProofValidation.verify(credential); - - if (isValid) { - log.debug("Credential validation result: (valid: {}, credential-id: {})", isValid, credential.getId()); - } else { - log.info("Credential validation result: (valid: {}, credential-id: {})", isValid, credential.getId()); - } + log.info("Credential validation result: (valid: {}, credential-id: {})", isValid, credential.getId()); return isValid; } @@ -298,10 +282,10 @@ public Map createVpWithRequiredScopes(SignedJWT innerJWT, boolea List verifiableCredentials = new ArrayList<>(); String scopeValue = getScope(jwtClaimsSet); - String[] scopes = scopeValue.split(BLANK_SEPARATOR); + String[] scopes = scopeValue.split(StringPool.BLANK_SEPARATOR); for (String scope : scopes) { - String[] scopeParts = scope.split(COLON_SEPARATOR); + String[] scopeParts = scope.split(StringPool.COLON_SEPARATOR); String vcType = scopeParts[1]; checkReadPermission(scopeParts[2]); String vcTypeNoVersion = removeVersion(vcType); @@ -357,12 +341,12 @@ private void checkReadPermission(String permission) { private void checkMissingVcs(List missingVCTypes) { if (!missingVCTypes.isEmpty()) { throw new MissingVcTypesException(String.format("Missing VC types: %s", - String.join(COMA_SEPARATOR, missingVCTypes))); + String.join(StringPool.COMA_SEPARATOR, missingVCTypes))); } } private String removeVersion(String vcType) { - String[] parts = vcType.split(UNDERSCORE); + String[] parts = vcType.split(StringPool.UNDERSCORE); return (parts.length > 1) ? parts[0] : vcType; } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationService.java index af9917297..64e95ea86 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationService.java @@ -26,9 +26,10 @@ import com.nimbusds.jwt.SignedJWT; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.TokenValidationErrors; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils; import org.eclipse.tractusx.managedidentitywallets.dto.ValidationResult; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.utils.CustomSignedJWTVerifier; import org.eclipse.tractusx.managedidentitywallets.utils.TokenValidationUtils; import org.springframework.stereotype.Service; @@ -38,9 +39,8 @@ import java.util.List; import java.util.Optional; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getAccessToken; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getClaimsSet; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.parseToken; +import static org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils.getClaimsSet; +import static org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils.parseToken; import static org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.NONCE; @Service @@ -68,7 +68,7 @@ public ValidationResult validateToken(String token) { validationResults.add(tokenValidationUtils.checkIfIssuerEqualsSubject(claimsSI)); validationResults.add(tokenValidationUtils.checkTokenExpiry(claimsSI)); - Optional accessToken = getAccessToken(claimsSI); + Optional accessToken = TokenParsingUtils.getAccessToken(claimsSI); if (accessToken.isPresent()) { SignedJWT jwtAT = parseToken(accessToken.get()); JWTClaimsSet claimsAT = getClaimsSet(jwtAT); diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/WalletKeyService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/WalletKeyService.java index ab7a636a6..5f7f7dde7 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/WalletKeyService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/WalletKeyService.java @@ -21,13 +21,12 @@ package org.eclipse.tractusx.managedidentitywallets.service; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; -import com.smartsensesolutions.java.commons.base.service.BaseService; -import com.smartsensesolutions.java.commons.specification.SpecificationUtil; +import com.smartsensesolutions.commons.dao.base.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseService; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.bouncycastle.util.io.pem.PemReader; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletKeyRepository; import org.eclipse.tractusx.managedidentitywallets.exception.UnsupportedAlgorithmException; @@ -55,7 +54,6 @@ public class WalletKeyService extends BaseService { private final WalletKeyRepository walletKeyRepository; - private final SpecificationUtil specificationUtil; private final EncryptionUtils encryptionUtils; @@ -64,10 +62,6 @@ public BaseRepository getRepository() { return walletKeyRepository; } - @Override - protected SpecificationUtil getSpecificationUtil() { - return specificationUtil; - } /** * Get private key by wallet identifier as bytes byte [ ]. @@ -97,6 +91,7 @@ public Object getPrivateKeyByKeyId(String keyId, SupportedAlgorithms supportedAl return privateKey; } } + /** * Gets private key by wallet identifier. * @@ -126,7 +121,7 @@ private static Object getKeyObject(SupportedAlgorithms algorithm, String private /** * Gets wallet key by wallet id. * - * @param walletId the wallet id + * @param walletId the wallet id * @param supportedAlgorithms the algorithm of private key * @return the wallet key by wallet identifier */ diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/WalletService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/WalletService.java index 0ca388ac2..201473707 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/WalletService.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/WalletService.java @@ -25,21 +25,23 @@ import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.jwk.KeyType; import com.nimbusds.jose.jwk.KeyUse; -import com.smartsensesolutions.java.commons.FilterRequest; -import com.smartsensesolutions.java.commons.base.repository.BaseRepository; -import com.smartsensesolutions.java.commons.base.service.BaseService; -import com.smartsensesolutions.java.commons.sort.Sort; -import com.smartsensesolutions.java.commons.sort.SortType; -import com.smartsensesolutions.java.commons.specification.SpecificationUtil; +import com.smartsensesolutions.commons.dao.base.BaseRepository; +import com.smartsensesolutions.commons.dao.base.BaseService; +import com.smartsensesolutions.commons.dao.filter.FilterRequest; +import com.smartsensesolutions.commons.dao.filter.sort.Sort; +import com.smartsensesolutions.commons.dao.filter.sort.SortType; import jakarta.annotation.PostConstruct; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.text.StringEscapeUtils; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; @@ -48,13 +50,10 @@ import org.eclipse.tractusx.managedidentitywallets.domain.KeyCreationConfig; import org.eclipse.tractusx.managedidentitywallets.domain.SigningServiceType; import org.eclipse.tractusx.managedidentitywallets.dto.CreateWalletRequest; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.exception.DuplicateWalletProblem; -import org.eclipse.tractusx.managedidentitywallets.exception.ForbiddenException; import org.eclipse.tractusx.managedidentitywallets.signing.SigningService; import org.eclipse.tractusx.managedidentitywallets.utils.CommonUtils; import org.eclipse.tractusx.managedidentitywallets.utils.EncryptionUtils; -import org.eclipse.tractusx.managedidentitywallets.utils.Validate; import org.eclipse.tractusx.ssi.lib.crypt.KeyPair; import org.eclipse.tractusx.ssi.lib.crypt.jwk.JsonWebKey; import org.eclipse.tractusx.ssi.lib.did.web.DidWebFactory; @@ -78,10 +77,6 @@ import java.util.Map; import java.util.UUID; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.REFERENCE_KEY; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.VAULT_ACCESS_TOKEN; - /** * The type Wallet service. */ @@ -103,10 +98,6 @@ public class WalletService extends BaseService { private final HoldersCredentialRepository holdersCredentialRepository; - private final SpecificationUtil walletSpecificationUtil; - - private final IssuersCredentialService issuersCredentialService; - private final CommonService commonService; private final Map availableSigningServices; @@ -122,10 +113,6 @@ protected BaseRepository getRepository() { return walletRepository; } - @Override - protected SpecificationUtil getSpecificationUtil() { - return walletSpecificationUtil; - } /** * Store credential map. @@ -207,7 +194,7 @@ public Page getWallets(int pageNumber, int size, String sortColumn, Stri Sort sort = new Sort(); sort.setColumn(sortColumn); sort.setSortType(SortType.valueOf(sortType.toUpperCase())); - filterRequest.setSort(sort); + filterRequest.setSort(List.of(sort)); return filter(filterRequest); } @@ -302,8 +289,8 @@ private Wallet createWallet(CreateWalletRequest request, boolean authority, Stri WalletKey.builder() .wallet(wallet) .keyId(e.keyId) - .referenceKey(REFERENCE_KEY) - .vaultAccessToken(VAULT_ACCESS_TOKEN) + .referenceKey(StringPool.REFERENCE_KEY) + .vaultAccessToken(StringPool.VAULT_ACCESS_TOKEN) .privateKey(encryptionUtils.encrypt(CommonUtils.getKeyString(e.keyPair.getPrivateKey().asByte(), StringPool.PRIVATE_KEY))) .publicKey(encryptionUtils.encrypt(CommonUtils.getKeyString(e.keyPair.getPublicKey().asByte(), StringPool.PUBLIC_KEY))) .algorithm(e.algorithm.name()) @@ -319,13 +306,13 @@ private Wallet createWallet(CreateWalletRequest request, boolean authority, Stri } private Did createDidJson(String didUrl) { - String[] split = didUrl.split(COLON_SEPARATOR); + String[] split = didUrl.split(StringPool.COLON_SEPARATOR); if (split.length == 1) { return DidWebFactory.fromHostname(didUrl); } else if (split.length == 2) { return DidWebFactory.fromHostnameAndPath(split[0], split[1]); } else { - int i = didUrl.lastIndexOf(COLON_SEPARATOR); + int i = didUrl.lastIndexOf(StringPool.COLON_SEPARATOR); String[] splitByLast = { didUrl.substring(0, i), didUrl.substring(i + 1) }; return DidWebFactory.fromHostnameAndPath(splitByLast[0], splitByLast[1]); } @@ -345,7 +332,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { CreateWalletRequest request = CreateWalletRequest.builder() .companyName(miwSettings.authorityWalletName()) .businessPartnerNumber(miwSettings.authorityWalletBpn()) - .didUrl(miwSettings.host() + COLON_SEPARATOR + miwSettings.authorityWalletBpn()) + .didUrl(miwSettings.host() + StringPool.COLON_SEPARATOR + miwSettings.authorityWalletBpn()) .build(); wallets[0] = createWallet(request, true, miwSettings.authorityWalletBpn()); log.info("Authority wallet created with bpn {}", StringEscapeUtils.escapeJava(miwSettings.authorityWalletBpn())); @@ -369,11 +356,7 @@ private void validateCreateWallet(CreateWalletRequest request, String callerBpn) } - @RequiredArgsConstructor - private class WalletKeyInfo { - private final String keyId; - private final KeyPair keyPair; - private final SupportedAlgorithms algorithm; - private final VerificationMethod verificationMethod; + private record WalletKeyInfo(String keyId, KeyPair keyPair, SupportedAlgorithms algorithm, + VerificationMethod verificationMethod) { } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/revocation/RevocationService.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/revocation/RevocationService.java new file mode 100644 index 000000000..bd8088343 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/revocation/RevocationService.java @@ -0,0 +1,125 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.service.revocation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jwt.SignedJWT; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.CredentialStatus; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.RevocationPurpose; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; +import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; +import org.eclipse.tractusx.managedidentitywallets.dto.CredentialVerificationRequest; +import org.eclipse.tractusx.managedidentitywallets.dto.StatusListRequest; +import org.eclipse.tractusx.managedidentitywallets.exception.RevocationException; +import org.eclipse.tractusx.managedidentitywallets.revocation.RevocationClient; +import org.eclipse.tractusx.managedidentitywallets.service.CommonService; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialStatusList2021Entry; +import org.eclipse.tractusx.ssi.lib.serialization.SerializeUtil; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * The `RevocationService` class is a Java service that handles the revocation of credentials and + * the creation of status lists. + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class RevocationService { + + private final RevocationClient revocationClient; + + private final CommonService commonService; + + private final ObjectMapper objectMapper; + + + @SneakyThrows + public void revokeCredential(CredentialVerificationRequest verificationRequest, String callerBpn, String token) { + VerifiableCredential verifiableCredential; + if (verificationRequest.containsKey(StringPool.VC_JWT_KEY)) { + SignedJWT signedJWT = SignedJWT.parse((String) verificationRequest.get(StringPool.VC_JWT_KEY)); + Map claims = objectMapper.readValue(signedJWT.getPayload().toBytes(), Map.class); + String vcClaim = objectMapper.writeValueAsString(claims.get("vc")); + Map map = SerializeUtil.fromJson(vcClaim); + verifiableCredential = new VerifiableCredential(map); + } else { + verifiableCredential = new VerifiableCredential(verificationRequest); + } + //check if credential status is not null + Validate.isTrue(verifiableCredential.getVerifiableCredentialStatus().isEmpty()).launch(new BadDataException("Provided credential is not revocable")); + + //Fetch issuer waller + Wallet issuerWallet = commonService.getWalletByIdentifier(verifiableCredential.getIssuer().toString()); + + //check caller must be issuer of VC + Validate.isFalse(issuerWallet.getBpn().equals(callerBpn)).launch(new ForbiddenException("Invalid credential access")); + + //check if VC is already Revoked + CredentialStatus credentialStatus = checkRevocation(verifiableCredential, token); + + if (credentialStatus.getName().equals(CredentialStatus.REVOKED.getName())) { + throw new RevocationException(HttpStatus.CONFLICT.value(), "Credential is already revoked", Map.of("message", "Credential is already revoked")); + } + revocationClient.revokeCredential(verifiableCredential.getVerifiableCredentialStatus(), token); + log.info("Credential with id {} is revoked by caller bpn {}", verifiableCredential.getId(), callerBpn); + } + + /** + * Gets status list entry. + * + * @param issuerId the issuer id + * @param token the token + * @return the status list entry + */ + public VerifiableCredentialStatusList2021Entry getStatusListEntry(@NotNull String issuerId, String token) { + StatusListRequest statusListRequest = StatusListRequest.builder() + .issuerId(issuerId) + .purpose(RevocationPurpose.REVOCATION.name().toLowerCase()) + .build(); + + return new VerifiableCredentialStatusList2021Entry(revocationClient.getStatusListEntry(statusListRequest, token)); + } + + /** + * Check revocation credential status. + * + * @param verifiableCredential the verifiable credential + * @param token the token + * @return the credential status + */ + public CredentialStatus checkRevocation(@NotNull VerifiableCredential verifiableCredential, String token) { + Map response = revocationClient.verifyCredentialStatus(verifiableCredential.getVerifiableCredentialStatus(), token); + log.debug("Revocation status for VC id->{} -> {}", verifiableCredential.getId(), response.get("status")); + return CredentialStatus.valueOf(response.get("status").toUpperCase()); + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/KeyProvider.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/KeyProvider.java index e8b925dad..a2b45c27f 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/KeyProvider.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/KeyProvider.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.managedidentitywallets.signing; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; import org.eclipse.tractusx.managedidentitywallets.domain.DID; import org.eclipse.tractusx.managedidentitywallets.domain.KeyPair; @@ -39,7 +39,6 @@ public interface KeyProvider { /** * @param keyName the name of the key that is to be retrieved * @return the key as a byte-array - * */ Object getPrivateKey(String keyName, SupportedAlgorithms algorithm); @@ -54,11 +53,11 @@ public interface KeyProvider { /** * @return the type of KeyProvider - * * @see KeyStorageType */ KeyStorageType getKeyStorageType(); KeyPair getKeyPair(DID self); + KeyPair getKeyPair(String bpn); } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/LocalKeyProvider.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/LocalKeyProvider.java index 93353361d..b1c39b193 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/LocalKeyProvider.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/LocalKeyProvider.java @@ -22,7 +22,7 @@ package org.eclipse.tractusx.managedidentitywallets.signing; import lombok.RequiredArgsConstructor; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletKeyRepository; import org.eclipse.tractusx.managedidentitywallets.domain.DID; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/LocalSigningServiceImpl.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/LocalSigningServiceImpl.java index 1b1198ff3..5317ec822 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/LocalSigningServiceImpl.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/signing/LocalSigningServiceImpl.java @@ -32,8 +32,10 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.apache.commons.lang3.NotImplementedException; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.config.RevocationSettings; import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; import org.eclipse.tractusx.managedidentitywallets.domain.BusinessPartnerNumber; import org.eclipse.tractusx.managedidentitywallets.domain.CredentialCreationConfig; @@ -42,7 +44,6 @@ import org.eclipse.tractusx.managedidentitywallets.domain.PresentationCreationConfig; import org.eclipse.tractusx.managedidentitywallets.domain.SigningServiceType; import org.eclipse.tractusx.managedidentitywallets.domain.VerifiableEncoding; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.interfaces.SecureTokenService; import org.eclipse.tractusx.managedidentitywallets.service.JwtPresentationES256KService; import org.eclipse.tractusx.ssi.lib.crypt.IKeyGenerator; @@ -101,6 +102,8 @@ public class LocalSigningServiceImpl implements LocalSigningService { // Autowired by name!!! private final SecureTokenService localSecureTokenService; + private final RevocationSettings revocationSettings; + @Override public SignerResult createCredential(CredentialCreationConfig config) { @@ -112,6 +115,9 @@ public SignerResult createCredential(CredentialCreationConfig config) { return resultBuilder.jsonLd(createVerifiableCredential(config, privateKeyBytes)).build(); } case JWT -> { + + //TODO maybe this we want, currently in VC as JET, we are putting signed VC(VC with proof) as a JWT claim + //instead of this we should put VC without proof and utilize JWT signature as a proof SignedJWT verifiableCredentialAsJwt = createVerifiableCredentialAsJwt(config); return resultBuilder.jwt(verifiableCredentialAsJwt.serialize()).build(); } @@ -272,7 +278,7 @@ private SignedJWT generateJwtPresentation(PresentationCreationConfig config, byt private VerifiablePresentation generateJsonLdPresentation(PresentationCreationConfig config, byte[] privateKeyBytes) throws UnsupportedSignatureTypeException, InvalidPrivateKeyFormatException, SignatureGenerateFailedException, TransformJsonLdException { VerifiablePresentationBuilder verifiablePresentationBuilder = - new VerifiablePresentationBuilder().id(URI.create(config.getVpIssuerDid() + "#" + UUID.randomUUID().toString())) + new VerifiablePresentationBuilder().id(URI.create(config.getVpIssuerDid() + "#" + UUID.randomUUID())) .type(List.of(VerifiablePresentationType.VERIFIABLE_PRESENTATION)) .verifiableCredentials(config.getVerifiableCredentials()); @@ -295,15 +301,12 @@ private VerifiablePresentation generateJsonLdPresentation(PresentationCreationCo @SneakyThrows private static VerifiableCredential createVerifiableCredential(CredentialCreationConfig config, byte[] privateKeyBytes) { - //VC Builder - // if the credential does not contain the JWS proof-context add it URI jwsUri = URI.create("https://w3id.org/security/suites/jws-2020/v1"); if (!config.getContexts().contains(jwsUri)) { config.getContexts().add(jwsUri); } - URI id = URI.create(UUID.randomUUID().toString()); VerifiableCredentialBuilder builder = new VerifiableCredentialBuilder() .context(config.getContexts()) @@ -314,6 +317,10 @@ private static VerifiableCredential createVerifiableCredential(CredentialCreatio .issuanceDate(Instant.now()) .credentialSubject(config.getSubject()); + //set status list + if (config.isRevocable()) { + builder.verifiableCredentialStatus(config.getVerifiableCredentialStatus()); + } LinkedDataProofGenerator generator = LinkedDataProofGenerator.newInstance(SignatureType.JWS); URI verificationMethod = config.getIssuerDoc().getVerificationMethods().get(0).getId(); @@ -332,7 +339,7 @@ private SignedJWT buildED25519(PresentationCreationConfig config, byte[] private SerializedJwtPresentationFactory presentationFactory = new SerializedJwtPresentationFactoryImpl( new SignedJwtFactory(new OctetKeyPairFactory()), new JsonLdSerializerImpl(), config.getVpIssuerDid()); - X25519PrivateKey privateKey = null; + X25519PrivateKey privateKey; try { privateKey = new X25519PrivateKey(privateKeyBytes); } catch (InvalidPrivateKeyFormatException e) { diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/sts/LocalSecureTokenIssuer.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/sts/LocalSecureTokenIssuer.java index 32709faa7..95fa44015 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/sts/LocalSecureTokenIssuer.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/sts/LocalSecureTokenIssuer.java @@ -44,7 +44,7 @@ import java.util.Set; import java.util.UUID; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getNonceAccessToken; +import static org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils.getNonceAccessToken; import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.ACCESS_TOKEN; import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.SCOPE; import static org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.NONCE; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/BpnValidator.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/BpnValidator.java new file mode 100644 index 000000000..9c262a859 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/BpnValidator.java @@ -0,0 +1,53 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.utils; + +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.OAuth2TokenValidator; +import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; +import org.springframework.security.oauth2.jwt.Jwt; + +import java.util.Map; +import java.util.TreeMap; + +/** + * The type Bpn validator. + */ +public class BpnValidator implements OAuth2TokenValidator { + + OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, StringPool.BPN_NOT_FOUND, null); + + @Override + public OAuth2TokenValidatorResult validate(Jwt jwt) { + //this will misbehave if we have more then one claims with different case + // ie. BPN=123456 and bpn=789456 + Map claims = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + claims.putAll(jwt.getClaims()); + if (claims.containsKey(StringPool.BPN)) { + return OAuth2TokenValidatorResult.success(); + } else { + return OAuth2TokenValidatorResult.failure(error); + } + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/CommonUtils.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/CommonUtils.java index c38a50d64..b3514e7cd 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/CommonUtils.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/CommonUtils.java @@ -26,11 +26,12 @@ import lombok.experimental.UtilityClass; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemWriter; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.domain.CredentialCreationConfig; import org.eclipse.tractusx.managedidentitywallets.dto.SecureTokenRequest; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialType; import org.springframework.util.MultiValueMap; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/CustomSignedJWTVerifier.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/CustomSignedJWTVerifier.java index 903facebd..4df8e3a1a 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/CustomSignedJWTVerifier.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/CustomSignedJWTVerifier.java @@ -31,7 +31,7 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.service.DidDocumentService; import org.eclipse.tractusx.ssi.lib.did.resolver.DidResolver; import org.eclipse.tractusx.ssi.lib.exception.proof.UnsupportedVerificationMethodException; @@ -52,7 +52,7 @@ public class CustomSignedJWTVerifier { private final DidDocumentService didDocumentService; public static final String KID = "kid"; - @SneakyThrows({UnsupportedVerificationMethodException.class}) + @SneakyThrows({ UnsupportedVerificationMethodException.class }) public boolean verify(String did, SignedJWT jwt) throws JOSEException { VerificationMethod verificationMethod = checkVerificationMethod(did, jwt); if (JWKVerificationMethod.isInstance(verificationMethod)) { diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/ResourceUtil.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/ResourceUtil.java new file mode 100644 index 000000000..6bbd6fd81 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/ResourceUtil.java @@ -0,0 +1,61 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.utils; + +import lombok.SneakyThrows; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; + +public final class ResourceUtil { + + @SneakyThrows + public static InputStream getResourceStream(String resourceName) { + var stream= ResourceUtil.class.getClassLoader().getResourceAsStream(resourceName); + if(stream == null) { + throw new IllegalArgumentException("File not found"); + } + + return stream; + } + + @SneakyThrows + public static String loadResource(String resourceName) { + StringBuilder content = new StringBuilder(); + + // load resource and return it + try (final InputStream is = ResourceUtil.class.getClassLoader().getResourceAsStream(resourceName)) { + if (is == null) { + throw new IllegalArgumentException("File not found"); + } + + final BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append(System.lineSeparator()); + } + } + + return content.toString(); + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenValidationUtils.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenValidationUtils.java index cea94cb5c..3c58b8393 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenValidationUtils.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenValidationUtils.java @@ -23,7 +23,7 @@ import com.nimbusds.jwt.JWTClaimsSet; import lombok.RequiredArgsConstructor; -import org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.TokenValidationErrors; import org.eclipse.tractusx.managedidentitywallets.dto.ValidationResult; import org.eclipse.tractusx.managedidentitywallets.service.DidDocumentService; import org.springframework.stereotype.Component; diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/validator/SecureTokenRequestValidator.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/validator/SecureTokenRequestValidator.java index 1f6dd6e48..5ecd58f03 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/validator/SecureTokenRequestValidator.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/validator/SecureTokenRequestValidator.java @@ -33,6 +33,8 @@ public class SecureTokenRequestValidator implements Validator { public static final String LINKED_MULTI_VALUE_MAP_CLASS_NAME = "org.springframework.util.LinkedMultiValueMap"; + public static final String ACCESS_TOKEN = "accessToken"; + public static final String BEARER_ACCESS_SCOPE = "bearerAccessScope"; @Override public boolean supports(Class clazz) { @@ -53,18 +55,18 @@ public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errorsHandled, "grantType", "grant_type.empty", "The 'grant_type' cannot be empty or missing."); if (secureTokenRequest.getAccessToken() != null && secureTokenRequest.getBearerAccessScope() != null) { - errorsHandled.rejectValue("accessToken", "access_token.incompatible", "The 'access_token' and the 'bearer_access_token' cannot be set together."); - errorsHandled.rejectValue("bearerAccessScope", "bearer_access_scope.incompatible", "The 'access_token' and the 'bearer_access_token' cannot be set together."); + errorsHandled.rejectValue(ACCESS_TOKEN, "access_token.incompatible", "The 'access_token' and the 'bearer_access_token' cannot be set together."); + errorsHandled.rejectValue(BEARER_ACCESS_SCOPE, "bearer_access_scope.incompatible", "The 'access_token' and the 'bearer_access_token' cannot be set together."); } if (secureTokenRequest.getAccessToken() == null && secureTokenRequest.getBearerAccessScope() == null) { - errorsHandled.rejectValue("accessToken", "access_token.incompatible", "Both the 'access_token' and the 'bearer_access_scope' are missing. At least one must be set."); - errorsHandled.rejectValue("bearerAccessScope", "bearer_access_scope.incompatible", "Both the 'access_token' and the 'bearer_access_scope' are missing. At least one must be set."); + errorsHandled.rejectValue(ACCESS_TOKEN, "access_token.incompatible", "Both the 'access_token' and the 'bearer_access_scope' are missing. At least one must be set."); + errorsHandled.rejectValue(BEARER_ACCESS_SCOPE, "bearer_access_scope.incompatible", "Both the 'access_token' and the 'bearer_access_scope' are missing. At least one must be set."); } if (secureTokenRequest.getAccessToken() != null) { - ValidationUtils.rejectIfEmptyOrWhitespace(errorsHandled, "accessToken", "access_token.empty", "The 'access_token' cannot be empty or missing."); + ValidationUtils.rejectIfEmptyOrWhitespace(errorsHandled, ACCESS_TOKEN, "access_token.empty", "The 'access_token' cannot be empty or missing."); } if (secureTokenRequest.getBearerAccessScope() != null) { - ValidationUtils.rejectIfEmptyOrWhitespace(errorsHandled, "bearerAccessScope", "bearer_access_scope.empty", "The 'bearer_access_scope' cannot be empty or missing."); + ValidationUtils.rejectIfEmptyOrWhitespace(errorsHandled, BEARER_ACCESS_SCOPE, "bearer_access_scope.empty", "The 'bearer_access_scope' cannot be empty or missing."); } } } diff --git a/miw/src/main/resources/application.yaml b/miw/src/main/resources/application.yaml index aa638730d..5f86542d6 100644 --- a/miw/src/main/resources/application.yaml +++ b/miw/src/main/resources/application.yaml @@ -18,7 +18,7 @@ ################################################################################ server: - port: ${APPLICATION_PORT:8087} + port: ${APPLICATION_PORT:8080} shutdown: graceful compression: enabled: true @@ -30,9 +30,9 @@ spring: application: name: miw datasource: - url: jdbc:postgresql://${DB_HOST}:${DB_PORT:5432}/${DB_NAME}?useSSL=${USE_SSL} - username: ${DB_USER_NAME} - password: ${DB_PASSWORD} + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5434}/${DB_NAME:miw}?useSSL=${USE_SSL:false} + username: ${DB_USER_NAME:admin} + password: ${DB_PASSWORD:admin} initialization-mode: always hikari: maximumPoolSize: ${DB_POOL_SIZE:10} @@ -89,11 +89,11 @@ logging: managedidentitywallets: ${APP_LOG_LEVEL:INFO} miw: - host: ${MIW_HOST_NAME:localhost} - encryptionKey: ${ENCRYPTION_KEY} + host: ${MIW_HOST_NAME:a888-203-129-213-107.ngrok-free.app} + encryptionKey: ${ENCRYPTION_KEY:PQL+9tK5jaKXR+9hepTsNg==} authorityWalletBpn: ${AUTHORITY_WALLET_BPN:BPNL000000000000} authorityWalletName: ${AUTHORITY_WALLET_NAME:Catena-X} - authorityWalletDid: ${AUTHORITY_WALLET_DID:did:web:localhost:BPNL000000000000} + authorityWalletDid: ${AUTHORITY_WALLET_DID:did:web:a888-203-129-213-107.ngrok-free.app:BPNL000000000000} authoritySigningServiceType: ${AUTHORITY_SIGNING_SERVICE_TYPE:LOCAL} localSigningKeyStorageType: ${LOCAL_SIGNING_KEY_STORAGE_TYPE:DB} vcContexts: ${VC_SCHEMA_LINK:https://www.w3.org/2018/credentials/v1, https://catenax-ng.github.io/product-core-schemas/businessPartnerData.json} @@ -103,11 +103,14 @@ miw: security: enabled: true realm: ${KEYCLOAK_REALM:miw_test} - clientId: ${KEYCLOAK_CLIENT_ID} - auth-server-url: ${AUTH_SERVER_URL:http://localhost:8081} + clientId: ${KEYCLOAK_CLIENT_ID:miw_private_client} + auth-server-url: ${AUTH_SERVER_URL:http://localhost:28080} auth-url: ${miw.security.auth-server-url}/realms/${miw.security.realm}/protocol/openid-connect/auth token-url: ${miw.security.auth-server-url}/realms/${miw.security.realm}/protocol/openid-connect/token refresh-token-url: ${miw.security.token-url} + revocation: + url: ${REVOCATION_SERVICE_URL:http://localhost:8081} + statusList2021Context: ${STATUS_LIST_2021_CONTEXT_URL:https://w3id.org/vc/status-list/2021/v1} sts: diff --git a/miw/src/main/resources/jsonld/IdentityMinusTrust.json b/miw/src/main/resources/jsonld/IdentityMinusTrust.json new file mode 100644 index 000000000..4a1fa6fbb --- /dev/null +++ b/miw/src/main/resources/jsonld/IdentityMinusTrust.json @@ -0,0 +1,127 @@ +{ + "@context" : { + "@version" : 1.1, + "@protected" : true, + "iatp" : "https://w3id.org/tractusx-trust/v0.8/", + "cred" : "https://www.w3.org/2018/credentials/", + "xsd" : "http://www.w3.org/2001/XMLSchema/", + "CredentialContainer" : { + "@id" : "iatp:CredentialContainer", + "@context" : { + "payload" : { + "@id" : "iatp:payload", + "@type" : "xsd:string" + } + } + }, + "CredentialMessage" : { + "@id" : "iatp:CredentialMessage", + "@context" : { + "credentials" : "iatp:credentials" + } + }, + "CredentialObject" : { + "@id" : "iatp:CredentialObject", + "@context" : { + "credentialType" : { + "@id" : "iatp:credentialType", + "@container" : "@set" + }, + "format" : "iatp:format", + "offerReason" : { + "@id" : "iatp:offerReason", + "@type" : "xsd:string" + }, + "bindingMethods" : { + "@id" : "iatp:bindingMethods", + "@type" : "xsd:string", + "@container" : "@set" + }, + "cryptographicSuites" : { + "@id" : "iatp:cryptographicSuites", + "@type" : "xsd:string", + "@container" : "@set" + }, + "issuancePolicy" : "iatp:issuancePolicy" + } + }, + "CredentialOfferMessage" : { + "@id" : "iatp:CredentialOfferMessage", + "@context" : { + "credentialIssuer" : "cred:issuer", + "credentials" : "iatp:credentials" + } + }, + "CredentialRequestMessage" : { + "@id" : "iatp:CredentialRequestMessage", + "@context" : { + "format" : "iatp:format", + "type" : "@type" + } + }, + "CredentialService" : "iatp:CredentialService", + "CredentialStatus" : { + "@id" : "iatp:CredentialStatus", + "@context" : { + "requestId" : { + "@id" : "iatp:requestId", + "@type" : "@id" + }, + "status" : { + "@id" : "iatp:status", + "@type" : "xsd:string" + } + } + }, + "IssuerMetadata" : { + "@id" : "iatp:IssuerMetadata", + "@context" : { + "credentialIssuer" : "cred:issuer", + "credentialsSupported" : { + "@id" : "iatp:credentialsSupported", + "@container" : "@set" + } + } + }, + "PresentationQueryMessage" : { + "@id" : "iatp:PresentationQueryMessage", + "@context" : { + "presentationDefinition" : { + "@id" : "iatp:presentationDefinition", + "@type" : "@json" + }, + "scope" : { + "@id" : "iatp:scope", + "@type" : "xsd:string", + "@container" : "@set" + } + } + }, + "PresentationResponseMessage" : { + "@id" : "iatp:PresentationResponseMessage", + "@context" : { + "presentation" : { + "@id" : "iatp:presentation", + "@type" : "@json" + }, + "presentationSubmission" : { + "@id" : "iatp:presentationSubmission", + "@type" : "@json" + } + } + }, + "credentials" : { + "@id" : "iatp:credentials", + "@container" : "@set" + }, + "credentialSubject" : { + "@id" : "iatp:credentialSubject", + "@type" : "cred:credentialSubject" + }, + "format" : { + "@id" : "iatp:format", + "@type" : "xsd:string" + }, + "type" : "@type" + } +} diff --git a/miw/src/main/resources/jsonld/identity.foundation.presentation-exchange.submission.v1.json b/miw/src/main/resources/jsonld/identity.foundation.presentation-exchange.submission.v1.json new file mode 100644 index 000000000..49653cebb --- /dev/null +++ b/miw/src/main/resources/jsonld/identity.foundation.presentation-exchange.submission.v1.json @@ -0,0 +1,15 @@ +{ + "@context": { + "@version": 1.1, + "PresentationSubmission": { + "@id": "https://identity.foundation/presentation-exchange/#presentation-submission", + "@context": { + "@version": 1.1, + "presentation_submission": { + "@id": "https://identity.foundation/presentation-exchange/#presentation-submission", + "@type": "@json" + } + } + } + } +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/config/CustomAuthenticationEntryPointTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/config/CustomAuthenticationEntryPointTest.java new file mode 100644 index 000000000..9cb923af8 --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/config/CustomAuthenticationEntryPointTest.java @@ -0,0 +1,115 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.config; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.config.security.CustomAuthenticationEntryPoint; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.server.resource.BearerTokenError; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; + +class CustomAuthenticationEntryPointTest { + + private CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + private HttpServletRequest request; + private HttpServletResponse response; + + @BeforeEach + void setUp() { + customAuthenticationEntryPoint = new CustomAuthenticationEntryPoint(); + request = Mockito.mock(HttpServletRequest.class); + response = Mockito.mock(HttpServletResponse.class); + } + + @Test + @DisplayName("Commence should set unauthorized status and headers when OAuth2 authentication exception") + void commenceShouldSetUnauthorizedStatusAndHeadersWhenOAuth2AuthenticationException() { + OAuth2Error error = new OAuth2Error("invalid_token", "The token is invalid", "https://example.com"); + OAuth2AuthenticationException authException = new OAuth2AuthenticationException(error); + + customAuthenticationEntryPoint.commence(request, response, authException); + + ArgumentCaptor headerCaptor = ArgumentCaptor.forClass(String.class); + verify(response).addHeader(eq(HttpHeaders.WWW_AUTHENTICATE), headerCaptor.capture()); + verify(response).setStatus(HttpStatus.UNAUTHORIZED.value()); + + String wwwAuthenticate = headerCaptor.getValue(); + assertEquals("Bearer error=\"invalid_token\", error_description=\"The token is invalid\", error_uri=\"https://example.com\"", wwwAuthenticate); + } + + @Test + @DisplayName("Commence should set forbidden status when bpn not found exception") + void commence_ShouldSetForbiddenStatus_WhenBpnNotFoundException() { + AuthenticationException authException = new AuthenticationException(StringPool.BPN_NOT_FOUND) { + }; + + customAuthenticationEntryPoint.commence(request, response, authException); + + verify(response).setStatus(HttpStatus.FORBIDDEN.value()); + } + + @Test + @DisplayName("Commence should set custom realm when realm name is set") + void commence_ShouldSetCustomRealm_WhenRealmNameIsSet() { + customAuthenticationEntryPoint.setRealmName("custom-realm"); + + OAuth2Error error = new OAuth2Error("invalid_token", "The token is invalid", "https://example.com"); + OAuth2AuthenticationException authException = new OAuth2AuthenticationException(error); + + customAuthenticationEntryPoint.commence(request, response, authException); + + ArgumentCaptor headerCaptor = ArgumentCaptor.forClass(String.class); + verify(response).addHeader(eq(HttpHeaders.WWW_AUTHENTICATE), headerCaptor.capture()); + + String wwwAuthenticate = headerCaptor.getValue(); + assertEquals("Bearer realm=\"custom-realm\", error=\"invalid_token\", error_description=\"The token is invalid\", error_uri=\"https://example.com\"", wwwAuthenticate); + } + + @Test + @DisplayName("Commence should set scope when bearer token error has scope") + void commence_ShouldSetScope_WhenBearerTokenErrorHasScope() { + BearerTokenError error = new BearerTokenError("insufficient_scope", HttpStatus.UNAUTHORIZED, "Insufficient scope", "https://example.com", "scope1 scope2"); + OAuth2AuthenticationException authException = new OAuth2AuthenticationException(error); + + customAuthenticationEntryPoint.commence(request, response, authException); + + ArgumentCaptor headerCaptor = ArgumentCaptor.forClass(String.class); + verify(response).addHeader(eq(HttpHeaders.WWW_AUTHENTICATE), headerCaptor.capture()); + + String wwwAuthenticate = headerCaptor.getValue(); + assertEquals("Bearer error=\"insufficient_scope\", error_description=\"Insufficient scope\", error_uri=\"https://example.com\", scope=\"scope1 scope2\"", wwwAuthenticate); + } +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/config/TestContextInitializer.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/config/TestContextInitializer.java index 6c07d12f4..c27f5201e 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/config/TestContextInitializer.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/config/TestContextInitializer.java @@ -28,14 +28,15 @@ import org.springframework.context.ConfigurableApplicationContext; import org.testcontainers.containers.PostgreSQLContainer; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; import java.net.ServerSocket; import java.util.Base64; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; public class TestContextInitializer implements ApplicationContextInitializer { private static final int port = findFreePort(); + private static final int MANAGEMENT_PORT = findFreePort(); private static final KeycloakContainer KEYCLOAK_CONTAINER = new KeycloakContainer().withRealmImportFile("miw-test-realm.json"); private static final PostgreSQLContainer POSTGRE_SQL_CONTAINER = new PostgreSQLContainer<>("postgres:14.5") .withDatabaseName("integration-tests-db") @@ -53,10 +54,11 @@ public void initialize(ConfigurableApplicationContext applicationContext) { SecretKey secretKey = keyGen.generateKey(); TestPropertyValues.of( "server.port=" + port, + "management.server.port=" + MANAGEMENT_PORT, "miw.host: localhost:${server.port}", "miw.enforceHttps=false", "miw.vcExpiryDate=1-1-2030", - "miw.encryptionKey="+ Base64.getEncoder().encodeToString(secretKey.getEncoded()), + "miw.encryptionKey=" + Base64.getEncoder().encodeToString(secretKey.getEncoded()), "miw.authorityWalletBpn: BPNL000000000000", "miw.authorityWalletName: Test-X", "miw.authorityWalletDid: did:web:localhost%3A${server.port}:BPNL000000000000", diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationIatpFilterTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationIatpFilterTest.java index ed2555bdd..a181c2e1f 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationIatpFilterTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationIatpFilterTest.java @@ -22,9 +22,9 @@ package org.eclipse.tractusx.managedidentitywallets.controller; import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.dto.ValidationResult; import org.eclipse.tractusx.managedidentitywallets.service.STSTokenValidationService; import org.junit.jupiter.api.Assertions; @@ -47,8 +47,8 @@ import java.util.List; import java.util.Map; -import static org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors.NONCE_MISSING; -import static org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors.TOKEN_ALREADY_EXPIRED; +import static org.eclipse.tractusx.managedidentitywallets.commons.constant.TokenValidationErrors.NONCE_MISSING; +import static org.eclipse.tractusx.managedidentitywallets.commons.constant.TokenValidationErrors.TOKEN_ALREADY_EXPIRED; @DirtiesContext @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) @@ -70,7 +70,7 @@ void createPresentationFailure401Test() { HttpEntity entity = new HttpEntity<>(headers); ResponseEntity> response = testTemplate.exchange( RestURI.API_PRESENTATIONS_IATP, - HttpMethod.GET, + HttpMethod.POST, entity, new ParameterizedTypeReference<>() { } @@ -94,15 +94,12 @@ void createPresentationFailure401WithErrorsTest() { ResponseEntity response = testTemplate.exchange( RestURI.API_PRESENTATIONS_IATP, - HttpMethod.GET, + HttpMethod.POST, entity, new ParameterizedTypeReference<>() { } ); - String expectedBody = TOKEN_ALREADY_EXPIRED.name() + StringPool.COMA_SEPARATOR + NONCE_MISSING.name(); - Assertions.assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); - Assertions.assertEquals(expectedBody, response.getBody()); } } diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/SecureTokenControllerTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/SecureTokenControllerTest.java index 58dc11653..bcf1e4311 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/SecureTokenControllerTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/SecureTokenControllerTest.java @@ -45,7 +45,7 @@ import java.util.List; import java.util.Map; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool.COLON_SEPARATOR; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) @ContextConfiguration(initializers = { TestContextInitializer.class }) @@ -104,8 +104,8 @@ void tokenJSON() { new ParameterizedTypeReference<>() { } ); - Assertions.assertEquals(response.getStatusCode(), HttpStatus.OK); - Assertions.assertEquals(response.getHeaders().getContentType(), MediaType.APPLICATION_JSON); + Assertions.assertEquals(HttpStatus.OK, response.getStatusCode()); + Assertions.assertEquals(MediaType.APPLICATION_JSON, response.getHeaders().getContentType()); Assertions.assertNotNull(response.getBody()); Assertions.assertNotNull(response.getBody().getOrDefault("access_token", null)); Assertions.assertNotNull(response.getBody().getOrDefault("expiresAt", null)); @@ -127,7 +127,7 @@ void tokenFormUrlencoded() { new ParameterizedTypeReference<>() { } ); - Assertions.assertEquals(response.getStatusCode(), HttpStatus.OK); + Assertions.assertEquals(HttpStatus.OK, response.getStatusCode()); Assertions.assertEquals(response.getHeaders().getContentType(), MediaType.APPLICATION_JSON); Assertions.assertNotNull(response.getBody()); Assertions.assertNotNull(response.getBody().getOrDefault("access_token", null)); diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/did/DidDocumentsTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/did/DidDocumentsTest.java index 4e902df7e..5645132f9 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/did/DidDocumentsTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/did/DidDocumentsTest.java @@ -38,10 +38,10 @@ import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool.COLON_SEPARATOR; -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {ManagedIdentityWalletsApplication.class}) -@ContextConfiguration(initializers = {TestContextInitializer.class}) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) +@ContextConfiguration(initializers = { TestContextInitializer.class }) class DidDocumentsTest { @Autowired diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/domain/CredentialCreationConfigTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/domain/CredentialCreationConfigTest.java index 07ccba5ad..10da4a2ee 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/domain/CredentialCreationConfigTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/domain/CredentialCreationConfigTest.java @@ -21,7 +21,7 @@ package org.eclipse.tractusx.managedidentitywallets.domain; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; import org.eclipse.tractusx.ssi.lib.model.did.Did; import org.eclipse.tractusx.ssi.lib.model.did.DidDocument; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialStatus; @@ -115,7 +115,7 @@ void shouldThrowWhenSettingIllegalVcId() { } @Test - void shouldNotThrowWhenVcIdValid(){ + void shouldNotThrowWhenVcIdValid() { CredentialCreationConfig.CredentialCreationConfigBuilder builder = CredentialCreationConfig.builder(); assertDoesNotThrow(() -> builder.vcId("https://test.com")); assertDoesNotThrow(() -> builder.vcId(URI.create("https://test.com"))); diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseSerializationTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseSerializationTest.java new file mode 100644 index 000000000..43dc24fec --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseSerializationTest.java @@ -0,0 +1,112 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.dto; + +import com.github.tomakehurst.wiremock.common.StreamSources; +import lombok.SneakyThrows; +import org.eclipse.tractusx.managedidentitywallets.reader.TractusXJsonLdReader; +import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * This test verifies that the serialized output of the Presentation Response DTO is JsonLD and Tractus-X compliant. + *

+ * It does so by comparing the serialized output with a predefined expected output. Like a contract test. + *

+ */ +public class PresentationResponseSerializationTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /* Please note: The order of the properties is important, as the Unit Tests does some String comparison. */ + final String Presentation = """ + { + "id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5", + "type": ["VerifiablePresentation", "ExamplePresentation"], + "@context": [ + "https://www.w3.org/ns/credentials/v2" + ], + "verifiableCredential": [{ + "@context": "https://www.w3.org/ns/credentials/v2", + "id": "data:application/vc+sd-jwt;QzVjV...RMjU", + "issuer": "did:example:123", + "issuanceDate": "2020-03-10T04:24:12.164Z", + "type": "VerifiableCredential", + "credentialSubject": { + "id": "did:example:456", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + } + }] + }"""; + + /* Please note: The order of the properties is important, as the Unit Tests does some String comparison. */ + final String ExpectedPresentationResponse = "{\n" + + " \"@context\" : [\n" + + " \"https://w3id.org/tractusx-trust/v0.8\"\n" + + " ],\n" + + " \"@type\" : [\n" + + " \"PresentationResponseMessage\"\n" + + " ],\n" + + " \"presentation\" : [\n" + + Presentation + + " ]\n" + + " }\n" + + " ]\n" + + "}\n"; + + @Test + @SneakyThrows + public void testPresentationResponseSerialization() { + var presentation = getPresentation(); + + var response = new PresentationResponseMessage(presentation); + + var serialized = MAPPER.writeValueAsString(response); + + + var serializedDocument = new StreamSources.StringInputStreamSource(serialized, StandardCharsets.UTF_8).getStream(); + var expectedDocument = new StreamSources.StringInputStreamSource(ExpectedPresentationResponse, StandardCharsets.UTF_8).getStream(); + + var reader = new TractusXJsonLdReader(); + var normalizedSerializedDocument = reader.expand(serializedDocument).toString(); + var normalizedExpectedDocument = reader.expand(expectedDocument).toString(); + + + var isEqual = normalizedSerializedDocument.equals(normalizedExpectedDocument); + + Assertions.assertTrue(isEqual, "Expected both documents to be equal.\n%s\n%s".formatted(normalizedSerializedDocument, normalizedExpectedDocument)); + } + + @SneakyThrows + private VerifiablePresentation getPresentation() { + var map = MAPPER.readValue(Presentation, Map.class); + return new VerifiablePresentation(map); + } +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/identityminustrust/TokenRequestTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/identityminustrust/TokenRequestTest.java new file mode 100644 index 000000000..ff88fada3 --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/identityminustrust/TokenRequestTest.java @@ -0,0 +1,160 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.identityminustrust; + +import lombok.SneakyThrows; +import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; +import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; +import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; +import org.eclipse.tractusx.managedidentitywallets.service.IssuersCredentialService; +import org.eclipse.tractusx.managedidentitywallets.utils.AuthenticationUtils; +import org.eclipse.tractusx.managedidentitywallets.utils.ResourceUtil; +import org.eclipse.tractusx.managedidentitywallets.utils.TestUtils; +import org.eclipse.tractusx.ssi.lib.did.web.DidWebFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.List; +import java.util.Map; + + +@DirtiesContext +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) +@ContextConfiguration(initializers = { TestContextInitializer.class }) +class TokenRequestTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static final String PRESENTATION_QUERY_REQUEST = "identityminustrust/messages/presentation_query.json"; + + @Autowired + private MIWSettings miwSettings; + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private TestRestTemplate testTemplate; + + @Autowired + private IssuersCredentialService issuersCredentialService; + + private String bpn; + + private String clientId; + + private String clientSecret; + + @BeforeEach + @SneakyThrows + public void initWallets() { + // given + bpn = TestUtils.getRandomBpmNumber(); + String partnerBpn = TestUtils.getRandomBpmNumber(); + clientId = bpn; + clientSecret = bpn; + AuthenticationUtils.setupKeycloakClient(clientId, clientSecret, bpn); + AuthenticationUtils.setupKeycloakClient("partner", "partner", partnerBpn); + String did = DidWebFactory.fromHostnameAndPath(miwSettings.host(), bpn).toString(); + String didPartner = DidWebFactory.fromHostnameAndPath(miwSettings.host(), partnerBpn).toString(); + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; + TestUtils.createWallet(bpn, did, testTemplate, miwSettings.authorityWalletBpn(), defaultLocation); + String defaultLocationPartner = miwSettings.host() + StringPool.COLON_SEPARATOR + partnerBpn; + TestUtils.createWallet(partnerBpn, didPartner, testTemplate, miwSettings.authorityWalletBpn(), defaultLocationPartner); + + var vc = "{\n" + + " \"id\": \"did:web:foo#f255c392-82aa-483a-90a3-3c8697cd246a\",\n" + + " \"@context\": [\n" + + " \"https://www.w3.org/2018/credentials/v1\",\n" + + " \"https://w3id.org/security/suites/jws-2020/v1\"\n" + + " ],\n" + + " \"type\": [\"VerifiableCredential\", \"MembershipCredential\"],\n" + + " \"issuanceDate\": \"2021-06-16T18:56:59Z\",\n" + + " \"expirationDate\": \"2022-06-16T18:56:59Z\",\n" + + " \"issuer\": \"" + miwSettings.authorityWalletDid() + "\",\n" + + " \"credentialSubject\": {\n" + + " \"type\":\"MembershipCredential\",\n" + + " \"holderIdentifier\": \"" + did + "\",\n" + + " \"memberOf\":\"Catena-X\",\n" + + " \"status\":\"Active\",\n" + + " \"startTime656\":\"2021-06-16T18:56:59Z\"\n" + + " }\n" + + "}"; + + issuersCredentialService.issueCredentialUsingBaseWallet( + did, + MAPPER.readValue(vc, Map.class), + false, false, + miwSettings.authorityWalletBpn(), "token" + ); + } + + @Test + @SneakyThrows + void testPresentationQueryWithToken() { + // when + String body = "audience=%s&client_id=%s&client_secret=%s&grant_type=client_credentials&bearer_access_scope=org.eclipse.tractusx.vc.type:MembershipCredential:read"; + String requestBody = String.format(body, bpn, clientId, clientSecret); + // then + HttpHeaders headers = new HttpHeaders(); + headers.put(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_FORM_URLENCODED_VALUE)); + HttpEntity entity = new HttpEntity<>(requestBody, headers); + ResponseEntity> response = testTemplate.exchange( + "/api/token", + HttpMethod.POST, + entity, + new ParameterizedTypeReference<>() { + } + ); + + var jwt = (String) response.getBody().get("access_token"); + + final String message2 = ResourceUtil.loadResource(PRESENTATION_QUERY_REQUEST); + final Map data2 = MAPPER.readValue(message2, Map.class); + + final HttpHeaders headers2 = new HttpHeaders(); + headers2.set(HttpHeaders.AUTHORIZATION, "Bearer " + jwt); + final HttpEntity> entity2 = new HttpEntity<>(data2, headers2); + var result2 = restTemplate + .postForEntity(RestURI.API_PRESENTATIONS_IATP, entity2, String.class); + + System.out.println("RESULT:\n" + result2.toString()); + + Assertions.assertTrue(result2.getStatusCode().is2xxSuccessful()); + } + +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/reader/PresentationRequestReaderTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/reader/PresentationRequestReaderTest.java new file mode 100644 index 000000000..75ec3fd59 --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/reader/PresentationRequestReaderTest.java @@ -0,0 +1,49 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.reader; + +import lombok.SneakyThrows; +import org.eclipse.tractusx.managedidentitywallets.utils.ResourceUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.util.List; + +public class PresentationRequestReaderTest { + + private final TractusXPresentationRequestReader presentationRequestReader = new TractusXPresentationRequestReader(); + + @Test + @SneakyThrows + public void readCredentialsTest() { + + final InputStream is = ResourceUtil.getResourceStream("identityminustrust/messages/presentation_query.json"); + + final List credentialScopes = presentationRequestReader.readVerifiableCredentialScopes(is); + + final String expected = "org.eclipse.tractusx.vc.type:MembershipCredential:read"; + + System.out.printf("Found credentials: %s", credentialScopes.toString()); + Assertions.assertTrue(credentialScopes.contains(expected), "Expected %s".formatted(expected)); + } +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialServiceTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialServiceTest.java index 6be2a0c31..056e9a494 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialServiceTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/IssuersCredentialServiceTest.java @@ -25,14 +25,13 @@ import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.nimbusds.jose.JWSObject; import com.nimbusds.jwt.SignedJWT; -import com.smartsensesolutions.java.commons.specification.SpecificationUtil; import lombok.SneakyThrows; import org.apache.commons.lang3.time.DateUtils; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; +import org.eclipse.tractusx.managedidentitywallets.config.RevocationSettings; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; -import org.eclipse.tractusx.managedidentitywallets.dao.entity.IssuersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; import org.eclipse.tractusx.managedidentitywallets.dao.repository.HoldersCredentialRepository; @@ -42,6 +41,7 @@ import org.eclipse.tractusx.managedidentitywallets.dto.CredentialVerificationRequest; import org.eclipse.tractusx.managedidentitywallets.dto.CredentialsResponse; import org.eclipse.tractusx.managedidentitywallets.interfaces.SecureTokenService; +import org.eclipse.tractusx.managedidentitywallets.service.revocation.RevocationService; import org.eclipse.tractusx.managedidentitywallets.signing.LocalKeyProvider; import org.eclipse.tractusx.managedidentitywallets.signing.LocalSigningServiceImpl; import org.eclipse.tractusx.managedidentitywallets.signing.SigningService; @@ -119,6 +119,10 @@ class IssuersCredentialServiceTest { private static EncryptionUtils encryptionUtils; + private static RevocationService revocationService; + + private static RevocationSettings revocationSettings; + private static final ObjectMapper objectMapper = new ObjectMapper(); @BeforeAll @@ -131,6 +135,8 @@ public static void beforeAll() throws SQLException { issuersCredentialRepository = mock(IssuersCredentialRepository.class); secureTokenService = mock(SecureTokenService.class); walletKeyRepository = mock(WalletKeyRepository.class); + revocationService = mock(RevocationService.class); + revocationSettings = mock(RevocationSettings.class); Connection connection = mock(Connection.class); @@ -144,8 +150,7 @@ public static void beforeAll() throws SQLException { issuersCredentialService = new IssuersCredentialService( issuersCredentialRepository, miwSettings, - new SpecificationUtil(), - holdersCredentialRepository, commonService, objectMapper); + holdersCredentialRepository, commonService, objectMapper, revocationService, revocationSettings); } @BeforeEach @@ -208,7 +213,7 @@ public HoldersCredential answer(InvocationOnMock invocation) { when(walletKeyService.getPrivateKeyByKeyId(anyString(), any())).thenReturn(keyPair.getPrivateKey()); when(walletKeyRepository.getByAlgorithmAndWallet_Bpn(anyString(), anyString())).thenReturn(walletKey); - LocalSigningServiceImpl localSigningService = new LocalSigningServiceImpl(secureTokenService); + LocalSigningServiceImpl localSigningService = new LocalSigningServiceImpl(secureTokenService, revocationSettings); localSigningService.setKeyProvider(new LocalKeyProvider(walletKeyService, walletKeyRepository, encryptionUtils)); Map map = new HashMap<>(); @@ -220,8 +225,8 @@ public HoldersCredential answer(InvocationOnMock invocation) { () -> issuersCredentialService.issueCredentialUsingBaseWallet( holderWalletBpn, verifiableCredential, - true, - baseWalletBpn)); + true, false, + baseWalletBpn, "dummy token")); validateCredentialResponse(credentialsResponse, MockUtil.buildDidDocument(new Did(new DidMethod("web"), new DidMethodIdentifier("basewallet"), @@ -274,7 +279,7 @@ void shouldValidateAsJWT() throws DidParseException { credentialVerificationRequest.setJwt(serialized); Map stringObjectMap = assertDoesNotThrow( - () -> issuersCredentialService.credentialsValidation(credentialVerificationRequest, true)); + () -> issuersCredentialService.credentialsValidation(credentialVerificationRequest, true, "dummy token")); assertTrue((Boolean) stringObjectMap.get(StringPool.VALID)); } } diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java index 4614ea023..d357b96fd 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java @@ -27,9 +27,9 @@ import com.nimbusds.jose.jwk.gen.OctetKeyPairGenerator; import com.nimbusds.jwt.JWTClaimsSet; import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.TokenValidationErrors; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; -import org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; import org.eclipse.tractusx.managedidentitywallets.dto.ValidationResult; diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/AuthenticationUtils.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/AuthenticationUtils.java index 1f4be3d36..6a11009af 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/AuthenticationUtils.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/AuthenticationUtils.java @@ -21,8 +21,8 @@ package org.eclipse.tractusx.managedidentitywallets.utils; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.jetbrains.annotations.NotNull; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.KeycloakBuilder; @@ -157,10 +157,15 @@ private static String getJwtToken(String username) { .clientId(StringPool.CLIENT_ID) .clientSecret(StringPool.CLIENT_SECRET) .username(username) - .password(StringPool.USER_PASSWORD) + .password(getUserPassword()) .build(); String access_token = keycloakAdminClient.tokenManager().getAccessToken().getToken(); return StringPool.BEARER_SPACE + access_token; } + + @NotNull + private static String getUserPassword() { + return "s3cr3t"; + } } diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/BpnValidatorTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/BpnValidatorTest.java new file mode 100644 index 000000000..965a8137d --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/BpnValidatorTest.java @@ -0,0 +1,90 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.utils; + +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; +import org.springframework.security.oauth2.jwt.Jwt; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +class BpnValidatorTest { + + private BpnValidator bpnValidator; + private Jwt jwt; + + @BeforeEach + void setUp() { + bpnValidator = new BpnValidator(); + jwt = Mockito.mock(Jwt.class); + } + + @Test + @DisplayName("Validate when bpn claim is present") + void validateWhenBpnClaimIsPresent() { + Map claims = new HashMap<>(); + claims.put(StringPool.BPN, "123456"); + + when(jwt.getClaims()).thenReturn(claims); + + OAuth2TokenValidatorResult result = bpnValidator.validate(jwt); + + assertFalse(result.hasErrors()); + } + + @Test + @DisplayName("Validate when bpn claim is not present") + void validateWhenBpnClaimIsNotPresent() { + Map claims = new HashMap<>(); + + when(jwt.getClaims()).thenReturn(claims); + + OAuth2TokenValidatorResult result = bpnValidator.validate(jwt); + + assertTrue(result.hasErrors()); + assertEquals(bpnValidator.error.getErrorCode(), result.getErrors().iterator().next().getErrorCode()); + assertEquals(bpnValidator.error.getDescription(), result.getErrors().iterator().next().getDescription()); + } + + @Test + @DisplayName("Validate when bpn claim is present with different case") + void validateWhenBpnClaimIsPresentWithDifferentCase() { + Map claims = new HashMap<>(); + claims.put("BPN", "123456"); + + when(jwt.getClaims()).thenReturn(claims); + + OAuth2TokenValidatorResult result = bpnValidator.validate(jwt); + + assertFalse(result.hasErrors()); + } +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestUtils.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestUtils.java index 0304ed185..390033712 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestUtils.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestUtils.java @@ -33,18 +33,24 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import lombok.SneakyThrows; +import org.apache.commons.lang3.RandomUtils; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.CredentialStatus; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; +import org.eclipse.tractusx.managedidentitywallets.config.RevocationSettings; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; import org.eclipse.tractusx.managedidentitywallets.domain.SigningServiceType; import org.eclipse.tractusx.managedidentitywallets.dto.CreateWalletRequest; -import org.eclipse.tractusx.managedidentitywallets.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.dto.StatusListRequest; +import org.eclipse.tractusx.managedidentitywallets.revocation.RevocationClient; import org.eclipse.tractusx.ssi.lib.model.did.DidDocument; import org.eclipse.tractusx.ssi.lib.model.verifiable.Verifiable; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialBuilder; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialStatusList2021Entry; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialSubject; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialType; import org.jetbrains.annotations.NotNull; @@ -52,6 +58,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.jupiter.api.Assertions; +import org.mockito.Mockito; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -59,14 +66,19 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.net.URI; import java.time.Instant; import java.util.ArrayList; +import java.util.Base64; +import java.util.BitSet; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; +import java.util.zip.GZIPOutputStream; import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.ACCESS_TOKEN; import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.SCOPE; @@ -86,8 +98,7 @@ public static ResponseEntity createWallet(String bpn, String name, TestR HttpEntity entity = new HttpEntity<>(request, headers); - ResponseEntity exchange = testTemplate.exchange(RestURI.WALLETS, HttpMethod.POST, entity, String.class); - return exchange; + return testTemplate.exchange(RestURI.WALLETS, HttpMethod.POST, entity, String.class); } @@ -118,14 +129,14 @@ public static Wallet createWallet(String bpn, String did, WalletRepository walle return walletRepository.save(wallet); } - public static void checkVC(VerifiableCredential verifiableCredential, MIWSettings miwSettings) { - //text context URL - Assertions.assertEquals(verifiableCredential.getContext().size(), miwSettings.vcContexts().size() + 1); - + public static void checkVC(VerifiableCredential verifiableCredential, MIWSettings miwSettings, RevocationSettings revocationSettings) { for (URI link : miwSettings.vcContexts()) { Assertions.assertTrue(verifiableCredential.getContext().contains(link)); } + if (verifiableCredential.getVerifiableCredentialStatus() != null) { + Assertions.assertTrue(verifiableCredential.getContext().contains(revocationSettings.statusList2021Context())); + } //check expiry date Assertions.assertEquals(0, verifiableCredential.getExpirationDate().compareTo(miwSettings.vcExpiryDate().toInstant())); } @@ -160,8 +171,6 @@ public static Wallet getWalletFromString(String body) throws JsonProcessingExcep } - - @NotNull public static List getVerifiableCredentials(ResponseEntity response, ObjectMapper objectMapper) throws JsonProcessingException { Map map = objectMapper.readValue(response.getBody(), Map.class); @@ -229,7 +238,7 @@ public static VerifiableCredential issueCustomVCUsingBaseWallet(String holderBPn Map map = getCredentialAsMap(holderBPn, holderDid, issuerDid, type, miwSettings, objectMapper); HttpEntity entity = new HttpEntity<>(map, headers); - ResponseEntity response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?holderDid={did}", HttpMethod.POST, entity, String.class, holderDid); + ResponseEntity response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?holderDid={did}&asJwt={asJwt}", HttpMethod.POST, entity, String.class, holderDid, false); if (response.getStatusCode().value() == HttpStatus.FORBIDDEN.value()) { throw new ForbiddenException(); } @@ -252,7 +261,6 @@ public static Map getCredentialAsMap(String holderBpn, String ho //VC Subject VerifiableCredentialSubject verifiableCredentialSubject = new VerifiableCredentialSubject(subjectData); - //Using Builder VerifiableCredential credentialWithoutProof = verifiableCredentialBuilder @@ -267,4 +275,135 @@ public static Map getCredentialAsMap(String holderBpn, String ho return objectMapper.readValue(credentialWithoutProof.toJson(), Map.class); } + + + public static VerifiableCredentialStatusList2021Entry getStatusListEntry(int index) { + return new VerifiableCredentialStatusList2021Entry(Map.of( + "id", "https://example.com/credentials/bpn123456789000/revocation/3#" + index, + "type", "StatusList2021Entry", + "statusPurpose", "revocation", + "statusListIndex", String.valueOf(index), + "statusListCredential", "https://example.com/credentials/bpn123456789000/revocation/3" + )); + } + + public static VerifiableCredentialStatusList2021Entry getStatusListEntry() { + int index = RandomUtils.nextInt(1, 100); + return new VerifiableCredentialStatusList2021Entry(Map.of( + "id", "https://example.com/credentials/bpn123456789000/revocation/3#" + index, + "type", "StatusList2021Entry", + "statusPurpose", "revocation", + "statusListIndex", String.valueOf(index), + "statusListCredential", "https://example.com/credentials/bpn123456789000/revocation/3" + )); + } + + public static void mockGetStatusListEntry(RevocationClient revocationClient, int statusIndex) { + //mock revocation service + Mockito.when(revocationClient.getStatusListEntry(Mockito.any(StatusListRequest.class), Mockito.any(String.class))).thenReturn(TestUtils.getStatusListEntry(statusIndex)); + } + + public static void mockGetStatusListEntry(RevocationClient revocationClient) { + //mock revocation service + Mockito.when(revocationClient.getStatusListEntry(Mockito.any(StatusListRequest.class), Mockito.any(String.class))).thenReturn(TestUtils.getStatusListEntry()); + } + + + public static void mockRevocationVerification(RevocationClient revocationClient, CredentialStatus credentialStatus) { + Mockito.when(revocationClient.verifyCredentialStatus(Mockito.any(), Mockito.anyString())).thenReturn(Map.of("status", credentialStatus.getName().toLowerCase())); + } + + @SneakyThrows + public static void mockGetStatusListVC(RevocationClient revocationClient, ObjectMapper objectMapper, String encodedList) { + String vcString = """ + { + "type": [ + "VerifiableCredential","StatusList2021Credential" + ], + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "http://localhost:8085/api/v1/revocations/credentials/did:web:BPNL01-revocation", + "issuer": "did:key:z6MkhGTzcvb8BXh5aeoaFvb3XJ3MBmfLRamdYdXyV1pxJBce", + "issuanceDate": "2023-11-30T11:29:17Z", + "issued": "2023-11-30T11:29:17Z", + "validFrom": "2023-11-30T11:29:17Z", + "proof": { + "type": "JsonWebSignature2020", + "created": "2023-11-30T11:29:17Z", + "verificationMethod": "did:key:z6MkhGTzcvb8BXh5aeoaFvb3XJ3MBmfLRamdYdXyV1pxJBce#z6MkhGTzcvb8BXh5aeoaFvb3XJ3MBmfLRamdYdXyV1pxJBce", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..Iv6H_e4kfLj9dr0COsB2D_ZPpkMoFj3BVXW2iyKFC3q5QtvPWraWfzEDJ5fxtfd5bARJQIP6YhaXdfSRgJpACQ" + }, + "credentialSubject": { + "id": "did:key:z6MkhGTzcvb8BXh5aeoaFvb3XJ3MBmfLRamdYdXyV1pxJBce", + "type": "StatusList2021", + "statusPurpose": "revocation", + "encodedList": "##encodedList" + } + } + """; + vcString = vcString.replace("##encodedList", encodedList); + + VerifiableCredential verifiableCredential = new VerifiableCredential(objectMapper.readValue(vcString, Map.class)); + Mockito.when(revocationClient.getStatusListCredential(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(String.class), Mockito.any(String.class))).thenReturn(verifiableCredential); + } + + @SneakyThrows + public static void mockGetStatusListVC(RevocationClient revocationClient, ObjectMapper objectMapper) { + String vcString = """ + { + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "http://localhost:8085/api/v1/revocations/credentials/did:web:BPNL01-revocation", + "issuer": "did:key:z6MkhGTzcvb8BXh5aeoaFvb3XJ3MBmfLRamdYdXyV1pxJBce", + "issuanceDate": "2023-11-30T11:29:17Z", + "issued": "2023-11-30T11:29:17Z", + "validFrom": "2023-11-30T11:29:17Z", + "proof": { + "type": "JsonWebSignature2020", + "creator": "did:key:z6MkhGTzcvb8BXh5aeoaFvb3XJ3MBmfLRamdYdXyV1pxJBce", + "created": "2023-11-30T11:29:17Z", + "verificationMethod": "did:key:z6MkhGTzcvb8BXh5aeoaFvb3XJ3MBmfLRamdYdXyV1pxJBce#z6MkhGTzcvb8BXh5aeoaFvb3XJ3MBmfLRamdYdXyV1pxJBce", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..Iv6H_e4kfLj9dr0COsB2D_ZPpkMoFj3BVXW2iyKFC3q5QtvPWraWfzEDJ5fxtfd5bARJQIP6YhaXdfSRgJpACQ" + }, + "credentialSubject": { + "id": "did:key:z6MkhGTzcvb8BXh5aeoaFvb3XJ3MBmfLRamdYdXyV1pxJBce", + "type": "StatusList2021", + "statusPurpose": "revocation", + "encodedList": "H4sIAAAAAAAA/+3BMQEAAAjAoEqzfzk/SwjUmQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDXFiqoX4AAAAIA" + } + } + """; + + VerifiableCredential verifiableCredential = new VerifiableCredential(objectMapper.readValue(vcString, Map.class)); + Mockito.when(revocationClient.getStatusListCredential(Mockito.any(String.class), Mockito.any(String.class), Mockito.any(String.class), Mockito.any(String.class))).thenReturn(verifiableCredential); + } + + public static String createEncodedList() throws IOException { + BitSet bitSet = new BitSet(16 * 1024 * 8); + + byte[] bitstringBytes = bitSet.toByteArray(); + // Perform GZIP compression + ByteArrayOutputStream gzipOutput = new ByteArrayOutputStream(); + try (GZIPOutputStream gzipStream = new GZIPOutputStream(gzipOutput)) { + gzipStream.write(bitstringBytes); + } + + + // Base64 encode the compressed byte array + byte[] compressedBytes = gzipOutput.toByteArray(); + String encodedList = Base64.getEncoder().encodeToString(compressedBytes); + + + return encodedList; + } } diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtilsTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtilsTest.java new file mode 100644 index 000000000..b1979f6cd --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtilsTest.java @@ -0,0 +1,234 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.utils; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.TokenParsingUtils; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtClaimNames; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; + +import java.text.ParseException; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +class TokenParsingUtilsTest { + + @Test + void parseTokenShouldReturnSignedJWTWhenTokenIsValid() throws ParseException { + String token = "valid.token.here"; + SignedJWT signedJWT = mock(SignedJWT.class); + + try (MockedStatic mockedSignedJWT = mockStatic(SignedJWT.class)) { + mockedSignedJWT.when(() -> SignedJWT.parse(token)).thenReturn(signedJWT); + + SignedJWT result = TokenParsingUtils.parseToken(token); + + assertEquals(signedJWT, result); + } + } + + @Test + void parseTokenShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + String token = "invalid.token.here"; + + try (MockedStatic mockedSignedJWT = mockStatic(SignedJWT.class)) { + mockedSignedJWT.when(() -> SignedJWT.parse(token)).thenThrow(ParseException.class); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.parseToken(token)); + + assertEquals(TokenParsingUtils.PARSING_TOKEN_ERROR, exception.getMessage()); + } + } + + @Test + void getAccessTokenShouldReturnInnerSignedJWTWhenAccessTokenIsPresent() throws ParseException { + String outerToken = "outer.token.here"; + SignedJWT outerSignedJWT = mock(SignedJWT.class); + JWTClaimsSet outerClaimsSet = new JWTClaimsSet.Builder().claim("access_token", "inner.token.here").build(); + SignedJWT innerSignedJWT = mock(SignedJWT.class); + + try (MockedStatic mockedSignedJWT = mockStatic(SignedJWT.class)) { + mockedSignedJWT.when(() -> SignedJWT.parse(outerToken)).thenReturn(outerSignedJWT); + mockedSignedJWT.when(() -> SignedJWT.parse("inner.token.here")).thenReturn(innerSignedJWT); + when(outerSignedJWT.getJWTClaimsSet()).thenReturn(outerClaimsSet); + + SignedJWT result = TokenParsingUtils.getAccessToken(outerToken); + + assertEquals(innerSignedJWT, result); + } + } + + @Test + void getAccessTokenShouldThrowBadDataExceptionWhenAccessTokenIsNotPresent() throws ParseException { + String outerToken = "outer.token.here"; + SignedJWT outerSignedJWT = mock(SignedJWT.class); + JWTClaimsSet outerClaimsSet = new JWTClaimsSet.Builder().build(); + + try (MockedStatic mockedSignedJWT = mockStatic(SignedJWT.class)) { + mockedSignedJWT.when(() -> SignedJWT.parse(outerToken)).thenReturn(outerSignedJWT); + when(outerSignedJWT.getJWTClaimsSet()).thenReturn(outerClaimsSet); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getAccessToken(outerToken)); + + assertEquals(TokenParsingUtils.ACCESS_TOKEN_ERROR, exception.getMessage()); + } + } + + @Test + void getBPNFromTokenShouldReturnBPNWhenBPNClaimIsPresent() { + Authentication authentication = mock(JwtAuthenticationToken.class); + Jwt jwt = mock(Jwt.class); + when(((JwtAuthenticationToken) authentication).getToken()).thenReturn(jwt); + Map claims = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + claims.put(StringPool.BPN, "123456"); + when(jwt.getClaims()).thenReturn(claims); + + String result = TokenParsingUtils.getBPNFromToken(authentication); + + assertEquals("123456", result); + } + + // Other test methods for TokenParsingUtils... + + @Test + void getStringClaimShouldReturnClaimValueWhenClaimIsPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("claim")).thenReturn("value"); + + String result = TokenParsingUtils.getStringClaim(claimsSet, "claim"); + + assertEquals("value", result); + } + + @Test + void getStringClaimShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("claim")).thenThrow(ParseException.class); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getStringClaim(claimsSet, "claim")); + + assertEquals(TokenParsingUtils.PARSING_TOKEN_ERROR, exception.getMessage()); + } + + @Test + void getAccessTokenShouldReturnAccessTokenWhenAccessTokenIsPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("access_token")).thenReturn("accessToken"); + + Optional result = TokenParsingUtils.getAccessToken(claimsSet); + + assertTrue(result.isPresent()); + assertEquals("accessToken", result.get()); + } + + @Test + void getAccessTokenShouldReturnEmptyOptionalWhenAccessTokenIsNotPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("access_token")).thenReturn(null); + + Optional result = TokenParsingUtils.getAccessToken(claimsSet); + + assertFalse(result.isPresent()); + } + + @Test + void getAccessTokenShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("access_token")).thenThrow(ParseException.class); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getAccessToken(claimsSet)); + + assertEquals(TokenParsingUtils.PARSING_TOKEN_ERROR, exception.getMessage()); + } + + @Test + void getScopeShouldReturnScopeWhenScopeIsPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("scope")).thenReturn("scope1 scope2"); + + String result = TokenParsingUtils.getScope(claimsSet); + + assertEquals("scope1 scope2", result); + } + + @Test + void getScopeShouldReturnBearerAccessScopeWhenScopeIsNotPresentButBearerAccessScopeIs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("scope")).thenReturn(null); + when(claimsSet.getStringClaim(TokenParsingUtils.BEARER_ACCESS_SCOPE)).thenReturn("bearerAccessScope"); + + String result = TokenParsingUtils.getScope(claimsSet); + + assertEquals("bearerAccessScope", result); + } + + @Test + void getScopeShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim("scope")).thenThrow(ParseException.class); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getScope(claimsSet)); + + assertEquals("Token does not contain scope claim", exception.getMessage()); + } + + @Test + void getJtiAccessTokenShouldReturnJtiWhenClaimIsPresent() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim(JwtClaimNames.JTI)).thenReturn("jtiValue"); + SignedJWT signedJWT = mock(SignedJWT.class); + when(signedJWT.getJWTClaimsSet()).thenReturn(claimsSet); + + String result = TokenParsingUtils.getJtiAccessToken(signedJWT); + + assertEquals("jtiValue", result); + } + + @Test + void getJtiAccessTokenShouldThrowBadDataExceptionWhenParseExceptionOccurs() throws ParseException { + JWTClaimsSet claimsSet = mock(JWTClaimsSet.class); + when(claimsSet.getStringClaim(JwtClaimNames.JTI)).thenThrow(ParseException.class); + SignedJWT signedJWT = mock(SignedJWT.class); + when(signedJWT.getJWTClaimsSet()).thenReturn(claimsSet); + + BadDataException exception = assertThrows(BadDataException.class, () -> TokenParsingUtils.getJtiAccessToken(signedJWT)); + + assertEquals(TokenParsingUtils.PARSING_TOKEN_ERROR, exception.getMessage()); + } + +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenValidationUtilsTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenValidationUtilsTest.java index 6f9c0091b..ef5c52c27 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenValidationUtilsTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenValidationUtilsTest.java @@ -23,7 +23,7 @@ import com.nimbusds.jwt.JWTClaimsSet; import lombok.SneakyThrows; -import org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.TokenValidationErrors; import org.eclipse.tractusx.managedidentitywallets.dto.ValidationResult; import org.eclipse.tractusx.managedidentitywallets.service.DidDocumentService; import org.eclipse.tractusx.ssi.lib.model.did.DidDocument; diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/HoldersCredentialTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/HoldersCredentialTest.java index dcd0344e0..0eea9a1d9 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/HoldersCredentialTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/HoldersCredentialTest.java @@ -23,12 +23,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.teketik.test.mockinbean.MockInBean; import lombok.SneakyThrows; import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.CredentialStatus; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; +import org.eclipse.tractusx.managedidentitywallets.config.RevocationSettings; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.controller.IssuersCredentialController; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; @@ -36,6 +39,8 @@ import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; import org.eclipse.tractusx.managedidentitywallets.dto.CreateWalletRequest; import org.eclipse.tractusx.managedidentitywallets.dto.CredentialVerificationRequest; +import org.eclipse.tractusx.managedidentitywallets.revocation.RevocationClient; +import org.eclipse.tractusx.managedidentitywallets.service.revocation.RevocationService; import org.eclipse.tractusx.managedidentitywallets.utils.AuthenticationUtils; import org.eclipse.tractusx.managedidentitywallets.utils.TestUtils; import org.eclipse.tractusx.ssi.lib.did.resolver.DidResolver; @@ -54,7 +59,9 @@ import org.eclipse.tractusx.ssi.lib.proof.LinkedDataProofValidation; import org.eclipse.tractusx.ssi.lib.serialization.SerializeUtil; import org.json.JSONException; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -79,8 +86,6 @@ import java.util.Objects; import java.util.UUID; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) @ContextConfiguration(initializers = { TestContextInitializer.class }) @ExtendWith(MockitoExtension.class) @@ -97,9 +102,27 @@ class HoldersCredentialTest { @Autowired private TestRestTemplate restTemplate; + @Autowired + private RevocationSettings revocationSettings; + + @MockInBean(RevocationService.class) + private RevocationClient revocationClient; + @Autowired private IssuersCredentialController credentialController; + @SneakyThrows + @BeforeEach + void beforeEach() { + TestUtils.mockGetStatusListEntry(revocationClient); + TestUtils.mockGetStatusListVC(revocationClient, objectMapper, TestUtils.createEncodedList()); + TestUtils.mockRevocationVerification(revocationClient, CredentialStatus.ACTIVE); + } + + @AfterEach + void afterEach() { + Mockito.reset(revocationClient); + } @Test void issueCredentialTestWithInvalidBPNAccess403() throws JsonProcessingException { @@ -124,14 +147,15 @@ void issueCredentialTest200() throws JsonProcessingException { ResponseEntity response = issueVC(bpn, did, type, headers); - Assertions.assertEquals(HttpStatus.CREATED.value(), response.getStatusCode().value()); VerifiableCredential verifiableCredential = new VerifiableCredential(new ObjectMapper().readValue(response.getBody(), Map.class)); Assertions.assertNotNull(verifiableCredential.getProof()); + Assertions.assertNotNull(verifiableCredential.getVerifiableCredentialStatus()); + List credentials = holdersCredentialRepository.getByHolderDidAndType(did, type); Assertions.assertFalse(credentials.isEmpty()); - TestUtils.checkVC(credentials.get(0).getData(), miwSettings); + TestUtils.checkVC(credentials.get(0).getData(), miwSettings, revocationSettings); Assertions.assertTrue(credentials.get(0).isSelfIssued()); Assertions.assertFalse(credentials.get(0).isStored()); } @@ -170,20 +194,20 @@ void getCredentials200() throws com.fasterxml.jackson.core.JsonProcessingExcepti HttpEntity entity = new HttpEntity<>(headers); - ResponseEntity response = restTemplate.exchange(RestURI.CREDENTIALS + "?issuerIdentifier={did}" - , HttpMethod.GET, entity, String.class, baseDID); + ResponseEntity response = restTemplate.exchange(RestURI.CREDENTIALS + "?issuerIdentifier={did}&asJwt={asJwt}" + , HttpMethod.GET, entity, String.class, baseDID, false); List credentialList = TestUtils.getVerifiableCredentials(response, objectMapper); Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusCode().value()); Assertions.assertEquals(typesOfVcs.size(), Objects.requireNonNull(credentialList).size()); - response = restTemplate.exchange(RestURI.CREDENTIALS + "?credentialId={id}" - , HttpMethod.GET, entity, String.class, credentialList.get(0).getId()); + response = restTemplate.exchange(RestURI.CREDENTIALS + "?credentialId={id}&asJwt={asJwt}" + , HttpMethod.GET, entity, String.class, credentialList.get(0).getId(), false); credentialList = TestUtils.getVerifiableCredentials(response, objectMapper); Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusCode().value()); Assertions.assertEquals(1, Objects.requireNonNull(credentialList).size()); - response = restTemplate.exchange(RestURI.CREDENTIALS + "?type={list}" - , HttpMethod.GET, entity, String.class, String.join(",", typesOfVcs)); + response = restTemplate.exchange(RestURI.CREDENTIALS + "?type={list}&asJwt={asJwt}" + , HttpMethod.GET, entity, String.class, String.join(",", typesOfVcs), false); credentialList = TestUtils.getVerifiableCredentials(response, objectMapper); Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusCode().value()); Assertions.assertEquals(typesOfVcs.size(), Objects.requireNonNull(credentialList).size()); @@ -191,8 +215,8 @@ void getCredentials200() throws com.fasterxml.jackson.core.JsonProcessingExcepti //test get by type String type = typesOfVcs.get(0); - response = restTemplate.exchange(RestURI.CREDENTIALS + "?type={list}" - , HttpMethod.GET, entity, String.class, type); + response = restTemplate.exchange(RestURI.CREDENTIALS + "?type={list}&asJwt={asJwt}" + , HttpMethod.GET, entity, String.class, type, false); credentialList = TestUtils.getVerifiableCredentials(response, objectMapper); Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusCode().value()); Assertions.assertEquals(1, Objects.requireNonNull(credentialList).size()); @@ -252,7 +276,7 @@ void validateCredentialsWithInvalidVC() throws com.fasterxml.jackson.core.JsonPr }).thenReturn(mock); Mockito.when(mock.verify(Mockito.any(VerifiableCredential.class))).thenReturn(false); - Map stringObjectMap = credentialController.credentialsValidation(request, false).getBody(); + Map stringObjectMap = credentialController.credentialsValidation(request, false, "dummy token").getBody(); Assertions.assertFalse(Boolean.parseBoolean(stringObjectMap.get(StringPool.VALID).toString())); } } @@ -275,7 +299,7 @@ void validateCredentialsWithExpiryCheckTrue() { }).thenReturn(mock); Mockito.when(mock.verify(Mockito.any(VerifiableCredential.class))).thenReturn(true); - Map stringObjectMap = credentialController.credentialsValidation(request, true).getBody(); + Map stringObjectMap = credentialController.credentialsValidation(request, true, "dummy token").getBody(); Assertions.assertTrue(Boolean.parseBoolean(stringObjectMap.get(StringPool.VALID).toString())); Assertions.assertTrue(Boolean.parseBoolean(stringObjectMap.get(StringPool.VALIDATE_EXPIRY_DATE).toString())); } @@ -302,7 +326,7 @@ void validateCredentialsWithExpiryCheckFalse() throws com.fasterxml.jackson.core }).thenReturn(mock); Mockito.when(mock.verify(Mockito.any(VerifiableCredential.class))).thenReturn(true); - Map stringObjectMap = credentialController.credentialsValidation(request, false).getBody(); + Map stringObjectMap = credentialController.credentialsValidation(request, false, "dummt token").getBody(); Assertions.assertTrue(Boolean.parseBoolean(stringObjectMap.get(StringPool.VALID).toString())); } } @@ -330,7 +354,7 @@ void validateExpiredCredentialsWithExpiryCheckTrue() throws com.fasterxml.jackso }).thenReturn(mock); Mockito.when(mock.verify(Mockito.any(VerifiableCredential.class))).thenReturn(true); - Map stringObjectMap = credentialController.credentialsValidation(request, true).getBody(); + Map stringObjectMap = credentialController.credentialsValidation(request, true, "dummy token").getBody(); Assertions.assertFalse(Boolean.parseBoolean(stringObjectMap.get(StringPool.VALID).toString())); Assertions.assertFalse(Boolean.parseBoolean(stringObjectMap.get(StringPool.VALIDATE_EXPIRY_DATE).toString())); @@ -341,7 +365,7 @@ void validateExpiredCredentialsWithExpiryCheckTrue() throws com.fasterxml.jackso private Map issueVC() throws JsonProcessingException { String bpn = TestUtils.getRandomBpmNumber(); String baseBpn = miwSettings.authorityWalletBpn(); - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; ResponseEntity response = TestUtils.createWallet(bpn, "Test Wallet", restTemplate, baseBpn, defaultLocation); Wallet wallet = TestUtils.getWalletFromString(response.getBody()); VerifiableCredential verifiableCredential = TestUtils.issueCustomVCUsingBaseWallet(bpn, wallet.getDid(), miwSettings.authorityWalletDid(), "Type1", AuthenticationUtils.getValidUserHttpHeaders(miwSettings.authorityWalletBpn()), miwSettings, objectMapper, restTemplate); @@ -353,7 +377,7 @@ private Map issueVC() throws JsonProcessingException { private ResponseEntity issueVC(String bpn, String did, String type, HttpHeaders headers) throws JsonProcessingException { String baseBpn = miwSettings.authorityWalletBpn(); //save wallet - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; TestUtils.createWallet(bpn, did, restTemplate, baseBpn, defaultLocation); // Create VC without proof @@ -379,7 +403,7 @@ private ResponseEntity issueVC(String bpn, String did, String type, Http Map map = objectMapper.readValue(credentialWithoutProof.toJson(), Map.class); HttpEntity entity = new HttpEntity<>(map, headers); - ResponseEntity response = restTemplate.exchange(RestURI.CREDENTIALS, HttpMethod.POST, entity, String.class); + ResponseEntity response = restTemplate.exchange(RestURI.CREDENTIALS+ "?asJwt={asJwt}", HttpMethod.POST, entity, String.class, false); return response; } } diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/IssuersCredentialTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/IssuersCredentialTest.java index 46f602d29..ea8074e4c 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/IssuersCredentialTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/IssuersCredentialTest.java @@ -24,11 +24,16 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.teketik.test.mockinbean.MockInBean; +import lombok.SneakyThrows; import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.CredentialStatus; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; +import org.eclipse.tractusx.managedidentitywallets.config.RevocationSettings; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.IssuersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; @@ -36,16 +41,20 @@ import org.eclipse.tractusx.managedidentitywallets.dao.repository.IssuersCredentialRepository; import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; import org.eclipse.tractusx.managedidentitywallets.dto.CreateWalletRequest; -import org.eclipse.tractusx.managedidentitywallets.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.revocation.RevocationClient; +import org.eclipse.tractusx.managedidentitywallets.service.revocation.RevocationService; import org.eclipse.tractusx.managedidentitywallets.utils.AuthenticationUtils; import org.eclipse.tractusx.managedidentitywallets.utils.TestUtils; import org.eclipse.tractusx.ssi.lib.did.web.DidWebFactory; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.eclipse.tractusx.ssi.lib.serialization.SerializeUtil; import org.json.JSONException; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; @@ -61,7 +70,7 @@ import java.util.Map; import java.util.Objects; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool.COLON_SEPARATOR; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) @ContextConfiguration(initializers = { TestContextInitializer.class }) @@ -81,6 +90,25 @@ class IssuersCredentialTest { @Autowired private IssuersCredentialRepository issuersCredentialRepository; + @Autowired + private RevocationSettings revocationSettings; + + + @MockInBean(RevocationService.class) + private RevocationClient revocationClient; + + @SneakyThrows + @BeforeEach + void beforeEach() { + TestUtils.mockGetStatusListEntry(revocationClient); + TestUtils.mockGetStatusListVC(revocationClient, objectMapper, TestUtils.createEncodedList()); + TestUtils.mockRevocationVerification(revocationClient, CredentialStatus.ACTIVE); + } + + @AfterEach + void afterEach() { + Mockito.reset(revocationClient); + } @Test void getCredentials200() throws com.fasterxml.jackson.core.JsonProcessingException, JSONException { @@ -100,8 +128,8 @@ void getCredentials200() throws com.fasterxml.jackson.core.JsonProcessingExcepti HttpEntity entity = new HttpEntity<>(headers); - ResponseEntity response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?holderIdentifier={did}" - , HttpMethod.GET, entity, String.class, holderDID); + ResponseEntity response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?holderIdentifier={did}&asJwt={asJwt}" + , HttpMethod.GET, entity, String.class, holderDID, false); List credentialList = TestUtils.getVerifiableCredentials(response, objectMapper); @@ -109,15 +137,15 @@ void getCredentials200() throws com.fasterxml.jackson.core.JsonProcessingExcepti Assertions.assertEquals(typesOfVcs.size(), Objects.requireNonNull(credentialList).size()); - response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?credentialId={id}" - , HttpMethod.GET, entity, String.class, credentialList.get(0).getId()); + response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?credentialId={id}&asJwt={asJwt}" + , HttpMethod.GET, entity, String.class, credentialList.get(0).getId(), false); credentialList = TestUtils.getVerifiableCredentials(response, objectMapper); Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusCode().value()); Assertions.assertEquals(1, Objects.requireNonNull(credentialList).size()); - response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?type={list}&holderIdentifier={holderIdentifier}" - , HttpMethod.GET, entity, String.class, String.join(",", typesOfVcs), wallet.getBpn()); + response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?type={list}&holderIdentifier={holderIdentifier}&asJwt={asJwt}" + , HttpMethod.GET, entity, String.class, String.join(",", typesOfVcs), wallet.getBpn(), false); credentialList = TestUtils.getVerifiableCredentials(response, objectMapper); Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusCode().value()); @@ -125,8 +153,8 @@ void getCredentials200() throws com.fasterxml.jackson.core.JsonProcessingExcepti Assertions.assertEquals(typesOfVcs.size(), Objects.requireNonNull(credentialList).size()); - response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?type={list}&holderIdentifier={holderIdentifier}" - , HttpMethod.GET, entity, String.class, typesOfVcs.get(0), wallet.getBpn()); + response = restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?type={list}&holderIdentifier={holderIdentifier}&asJwt={asJwt}" + , HttpMethod.GET, entity, String.class, typesOfVcs.get(0), wallet.getBpn(), false); credentialList = TestUtils.getVerifiableCredentials(response, objectMapper); Assertions.assertEquals(HttpStatus.OK.value(), response.getStatusCode().value()); Assertions.assertEquals(1, Objects.requireNonNull(credentialList).size()); @@ -209,6 +237,7 @@ void issueCredentialsToBaseWallet200() throws JsonProcessingException { VerifiableCredential verifiableCredential = TestUtils.issueCustomVCUsingBaseWallet(baseBpn, miwSettings.authorityWalletDid(), miwSettings.authorityWalletDid(), type, headers, miwSettings, objectMapper, restTemplate); Assertions.assertNotNull(verifiableCredential.getProof()); + Assertions.assertNotNull(verifiableCredential.getVerifiableCredentialStatus()); List credentials = holdersCredentialRepository.getByHolderDidAndType(miwSettings.authorityWalletDid(), type); Assertions.assertFalse(credentials.isEmpty()); @@ -235,7 +264,7 @@ void issueCredentials200() throws com.fasterxml.jackson.core.JsonProcessingExcep List credentials = holdersCredentialRepository.getByHolderDidAndType(did, type); Assertions.assertFalse(credentials.isEmpty()); - TestUtils.checkVC(credentials.get(0).getData(), miwSettings); + TestUtils.checkVC(credentials.get(0).getData(), miwSettings, revocationSettings); Assertions.assertFalse(credentials.get(0).isStored()); //stored must be false Assertions.assertFalse(credentials.get(0).isSelfIssued()); //stored must be false @@ -246,5 +275,4 @@ void issueCredentials200() throws com.fasterxml.jackson.core.JsonProcessingExcep } - } diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/PresentationValidationTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/PresentationValidationTest.java index 4441d6385..cd316f022 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/PresentationValidationTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/PresentationValidationTest.java @@ -28,10 +28,10 @@ import lombok.Setter; import lombok.SneakyThrows; import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dto.CreateWalletRequest; import org.eclipse.tractusx.managedidentitywallets.dto.CredentialsResponse; @@ -68,7 +68,7 @@ import java.util.Map; import java.util.UUID; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool.COLON_SEPARATOR; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) @ContextConfiguration(initializers = { TestContextInitializer.class }) @@ -120,13 +120,13 @@ public void setup() throws DidParseException { Map type1 = TestUtils.getCredentialAsMap(miwSettings.authorityWalletBpn(), miwSettings.authorityWalletDid(), miwSettings.authorityWalletDid(), "Type1", miwSettings, new com.fasterxml.jackson.databind.ObjectMapper()); - CredentialsResponse rs1 = issuersCredentialService.issueCredentialUsingBaseWallet(tenantWallet.getDid(), type1, false, bpnOperator); + CredentialsResponse rs1 = issuersCredentialService.issueCredentialUsingBaseWallet(tenantWallet.getDid(), type1, false, false, bpnOperator, "dummy token"); vc_1 = new ObjectMapper().convertValue(rs1, VerifiableCredential.class); Map type2 = TestUtils.getCredentialAsMap(miwSettings.authorityWalletBpn(), miwSettings.authorityWalletDid(), miwSettings.authorityWalletDid(), "Type2", miwSettings, new com.fasterxml.jackson.databind.ObjectMapper()); - CredentialsResponse rs2 = issuersCredentialService.issueCredentialUsingBaseWallet(tenantWallet.getDid(), type2, false, bpnOperator); + CredentialsResponse rs2 = issuersCredentialService.issueCredentialUsingBaseWallet(tenantWallet.getDid(), type2, false, false, bpnOperator, "dummy token"); vc_2 = new ObjectMapper().convertValue(rs2, VerifiableCredential.class); } @@ -139,7 +139,7 @@ void testSuccessfulValidation() { @Test @SneakyThrows - public void testSuccessfulValidationForMultipleVC() { + void testSuccessfulValidationForMultipleVC() { Map creationResponse = createPresentationJwt(List.of(vc_1, vc_2), tenant_1); // get the payload of the json web token String encodedJwtPayload = ((String) creationResponse.get("vp")).split("\\.")[1]; @@ -153,7 +153,7 @@ public void testSuccessfulValidationForMultipleVC() { } @Test - public void testValidationFailureOfCredentialWitInvalidExpirationDate() { + void testValidationFailureOfCredentialWitInvalidExpirationDate() { // test is related to this old issue where the signature check still succeeded // https://github.com/eclipse-tractusx/SSI-agent-lib/issues/4 VerifiableCredential copyCredential = new VerifiableCredential(vc_1); @@ -166,7 +166,7 @@ public void testValidationFailureOfCredentialWitInvalidExpirationDate() { @Test - public void testValidationFailureOfCredentialWitInvalidExpirationDateInSecondCredential() { + void testValidationFailureOfCredentialWitInvalidExpirationDateInSecondCredential() { // test is related to this old issue where the signature check still succeeded // https://github.com/eclipse-tractusx/SSI-agent-lib/issues/4 VerifiableCredential copyCredential = new VerifiableCredential(vc_1); diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/VerifiableCredentialIssuerEqualProofSignerTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/VerifiableCredentialIssuerEqualProofSignerTest.java index 4537be004..5c9c44f41 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/VerifiableCredentialIssuerEqualProofSignerTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vc/VerifiableCredentialIssuerEqualProofSignerTest.java @@ -23,9 +23,9 @@ import lombok.SneakyThrows; import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.service.CommonService; import org.eclipse.tractusx.managedidentitywallets.service.PresentationService; @@ -53,11 +53,11 @@ import java.util.Map; import java.util.UUID; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool.COLON_SEPARATOR; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) @ContextConfiguration(initializers = { TestContextInitializer.class }) -public class VerifiableCredentialIssuerEqualProofSignerTest { +class VerifiableCredentialIssuerEqualProofSignerTest { @Autowired private MIWSettings miwSettings; @@ -76,7 +76,7 @@ public class VerifiableCredentialIssuerEqualProofSignerTest { @SneakyThrows @Test - public void test() { + void test() { var bpn1 = "BPNL000000000FOO"; String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn1; var response1 = TestUtils.createWallet(bpn1, "did:web:localhost%3A8080:BPNL000000000FOO", restTemplate, miwSettings.authorityWalletBpn(), defaultLocation); diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java index 31768e042..3e92c03d3 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java @@ -26,20 +26,24 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTParser; import com.nimbusds.jwt.SignedJWT; +import com.teketik.test.mockinbean.MockInBean; import lombok.SneakyThrows; import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.CredentialStatus; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.dao.entity.JtiRecord; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.repository.JtiRepository; import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; import org.eclipse.tractusx.managedidentitywallets.exception.MissingVcTypesException; import org.eclipse.tractusx.managedidentitywallets.exception.PermissionViolationException; +import org.eclipse.tractusx.managedidentitywallets.revocation.RevocationClient; import org.eclipse.tractusx.managedidentitywallets.service.IssuersCredentialService; import org.eclipse.tractusx.managedidentitywallets.service.PresentationService; +import org.eclipse.tractusx.managedidentitywallets.service.revocation.RevocationService; import org.eclipse.tractusx.managedidentitywallets.utils.AuthenticationUtils; import org.eclipse.tractusx.managedidentitywallets.utils.TestConstants; import org.eclipse.tractusx.managedidentitywallets.utils.TestUtils; @@ -47,8 +51,11 @@ import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialSubject; import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; @@ -58,7 +65,7 @@ import java.util.Map; import java.util.UUID; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool.COLON_SEPARATOR; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_CREDENTIAL_READ; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_CREDENTIAL_WRITE; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_1; @@ -98,6 +105,23 @@ class PresentationServiceTest { @Autowired private WalletRepository walletRepository; + @MockInBean(RevocationService.class) + private RevocationClient revocationClient; + + @SneakyThrows + @BeforeEach + void beforeEach() { + TestUtils.mockGetStatusListEntry(revocationClient); + TestUtils.mockGetStatusListVC(revocationClient, objectMapper, TestUtils.createEncodedList()); + TestUtils.mockRevocationVerification(revocationClient, CredentialStatus.ACTIVE); + } + + @AfterEach + void afterEach() { + Mockito.reset(revocationClient); + } + + @SneakyThrows @Test void createPresentation200ResponseAsJWT() { diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationTest.java index 6b9e55b2e..81fe85a5f 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationTest.java @@ -25,18 +25,22 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import com.teketik.test.mockinbean.MockInBean; import lombok.SneakyThrows; import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.CredentialStatus; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; import org.eclipse.tractusx.managedidentitywallets.controller.PresentationController; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.repository.HoldersCredentialRepository; import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; +import org.eclipse.tractusx.managedidentitywallets.revocation.RevocationClient; import org.eclipse.tractusx.managedidentitywallets.service.IssuersCredentialService; +import org.eclipse.tractusx.managedidentitywallets.service.revocation.RevocationService; import org.eclipse.tractusx.managedidentitywallets.utils.AuthenticationUtils; import org.eclipse.tractusx.managedidentitywallets.utils.TestUtils; import org.eclipse.tractusx.ssi.lib.did.resolver.DidResolver; @@ -48,7 +52,9 @@ import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialType; import org.jetbrains.annotations.NotNull; import org.json.JSONException; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockedConstruction; import org.mockito.Mockito; @@ -74,7 +80,7 @@ import java.util.Objects; import java.util.UUID; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool.COLON_SEPARATOR; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) @ContextConfiguration(initializers = { TestContextInitializer.class }) @@ -101,6 +107,22 @@ class PresentationTest { @Autowired private WalletRepository walletRepository; + @MockInBean(RevocationService.class) + private RevocationClient revocationClient; + + @SneakyThrows + @BeforeEach + void beforeEach() { + TestUtils.mockGetStatusListEntry(revocationClient); + TestUtils.mockGetStatusListVC(revocationClient, objectMapper, TestUtils.createEncodedList()); + TestUtils.mockRevocationVerification(revocationClient, CredentialStatus.ACTIVE); + } + + @AfterEach + void afterEach() { + Mockito.reset(revocationClient); + } + @Test void validateVPAssJsonLd400() throws JsonProcessingException, JSONException { @@ -114,7 +136,7 @@ void validateVPAssJsonLd400() throws JsonProcessingException, JSONException { HttpHeaders headers = AuthenticationUtils.getValidUserHttpHeaders(bpn); HttpEntity entity = new HttpEntity<>(body, headers); - ResponseEntity validationResponse = restTemplate.exchange(RestURI.API_PRESENTATIONS_VALIDATION, HttpMethod.POST, entity, Map.class); + ResponseEntity validationResponse = restTemplate.exchange(RestURI.API_PRESENTATIONS_VALIDATION + "?asJwt={asJwt}", HttpMethod.POST, entity, Map.class, false); Assertions.assertEquals(validationResponse.getStatusCode().value(), HttpStatus.BAD_REQUEST.value()); } @@ -243,7 +265,7 @@ void createPresentationAsJsonLD201() throws JsonProcessingException, JSONExcepti HttpEntity entity = new HttpEntity<>(objectMapper.writeValueAsString(request), headers); - ResponseEntity vpResponse = restTemplate.exchange(RestURI.API_PRESENTATIONS, HttpMethod.POST, entity, Map.class); + ResponseEntity vpResponse = restTemplate.exchange(RestURI.API_PRESENTATIONS + "?asJwt={asJwt}", HttpMethod.POST, entity, Map.class, false); Assertions.assertEquals(vpResponse.getStatusCode().value(), HttpStatus.CREATED.value()); } @@ -347,6 +369,6 @@ private ResponseEntity issueVC(String bpn, String holderDid, String issu Map map = objectMapper.readValue(credentialWithoutProof.toJson(), Map.class); HttpEntity entity = new HttpEntity<>(map, headers); - return restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?holderDid={did}", HttpMethod.POST, entity, String.class, holderDid); + return restTemplate.exchange(RestURI.ISSUERS_CREDENTIALS + "?holderDid={did}&asJwt={asJwt}", HttpMethod.POST, entity, String.class, holderDid, false); } } diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/wallet/WalletTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/wallet/WalletTest.java index 8371a0fea..61d18ee4a 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/wallet/WalletTest.java +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/wallet/WalletTest.java @@ -23,11 +23,13 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.jwk.Curve; import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.SupportedAlgorithms; import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.SupportedAlgorithms; import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; @@ -40,6 +42,8 @@ import org.eclipse.tractusx.managedidentitywallets.utils.AuthenticationUtils; import org.eclipse.tractusx.managedidentitywallets.utils.TestUtils; import org.eclipse.tractusx.ssi.lib.did.web.DidWebFactory; +import org.eclipse.tractusx.ssi.lib.model.did.JWKVerificationMethod; +import org.eclipse.tractusx.ssi.lib.model.did.VerificationMethod; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -60,12 +64,12 @@ import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; - @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) @ContextConfiguration(initializers = { TestContextInitializer.class }) @@ -133,7 +137,7 @@ void createWalletTestWithUserToken403() { String name = "Sample Wallet"; HttpHeaders headers = AuthenticationUtils.getValidUserHttpHeaders(bpn); - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; CreateWalletRequest request = CreateWalletRequest.builder().businessPartnerNumber(bpn).companyName(name).didUrl(defaultLocation).build(); HttpEntity entity = new HttpEntity<>(request, headers); @@ -149,7 +153,7 @@ void createWalletWithInvalidBPNTest400() throws JSONException { String name = "Sample Wallet"; String baseBpn = miwSettings.authorityWalletBpn(); - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; ResponseEntity response = TestUtils.createWallet(bpn, name, restTemplate, baseBpn, defaultLocation); Assertions.assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode().value()); } @@ -161,14 +165,32 @@ void createWalletTest201() throws JsonProcessingException, JSONException { String name = "Sample Wallet"; String baseBpn = miwSettings.authorityWalletBpn(); - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; ResponseEntity response = TestUtils.createWallet(bpn, name, restTemplate, baseBpn, defaultLocation); Assertions.assertEquals(HttpStatus.CREATED.value(), response.getStatusCode().value()); Wallet wallet = TestUtils.getWalletFromString(response.getBody()); Assertions.assertNotNull(response.getBody()); Assertions.assertNotNull(wallet.getDidDocument()); - Assertions.assertEquals(2, wallet.getDidDocument().getVerificationMethods().size()); + List verificationMethods = wallet.getDidDocument().getVerificationMethods(); + Assertions.assertEquals(2, verificationMethods.size()); + + // both public keys will include the publicKeyJwk format to express the public key + List curves = verificationMethods.stream().map(vm -> (LinkedHashMap) vm.get(JWKVerificationMethod.PUBLIC_KEY_JWK)) + .map(lhm -> lhm.get(JWKVerificationMethod.JWK_CURVE).toString()).toList(); + List algorithms = Arrays.asList(Curve.SECP256K1.toString(), Curve.Ed25519.toString()); + // both the Ed25519 and the secp256k1 curve keys must be present in the verificationMethod of a did document + Assertions.assertTrue(curves.containsAll(algorithms)); + List assertionMethod = (List) wallet.getDidDocument().get(StringPool.ASSERTION_METHOD); + // both public keys must be expressed in the assertionMethod + Assertions.assertEquals(2, assertionMethod.size()); + // both public keys will use the JsonWebKey2020 verification method type + Assertions.assertTrue(verificationMethods.get(0).getType().equals(JWKVerificationMethod.DEFAULT_TYPE) && + verificationMethods.get(1).getType().equals(JWKVerificationMethod.DEFAULT_TYPE)); + // the controller for the keys is the MIW + Assertions.assertEquals(verificationMethods.get(0).getController().toString(), wallet.getDid()); + Assertions.assertEquals(verificationMethods.get(1).getController().toString(), wallet.getDid()); + List context = wallet.getDidDocument().getContext(); miwSettings.didDocumentContextUrls().forEach(uri -> { Assertions.assertTrue(context.contains(uri)); @@ -218,7 +240,7 @@ void storeCredentialsTest201() throws JsonProcessingException { String did = DidWebFactory.fromHostnameAndPath(miwSettings.host(), bpn).toString(); String baseBpn = miwSettings.authorityWalletBpn(); - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; TestUtils.createWallet(bpn, "name", restTemplate, baseBpn, defaultLocation); ResponseEntity response = storeCredential(bpn, did); @@ -296,7 +318,7 @@ void storeCredentialsWithDifferentHolder403() throws JsonProcessingException { String bpn = TestUtils.getRandomBpmNumber(); String did = DidWebFactory.fromHostnameAndPath(miwSettings.host(), bpn).toString(); String baseBpn = miwSettings.authorityWalletBpn(); - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; TestUtils.createWallet(bpn, "name", restTemplate, baseBpn, defaultLocation); HttpHeaders headers = AuthenticationUtils.getValidUserHttpHeaders("Some random pbn"); @@ -315,7 +337,7 @@ void createWalletWithDuplicateBpn409() throws JsonProcessingException, JSONExcep String baseBpn = miwSettings.authorityWalletBpn(); //save wallet - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; ResponseEntity response = TestUtils.createWallet(bpn, name, restTemplate, baseBpn, defaultLocation); TestUtils.getWalletFromString(response.getBody()); Assertions.assertEquals(HttpStatus.CREATED.value(), response.getStatusCode().value()); @@ -342,7 +364,7 @@ void getWalletByIdentifierWithInvalidBPNTest403() { String bpn = TestUtils.getRandomBpmNumber(); String baseBpn = miwSettings.authorityWalletBpn(); - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; TestUtils.createWallet(bpn, "sample name", restTemplate, baseBpn, defaultLocation); //create token with different BPN @@ -361,7 +383,7 @@ void getWalletByIdentifierBPNTest200() throws JsonProcessingException, JSONExcep String baseBpn = miwSettings.authorityWalletBpn(); //Create entry - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; Wallet wallet = TestUtils.getWalletFromString(TestUtils.createWallet(bpn, name, restTemplate, baseBpn, defaultLocation).getBody()); //get wallet without credentials @@ -386,7 +408,7 @@ void getWalletByIdentifierBPNWithCredentialsTest200() throws JsonProcessingExcep String baseBpn = miwSettings.authorityWalletBpn(); //Create entry - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; Wallet wallet = TestUtils.getWalletFromString(TestUtils.createWallet(bpn, name, restTemplate, baseBpn, defaultLocation).getBody()); //store credentials @@ -416,7 +438,7 @@ void getWalletByIdentifierDidTest200() throws JsonProcessingException, JSONExcep String baseBpn = miwSettings.authorityWalletBpn(); //Create entry - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; Wallet wallet = TestUtils.getWalletFromString(TestUtils.createWallet(bpn, name, restTemplate, baseBpn, defaultLocation).getBody()); HttpHeaders headers = AuthenticationUtils.getValidUserHttpHeaders(bpn); @@ -461,7 +483,7 @@ void getWallets200() throws JsonProcessingException, JSONException { String name = "Sample Name"; String baseBpn = miwSettings.authorityWalletBpn(); //Create entry - String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + String defaultLocation = miwSettings.host() + StringPool.COLON_SEPARATOR + bpn; TestUtils.createWallet(bpn, name, restTemplate, baseBpn, defaultLocation); HttpHeaders headers = AuthenticationUtils.getValidUserHttpHeaders(); diff --git a/miw/src/test/resources/identityminustrust/messages/presentation_query.json b/miw/src/test/resources/identityminustrust/messages/presentation_query.json new file mode 100644 index 000000000..5b0cd0345 --- /dev/null +++ b/miw/src/test/resources/identityminustrust/messages/presentation_query.json @@ -0,0 +1,10 @@ +{ + "scope" : [ + "org.eclipse.tractusx.vc.type:MembershipCredential:read" + ], + "@context" : [ + "https://identity.foundation/presentation-exchange/submission/v1", + "https://w3id.org/tractusx-trust/v0.8" + ], + "@type" : "PresentationQueryMessage" +} diff --git a/revocation-service/DEPENDENCIES b/revocation-service/DEPENDENCIES new file mode 100644 index 000000000..17dac72b7 --- /dev/null +++ b/revocation-service/DEPENDENCIES @@ -0,0 +1,194 @@ +maven/mavencentral/ch.qos.logback/logback-classic/1.5.6, EPL-1.0 AND LGPL-2.1-only, approved, #15279 +maven/mavencentral/ch.qos.logback/logback-core/1.5.6, EPL-1.0 AND LGPL-2.1-only, approved, #15210 +maven/mavencentral/com.apicatalog/titanium-json-ld/1.3.3, Apache-2.0, approved, #8912 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.17.2, Apache-2.0, approved, #13672 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.17.2, , approved, #13665 +maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.17.2, Apache-2.0, approved, #13671 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/2.17.2, Apache-2.0, approved, #13666 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.17.2, Apache-2.0, approved, #13669 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.17.2, Apache-2.0, approved, #15117 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.17.2, Apache-2.0, approved, #14160 +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-parameter-names/2.17.2, Apache-2.0, approved, #15122 +maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.17.2, Apache-2.0, approved, #14162 +maven/mavencentral/com.fasterxml.woodstox/woodstox-core/6.7.0, Apache-2.0, approved, #15476 +maven/mavencentral/com.fasterxml/classmate/1.7.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.github.ben-manes.caffeine/caffeine/3.1.8, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.github.docker-java/docker-java-api/3.3.6, Apache-2.0, approved, #10346 +maven/mavencentral/com.github.docker-java/docker-java-transport-zerodep/3.3.6, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #15251 +maven/mavencentral/com.github.docker-java/docker-java-transport/3.3.6, Apache-2.0, approved, #7942 +maven/mavencentral/com.github.multiformats/java-multibase/v1.1.0, MIT AND BSD-3-Clause AND EPL-1.0 AND Apache-2.0, approved, #4095 +maven/mavencentral/com.github.stephenc.jcip/jcip-annotations/1.0-1, Apache-2.0, approved, CQ21949 +maven/mavencentral/com.google.code.findbugs/jsr305/3.0.2, CC-BY-2.5, approved, #15220 +maven/mavencentral/com.google.code.gson/gson/2.10.1, Apache-2.0, approved, #6159 +maven/mavencentral/com.google.crypto.tink/tink/1.11.0, Apache-2.0, approved, #10719 +maven/mavencentral/com.google.errorprone/error_prone_annotations/2.21.1, Apache-2.0, approved, #9834 +maven/mavencentral/com.google.protobuf/protobuf-java/3.19.6, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/com.h2database/h2/2.2.224, (EPL-1.0 OR MPL-2.0) AND (LGPL-3.0-or-later OR EPL-1.0 OR MPL-2.0), approved, #9322 +maven/mavencentral/com.jayway.jsonpath/json-path/2.9.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.nimbusds/content-type/2.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.nimbusds/lang-tag/1.7, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.37.3, Apache-2.0, approved, #11701 +maven/mavencentral/com.nimbusds/oauth2-oidc-sdk/9.43.4, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.opencsv/opencsv/5.9, Apache-2.0, approved, clearlydefined +maven/mavencentral/com.sun.istack/istack-commons-runtime/4.1.2, BSD-3-Clause, approved, #15290 +maven/mavencentral/com.vaadin.external.google/android-json/0.0.20131108.vaadin1, Apache-2.0, approved, CQ21310 +maven/mavencentral/com.zaxxer/HikariCP/5.1.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/commons-beanutils/commons-beanutils/1.9.4, Apache-2.0, approved, CQ12654 +maven/mavencentral/commons-collections/commons-collections/3.2.2, Apache-2.0, approved, #15185 +maven/mavencentral/commons-digester/commons-digester/2.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/commons-fileupload/commons-fileupload/1.5, Apache-2.0, approved, #7109 +maven/mavencentral/commons-io/commons-io/2.11.0, Apache-2.0, approved, CQ23745 +maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ10162 +maven/mavencentral/commons-validator/commons-validator/1.7, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.github.openfeign.form/feign-form-spring/3.8.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.github.openfeign.form/feign-form/3.8.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.github.openfeign/feign-core/13.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.github.openfeign/feign-slf4j/13.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.micrometer/micrometer-commons/1.13.2, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #14826 +maven/mavencentral/io.micrometer/micrometer-core/1.13.2, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #14827 +maven/mavencentral/io.micrometer/micrometer-jakarta9/1.13.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.micrometer/micrometer-observation/1.13.2, Apache-2.0, approved, #14829 +maven/mavencentral/io.setl/rdf-urdna/1.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.smallrye/jandex/3.1.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/io.swagger.core.v3/swagger-annotations-jakarta/2.2.21, Apache-2.0, approved, #5947 +maven/mavencentral/io.swagger.core.v3/swagger-core-jakarta/2.2.21, Apache-2.0, approved, #5929 +maven/mavencentral/io.swagger.core.v3/swagger-models-jakarta/2.2.21, 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/2.1.1, 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.persistence/jakarta.persistence-api/3.1.0, EPL-2.0 OR BSD-3-Clause, approved, ee4j.jpa +maven/mavencentral/jakarta.transaction/jakarta.transaction-api/2.0.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jta +maven/mavencentral/jakarta.validation/jakarta.validation-api/3.0.2, Apache-2.0, approved, ee4j.validation +maven/mavencentral/jakarta.xml.bind/jakarta.xml.bind-api/4.0.2, BSD-3-Clause, approved, ee4j.jaxb +maven/mavencentral/javax.xml.bind/jaxb-api/2.3.1, CDDL-1.1 OR GPL-2.0-only WITH Classpath-exception-2.0, approved, CQ16911 +maven/mavencentral/junit/junit/4.13.2, EPL-2.0, approved, CQ23636 +maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.14.18, Apache-2.0, approved, #7164 +maven/mavencentral/net.bytebuddy/byte-buddy/1.14.18, Apache-2.0 AND BSD-3-Clause, approved, #7163 +maven/mavencentral/net.i2p.crypto/eddsa/0.3.0, CC0-1.0, approved, CQ22537 +maven/mavencentral/net.java.dev.jna/jna/5.13.0, Apache-2.0 AND LGPL-2.1-or-later, approved, #15196 +maven/mavencentral/net.minidev/accessors-smart/2.5.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/net.minidev/json-smart/2.5.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.antlr/antlr4-runtime/4.13.0, BSD-3-Clause, approved, #10767 +maven/mavencentral/org.apache.commons/commons-collections4/4.4, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.apache.commons/commons-compress/1.24.0, Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND LicenseRef-Public-Domain, approved, #10368 +maven/mavencentral/org.apache.commons/commons-lang3/3.14.0, Apache-2.0, approved, #11677 +maven/mavencentral/org.apache.commons/commons-text/1.11.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.apache.logging.log4j/log4j-api/2.23.1, Apache-2.0, approved, #13368 +maven/mavencentral/org.apache.logging.log4j/log4j-to-slf4j/2.23.1, Apache-2.0, approved, #15121 +maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-core/10.1.26, Apache-2.0 AND (EPL-2.0 OR (GPL-2.0 WITH Classpath-exception-2.0)) AND CDDL-1.0 AND (CDDL-1.1 OR (GPL-2.0-only WITH Classpath-exception-2.0)) AND EPL-2.0, approved, #15195 +maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-el/10.1.26, Apache-2.0, approved, #6997 +maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-websocket/10.1.26, Apache-2.0, approved, #7920 +maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.aspectj/aspectjweaver/1.9.22.1, Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND BSD-3-Clause AND Apache-1.1, approved, #15252 +maven/mavencentral/org.assertj/assertj-core/3.25.3, Apache-2.0, approved, #12585 +maven/mavencentral/org.awaitility/awaitility/4.2.1, Apache-2.0, approved, #14178 +maven/mavencentral/org.bouncycastle/bcprov-jdk18on/1.78, MIT AND CC0-1.0, approved, #14433 +maven/mavencentral/org.checkerframework/checker-qual/3.37.0, MIT, approved, clearlydefined +maven/mavencentral/org.checkerframework/checker-qual/3.42.0, MIT, approved, clearlydefined +maven/mavencentral/org.codehaus.woodstox/stax2-api/4.2.2, BSD-2-Clause, approved, #2670 +maven/mavencentral/org.eclipse.angus/angus-activation/2.0.2, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.angus +maven/mavencentral/org.eclipse.parsson/parsson/1.1.5, EPL-2.0, approved, ee4j.parsson +maven/mavencentral/org.eclipse.tractusx.ssi/cx-ssi-lib/0.0.19, Apache-2.0, approved, automotive.tractusx +maven/mavencentral/org.glassfish.jaxb/jaxb-core/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl +maven/mavencentral/org.glassfish.jaxb/jaxb-runtime/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl +maven/mavencentral/org.glassfish.jaxb/txw2/4.0.5, BSD-3-Clause, approved, ee4j.jaxb-impl +maven/mavencentral/org.hamcrest/hamcrest-core/2.2, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.hdrhistogram/HdrHistogram/2.2.2, BSD-2-Clause AND CC0-1.0 AND CC0-1.0, approved, #14828 +maven/mavencentral/org.hibernate.common/hibernate-commons-annotations/6.0.6.Final, LGPL-2.1-only, approved, #6962 +maven/mavencentral/org.hibernate.orm/hibernate-core/6.5.2.Final, LGPL-2.1-only AND (EPL-2.0 OR BSD-3-Clause) AND LGPL-2.1-or-later AND MIT, approved, #15118 +maven/mavencentral/org.hibernate.validator/hibernate-validator/8.0.1.Final, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.jacoco/org.jacoco.agent/0.8.9, EPL-2.0, approved, CQ23285 +maven/mavencentral/org.jacoco/org.jacoco.ant/0.8.9, EPL-2.0, approved, #1068 +maven/mavencentral/org.jacoco/org.jacoco.core/0.8.9, EPL-2.0, approved, CQ23283 +maven/mavencentral/org.jacoco/org.jacoco.report/0.8.9, EPL-2.0 AND Apache-2.0, approved, CQ23284 +maven/mavencentral/org.jboss.logging/jboss-logging/3.5.3.Final, Apache-2.0, approved, #9471 +maven/mavencentral/org.jetbrains/annotations/17.0.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.10.3, EPL-2.0, approved, #9714 +maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.10.3, EPL-2.0, approved, #9711 +maven/mavencentral/org.junit.jupiter/junit-jupiter-params/5.10.3, EPL-2.0, approved, #15250 +maven/mavencentral/org.junit.jupiter/junit-jupiter/5.10.3, EPL-2.0, approved, #15197 +maven/mavencentral/org.junit.platform/junit-platform-commons/1.10.3, EPL-2.0, approved, #9715 +maven/mavencentral/org.junit.platform/junit-platform-engine/1.10.3, EPL-2.0, approved, #9709 +maven/mavencentral/org.junit/junit-bom/5.10.3, EPL-2.0, approved, #9844 +maven/mavencentral/org.latencyutils/LatencyUtils/2.0.3, CC0-1.0, approved, #15280 +maven/mavencentral/org.liquibase/liquibase-core/4.27.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.mockito/mockito-core/5.11.0, MIT AND (Apache-2.0 AND MIT) AND Apache-2.0, approved, #13505 +maven/mavencentral/org.mockito/mockito-junit-jupiter/5.11.0, MIT, approved, #13504 +maven/mavencentral/org.objenesis/objenesis/3.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.opentest4j/opentest4j/1.3.0, Apache-2.0, approved, #9713 +maven/mavencentral/org.ow2.asm/asm-commons/9.5, BSD-3-Clause, approved, #7553 +maven/mavencentral/org.ow2.asm/asm-tree/9.5, BSD-3-Clause, approved, #7555 +maven/mavencentral/org.ow2.asm/asm/9.5, BSD-3-Clause, approved, #7554 +maven/mavencentral/org.ow2.asm/asm/9.6, BSD-3-Clause, approved, #10776 +maven/mavencentral/org.postgresql/postgresql/42.7.3, BSD-2-Clause AND Apache-2.0, approved, #11681 +maven/mavencentral/org.projectlombok/lombok/1.18.32, MIT, approved, #15192 +maven/mavencentral/org.rnorth.duct-tape/duct-tape/1.0.8, MIT, approved, clearlydefined +maven/mavencentral/org.skyscreamer/jsonassert/1.5.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.slf4j/jul-to-slf4j/2.0.13, MIT, approved, #7698 +maven/mavencentral/org.slf4j/slf4j-api/2.0.13, MIT, approved, #5915 +maven/mavencentral/org.springdoc/springdoc-openapi-starter-common/2.5.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-api/2.5.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-ui/2.5.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-actuator-autoconfigure/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-actuator/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-autoconfigure/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-devtools/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-actuator/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-aop/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-data-jpa/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-jdbc/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-json/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-logging/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-oauth2-client/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-oauth2-resource-server/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-security/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-test/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-tomcat/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-validation/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter-web/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-starter/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-test-autoconfigure/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-test/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-testcontainers/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.cloud/spring-cloud-commons/4.1.4, Apache-2.0, approved, #13495 +maven/mavencentral/org.springframework.cloud/spring-cloud-context/4.1.4, Apache-2.0, approved, #13494 +maven/mavencentral/org.springframework.cloud/spring-cloud-openfeign-core/4.1.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.cloud/spring-cloud-starter-openfeign/4.1.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.cloud/spring-cloud-starter/4.1.4, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.data/spring-data-commons/3.3.2, Apache-2.0, approved, #15116 +maven/mavencentral/org.springframework.data/spring-data-jpa/3.3.2, Apache-2.0, approved, #15120 +maven/mavencentral/org.springframework.security/spring-security-config/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-core/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-crypto/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-oauth2-client/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-oauth2-core/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-oauth2-jose/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-oauth2-resource-server/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-rsa/1.1.3, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-test/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.security/spring-security-web/6.3.1, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework/spring-aop/6.1.11, Apache-2.0, approved, #15221 +maven/mavencentral/org.springframework/spring-aspects/6.1.11, Apache-2.0, approved, #15193 +maven/mavencentral/org.springframework/spring-beans/6.1.11, Apache-2.0, approved, #15213 +maven/mavencentral/org.springframework/spring-context/6.1.11, Apache-2.0, approved, #15261 +maven/mavencentral/org.springframework/spring-core/6.1.11, Apache-2.0 AND BSD-3-Clause, approved, #15206 +maven/mavencentral/org.springframework/spring-expression/6.1.11, Apache-2.0, approved, #15264 +maven/mavencentral/org.springframework/spring-jcl/6.1.11, Apache-2.0, approved, #15266 +maven/mavencentral/org.springframework/spring-jdbc/6.1.11, Apache-2.0, approved, #15191 +maven/mavencentral/org.springframework/spring-orm/6.1.11, Apache-2.0, approved, #15278 +maven/mavencentral/org.springframework/spring-test/6.1.11, Apache-2.0, approved, #15265 +maven/mavencentral/org.springframework/spring-tx/6.1.11, Apache-2.0, approved, #15229 +maven/mavencentral/org.springframework/spring-web/6.1.11, Apache-2.0, approved, #15188 +maven/mavencentral/org.springframework/spring-webmvc/6.1.11, Apache-2.0, approved, #15182 +maven/mavencentral/org.testcontainers/database-commons/1.19.8, Apache-2.0, approved, #10345 +maven/mavencentral/org.testcontainers/jdbc/1.19.8, Apache-2.0, approved, #10348 +maven/mavencentral/org.testcontainers/junit-jupiter/1.19.8, MIT, approved, #10344 +maven/mavencentral/org.testcontainers/postgresql/1.19.8, MIT, approved, #10350 +maven/mavencentral/org.testcontainers/testcontainers/1.19.8, MIT, approved, #15203 +maven/mavencentral/org.webjars/swagger-ui/5.13.0, Apache-2.0, approved, #14547 +maven/mavencentral/org.wiremock/wiremock-standalone/3.4.2, MIT AND Apache-2.0, approved, #14889 +maven/mavencentral/org.xmlunit/xmlunit-core/2.9.1, Apache-2.0, approved, #6272 +maven/mavencentral/org.yaml/snakeyaml/2.2, 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, #10232 diff --git a/revocation-service/Dockerfile b/revocation-service/Dockerfile new file mode 100644 index 000000000..c916b3b7f --- /dev/null +++ b/revocation-service/Dockerfile @@ -0,0 +1,36 @@ +# /******************************************************************************** +# * Copyright (c) 2021,2024 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 +# ********************************************************************************/ + +FROM eclipse-temurin:17-jre-alpine + +# run as non-root user +RUN addgroup -g 11111 -S revocation-service && adduser -u 11111 -S -s /bin/false -G revocation-service revocation-service + +# add curl for healthcheck +RUN apk add curl + +USER revocation-service + +COPY ./LICENSE ./NOTICE.md ./revocation-service/DEPENDENCIES ./SECURITY.md ./revocation-service/build/libs/revocation-service-latest.jar /app/ + +WORKDIR /app + +HEALTHCHECK --start-period=30s CMD curl --fail http://localhost:8090/actuator/health/liveness || exit 1 + +CMD ["java", "-jar", "revocation-service-latest.jar"] diff --git a/revocation-service/README.md b/revocation-service/README.md new file mode 100644 index 000000000..9130d0999 --- /dev/null +++ b/revocation-service/README.md @@ -0,0 +1,131 @@ +# Statuslist2021 revocation service Service + +This service is responsible for managing the status of credentials using a status list 2021. +It supports operations such as creating, revoking, and retrieving credential statuses. + +## Prerequisites + +Before you begin, ensure you have met the following requirements: + +- Java JDK 17 is installed. +- Docker is running if you are using containers for services like Keycloak and Postgres. +- Keycloak service is operational and accessible. +- Postgres database service is running and accessible. +- Environment variables are configured according to the application's requirements. +- MIW is deployed and accessible +- Be sure the right ssi-lib version is installed + +## Environment Configuration + +The application can be configured using environment variables. Below are the available configuration properties with their default values (if any). Ensure these variables are set in your environment before starting the service. + +### Application Configuration + +- **APPLICATION_NAME**: The name of the Spring Boot application. Defaults to "Revocation". +- **APPLICATION_PORT**: The HTTP port for the application. Defaults to 8080. +- **APPLICATION_PROFILE**: The active Spring profile. Defaults to "local". + +### Database Configuration + +- **DATABASE_HOST**: The hostname or IP address of the Postgres database. +- **DATABASE_PORT**: The port number on which the Postgres server is running. Defaults to 5432. +- **DATABASE_NAME**: The name of the database to connect to. +- **DATABASE_USERNAME**: The username for accessing the database. +- **DATABASE_USE_SSL_COMMUNICATION**: Whether to use SSL for database communication. Defaults to false. +- **DATABASE_PASSWORD**: The password for accessing the database. +- **DATABASE_CONNECTION_POOL_SIZE**: The size of the database connection pool. Defaults to 10. + +### Swagger Configuration + +- **ENABLE_SWAGGER_UI**: Flag to enable or disable Swagger UI. Defaults to false. +- **ENABLE_API_DOC**: Flag to enable or disable API documentation. Defaults to false. + +### Logging Configuration + +- **APPLICATION_LOG_LEVEL**: The application-wide log level. Defaults to "DEBUG". + +### Security Configuration + +The application integrates with Keycloak for OAuth2 authentication and authorization: + +- **SERVICE_SECURITY_ENABLED**: Flag to enable or disable service Security integration for Disabling Swagger and other Endpoints. Defaults to true, false only for test purposes recommended. + +The application integrates with Keycloak for OAuth2 authentication and authorization: + +- **KEYCLOAK_ENABLED**: Flag to enable or disable Keycloak integration. Defaults to true. +- **KEYCLOAK_REALM**: The Keycloak realm to connect to. +- **KEYCLOAK_CLIENT_ID**: The Keycloak client ID for the application. +- **KEYCLOAK_PUBLIC_CLIENT_ID**: The Keycloak public client ID, used for Swagger UI authentication. +- **AUTH_SERVER_URL**: The URL for the Keycloak authentication server. + +Keycloak URLs for token and authentication management: + +- **KEYCLOAK_AUTH_URL**: Constructed from `AUTH_SERVER_URL`, `KEYCLOAK_REALM`. +- **KEYCLOAK_TOKEN_URL**: Constructed from `AUTH_SERVER_URL`, `KEYCLOAK_REALM`. +- **KEYCLOAK_REFRESH_TOKEN_URL**: Same as `KEYCLOAK_TOKEN_URL`. +- **KEYCLOAK_USERNAME**: The username for accessing the Keycloak management APIs. +- **KEYCLOAK_PASSWORD**: The password for accessing the Keycloak management APIs. + +### External Service URLs + +- **MIW_URL**: The URL for the Middleware (MIW) used for signing status list credentials. +- **DOMAIN_URL**: The base URL for your domain, which may be used for service-to-service communication or callbacks. + +## Spring Boot Configuration + +The `server`, `spring`, `springdoc`, `management`, and `logging` sections of the YAML are Spring Boot-specific configurations. They configure the application's behavior, data source, OpenAPI documentation, and logging levels, among other things. + +Be sure to replace placeholder values in the environment variables with actual data according to your application's specific requirements. + +## Middleware (MIW) Setup + +Ensure that the middleware (MIW) is running, as it is used to sign the status list credentials. + +An Overview of how to start the middleware can be found under the Readme.md in here:[README.md](..%2Fmiw%2FREADME.md) + +## Starting Services + +To start the Statuslist2021 Service, follow these steps: + +1. **Start Keycloak and Postgres:** + + Ensure that both Keycloak and Postgres services are running. For development purposes, the Keycloak and + Postgres from the MIW Dev Setup can be used if not + already running with the MIW Task deployment. + + [dev-assets](..%2Fdev-assets) + + For starting it, follow the Guidelines in the MIW-Repo Readme.md -> Development Setup -> local + with `task docker:start-middleware` or manually + call start the docker compose file. + +2. **Start the Service:** + +Execute the following command from the root of the project to start the service: +./gradlew bootRun + +## Access Swagger UI + +After starting the service, access the Swagger UI to test API endpoints by navigating to the following URL: +http://localhost:8080/ui/swagger-ui/ + +Replace `localhost` and `8080` with your service's host and port if they are different. + +## Build and Run Using Gradle + +- To build the project, run the following command: + +``` +cd revpcation-service +./../gradlew clean build +``` + +- To run the tests: +``` +cd revpcation-service +./../gradlew clean test +``` + +## Additional Information + +For more information on how to configure and use the service, refer to the provided documentation or contact the development team. diff --git a/revocation-service/build.gradle b/revocation-service/build.gradle new file mode 100644 index 000000000..185d8efa7 --- /dev/null +++ b/revocation-service/build.gradle @@ -0,0 +1,91 @@ +/******************************************************************************** + * Copyright (c) 2021, 2024 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 { + id 'java' +} + +group = "${groupName}" +version = "${applicationVersion}" +sourceCompatibility = JavaVersion.VERSION_17 + +jar { + enabled = false +} + +java { + sourceCompatibility = '17' +} + +dependencies { + + //project deps + implementation project(":wallet-commons") + + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + implementation "org.springdoc:springdoc-openapi-starter-common:${openApiVersion}" + implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:${openApiVersion}" + implementation "com.google.code.gson:gson:${gsonVersion}" + implementation 'org.liquibase:liquibase-core' + implementation "org.eclipse.tractusx.ssi:cx-ssi-lib:${ssiLibVersion}" + compileOnly "org.projectlombok:lombok:${lombokVersion}" + developmentOnly 'org.springframework.boot:spring-boot-devtools' + annotationProcessor "org.projectlombok:lombok:${lombokVersion}" + developmentOnly 'org.springframework.boot:spring-boot-devtools' + runtimeOnly 'org.postgresql:postgresql' + + testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" + testImplementation "org.wiremock:wiremock-standalone:${wiremockVersion}" + testImplementation "org.projectlombok:lombok:${lombokVersion}" + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.boot:spring-boot-testcontainers' + testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'org.testcontainers:junit-jupiter' + testImplementation 'org.testcontainers:postgresql' + testImplementation 'com.h2database:h2' +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + } +} + +build { + archivesBaseName = "revocation-service" + version = "latest" + +} + +bootJar { + enabled = true + metaInf { + from 'DEPENDENCIES' + from '../SECURITY.md' + from '../NOTICE.md' + from '../LICENSE' + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/VerifiableCredentialsRevocationServiceApplication.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/VerifiableCredentialsRevocationServiceApplication.java new file mode 100644 index 000000000..d25d78687 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/VerifiableCredentialsRevocationServiceApplication.java @@ -0,0 +1,35 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@SpringBootApplication +@ConfigurationPropertiesScan +public class VerifiableCredentialsRevocationServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(VerifiableCredentialsRevocationServiceApplication.class, args); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/apidocs/RevocationApiControllerApiDocs.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/apidocs/RevocationApiControllerApiDocs.java new file mode 100644 index 000000000..c93c07944 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/apidocs/RevocationApiControllerApiDocs.java @@ -0,0 +1,291 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.apidocs; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.MediaType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +public class RevocationApiControllerApiDocs { + + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "Status of credential", + content = { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + examples = { + @ExampleObject(name = "if credential is revoked", description = "if credential is revoked", value = """ + { + "status":"revoked" + } + """), + @ExampleObject(name = "if credential is active", description = "if credential is is active", value = """ + { + "status":"active" + } + """) }) }), + @ApiResponse( + responseCode = "401", + description = "UnauthorizedException: invalid token", + content = @Content()), + @ApiResponse( + responseCode = "403", + description = "ForbiddenException: invalid caller", + content = @Content()), + @ApiResponse( + responseCode = "500", + description = "RevocationServiceException: Internal Server Error", + content = @Content()) + }) + @RequestBody( + content = { + @Content( + examples = + @ExampleObject( + value = """ + { + "id": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1#12", + "statusPurpose": "revocation", + "statusListIndex": "12", + "statusListCredential": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": "StatusList2021" + } + """), + mediaType = "application/json") + }) + @Operation( + summary = "Verify Revocation status", + description = "Verify revocation status of Credential") + public @interface verifyCredentialDocs { + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "Verifiable credential revoked successfully.", + content = @Content()), + @ApiResponse( + responseCode = "401", + description = "UnauthorizedException: invalid token", + content = @Content()), + @ApiResponse( + responseCode = "403", + description = "ForbiddenException: invalid caller", + content = @Content()), + @ApiResponse( + responseCode = "409", + description = "ConflictException: Revocation service error", + content = + @Content( + examples = + @ExampleObject( + value = """ + { + "type": "StatusList2021", + "title": "Revocation service error", + "status": "409", + "detail": "Credential already revoked", + "instance": "/api/v1/revocations/revoke", + "timestamp": 1707133388128 + } + """), + mediaType = "application/json")), + @ApiResponse( + responseCode = "500", + description = "RevocationServiceException: Internal Server Error", + content = @Content()) + }) + @RequestBody( + content = { + @Content( + examples = + @ExampleObject( + value = """ + { + "id": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1#12", + "statusPurpose": "revocation", + "statusListIndex": "12", + "statusListCredential": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": "StatusList2021" + } + """), + mediaType = "application/json") + }) + @Operation( + summary = "Revoke a VerifiableCredential", + description = "Revoke a VerifiableCredential using the provided Credential Status") + public @interface revokeCredentialDocs { + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "Status list credential created/updated successfully.", + content = { + @Content( + examples = + @ExampleObject( + value = """ + { + "id": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1#17", + "statusPurpose": "revocation", + "statusListIndex": "17", + "statusListCredential": "https://977d-203-129-213-107.ngrok-free.app/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": "StatusList2021" + } + """), + mediaType = "application/json") + }), + @ApiResponse( + responseCode = "401", + description = "UnauthorizedException: invalid token", + content = @Content()), + @ApiResponse( + responseCode = "403", + description = "ForbiddenException: invalid caller", + content = @Content()), + @ApiResponse( + responseCode = "500", + description = "RevocationServiceException: Internal Server Error", + content = @Content()) + }) + @RequestBody( + content = { + @Content( + examples = + @ExampleObject( + value = """ + { + "purpose": "revocation", + "issuerId": "did:web:localhost:BPNL000000000000" + } + """), + mediaType = "application/json") + }) + @Operation( + summary = "Create or Update a Status List Credential", + description = "Create the status list credential if it does not exist, else update it.") + public @interface StatusEntryApiDocs { + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "Get Status list credential ", + content = { + @Content( + examples = + @ExampleObject( + value = """ + { + "@context": + [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "http://localhost/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": + [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "issuanceDate": "2024-02-05T09:39:58Z", + "credentialSubject": + [ + { + "statusPurpose": "revocation", + "id": "http://localhost/api/v1/revocations/credentials/BPNL000000000000/revocation/1", + "type": "StatusList2021", + "encodedList": "H4sIAAAAAAAA/wMAAAAAAAAAAAA=" + } + ], + "proof": + { + "proofPurpose": "assertionMethod", + "type": "JsonWebSignature2020", + "verificationMethod": "did:web:localhost:BPNL000000000000#ed463e4c-b900-481a-b5d0-9ae439c434ae", + "created": "2024-02-05T09:39:58Z", + "jws": "eyJhbGciOiJFZERTQSJ9..swX1PLJkSlxB6JMmY4a2uUzR-uszlyLrVdNppoYSx4PTV1LzQrDb0afzp_dvTNUWEYDI57a8iPh78BDjqMjSDQ" + } + } + """), + mediaType = "application/json") + }), + @ApiResponse( + responseCode = "404", + description = "Status list credential not found", + content = @Content()), + @ApiResponse( + responseCode = "500", + description = "RevocationServiceException: Internal Server Error", + content = @Content()) + }) + @Operation( + summary = "Get status list credential", + description = + "Get status list credential using the provided issuer BPN and status purpose and status list index") + public @interface GetStatusListCredentialDocs { + } + + @Parameter(description = "Issuer BPN", example = "BPNL000000000000") + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface IssuerBPNPathParamDoc { + } + + @Parameter(description = "Status Purpose ( Revocation or Suspension)", example = "revocation") + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface StatusPathParamDoc { + } + + @Parameter(description = "status list index", example = "1") + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface IndexPathParamDoc { + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/ApplicationConfig.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/ApplicationConfig.java new file mode 100644 index 000000000..6664b30e9 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/ApplicationConfig.java @@ -0,0 +1,51 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.text.StringEscapeUtils; +import org.springdoc.core.properties.SwaggerUiConfigProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@ConditionalOnProperty(name = "springdoc.swagger-ui.enabled", havingValue = "true") +@Slf4j +public class ApplicationConfig implements WebMvcConfigurer { + + private final SwaggerUiConfigProperties properties; + + @Autowired + public ApplicationConfig(SwaggerUiConfigProperties properties) { + this.properties = properties; + } + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + String redirectUri = properties.getPath(); + log.info("Set landing page to path {}", StringEscapeUtils.escapeJava(redirectUri)); + registry.addRedirectViewController("/", redirectUri); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/ExceptionHandling.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/ExceptionHandling.java new file mode 100644 index 000000000..f8b22a5ff --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/ExceptionHandling.java @@ -0,0 +1,67 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.revocation.exception.CredentialAlreadyRevokedException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +@Slf4j +public class ExceptionHandling { + + public static final String TIMESTAMP_KEY = "timestamp"; + + @ExceptionHandler(CredentialAlreadyRevokedException.class) + ProblemDetail handleCredentialAlreadyRevokedException(CredentialAlreadyRevokedException e) { + String errorMsg = e.getMessage(); + ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, errorMsg); + problemDetail.setTitle("Revocation service error"); + problemDetail.setProperty(TIMESTAMP_KEY, System.currentTimeMillis()); + return problemDetail; + } + + + @ExceptionHandler(ForbiddenException.class) + ProblemDetail handleForbiddenException(ForbiddenException e) { + String errorMsg = ExceptionUtils.getMessage(e); + ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.FORBIDDEN, errorMsg); + problemDetail.setTitle(errorMsg); + problemDetail.setProperty(TIMESTAMP_KEY, System.currentTimeMillis()); + return problemDetail; + } + + @ExceptionHandler(IllegalArgumentException.class) + ProblemDetail handleIllegalArgumentException(IllegalArgumentException e) { + String errorMsg = ExceptionUtils.getMessage(e); + ProblemDetail problemDetail = + ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, errorMsg); + problemDetail.setTitle(errorMsg); + problemDetail.setProperty(TIMESTAMP_KEY, System.currentTimeMillis()); + return problemDetail; + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/MIWSettings.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/MIWSettings.java new file mode 100644 index 000000000..67a71e9fb --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/MIWSettings.java @@ -0,0 +1,40 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.net.URI; +import java.util.List; + +@ConfigurationProperties(prefix = "revocation.miw") +public record MIWSettings(List vcContexts) { + + public MIWSettings { + if (vcContexts == null) { + throw new NullPointerException("vcContexts cannot be null"); + } + if (vcContexts.isEmpty()) { + throw new IllegalArgumentException("vcContexts cannot be empty"); + } + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/OpenApiConfig.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/OpenApiConfig.java new file mode 100644 index 000000000..0f09edc70 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/OpenApiConfig.java @@ -0,0 +1,106 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import lombok.AllArgsConstructor; +import org.eclipse.tractusx.managedidentitywallets.revocation.config.security.SecurityConfigProperties; +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; + +import java.util.Collections; + +/** + * OpenApiConfig is used for managing the swagger with basic security setup if security is enabled. + */ +@Configuration +@AllArgsConstructor +public class OpenApiConfig { + + private final SecurityConfigProperties properties; + + /** + * Open api open api. + * + * @return the open api + */ + @Bean + public OpenAPI openAPI() { + Info info = new Info(); + info.setTitle("Reovcation Service API"); + info.setDescription("Revocation Service API"); + info.setVersion("0.0.1"); + + Contact contact = new Contact(); + contact.name("eclipse-tractusx"); + contact.email("tractusx-dev@eclipse.org"); + contact.url("https://projects.eclipse.org/projects/automotive.tractusx"); + info.contact(contact); + + OpenAPI openAPI = new OpenAPI(); + if (Boolean.TRUE.equals(properties.enabled())) { + openAPI = enableSecurity(openAPI); + } + return openAPI.info(info); + } + + /** + * Open api definition grouped open api. + * + * @return the grouped open api + */ + @Bean + public GroupedOpenApi openApiDefinition() { + return GroupedOpenApi.builder().group("docs").pathsToMatch("/**").displayName("Docs").build(); + } + + private OpenAPI enableSecurity(OpenAPI openAPI) { + Components components = new Components(); + + // Auth using access_token + String accessTokenAuth = "Authenticate using access_token"; + components.addSecuritySchemes( + accessTokenAuth, + new SecurityScheme() + .name(accessTokenAuth) + .description( + """ + **Bearer (apiKey)** + JWT Authorization header using the Bearer scheme. + Enter **Bearer** [space] and then your token in the text input below. + Example: Bearer 12345abcdef""") + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .name(HttpHeaders.AUTHORIZATION)); + return openAPI + .components(components) + .addSecurityItem( + new SecurityRequirement().addList(accessTokenAuth, Collections.emptyList())); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/CustomAuthenticationConverter.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/CustomAuthenticationConverter.java new file mode 100644 index 000000000..92bca9d85 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/CustomAuthenticationConverter.java @@ -0,0 +1,78 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config.security; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; + +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * The type Custom authentication converter. + */ +public class CustomAuthenticationConverter implements Converter { + + private final JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter; + private final String resourceId; + + /** + * Instantiates a new Custom authentication converter. + * + * @param resourceId the resource id + */ + public CustomAuthenticationConverter(String resourceId) { + this.resourceId = resourceId; + grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + } + + @Override + public AbstractAuthenticationToken convert(Jwt source) { + Collection authorities = grantedAuthoritiesConverter.convert(source); + Optional.of(authorities).ifPresent(a -> a.addAll(extractResourceRoles(source, resourceId))); + return new JwtAuthenticationToken(source, authorities); + } + + private Collection extractResourceRoles(Jwt jwt, String resourceId) { + Map resourceAccess = jwt.getClaim("resource_access"); + Map resource = (Map) resourceAccess.get(resourceId); + if (Objects.isNull(resource)) { + return Set.of(); + } + Collection resourceRoles = (Collection) resource.get("roles"); + if (Objects.isNull(resourceRoles)) { + return Set.of(); + } + return resourceRoles.stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) + .collect(Collectors.toSet()); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/SecurityConfig.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/SecurityConfig.java new file mode 100644 index 000000000..ca9e786fb --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/SecurityConfig.java @@ -0,0 +1,119 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config.security; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.ApplicationRole; +import org.eclipse.tractusx.managedidentitywallets.revocation.constant.RevocationApiEndpoints; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAnyRole; + +@Slf4j +@EnableWebSecurity +@EnableMethodSecurity(securedEnabled = true) +@Configuration +@AllArgsConstructor +public class SecurityConfig { + + private final SecurityConfigProperties securityConfigProperties; + + /** + * Filter chain security filter chain. + * + * @param http the http + * @return the security filter chain + * @throws Exception the exception + */ + @Bean + @ConditionalOnProperty( + value = "service.security.enabled", + havingValue = "true", + matchIfMissing = true) + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.cors(Customizer.withDefaults()) + .csrf(AbstractHttpConfigurer::disable) + .headers( + httpSecurityHeadersConfigurer -> + httpSecurityHeadersConfigurer + .xssProtection(Customizer.withDefaults()) + .contentSecurityPolicy( + contentSecurityPolicyConfig -> + contentSecurityPolicyConfig.policyDirectives("script-src 'self'"))) + .sessionManagement( + sessionManagement -> + sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests( + authorizeHttpRequests -> + authorizeHttpRequests + .requestMatchers("/") + .permitAll() // forwards to swagger + .requestMatchers("/docs/api-docs/**") + .permitAll() + .requestMatchers("/ui/swagger-ui/**") + .permitAll() + .requestMatchers("/error") + .permitAll() + .requestMatchers(new AntPathRequestMatcher("/actuator/health/**")) + .permitAll() + .requestMatchers(new AntPathRequestMatcher("/actuator/loggers/**")) + .hasRole(ApplicationRole.ROLE_MANAGE_APP) + .requestMatchers( + HttpMethod.GET, RevocationApiEndpoints.REVOCATION_API + "/credentials/**") + .permitAll() + .requestMatchers(RevocationApiEndpoints.REVOCATION_API + "/**") + .access(hasAnyRole(ApplicationRole.ROLE_UPDATE_WALLET))) + .oauth2ResourceServer( + resourceServer -> + resourceServer.jwt( + jwt -> + jwt.jwtAuthenticationConverter( + new CustomAuthenticationConverter( + securityConfigProperties.clientId())))); + return http.build(); + } + + /** + * Security customizer web security customizer. + * + * @return the web security customizer + */ + @Bean + @ConditionalOnProperty(value = "service.security.enabled", havingValue = "false") + public WebSecurityCustomizer securityCustomizer() { + log.warn("Disable security : This is not recommended to use in production environments."); + return web -> web.ignoring().requestMatchers(new AntPathRequestMatcher("**")); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/SecurityConfigProperties.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/SecurityConfigProperties.java new file mode 100644 index 000000000..0c981ed1b --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/SecurityConfigProperties.java @@ -0,0 +1,37 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config.security; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The type Security config properties. + */ +@ConfigurationProperties("revocation.security.keycloak") +public record SecurityConfigProperties( + Boolean enabled, + String clientId, + String publicClientId, + String authUrl, + String tokenUrl, + String refreshTokenUrl) { +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/constant/RevocationApiEndpoints.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/constant/RevocationApiEndpoints.java new file mode 100644 index 000000000..25f040c92 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/constant/RevocationApiEndpoints.java @@ -0,0 +1,38 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.constant; + +public class RevocationApiEndpoints { + + public static final String REVOCATION_API = "/api/v1/revocations"; + public static final String CREDENTIALS = "/api/credentials"; + public static final String REVOKE = "/revoke"; + public static final String VERIFY = "/verify"; + public static final String STATUS_ENTRY = "/status-entry"; + public static final String CREDENTIALS_BY_ISSUER = "/credentials"; + public static final String CREDENTIALS_STATUS_INDEX = + CREDENTIALS_BY_ISSUER + "/{issuerBPN}/{status}/{index}"; + + private RevocationApiEndpoints() { + // static + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/BaseController.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/controllers/BaseController.java similarity index 73% rename from miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/BaseController.java rename to revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/controllers/BaseController.java index dd3f958ea..341a47806 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/BaseController.java +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/controllers/BaseController.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,11 +19,10 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.controller; +package org.eclipse.tractusx.managedidentitywallets.revocation.controllers; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; -import org.eclipse.tractusx.managedidentitywallets.exception.ForbiddenException; -import org.eclipse.tractusx.managedidentitywallets.utils.Validate; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; @@ -31,9 +30,6 @@ import java.util.Map; import java.util.TreeMap; -/** - * The type Base controller. - */ public class BaseController { /** @@ -45,12 +41,12 @@ public class BaseController { public String getBPNFromToken(Principal principal) { Object principal1 = ((JwtAuthenticationToken) principal).getPrincipal(); Jwt jwt = (Jwt) principal1; - - //this will misbehave if we have more then one claims with different case + // this will misbehave if we have more then one claims with different case // ie. BPN=123456 and bpn=789456 Map claims = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); claims.putAll(jwt.getClaims()); - Validate.isFalse(claims.containsKey(StringPool.BPN)).launch(new ForbiddenException("Invalid token, BPN not found")); + Validate.isFalse(claims.containsKey(StringPool.BPN)) + .launch(new SecurityException("Invalid token, BPN not found")); return claims.get(StringPool.BPN).toString(); } } diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/controllers/RevocationApiController.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/controllers/RevocationApiController.java new file mode 100644 index 000000000..8c1ed6196 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/controllers/RevocationApiController.java @@ -0,0 +1,152 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.controllers; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; +import org.eclipse.tractusx.managedidentitywallets.revocation.apidocs.RevocationApiControllerApiDocs; +import org.eclipse.tractusx.managedidentitywallets.revocation.constant.RevocationApiEndpoints; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.CredentialStatusDto; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.StatusEntryDto; +import org.eclipse.tractusx.managedidentitywallets.revocation.exception.RevocationServiceException; +import org.eclipse.tractusx.managedidentitywallets.revocation.services.RevocationService; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; +import java.util.Map; + +/** + * The RevocationApiController class is a REST controller that handles revocation-related API + * endpoints. + */ +@RestController +@RequestMapping(RevocationApiEndpoints.REVOCATION_API) +@RequiredArgsConstructor +@Slf4j +@Tag(name = "Revocation Service", description = "Revocation Service API") +public class RevocationApiController extends BaseController { + + private final RevocationService revocationService; + + /** + * The above function is a Java POST endpoint that creates a status list for a credential using + * the provided DTO. + * + * @param dto The parameter "dto" is of type "StatusEntryDto" and is annotated with + * "@RequestBody". It is used to receive the request body data from the client. The "@Valid" + * annotation is used to perform validation on the "dto" object based on the validation + * constraints defined in the "StatusEntry + * @param token The authentication token + * @return The method is returning a CredentialStatusDto object. + */ + @RevocationApiControllerApiDocs.StatusEntryApiDocs + @PostMapping( + value = RevocationApiEndpoints.STATUS_ENTRY, + produces = MediaType.APPLICATION_JSON_VALUE) + public CredentialStatusDto createStatusListVC( + @Valid @RequestBody StatusEntryDto dto, + @Parameter(hidden = true) @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token, + Principal principal) { + Validate.isFalse( + getBPNFromToken(principal).equals(revocationService.extractBpnFromDid(dto.issuerId()))) + .launch(new ForbiddenException("invalid caller")); + return revocationService.createStatusList(dto, token); + } + + /** + * The above function is a Java POST endpoint that revokes a credential and returns an HTTP status + * code. + * + * @param dto The `dto` parameter is of type `CredentialStatusDto` and is annotated with + * `@RequestBody`. This means that it is expected to be the request body of the HTTP POST + * request. The `@Valid` annotation indicates that the `dto` object should be validated before + * being processed further. + * @param token The authentication token + * @return The method is returning a ResponseEntity object with a HttpStatus of OK. + */ + @RevocationApiControllerApiDocs.revokeCredentialDocs + @PostMapping(RevocationApiEndpoints.REVOKE) + public ResponseEntity revokeCredential( + @Valid @RequestBody CredentialStatusDto dto, + @Parameter(hidden = true) @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token, + Principal principal) + throws RevocationServiceException { + Validate.isFalse( + getBPNFromToken(principal).equals(revocationService.extractBpnFromURL(dto.id()))) + .launch(new ForbiddenException("Invalid caller")); + revocationService.revoke(dto, token); + return new ResponseEntity<>(HttpStatus.OK); + } + + + @RevocationApiControllerApiDocs.verifyCredentialDocs + @PostMapping(RevocationApiEndpoints.VERIFY) + public ResponseEntity> verifyRevocation( + @Valid @RequestBody CredentialStatusDto dto, + @Parameter(hidden = true) @RequestHeader(name = HttpHeaders.AUTHORIZATION) String token, + Principal principal) { + Validate.isFalse( + getBPNFromToken(principal).equals(revocationService.extractBpnFromURL(dto.id()))) + .launch(new ForbiddenException("Invalid caller")); + + + return ResponseEntity.ofNullable(revocationService.verifyStatus(dto)); + } + + /** + * The function `getCredentialsByIssuerId` retrieves a list of credentials by their issuer ID. + * + * @param issuerBPN The `issuerBPN` parameter is a string that represents the BPn of the + * issuer. + * @return The method is returning a ResponseEntity object that wraps a VerifiableCredential + * object. + */ + @RevocationApiControllerApiDocs.GetStatusListCredentialDocs + @GetMapping( + path = RevocationApiEndpoints.CREDENTIALS_STATUS_INDEX, + produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getStatusListCredential( + @RevocationApiControllerApiDocs.IssuerBPNPathParamDoc @PathVariable(name = "issuerBPN") String issuerBPN, + @RevocationApiControllerApiDocs.StatusPathParamDoc @PathVariable(name = "status") String status, + @RevocationApiControllerApiDocs.IndexPathParamDoc @PathVariable(name = "index") String index) { + log.debug("received get list for {}", issuerBPN); + return ResponseEntity.ofNullable( + revocationService.getStatusListCredential( + issuerBPN.toUpperCase(), status.toLowerCase(), index)); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/domain/BPN.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/domain/BPN.java new file mode 100644 index 000000000..9a53b0073 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/domain/BPN.java @@ -0,0 +1,62 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.domain; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BPN { + + private static final String PATTERN = "^(BPN)([LSA])[0-9A-Z]{12}"; + + private final String value; + + public BPN(final String bpn) { + + Pattern pattern = Pattern.compile(PATTERN); + Matcher matcher = pattern.matcher(bpn); + if (!matcher.matches()) { + throw new IllegalArgumentException("BPN %s is not valid".formatted(bpn)); + } + + this.value = bpn; + } + + public String value() { + return value; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final BPN bpn = (BPN) o; + + return value.equals(bpn.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/CredentialStatusDto.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/CredentialStatusDto.java new file mode 100644 index 000000000..e3f0bdc10 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/CredentialStatusDto.java @@ -0,0 +1,48 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.RevocationPurpose; +import org.eclipse.tractusx.managedidentitywallets.revocation.utils.BitSetManager; + +public record CredentialStatusDto( + @NotBlank @NotNull @JsonProperty("id") String id, + @NotBlank @NotNull @JsonProperty("statusPurpose") String statusPurpose, + @NotBlank @NotNull @JsonProperty("statusListIndex") String statusListIndex, + @NotBlank @NotNull @JsonProperty("statusListCredential") String statusListCredential, + @NotBlank @NotNull @JsonProperty("type") String type) { + public CredentialStatusDto { + if (Integer.parseInt(statusListIndex) < 0 + || Integer.parseInt(statusListIndex) > BitSetManager.BITSET_SIZE - 1) { + throw new IllegalArgumentException("statusListIndex is out of range"); + } + if (!statusPurpose.equalsIgnoreCase(RevocationPurpose.REVOCATION.name())) { + throw new IllegalArgumentException("invalid statusPurpose"); + } + if (!type.equals(StatusListCredentialSubject.TYPE_ENTRY)) { + throw new IllegalArgumentException("invalid type"); + } + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusEntryDto.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusEntryDto.java new file mode 100644 index 000000000..8c033c24a --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusEntryDto.java @@ -0,0 +1,41 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.RevocationPurpose; + +@Valid +public record StatusEntryDto( + // #TODO should be an Identifier (add validation) + @NotNull @NotBlank @JsonProperty("purpose") String purpose, + @NotNull @NotBlank @JsonProperty("issuerId") String issuerId) { + + public StatusEntryDto { + if (!purpose.equalsIgnoreCase(RevocationPurpose.REVOCATION.name())) { + throw new IllegalArgumentException("purpose should only be revocation at this time"); + } + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusListCredentialSubject.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusListCredentialSubject.java new file mode 100644 index 000000000..02971bb65 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusListCredentialSubject.java @@ -0,0 +1,42 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.dto; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder() +public class StatusListCredentialSubject { + public static final String TYPE_ENTRY = "StatusList2021Entry"; + public static final String TYPE_CREDENTIAL = "StatusList2021"; + public static final String TYPE_LIST = "StatusList2021Credential"; + + public static final String SUBJECT_ID = "id"; + public static final String SUBJECT_TYPE = "type"; + public static final String SUBJECT_STATUS_PURPOSE = "statusPurpose"; + public static final String SUBJECT_ENCODED_LIST = "encodedList"; + private final String type; + private String id; + private String statusPurpose; + private String encodedList; +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/TokenResponse.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/TokenResponse.java new file mode 100644 index 000000000..9dc3def98 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/TokenResponse.java @@ -0,0 +1,34 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.dto; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TokenResponse { + + @JsonAlias("access_token") + private String accessToken; +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/BitSetManagerException.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/BitSetManagerException.java new file mode 100644 index 000000000..c20bbc120 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/BitSetManagerException.java @@ -0,0 +1,45 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.exception; + +public class BitSetManagerException extends RuntimeException { + + public BitSetManagerException() { + } + + public BitSetManagerException(String message) { + super(message); + } + + public BitSetManagerException(String message, Throwable cause) { + super(message, cause); + } + + public BitSetManagerException(Throwable cause) { + super(cause); + } + + public BitSetManagerException( + String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/CredentialAlreadyRevokedException.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/CredentialAlreadyRevokedException.java new file mode 100644 index 000000000..7d8ac72c5 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/CredentialAlreadyRevokedException.java @@ -0,0 +1,29 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.exception; + +public class CredentialAlreadyRevokedException extends BitSetManagerException { + + public CredentialAlreadyRevokedException(String message) { + super(message); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/RevocationServiceException.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/RevocationServiceException.java new file mode 100644 index 000000000..3e0672fd9 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/RevocationServiceException.java @@ -0,0 +1,62 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.exception; + +/** + * Custom exception class to encapsulate service-related exceptions. + */ +public class RevocationServiceException extends Exception { + + /** + * Constructs a new exception with the specified detail message. The cause is not initialized. + * + * @param message the detail message. The detail message is saved for later retrieval by the + * {@link #getMessage()} method. + */ + public RevocationServiceException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and cause. + * + * @param message the detail message. The detail message is saved for later retrieval by the + * {@link #getMessage()} method. + * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). + * A null value is permitted, and indicates that the cause is nonexistent or unknown. + */ + public RevocationServiceException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new exception with the specified cause and a detail message of (cause==null ? null + * : cause.toString()) (which typically contains the class and detail message of cause). This + * constructor is useful for exceptions that are little more than wrappers for other throwables. + * + * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). + * A null value is permitted, and indicates that the cause is nonexistent or unknown. + */ + public RevocationServiceException(Throwable cause) { + super(cause); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListCredential.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListCredential.java new file mode 100644 index 000000000..654c6dd83 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListCredential.java @@ -0,0 +1,95 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.jpa; + +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.OneToOne; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.eclipse.tractusx.managedidentitywallets.revocation.validation.ValidVerifiableCredential; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class StatusListCredential { + @Id + @Column(name = "id", length = 256) // Adjust length as per your schema constraints + @NotBlank(message = "ID cannot be blank") + @Size(max = 256, message = "ID cannot exceed 256 characters") + private String id; + + /** + * issuerBpn is a string field that represents the issuer' BPN with the status Purpose at the end + * + *

Example: "BPNL123456789123" + */ + @Column(name = "issuer_bpn", length = 16) // Adjust length as per your schema constraints + @NotBlank(message = "Issuer BPN cannot be blank") + @Size(max = 16, message = "Issuer Bpn cannot exceed 16 characters") + private String issuerBpn; + + // Annotation @Lob indicates that the field should be persisted as a large object to the database. + @Lob + @ValidVerifiableCredential // Custom validation annotation + @NotNull(message = "Credential cannot be null") + @Column(name = "credential", columnDefinition = "TEXT") + @Convert(converter = StringToCredentialConverter.class) + private VerifiableCredential credential; + + @CreationTimestamp + @Column(name = "created_at", updatable = false, nullable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "modified_at", nullable = false) + private LocalDateTime updatedAt; + + // FetchType.LAZY should be used for large objects as Lob + @OneToOne(mappedBy = "statusListCredential", fetch = FetchType.LAZY) + private StatusListIndex index; + + // Ensure proper validation inside the setter if additional rules are needed + public void setCredential(VerifiableCredential vc) { + if (vc != null) { + credential = vc; + } else { + throw new IllegalArgumentException("Credential cannot be null"); + } + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListIndex.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListIndex.java new file mode 100644 index 000000000..7ead8470f --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListIndex.java @@ -0,0 +1,79 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.jpa; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class StatusListIndex { + @Id + @Column(name = "id", length = 256) // Adjust length as per your schema constraints + @NotBlank(message = "ID cannot be blank") + @Size(max = 256, message = "ID cannot exceed 256 characters") + private String id; + + /** + * issuerBpn is a string field that represents the issuer' BPN with the status Purpose at the end + * + *

Example: "issuerBpn-revocation" + */ + @Column(name = "issuer_bpn_status", length = 27) // Adjust length as per your schema constraints + @NotBlank(message = "Issuer BPN with status cannot be blank") + @Size(max = 27, message = "Issuer Bpn with status cannot exceed 27 characters") + private String issuerBpnStatus; + + @Column(name = "current_index", length = 16) // Adjust length as per your schema constraints + @NotBlank(message = "Current index cannot be blank") + @Pattern(regexp = "^[0-9]+$", message = "Current index must be numeric") + @Size(max = 16, message = "Current index cannot exceed 16 characters") + private String currentIndex; + + // Using LAZY fetching strategy to fetch statusListCredential on-demand + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "status_list_credential_id", referencedColumnName = "id") + private StatusListCredential statusListCredential; + + public void setCurrentIndex(String index) { + if (index != null && !index.trim().isEmpty() && index.matches("^[0-9]+$")) { + this.currentIndex = index; + } else { + throw new IllegalArgumentException("Invalid index value"); + } + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StringToCredentialConverter.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StringToCredentialConverter.java new file mode 100644 index 000000000..b4c7c84ec --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StringToCredentialConverter.java @@ -0,0 +1,52 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.jpa; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import lombok.SneakyThrows; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; + +import java.util.Map; + +@Converter +public class StringToCredentialConverter + implements AttributeConverter { + + private final ObjectMapper objectMapper; + + public StringToCredentialConverter(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public String convertToDatabaseColumn(VerifiableCredential attribute) { + return attribute.toJson(); + } + + @SneakyThrows + @Override + public VerifiableCredential convertToEntityAttribute(String data) { + return new VerifiableCredential(objectMapper.readValue(data, Map.class)); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/repository/StatusListCredentialRepository.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/repository/StatusListCredentialRepository.java new file mode 100644 index 000000000..9ded87a5b --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/repository/StatusListCredentialRepository.java @@ -0,0 +1,38 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.repository; + +import jakarta.persistence.LockModeType; +import org.eclipse.tractusx.managedidentitywallets.revocation.jpa.StatusListCredential; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface StatusListCredentialRepository + extends JpaRepository { + + @Lock(LockModeType.PESSIMISTIC_WRITE) + Optional findById(String id); +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/repository/StatusListIndexRepository.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/repository/StatusListIndexRepository.java new file mode 100644 index 000000000..4a51da969 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/repository/StatusListIndexRepository.java @@ -0,0 +1,37 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.repository; + +import jakarta.persistence.LockModeType; +import org.eclipse.tractusx.managedidentitywallets.revocation.jpa.StatusListIndex; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface StatusListIndexRepository extends JpaRepository { + + @Lock(LockModeType.PESSIMISTIC_WRITE) + List findByIssuerBpnStatus(String issuerBpnStatus); +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/HttpClientService.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/HttpClientService.java new file mode 100644 index 000000000..dd8f61b05 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/HttpClientService.java @@ -0,0 +1,92 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.services; + +import lombok.RequiredArgsConstructor; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.revocation.config.security.SecurityConfigProperties; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.TokenResponse; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClient; +import org.springframework.web.util.UriComponentsBuilder; + +@Service +@RequiredArgsConstructor +public class HttpClientService { + + private final RestClient restClient = RestClient.create(); + + private final SecurityConfigProperties securityConfigProperties; + + @Value("${revocation.domain.url}") + public String domainUrl; + + @Value("${revocation.miw.url}") + private String miwUrl; + + public String getBearerToken() { + MultiValueMap data = new LinkedMultiValueMap<>(); + data.add("client_id", securityConfigProperties.publicClientId()); + data.add("client_secret", securityConfigProperties.clientId()); + data.add("grant_type", "client_credentials"); + var result = + restClient + .post() + .uri(securityConfigProperties.tokenUrl()) + .accept(MediaType.APPLICATION_FORM_URLENCODED) + .body(data) + .retrieve(); + TokenResponse tokenResponse = result.toEntity(TokenResponse.class).getBody(); + if (tokenResponse == null) { + return null; + } + return tokenResponse.getAccessToken(); + + } + + public VerifiableCredential signStatusListVC(VerifiableCredential vc, String token) { + String uri = + UriComponentsBuilder.fromHttpUrl(miwUrl) + .path("/api/credentials") + .queryParam(StringPool.REVOCABLE, "false") + .queryParam(StringPool.AS_JWT, "false") + .build() + .toUriString(); + + var result = + restClient + .post() + .uri(uri) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, token) + .body(vc.toJson()) + .retrieve(); + return result.toEntity(VerifiableCredential.class).getBody(); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/RevocationService.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/RevocationService.java new file mode 100644 index 000000000..a99adabe8 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/RevocationService.java @@ -0,0 +1,439 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.services; + + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.CredentialStatus; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.revocation.config.MIWSettings; +import org.eclipse.tractusx.managedidentitywallets.revocation.constant.RevocationApiEndpoints; +import org.eclipse.tractusx.managedidentitywallets.revocation.domain.BPN; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.CredentialStatusDto; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.StatusEntryDto; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.StatusListCredentialSubject; +import org.eclipse.tractusx.managedidentitywallets.revocation.exception.BitSetManagerException; +import org.eclipse.tractusx.managedidentitywallets.revocation.exception.RevocationServiceException; +import org.eclipse.tractusx.managedidentitywallets.revocation.jpa.StatusListCredential; +import org.eclipse.tractusx.managedidentitywallets.revocation.jpa.StatusListIndex; +import org.eclipse.tractusx.managedidentitywallets.revocation.repository.StatusListCredentialRepository; +import org.eclipse.tractusx.managedidentitywallets.revocation.repository.StatusListIndexRepository; +import org.eclipse.tractusx.managedidentitywallets.revocation.utils.BitSetManager; +import org.eclipse.tractusx.managedidentitywallets.revocation.utils.CommonUtils; +import org.eclipse.tractusx.ssi.lib.did.resolver.DidResolver; +import org.eclipse.tractusx.ssi.lib.did.web.DidWebResolver; +import org.eclipse.tractusx.ssi.lib.did.web.util.DidWebParser; +import org.eclipse.tractusx.ssi.lib.exception.did.DidParseException; +import org.eclipse.tractusx.ssi.lib.exception.json.TransformJsonLdException; +import org.eclipse.tractusx.ssi.lib.exception.key.InvalidPublicKeyFormatException; +import org.eclipse.tractusx.ssi.lib.exception.proof.NoVerificationKeyFoundException; +import org.eclipse.tractusx.ssi.lib.exception.proof.SignatureParseException; +import org.eclipse.tractusx.ssi.lib.exception.proof.SignatureVerificationFailedException; +import org.eclipse.tractusx.ssi.lib.exception.proof.UnsupportedSignatureTypeException; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialBuilder; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialSubject; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialType; +import org.eclipse.tractusx.ssi.lib.proof.LinkedDataProofValidation; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.net.URI; +import java.net.http.HttpClient; +import java.time.Instant; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * The `RevocationService` class is a Java service that handles the revocation of credentials and + * the creation of status lists. + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class RevocationService { + + public static final String ENCODED_LIST = "encodedList"; + + private final StatusListCredentialRepository statusListCredentialRepository; + + private final StatusListIndexRepository statusListIndexRepository; + + private final HttpClientService httpClientService; + + private final MIWSettings miwSettings; + + /** + * Verifies the status of a credential based on the provided CredentialStatusDto object. + * + * @param statusDto The CredentialStatusDto object containing the necessary information for status verification. + * @return A Map object with the key "status" and the value "revoked" or "active" indicating the status of the credential. + * @throws BadDataException If the status list VC is not found for the issuer. + */ + @Transactional + + public Map verifyStatus(CredentialStatusDto statusDto) { + + validateCredentialStatus(statusDto); + + String url = statusDto.statusListCredential(); + + String[] values = CommonUtils.extractValuesFromURL(url); + VerifiableCredential statusListCredential = getStatusListCredential(values[0], values[1], values[2]); + if (Objects.isNull(statusListCredential)) { + log.error("Status list VC not found for issuer -> {}", + values[0]); + throw new BadDataException("Status list VC not found for issuer -> " + values[0]); + } + + //validate status list VC + validateStatusListVC(statusListCredential); + + String encodedList = statusListCredential.getCredentialSubject().get(0).get(ENCODED_LIST).toString(); + + BitSet bitSet = BitSetManager.decompress(BitSetManager.decodeFromString(encodedList)); + int index = Integer.parseInt(statusDto.statusListIndex()); + boolean status = bitSet.get(index); + return Map.of(StringPool.STATUS, status ? CredentialStatus.REVOKED.getName() : CredentialStatus.ACTIVE.getName()); + } + + + private void validateStatusListVC(VerifiableCredential statusListCredential) { + HttpClient httpClient = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.ALWAYS) + .build(); + DidResolver didResolver = new DidWebResolver(httpClient, new DidWebParser(), true); + LinkedDataProofValidation proofValidation = LinkedDataProofValidation.newInstance(didResolver); + boolean valid = false; + try { + valid = proofValidation.verify(statusListCredential); + } catch (UnsupportedSignatureTypeException | SignatureParseException | DidParseException | + InvalidPublicKeyFormatException | SignatureVerificationFailedException | + NoVerificationKeyFoundException | TransformJsonLdException e) { + log.error("Verification failed with error -> {}", e.getMessage(), e); + } + if (!valid) { + throw new BadDataException("Status list credential is not valid"); + } + } + + /** + * The `revoke` function revokes a credential by updating the status list credential with a new + * subject and saving it to the repository. + * + * @param dto The `dto` parameter is an instance of the `CredentialStatusDto` class. + * @param token the token + * @throws RevocationServiceException the revocation service exception + */ + @Transactional + public void revoke(CredentialStatusDto dto, String token) throws RevocationServiceException { + StatusListCredential statusListCredential; + StatusListCredentialSubject newSubject; + VerifiableCredential statusListVC; + VerifiableCredential signedStatusListVC; + String encodedList; + VerifiableCredentialSubject subjectCredential; + + validateCredentialStatus(dto); + + statusListCredential = + statusListCredentialRepository + .findById(extractIdFromURL(dto.statusListCredential())) + .orElseThrow(() -> new RevocationServiceException("Status list credential not found")); + statusListVC = statusListCredential.getCredential(); + subjectCredential = statusListVC.getCredentialSubject().get(0); + encodedList = (String) subjectCredential.get(StatusListCredentialSubject.SUBJECT_ENCODED_LIST); + String newEncodedList; + try { + newEncodedList = + BitSetManager.revokeCredential(encodedList, Integer.parseInt(dto.statusListIndex())); + } catch (BitSetManagerException e) { + log.error(null, e); + throw new RevocationServiceException(e); + } + newSubject = + StatusListCredentialSubject.builder() + .id((String) subjectCredential.get(StatusListCredentialSubject.SUBJECT_ID)) + .type(StatusListCredentialSubject.TYPE_CREDENTIAL) + .statusPurpose( + (String) subjectCredential.get(StatusListCredentialSubject.SUBJECT_STATUS_PURPOSE)) + .encodedList(newEncodedList) + .build(); + statusListVC.remove("proof"); + // #TODO credentialSubject should not be a list fix that in SSI LIB + statusListVC.put("credentialSubject", List.of(createCredentialSubject(newSubject))); + signedStatusListVC = httpClientService.signStatusListVC(statusListVC, token); + statusListCredential.setCredential(signedStatusListVC); + log.info("Revoked credential with id:{} , index->{}", dto.id(), dto.statusListIndex()); + statusListCredentialRepository.saveAndFlush(statusListCredential); + } + + /** + * The function creates or updates a status list for a given issuer and purpose, and returns a + * CredentialStatusDto object. + * + * @param dto The parameter "dto" is of type "StatusEntryDto". + * @param token the token + * @return The method is returning a CredentialStatusDto object. + */ + @Transactional + public CredentialStatusDto createStatusList(StatusEntryDto dto, String token) { + StatusListIndex statusListIndex; + String vcUrl; + List statusListIndexs; + String bpn; + + bpn = extractBpnFromDid(dto.issuerId()); + statusListIndexs = + statusListIndexRepository.findByIssuerBpnStatus(bpn + "-" + dto.purpose().toLowerCase()); + statusListIndex = + statusListIndexs.stream() + .filter(li -> Integer.parseInt(li.getCurrentIndex()) + 1 < BitSetManager.BITSET_SIZE) + .findFirst() + .orElseGet( + () -> + createStatusListIndex( + dto, + statusListIndexs.size() + 1, + createStatusListCredential(dto, statusListIndexs.size() + 1, token))); + if (statusListIndex.getCurrentIndex().equals("-1")) { + statusListIndex.setCurrentIndex("0"); + statusListIndexRepository.save(statusListIndex); + log.info("Created new status list for issuer: " + bpn); + } else { + statusListIndex.setCurrentIndex( + String.valueOf(Integer.parseInt(statusListIndex.getCurrentIndex()) + 1)); + statusListIndexRepository.save(statusListIndex); + log.info("Updated status list for issuer: " + bpn); + } + vcUrl = + httpClientService.domainUrl + + RevocationApiEndpoints.REVOCATION_API + + RevocationApiEndpoints.CREDENTIALS_STATUS_INDEX + .replace("{issuerBPN}", bpn) + .replace("{status}", dto.purpose().toLowerCase()) + .replace("{index}", String.valueOf(statusListIndex.getId().split("#")[1])); + return new CredentialStatusDto( + vcUrl + "#" + statusListIndex.getCurrentIndex(), + dto.purpose(), + statusListIndex.getCurrentIndex(), + vcUrl, + StatusListCredentialSubject.TYPE_ENTRY); + } + + + /** + * The function `getStatusLisCredential` retrieves a `VerifiableCredential` object from the + * `statusListCredentialRepository` based on identity + * + * @param issuerBpn the issuer bpn + * @param status the status + * @param index the index + * @return the status list credential + */ + @Transactional + public VerifiableCredential getStatusListCredential( + String issuerBpn, String status, String index) { + return statusListCredentialRepository + .findById(issuerBpn + "-" + status + "#" + index) + .map(StatusListCredential::getCredential) + .orElse(null); + } + + /** + * The function creates a map of key-value pairs representing the properties of a + * StatusListCredentialSubject object. + * + * @param subject The `subject` parameter is an instance of the `StatusListCredentialSubject` + * class. + * @return The method is returning a Map object. + */ + private Map createCredentialSubject(StatusListCredentialSubject subject) { + Map credentialSubjectMap = new HashMap<>(); + credentialSubjectMap.put(StatusListCredentialSubject.SUBJECT_ID, subject.getId()); + credentialSubjectMap.put(StatusListCredentialSubject.SUBJECT_TYPE, subject.getType()); + credentialSubjectMap.put( + StatusListCredentialSubject.SUBJECT_STATUS_PURPOSE, subject.getStatusPurpose()); + credentialSubjectMap.put( + StatusListCredentialSubject.SUBJECT_ENCODED_LIST, subject.getEncodedList()); + return credentialSubjectMap; + } + + /** + * The function creates a status list credential with a given status entry DTO. + * + * @param dto The "dto" parameter is an object of type "StatusEntryDto". It contains information + * about the status entry, such as the issuer ID and the purpose of the status. + * @return The method `createStatusListCredential` returns a `StatusListCredential` object. + */ + @SneakyThrows + private StatusListCredential createStatusListCredential( + StatusEntryDto dto, Integer size, String token) { + String id; + String bpn; + List types = new ArrayList<>(); + VerifiableCredential statusListVC; + StatusListCredentialSubject subject; + types.add(VerifiableCredentialType.VERIFIABLE_CREDENTIAL); + types.add(StatusListCredentialSubject.TYPE_LIST); + + bpn = extractBpnFromDid(dto.issuerId()); + id = + httpClientService.domainUrl + + RevocationApiEndpoints.REVOCATION_API + + RevocationApiEndpoints.CREDENTIALS_STATUS_INDEX + .replace("{issuerBPN}", bpn) + .replace("{status}", dto.purpose().toLowerCase()) + .replace("{index}", String.valueOf(size)); + subject = + StatusListCredentialSubject.builder() + .id(id) + .statusPurpose(dto.purpose().toLowerCase()) + .type(StatusListCredentialSubject.TYPE_CREDENTIAL) + .encodedList(BitSetManager.initializeEncodedListString()) + .build(); + + // TODO credentialSubject should not be a list fix that in SSI LIB + statusListVC = + new VerifiableCredentialBuilder() + .context(miwSettings.vcContexts()) + .id(URI.create(id)) + .type(types) + .issuer(URI.create(dto.issuerId())) + .issuanceDate(Instant.now()) + .credentialSubject(new VerifiableCredentialSubject(createCredentialSubject(subject))) + .build(); + return StatusListCredential.builder() + .id(bpn + "-" + dto.purpose().toLowerCase() + "#" + size) + .issuerBpn(bpn) + .credential(httpClientService.signStatusListVC(statusListVC, token)) + .build(); + } + + + private StatusListIndex createStatusListIndex( + StatusEntryDto dto, Integer size, StatusListCredential statusListCredential) { + String bpn = extractBpnFromDid(dto.issuerId()); + return StatusListIndex.builder() + .id(bpn + "-" + dto.purpose().toLowerCase() + "#" + size) + .currentIndex("-1") + .statusListCredential(statusListCredential) + .issuerBpnStatus(bpn + "-" + dto.purpose().toLowerCase()) + .build(); + } + + /** + * The function extracts a BPN from a credential status. + * + * @param url The `url` parameter is a string that represents a credential status. + * @return The method is returning a string that is a combination of the first part of the "id" + */ + @SneakyThrows + public String extractBpnFromURL(String url) { + Pattern pattern = Pattern.compile("/credentials/(B\\w+)/", Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(url); + if (matcher.find()) { + return matcher.group(1).toUpperCase(); + } else { + throw new IllegalArgumentException("No match found"); + } + } + + /** + * The function extracts an ID from a status credential id . + * + * @param url The `url` parameter is a string that represents a credential status. + * @return The method is returning a string that is a combination of the first part of the "id" + */ + public String extractIdFromURL(String url) { + Pattern pattern = + Pattern.compile("/credentials/(B\\w+)/(.*?)/(\\d+)", Pattern.CASE_INSENSITIVE); + // Create a Matcher object and find the first match in the URL + Matcher matcher = pattern.matcher(url); + if (matcher.find()) { + String bpnlNumber = matcher.group(1); + String purpose = matcher.group(2); + String credentialIndex = matcher.group(3); + return bpnlNumber.toUpperCase() + "-" + purpose + "#" + credentialIndex; + } else { + throw new IllegalArgumentException("No match found"); + } + } + + /** + * The function extracts an ID from a credential status. + * + * @param did The `did` parameter is a string that represents a credential status. + * @return The method is returning a string that is a combination of the first part of the "id" + */ + public String extractBpnFromDid(String did) { + return new BPN(did.substring(did.lastIndexOf(":") + 1).toUpperCase()).value(); + } + + /** + * The function should validate the Credential Status group(1) = BPN Number group(2) = Purpose + * group(3) = status list credential Index group(4) = index of the verifiable credential in the + * status list + * + * @param dto the dto + * @throws IllegalArgumentException if the Credential Status is invalid + */ + public void validateCredentialStatus(CredentialStatusDto dto) { + String domainUrl = httpClientService.domainUrl + RevocationApiEndpoints.REVOCATION_API; + String urlPattern = domainUrl + "/credentials/(B\\w+)/(.*?)/(\\d+)"; + Pattern pattern0 = Pattern.compile(urlPattern, Pattern.CASE_INSENSITIVE); + Pattern pattern1 = Pattern.compile(urlPattern + "#(\\d+)", Pattern.CASE_INSENSITIVE); + Matcher matcher0 = pattern0.matcher(dto.statusListCredential()); + Matcher matcher1 = pattern1.matcher(dto.id()); + if (!matcher0.find()) { + throw new IllegalArgumentException("Invalid credential status url"); + } + if (!matcher1.find()) { + throw new IllegalArgumentException("Invalid credential status id"); + } + + if (!matcher0.group(1).equals(matcher1.group(1)) + || !matcher0.group(2).equals(matcher1.group(2)) + || !matcher0.group(3).equals(matcher1.group(3))) { + throw new IllegalArgumentException("Credential status url and id do not match"); + } + + if (!matcher1.group(4).equals(dto.statusListIndex())) { + throw new IllegalArgumentException( + "Credential status index in the id does not match the current index in the dto"); + } + + if (!matcher0.group(2).equals(dto.statusPurpose())) { + throw new IllegalArgumentException( + "Credential status purpose does not match the statusPurpose in the dto"); + } + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/utils/BitSetManager.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/utils/BitSetManager.java new file mode 100644 index 000000000..a195a3584 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/utils/BitSetManager.java @@ -0,0 +1,105 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.utils; + +import org.eclipse.tractusx.managedidentitywallets.revocation.exception.BitSetManagerException; +import org.eclipse.tractusx.managedidentitywallets.revocation.exception.CredentialAlreadyRevokedException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; +import java.util.BitSet; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +public class BitSetManager { + + public static final int BITSET_SIZE = 131072; + + private BitSetManager() { + // static methods only + } + + public static String initializeEncodedListString() { + BitSet bitSet = new BitSet(BITSET_SIZE); + byte[] compressedBitSet = compress(bitSet); + return encodeToString(compressedBitSet); + } + + public static byte[] compress(BitSet bitSet) { + byte[] byteArray = bitSet.toByteArray(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream); + gzipOutputStream.write(byteArray); + gzipOutputStream.close(); + return outputStream.toByteArray(); + } catch (IOException e) { + throw new BitSetManagerException(e); + } + } + + public static String revokeCredential(String encodedList, int index) + throws BitSetManagerException { + BitSet bitSet = decompress(decodeFromString(encodedList)); + if (bitSet.get(index)) { + throw new CredentialAlreadyRevokedException("Credential already revoked"); + } + bitSet.set(index); + byte[] compressedBitSet = compress(bitSet); + return encodeToString(compressedBitSet); + } + + public static String suspendCredential(String encodedList, int index) { + BitSet bitSet = decompress(decodeFromString(encodedList)); + bitSet.flip(index); + byte[] compressedBitSet = compress(bitSet); + return encodeToString(compressedBitSet); + } + + public static BitSet decompress(byte[] data) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data); + GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) { + byte[] buffer = new byte[1024]; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + int len; + while ((len = gzipInputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, len); + } + BitSet bitSet = BitSet.valueOf(outputStream.toByteArray()); + outputStream.close(); + return bitSet; + + } catch (IOException e) { + throw new BitSetManagerException(e); + } + } + + public static String encodeToString(byte[] data) { + return Base64.getEncoder().encodeToString(data); + } + + public static byte[] decodeFromString(String data) { + return Base64.getDecoder().decode(data); + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/utils/CommonUtils.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/utils/CommonUtils.java new file mode 100644 index 000000000..767d0c0d6 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/utils/CommonUtils.java @@ -0,0 +1,53 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.utils; + +import lombok.experimental.UtilityClass; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@UtilityClass +public class CommonUtils { + + /** + * Extracts the BPN number, purpose and credential index from the URL + * + * @param url the URL to extract the values from + * @return an array containing the BPN number [0], purpose [1] and credential index [2] + */ + public String[] extractValuesFromURL(String url) { + // Define a regular expression pattern to match the desired parts + Pattern pattern = + Pattern.compile("/credentials/(B\\w+)/(.*?)/(\\d+)", Pattern.CASE_INSENSITIVE); + // Create a Matcher object and find the first match in the URL + Matcher matcher = pattern.matcher(url); + if (matcher.find()) { + String bpnlNumber = matcher.group(1); + String purpose = matcher.group(2); + String credentialIndex = matcher.group(3); + return new String[]{ bpnlNumber.toUpperCase(), purpose, credentialIndex }; + } else { + throw new IllegalArgumentException("No match found"); + } + } +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/validation/ValidVerifiableCredential.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/validation/ValidVerifiableCredential.java new file mode 100644 index 000000000..2df9f5350 --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/validation/ValidVerifiableCredential.java @@ -0,0 +1,43 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Constraint(validatedBy = VerifiableCredentialValidator.class) +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidVerifiableCredential { + String message() default "Invalid Verifiable Credential data"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/validation/VerifiableCredentialValidator.java b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/validation/VerifiableCredentialValidator.java new file mode 100644 index 000000000..d049f11fb --- /dev/null +++ b/revocation-service/src/main/java/org/eclipse/tractusx/managedidentitywallets/revocation/validation/VerifiableCredentialValidator.java @@ -0,0 +1,89 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.validation; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.NonNull; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialSubject; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +public class VerifiableCredentialValidator + implements ConstraintValidator { + + @Override + public void initialize(ValidVerifiableCredential constraintAnnotation) { + // You can add initialization logic here if needed. + } + + @Override + public boolean isValid(VerifiableCredential credential, ConstraintValidatorContext context) { + if (credential == null) { + return true; // @NotNull should handle null cases + } + + // Assuming 'id' within 'credentialSubject' is the field to be validated as a URI + @NonNull + List credentialSubject = credential.getCredentialSubject(); + return validateCredentialSubject(credentialSubject, context); + + // Additional validation checks can be added here, e.g., checking the proof object + } + + private boolean validateCredentialSubject( + List credentialSubjects, ConstraintValidatorContext context) { + // We iterate over the list of credential subjects to validate each one + for (Map subject : credentialSubjects) { + // Extract the 'id' of the credential subject if it exists + Object subjectId = subject.get("id"); + if (!(subjectId instanceof String)) { + addConstraintViolation(context, "credentialSubject.id must be a valid String"); + return false; + } + + // Check for a valid URI in the subject ID + if (!isValidUri((String) subjectId)) { + addConstraintViolation(context, "credentialSubject.id must be a valid URI"); + return false; + } + } + return true; + } + + private void addConstraintViolation(ConstraintValidatorContext context, String message) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(message).addConstraintViolation(); + } + + private boolean isValidUri(String uriStr) { + try { + URI uri = new URI(uriStr); + return uri.isAbsolute(); + } catch (Exception e) { + return false; + } + } +} diff --git a/revocation-service/src/main/resources/application.yaml b/revocation-service/src/main/resources/application.yaml new file mode 100644 index 000000000..3da1057f7 --- /dev/null +++ b/revocation-service/src/main/resources/application.yaml @@ -0,0 +1,130 @@ +################################################################################ +# Copyright (c) 2021,2024 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 +################################################################################ + +#set all env here +revocation: + application: + name: ${APPLICATION_NAME:Revocation} + port: ${APPLICATION_PORT:8081} + profile: ${APPLICATION_PROFILE:local} + database: + host: ${DATABASE_HOST:localhost} + port: ${DATABASE_PORT:5434} + name: ${DATABASE_NAME:revocation} + useSSL: ${DATABASE_USE_SSL_COMMUNICATION:false} + username: ${DATABASE_USERNAME:admin} + password: ${DATABASE_PASSWORD:admin} + connectionPoolSize: ${DATABASE_CONNECTION_POOL_SIZE:10} + swagger: + enableSwaggerUi: ${ENABLE_SWAGGER_UI:true} + enableApiDoc: ${ENABLE_API_DOC:true} + config: + applicationLogLevel: ${APPLICATION_LOG_LEVEL:DEBUG} + security: + service: + enabled: ${SERVICE_SECURITY_ENABLED:true} + keycloak: + enabled: ${KEYCLOAK_ENABLED:true} + realm: ${KEYCLOAK_REALM:miw_test} + clientId: ${KEYCLOAK_CLIENT_ID:miw_private_client} + publicClientId: ${KEYCLOAK_PUBLIC_CLIENT_ID:miw_public_client} #used for swagger login + auth-server-url: ${AUTH_SERVER_URL:http://localhost:28080} + auth-url: ${revocation.security.keycloak.auth-server-url}/realms/${revocation.security.keycloak.realm}/protocol/openid-connect/auth + token-url: ${revocation.security.keycloak.auth-server-url}/realms/${revocation.security.keycloak.realm}/protocol/openid-connect/token + refresh-token-url: ${revocation.security.keycloak.token-url} + miw: + url: ${MIW_URL:https://a888-203-129-213-107.ngrok-free.app} + vcContexts: ${VC_SCHEMA_LINK:https://www.w3.org/2018/credentials/v1, https://w3id.org/vc/status-list/2021/v1} + domain: + url: ${DOMAIN_URL:https://977d-203-129-213-107.ngrok-free.app} + + + +#Spring boot configuration +server: + port: ${revocation.application.port} + shutdown: graceful + compression: + enabled: true +spring: + profiles: + active: ${revocation.application.profile} + liquibase: + change-log: classpath:/db/changelog/changelog-master.xml + application: + name: revocation + datasource: + url: jdbc:postgresql://${revocation.database.host}:${revocation.database.port}/${revocation.database.name}?useSSL=${revocation.database.useSSL} + username: ${revocation.database.username} + password: ${revocation.database.password} + initialization-mode: always + hikari: + maximumPoolSize: ${revocation.database.connectionPoolSize} + jpa: + show-sql: false + security: + oauth2: + resourceserver: + jwt: + #Issuer-uri indicates the iss claims from jwt token + issuer-uri: ${revocation.security.keycloak.auth-server-url}/realms/${revocation.security.keycloak.realm} + jwk-set-uri: ${revocation.security.keycloak.auth-server-url}/realms/${revocation.security.keycloak.realm}/protocol/openid-connect/certs + +## only needed if you want to enable API doc +springdoc: + swagger-ui: + enabled: ${revocation.swagger.enableSwaggerUi} + oauth: + clientId: ${revocation.security.keycloak.publicClientId} #It should be public client created in keycloak + disable-swagger-default-url: true + path: /ui/swagger-ui + show-common-extensions: true + csrf: + enabled: true + api-docs: + enabled: ${revocation.swagger.enableApiDoc} + path: /docs/api-docs + +management: + endpoint: + loggers: + enabled: true + health: + probes: + enabled: true + endpoints: + web: + exposure: + include: '*, pre-stop, loggers' + health: + db: + enabled: true + livenessState: + enabled: true + readinessState: + enabled: true + +# log level +logging: + level: + org: + eclipse: + tractusx: + managedidentitywallets: + revocation: ${APP_LOG_LEVEL:INFO} diff --git a/revocation-service/src/main/resources/db/changelog/changelog-master.xml b/revocation-service/src/main/resources/db/changelog/changelog-master.xml new file mode 100755 index 000000000..01cb26408 --- /dev/null +++ b/revocation-service/src/main/resources/db/changelog/changelog-master.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/revocation-service/src/main/resources/db/changelog/changes/init.sql b/revocation-service/src/main/resources/db/changelog/changes/init.sql new file mode 100755 index 000000000..696ad8b26 --- /dev/null +++ b/revocation-service/src/main/resources/db/changelog/changes/init.sql @@ -0,0 +1,69 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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 + * ****************************************************************************** + */ + +-- liquibase formatted sql + +-- changeset pmanaras:1.0 dbms:postgresql +CREATE TABLE status_list_credential ( + id VARCHAR(256) NOT NULL, + issuer_bpn VARCHAR(16), + credential TEXT NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + modified_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + CONSTRAINT pk_statuslistcredential PRIMARY KEY (id) +); + +CREATE TABLE status_list_index ( + id VARCHAR(256) NOT NULL, + issuer_bpn_status VARCHAR(27), + current_index VARCHAR(16), + status_list_credential_id VARCHAR(256), + CONSTRAINT pk_statuslistindex PRIMARY KEY (id) +); + +ALTER TABLE status_list_index ADD CONSTRAINT uc_statuslistindex_status_list_credential UNIQUE (status_list_credential_id); + +ALTER TABLE status_list_index ADD CONSTRAINT FK_STATUSLISTINDEX_ON_STATUS_LIST_CREDENTIAL FOREIGN KEY (status_list_credential_id) REFERENCES status_list_credential (id); + +-- changeset pmanaras:1.0 dbms:h2 +CREATE TABLE IF NOT EXISTS status_list_credential ( + id VARCHAR(256) NOT NULL, + issuer_bpn VARCHAR(16), + credential CLOB NOT NULL, + created_at TIMESTAMP NOT NULL, + modified_at TIMESTAMP NOT NULL, + CONSTRAINT pk_statuslistcredential PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS status_list_index ( + id VARCHAR(256) NOT NULL, + issuer_bpn_status VARCHAR(27), + current_index VARCHAR(16), + status_list_credential_id VARCHAR(256), + CONSTRAINT pk_statuslistindex PRIMARY KEY (id) +); + +ALTER TABLE status_list_index DROP CONSTRAINT IF EXISTS uc_statuslistindex_status_list_credential; +ALTER TABLE status_list_index DROP CONSTRAINT IF EXISTS FK_STATUSLISTINDEX_ON_STATUS_LIST_CREDENTIAL; + +ALTER TABLE status_list_index ADD CONSTRAINT uc_statuslistindex_status_list_credential UNIQUE (status_list_credential_id); + +ALTER TABLE status_list_index ADD CONSTRAINT FK_STATUSLISTINDEX_ON_STATUS_LIST_CREDENTIAL FOREIGN KEY (status_list_credential_id) REFERENCES status_list_credential (id); diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/TestUtil.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/TestUtil.java new file mode 100644 index 000000000..69df4ade1 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/TestUtil.java @@ -0,0 +1,203 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation; + +import lombok.SneakyThrows; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.StatusListCredentialSubject; +import org.eclipse.tractusx.managedidentitywallets.revocation.jpa.StatusListCredential; +import org.eclipse.tractusx.managedidentitywallets.revocation.jpa.StatusListIndex; +import org.eclipse.tractusx.ssi.lib.crypt.KeyPair; +import org.eclipse.tractusx.ssi.lib.crypt.x25519.X25519Generator; +import org.eclipse.tractusx.ssi.lib.exception.json.TransformJsonLdException; +import org.eclipse.tractusx.ssi.lib.exception.key.InvalidPrivateKeyFormatException; +import org.eclipse.tractusx.ssi.lib.exception.key.KeyGenerationException; +import org.eclipse.tractusx.ssi.lib.exception.proof.SignatureGenerateFailedException; +import org.eclipse.tractusx.ssi.lib.exception.proof.UnsupportedSignatureTypeException; +import org.eclipse.tractusx.ssi.lib.model.proof.Proof; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialBuilder; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialSubject; +import org.eclipse.tractusx.ssi.lib.proof.LinkedDataProofGenerator; +import org.eclipse.tractusx.ssi.lib.proof.SignatureType; +import org.mockito.Mockito; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Base64; +import java.util.BitSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +import static org.mockito.Mockito.when; + +public class TestUtil { + + public static final int BITSET_SIZE = 131072; + + public static final String STATUS_LIST_ID = "https://example.com/credentials/status/3"; + + public static final String BPN = "BPNL123456789000"; + + public static final String DID = "did:web:example:BPNL123456789000"; + + public static final List VC_CONTEXTS = + List.of(URI.create("https://w3id.org/vc/status-list/2021/v1")); + + public static final String STATUS_LIST_CREDENTIAL_SUBJECT_ID = + "https://example.com/status/3#list"; + + public static StatusListIndex mockStatusListIndex( + String issuerBpnStatus, StatusListCredential statusListCredential, String currentIndex) { + var statusListIndex = Mockito.mock(StatusListIndex.class); + when(statusListIndex.getStatusListCredential()).thenReturn(statusListCredential); + when(statusListIndex.getCurrentIndex()).thenReturn(currentIndex); + when(statusListIndex.getIssuerBpnStatus()).thenReturn(issuerBpnStatus); + return statusListIndex; + } + + public static String mockEmptyEncodedList() { + BitSet bitSet = new BitSet(BITSET_SIZE); + return Base64.getEncoder().encodeToString(gzipCompress(bitSet)); + } + + public static VerifiableCredentialBuilder mockStatusListVC( + String issuer, String index, String encodedList) { + var builder = + new VerifiableCredentialBuilder() + .context(VC_CONTEXTS) + .id(URI.create(issuer + "#" + index)) + .type(List.of("VerifiableCredential", "StatusList2021Credential")) + .issuer(URI.create(issuer)) + .expirationDate(Instant.now().plusSeconds(200000000L)) + .issuanceDate(Instant.now()) + .credentialSubject(new VerifiableCredentialSubject(mockStatusList(encodedList))); + return builder; + } + + public static StatusListCredential mockStatusListCredential( + String issuer, VerifiableCredentialBuilder builder) { + + final LinkedDataProofGenerator generator; + try { + generator = LinkedDataProofGenerator.newInstance(SignatureType.ED25519); + } catch (UnsupportedSignatureTypeException e) { + throw new AssertionError(e); + } + + final Proof proof; + try { + proof = + generator.createProof( + builder.build(), URI.create(issuer + "#key-1"), generateKeys().getPrivateKey()); + } catch (InvalidPrivateKeyFormatException + | SignatureGenerateFailedException + | TransformJsonLdException e) { + throw new AssertionError(e); + } + + // Adding Proof to VC + builder.proof(proof); + + StatusListCredential statusListCredential = Mockito.mock(StatusListCredential.class); + when(statusListCredential.getCredential()).thenReturn(builder.build()); + when(statusListCredential.getId()).thenReturn(STATUS_LIST_ID); + when(statusListCredential.getIssuerBpn()).thenReturn(issuer); + when(statusListCredential.getCreatedAt()).thenReturn(LocalDateTime.now()); + + return statusListCredential; + } + + public static KeyPair generateKeys() { + X25519Generator gen = new X25519Generator(); + KeyPair baseWalletKeys; + try { + baseWalletKeys = gen.generateKey(); + } catch (KeyGenerationException e) { + throw new AssertionError(e); + } + return baseWalletKeys; + } + + public static Map mockStatusList(String encodedList) { + Map credentialSubjectMap = new HashMap(); + credentialSubjectMap.put( + StatusListCredentialSubject.SUBJECT_ID, STATUS_LIST_CREDENTIAL_SUBJECT_ID); + credentialSubjectMap.put(StatusListCredentialSubject.SUBJECT_TYPE, "StatusList2021"); + credentialSubjectMap.put(StatusListCredentialSubject.SUBJECT_STATUS_PURPOSE, "revocation"); + credentialSubjectMap.put(StatusListCredentialSubject.SUBJECT_ENCODED_LIST, encodedList); + return credentialSubjectMap; + } + + public static byte[] gzipCompress(BitSet bitSet) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (GZIPOutputStream gzipOut = new GZIPOutputStream(out)) { + gzipOut.write(bitSet.toByteArray()); + } catch (IOException e) { + throw new AssertionError("failed."); + } + + return out.toByteArray(); + } + + public static BitSet decompressGzip(byte[] bytes) { + ByteArrayInputStream in = new ByteArrayInputStream(bytes); + try (GZIPInputStream gzipIn = new GZIPInputStream(in)) { + byte[] buffer = new byte[1024]; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + int len; + while ((len = gzipIn.read(buffer)) > 0) { + outputStream.write(buffer, 0, len); + } + + BitSet bitSet = BitSet.valueOf(outputStream.toByteArray()); + outputStream.close(); + return bitSet; + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String extractBpnFromDid(String did) { + return did.substring(did.lastIndexOf(":") + 1).toUpperCase(); + } + + @SneakyThrows + public static void main(String[] args) { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + // use explicit initialization as the platform default might fail + keyGen.init(128); + SecretKey secretKey = keyGen.generateKey(); + String s = Base64.getEncoder().encodeToString(secretKey.getEncoded()); + + System.out.println(s); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/TestVerifiableCredentialsRevocationServiceApplication.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/TestVerifiableCredentialsRevocationServiceApplication.java new file mode 100644 index 000000000..e7d5f139f --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/TestVerifiableCredentialsRevocationServiceApplication.java @@ -0,0 +1,45 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; + +@TestConfiguration(proxyBeanMethods = false) +public class TestVerifiableCredentialsRevocationServiceApplication { + + public static void main(String[] args) { + SpringApplication.from(VerifiableCredentialsRevocationServiceApplication::main) + .with(TestVerifiableCredentialsRevocationServiceApplication.class) + .run(args); + } + + @Bean + @ServiceConnection + PostgreSQLContainer postgresContainer() { + return new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest")); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/ExceptionHandlingTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/ExceptionHandlingTest.java new file mode 100644 index 000000000..8beebbd0e --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/ExceptionHandlingTest.java @@ -0,0 +1,73 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config; + +import org.eclipse.tractusx.managedidentitywallets.commons.exception.ForbiddenException; +import org.eclipse.tractusx.managedidentitywallets.revocation.exception.CredentialAlreadyRevokedException; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.ProblemDetail; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class ExceptionHandlingTest { + + private static final ExceptionHandling exceptionHandling = new ExceptionHandling(); + + @Test + void handleCredentialAlreadyRevokedException() { + CredentialAlreadyRevokedException credentialAlreadyRevokedException = + new CredentialAlreadyRevokedException("credential xyz was already revoked"); + ProblemDetail problemDetail = + exceptionHandling.handleCredentialAlreadyRevokedException( + credentialAlreadyRevokedException); + + assertNotNull(problemDetail); + assertNotNull(problemDetail.getTitle()); + assertEquals("Revocation service error", problemDetail.getTitle()); + assertEquals(HttpStatus.CONFLICT.value(), problemDetail.getStatus()); + } + + @Test + void handleForbiddenException() { + ForbiddenException forbiddenException = new ForbiddenException("no!"); + ProblemDetail problemDetail = exceptionHandling.handleForbiddenException(forbiddenException); + + assertNotNull(problemDetail); + assertNotNull(problemDetail.getTitle()); + assertEquals("ForbiddenException: no!", problemDetail.getTitle()); + assertEquals(HttpStatus.FORBIDDEN.value(), problemDetail.getStatus()); + } + + @Test + void handleIllegalArgumentException() { + IllegalArgumentException illegalArgumentException = new IllegalArgumentException("illegal"); + ProblemDetail problemDetail = + exceptionHandling.handleIllegalArgumentException(illegalArgumentException); + + assertNotNull(problemDetail); + assertNotNull(problemDetail.getTitle()); + assertEquals("IllegalArgumentException: illegal", problemDetail.getTitle()); + assertEquals(HttpStatus.BAD_REQUEST.value(), problemDetail.getStatus()); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/MIWSettingsTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/MIWSettingsTest.java new file mode 100644 index 000000000..4ee8efe22 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/MIWSettingsTest.java @@ -0,0 +1,56 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config; + +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class MIWSettingsTest { + + @Test + void testMIWSettingsWithValidData() { + List vcContexts = + Arrays.asList( + URI.create("https://example.com/context1"), URI.create("https://example.com/context2")); + + MIWSettings miwSettings = new MIWSettings(vcContexts); + + assertEquals(vcContexts, miwSettings.vcContexts()); + } + + @Test + void testMIWSettingsWithNullVCContexts() { + assertThrows(NullPointerException.class, () -> new MIWSettings(null)); + } + + @Test + void testMIWSettingsWithEmptyVCContexts() { + List list = List.of(); + assertThrows(IllegalArgumentException.class, () -> new MIWSettings(list)); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/OpenApiConfigTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/OpenApiConfigTest.java new file mode 100644 index 000000000..db45d7057 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/OpenApiConfigTest.java @@ -0,0 +1,73 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config; + +import io.swagger.v3.oas.models.OpenAPI; +import org.eclipse.tractusx.managedidentitywallets.revocation.config.security.SecurityConfigProperties; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springdoc.core.models.GroupedOpenApi; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + +class OpenApiConfigTest { + + private static SecurityConfigProperties securityConfigProperties; + + private static OpenApiConfig openApiconfig; + + @BeforeAll + public static void beforeAll() { + securityConfigProperties = Mockito.mock(SecurityConfigProperties.class); + openApiconfig = new OpenApiConfig(securityConfigProperties); + } + + @Test + void testOpenApiSecurityEnabled() { + when(securityConfigProperties.enabled()).thenReturn(true); + OpenAPI openAPI = assertDoesNotThrow(() -> openApiconfig.openAPI()); + + assertFalse(openAPI.getSecurity().isEmpty()); + openAPI + .getSecurity() + .forEach(s -> assertTrue(s.containsKey("Authenticate using access_token"))); + } + + @Test + void testOpenApiSecurityDisabled() { + when(securityConfigProperties.enabled()).thenReturn(false); + OpenAPI openAPI = assertDoesNotThrow(() -> openApiconfig.openAPI()); + assertNull(openAPI.getSecurity()); + } + + @Test + void testOpenApiDefinition() { + GroupedOpenApi groupedOpenApi = assertDoesNotThrow(() -> openApiconfig.openApiDefinition()); + assertNotNull(groupedOpenApi); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/CustomAuthenticationConverterTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/CustomAuthenticationConverterTest.java new file mode 100644 index 000000000..b5b050c61 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/config/security/CustomAuthenticationConverterTest.java @@ -0,0 +1,97 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.config.security; + +import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.oauth2.jwt.Jwt; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CustomAuthenticationConverterTest { + + + @Test + void shouldConvertSuccessfullyWithAuthorities() { + + Map roles = Map.of("roles", List.of("dei_muda")); + Map resourceId = Map.of("resourceId", roles); + Map resourceAccess = Map.of("resource_access", resourceId); + + Jwt jwt = + new Jwt( + "32453453", + Instant.now(), + Instant.now().plus(Duration.ofDays(12)), + Map.of("kid", "1234"), + resourceAccess); + + CustomAuthenticationConverter converter = new CustomAuthenticationConverter("resourceId"); + AbstractAuthenticationToken abstractAuthenticationToken = + assertDoesNotThrow(() -> converter.convert(jwt)); + assertFalse(abstractAuthenticationToken.getAuthorities().isEmpty()); + } + + @Test + void shouldConvertSuccessfullyWithoutAuthoritiesWhenRolesMissing() { + Map resourceId = Map.of("resourceId", Map.of()); + Map resourceAccess = Map.of("resource_access", resourceId); + + Jwt jwt = + new Jwt( + "32453453", + Instant.now(), + Instant.now().plus(Duration.ofDays(12)), + Map.of("kid", "1234"), + resourceAccess); + + CustomAuthenticationConverter converter = new CustomAuthenticationConverter("resourceId"); + AbstractAuthenticationToken abstractAuthenticationToken = + assertDoesNotThrow(() -> converter.convert(jwt)); + assertTrue(abstractAuthenticationToken.getAuthorities().isEmpty()); + } + + @Test + void shouldConvertSuccessfullyWithoutAuthoritiesWhenResourceAccessMissing() { + Map resourceAccess = Map.of("resource_access", Map.of()); + + Jwt jwt = + new Jwt( + "32453453", + Instant.now(), + Instant.now().plus(Duration.ofDays(12)), + Map.of("kid", "1234"), + resourceAccess); + + CustomAuthenticationConverter converter = new CustomAuthenticationConverter("resourceId"); + AbstractAuthenticationToken abstractAuthenticationToken = + assertDoesNotThrow(() -> converter.convert(jwt)); + assertTrue(abstractAuthenticationToken.getAuthorities().isEmpty()); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/controllers/RevocationApiControllerTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/controllers/RevocationApiControllerTest.java new file mode 100644 index 000000000..6f0379f4e --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/controllers/RevocationApiControllerTest.java @@ -0,0 +1,204 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.controllers; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.RevocationPurpose; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.revocation.constant.RevocationApiEndpoints; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.CredentialStatusDto; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.StatusEntryDto; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.StatusListCredentialSubject; +import org.eclipse.tractusx.managedidentitywallets.revocation.services.RevocationService; +import org.eclipse.tractusx.managedidentitywallets.revocation.utils.BitSetManager; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.security.Principal; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.BPN; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.DID; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(MockitoExtension.class) +class RevocationApiControllerTest { + + private static final String CALLER_BPN = UUID.randomUUID().toString(); + + private MockMvc mockMvc; + + private ObjectMapper objectMapper; + + @Mock + private RevocationService revocationService; + + @InjectMocks + private RevocationApiController revocationApiController; + + @BeforeEach + public void setup() { + mockMvc = MockMvcBuilders.standaloneSetup(revocationApiController).build(); + objectMapper = new ObjectMapper(); + Mockito.reset(revocationService); + } + + @Test + void whenPostCreateStatusListVC_thenReturnStatus() throws Exception { + // Given + String validPurpose = RevocationPurpose.REVOCATION.name(); + StatusEntryDto statusEntryDto = new StatusEntryDto(validPurpose, DID); + String validIndex = + String.valueOf(BitSetManager.BITSET_SIZE / 2); // any valid index within range + CredentialStatusDto credentialStatusDto = + new CredentialStatusDto( + "https://example.com/revocations/credentials/" + BPN + "/revocation/1#" + validIndex, + RevocationPurpose.REVOCATION.name(), + validIndex, // this value is within the range [0, BitSetManager.BITSET_SIZE - 1] + "https://example.com/revocations/credentials/" + BPN + "/revocation/1", + StatusListCredentialSubject.TYPE_ENTRY); + given(revocationService.createStatusList(statusEntryDto, "token")) + .willReturn(credentialStatusDto); + when(revocationService.extractBpnFromDid(DID)).thenReturn(BPN); + + Principal mockPrincipal = mockPrincipal(BPN); + // When & Then + mockMvc + .perform( + MockMvcRequestBuilders.post(RevocationApiEndpoints.REVOCATION_API + RevocationApiEndpoints.STATUS_ENTRY) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "token") + .principal(mockPrincipal) + .content(objectMapper.writeValueAsString(statusEntryDto))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").value(credentialStatusDto.id())); + } + + private Principal mockPrincipal(String name) { + + Jwt jwt = mock(Jwt.class); + when(jwt.getClaims()).thenReturn(Map.of(StringPool.BPN, BPN)); + + JwtAuthenticationToken principal = Mockito.mock(JwtAuthenticationToken.class); + when(principal.getName()).thenReturn(name); + when(principal.getPrincipal()).thenReturn(jwt); + + return principal; + } + + @Test + void whenPostRevokeCredential_thenReturnOkStatus() throws Exception { + // Given + String validIndex = + String.valueOf(BitSetManager.BITSET_SIZE / 2); // any valid index within range + CredentialStatusDto credentialStatusDto = + new CredentialStatusDto( + "http://example.com/credentials/" + BPN + "/revocation/1#" + validIndex, + "revocation", + validIndex, // this value is within the range [0, BitSetManager.BITSET_SIZE - 1] + "http://example.com/credentials/" + BPN + "/revocation/1", + StatusListCredentialSubject.TYPE_ENTRY); + doNothing().when(revocationService).revoke(credentialStatusDto, "token"); + when(revocationService.extractBpnFromURL(any())).thenReturn(BPN); + + Principal mockPrincipal = mockPrincipal(BPN); + // When & Then + mockMvc + .perform( + MockMvcRequestBuilders.post(RevocationApiEndpoints.REVOCATION_API + RevocationApiEndpoints.REVOKE) + .contentType(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "token") + .principal(mockPrincipal) + .content(objectMapper.writeValueAsString(credentialStatusDto))) + .andExpect(status().isOk()); + verify(revocationService).revoke(credentialStatusDto, "token"); + } + + @Test + void whenGetCredential_thenReturnCredentials() throws Exception { + // Given + String validPurpose = RevocationPurpose.REVOCATION.name(); + VerifiableCredential verifiableCredential = + new VerifiableCredential( + createVerifiableCredentialTestData()); // Populate with valid test data + given(revocationService.getStatusListCredential(any(), any(), any())) + .willReturn(verifiableCredential); + // When & Then + mockMvc + .perform( + MockMvcRequestBuilders.get( + RevocationApiEndpoints.REVOCATION_API + + RevocationApiEndpoints.CREDENTIALS_STATUS_INDEX + .replace("{issuerBPN}", BPN) + .replace("{status}", validPurpose) + .replace("{index}", "1"))) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").value(verifiableCredential.getId().toString())); + } + + private VerifiableCredential createVerifiableCredentialTestData() { + Map credentialData = new HashMap<>(); + credentialData.put( + "id", "http://example/api/v1/revocations/credentials/" + BPN + "/revocation/1"); + credentialData.put("issuer", "https://issuer.example.com"); + credentialData.put("issuanceDate", Instant.now().toString()); + // Include 'type' field as a list because VerifiableCredential expects it to be non-null and a + // list + credentialData.put("type", List.of("VerifiableCredential", "StatusListCredential")); + Map subjectData = new HashMap<>(); + subjectData.put("id", "subjectId"); + subjectData.put("type", "StatusList2021Credential"); + // 'credentialSubject' can be either a List or a single Map according to the code, so I'm + // keeping it as a single Map + credentialData.put("credentialSubject", subjectData); + credentialData.put("@context", VerifiableCredential.DEFAULT_CONTEXT.toString()); + VerifiableCredential credential = new VerifiableCredential(credentialData); + return credential; + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/domain/BPNTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/domain/BPNTest.java new file mode 100644 index 000000000..017d06b7d --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/domain/BPNTest.java @@ -0,0 +1,78 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.BPN; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class BPNTest { + + @Test + @DisplayName("BPN Should not be valid") + void invalidBPN() { + String bpn = "thisnotbpn"; + + assertThrows( + IllegalArgumentException.class, + () -> { + new BPN(bpn); + }); + } + + @Test + @DisplayName("BPN Should be valid") + void validBPN() { + assertDoesNotThrow( + () -> { + new BPN(BPN); + }); + } + + @Test + @DisplayName("BPN Should return value") + void bpnValue() { + BPN bpn = new BPN(BPN); + assertEquals(BPN, bpn.value()); + } + + @Test + @DisplayName("BPN Should be equal") + void bpnEqual() { + BPN bpn1 = new BPN(BPN); + BPN bpn2 = new BPN(BPN); + assertEquals(bpn1, bpn2); + } + + @Test + @DisplayName("BPN Should not be equal") + void bpnNotEqual() { + BPN bpn1 = new BPN(BPN); + BPN bpn2 = new BPN("BPNL000000000000"); + assertNotEquals(bpn1, bpn2); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/CredentialStatusDtoTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/CredentialStatusDtoTest.java new file mode 100644 index 000000000..83e966ff5 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/CredentialStatusDtoTest.java @@ -0,0 +1,154 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.dto; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import org.eclipse.tractusx.managedidentitywallets.revocation.utils.BitSetManager; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CredentialStatusDtoTest { + + @Test + void validCredentialStatusDto_CreatesSuccessfully() { + // Arrange + String validIndex = + String.valueOf(BitSetManager.BITSET_SIZE / 2); // any valid index within range + + // Act + CredentialStatusDto dto = + new CredentialStatusDto( + "id", + "revocation", + validIndex, // this value is within the range [0, BitSetManager.BITSET_SIZE - 1] + "statusListCredential", + StatusListCredentialSubject.TYPE_ENTRY); + + // Assert + assertNotNull(dto); + assertEquals("id", dto.id()); + assertEquals("revocation", dto.statusPurpose()); + assertEquals(validIndex, dto.statusListIndex()); + assertEquals("statusListCredential", dto.statusListCredential()); + assertEquals(StatusListCredentialSubject.TYPE_ENTRY, dto.type()); + } + + @ParameterizedTest + @ValueSource(ints = { BitSetManager.BITSET_SIZE, -6 }) + void statusListIndexOutOfRange_ThrowsIllegalArgumentException(int value) { + // Arrange + String outOfRangeIndex = String.valueOf(value); // one more than the max index + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> { + new CredentialStatusDto( + "id", "statusPurpose", outOfRangeIndex, "statusListCredential", "type"); + }); + } + + @Test + void anyParameterIsBlank_ThrowsValidationException() { + + Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + assertFalse( + validator + .validate( + new CredentialStatusDto( + "", // id is blank + "revocation", + "0", + "statusListCredential", + StatusListCredentialSubject.TYPE_ENTRY)) + .isEmpty()); + + assertFalse( + validator + .validate( + new CredentialStatusDto( + "id", + "revocation", + "0", + "", // statusListCredential is blank + StatusListCredentialSubject.TYPE_ENTRY)) + .isEmpty()); + } + + @Test + @DisplayName("statusPurpose is invalid") + void invalidStatusPurpose_ThrowsIllegalArgumentException() { + String invalidPurpose = "invalidPurpose"; + + assertThrows( + IllegalArgumentException.class, + () -> { + new CredentialStatusDto( + "id", invalidPurpose, "0", "statusListCredential", StatusListCredentialSubject.TYPE_ENTRY); + }); + } + + @Test + @DisplayName("type is invalid") + void invalidType_ThrowsIllegalArgumentException() { + String invalidType = "invalidType"; + + assertThrows( + IllegalArgumentException.class, + () -> { + new CredentialStatusDto("id", "revocation", "0", "statusListCredential", invalidType); + }); + } + + @Test + @DisplayName("statusPurpose is valid") + void validStatusPurpose_DoesNotThrowException() { + String validPurpose = "revocation"; + + assertDoesNotThrow( + () -> { + new CredentialStatusDto( + "id", validPurpose, "0", "statusListCredential", StatusListCredentialSubject.TYPE_ENTRY); + }); + } + + @Test + @DisplayName("type is valid") + void validType_DoesNotThrowException() { + String validType = StatusListCredentialSubject.TYPE_ENTRY; + + assertDoesNotThrow( + () -> { + new CredentialStatusDto("id", "revocation", "0", "statusListCredential", validType); + }); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusEntryDtoTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusEntryDtoTest.java new file mode 100644 index 000000000..ff11b2fc6 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusEntryDtoTest.java @@ -0,0 +1,88 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.dto; + +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.RevocationPurpose; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class StatusEntryDtoTest { + + @Test + void validStatusEntryDto_CreatesSuccessfully() { + // Arrange + String validPurpose = RevocationPurpose.REVOCATION.name(); + + // Act + StatusEntryDto dto = new StatusEntryDto(validPurpose, "issuerId"); + + // Assert + assertNotNull(dto); + assertEquals(validPurpose, dto.purpose()); + assertEquals("issuerId", dto.issuerId()); + } + + @Test + void purposeIsInvalid_ThrowsIllegalArgumentException() { + // Arrange + String invalidPurpose = "invalidPurpose"; + + // Act & Assert + assertThrows( + IllegalArgumentException.class, + () -> { + new StatusEntryDto(invalidPurpose, "issuerId"); + }); + } + + @Test + void anyParameterIsBlank_ThrowsValidationException() { + Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + // Act & Assert for each field that should not be blank or null + assertThrows( + IllegalArgumentException.class, + () -> + new StatusEntryDto( + "", // purpose is blank + "issuerId")); + assertThrows( + IllegalArgumentException.class, + () -> + new StatusEntryDto( + "suspension", // purpose is blank + "issuerId")); + assertFalse( + validator + .validate( + new StatusEntryDto( + RevocationPurpose.REVOCATION.name(), "" // issuerId is blank + )) + .isEmpty()); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusListCredentialSubjectTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusListCredentialSubjectTest.java new file mode 100644 index 000000000..f720cf9bf --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/StatusListCredentialSubjectTest.java @@ -0,0 +1,144 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.dto; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +class StatusListCredentialSubjectTest { + + @Test + void builderCreatesObjectWithCorrectValues() { + // Arrange + String id = "12345"; + String statusPurpose = "SomeStatusPurpose"; + String encodedList = "EncodedListData"; + // Act + StatusListCredentialSubject subject = + StatusListCredentialSubject.builder() + .id(id) + .type(StatusListCredentialSubject.TYPE_ENTRY) + .statusPurpose(statusPurpose) + .encodedList(encodedList) + .build(); + // Assert + assertNotNull(subject); + assertEquals(id, subject.getId()); + assertEquals(StatusListCredentialSubject.TYPE_ENTRY, subject.getType()); + assertEquals(statusPurpose, subject.getStatusPurpose()); + assertEquals(encodedList, subject.getEncodedList()); + } + + @Test + void defaultConstantsAreCorrect() { + // Assert + assertEquals("StatusList2021Entry", StatusListCredentialSubject.TYPE_ENTRY); + assertEquals("StatusList2021Credential", StatusListCredentialSubject.TYPE_LIST); + assertEquals("id", StatusListCredentialSubject.SUBJECT_ID); + assertEquals("type", StatusListCredentialSubject.SUBJECT_TYPE); + assertEquals("statusPurpose", StatusListCredentialSubject.SUBJECT_STATUS_PURPOSE); + assertEquals("encodedList", StatusListCredentialSubject.SUBJECT_ENCODED_LIST); + } + + @Test + void builderCreatesCredentialTypeObject() { + // Arrange + String id = "67890"; + String statusPurpose = "AnotherStatusPurpose"; + String encodedList = "AnotherEncodedListData"; + // Act + StatusListCredentialSubject subject = + StatusListCredentialSubject.builder() + .id(id) + .type(StatusListCredentialSubject.TYPE_LIST) + .statusPurpose(statusPurpose) + .encodedList(encodedList) + .build(); + // Assert + assertNotNull(subject); + assertEquals(id, subject.getId()); + assertEquals(StatusListCredentialSubject.TYPE_LIST, subject.getType()); + assertEquals(statusPurpose, subject.getStatusPurpose()); + assertEquals(encodedList, subject.getEncodedList()); + } + + @Test + void builderWithNullValuesForOptionalFields() { + // Arrange and Act + StatusListCredentialSubject subject = + StatusListCredentialSubject.builder() + .id("idWithNulls") + .type(StatusListCredentialSubject.TYPE_ENTRY) + // Optional fields are not set (statusPurpose, encodedList) + .build(); + // Assert + assertNotNull(subject); + assertEquals("idWithNulls", subject.getId()); + assertEquals(StatusListCredentialSubject.TYPE_ENTRY, subject.getType()); + assertNull(subject.getStatusPurpose()); // Checks if the field is truly optional and nullable + assertNull(subject.getEncodedList()); + } + + @Test + void objectsAreImmutable() { + // Arrange + StatusListCredentialSubject subject = + StatusListCredentialSubject.builder() + .id("immutableId") + .type(StatusListCredentialSubject.TYPE_ENTRY) + .statusPurpose("PurposeBeforeChange") + .encodedList("ListBeforeChange") + .build(); + // Act + // Attempt to change the properties after creation to check if the object is immutable + // This attempt should not change the object since the class is expected to be immutable + // Since the class doesn't provide setters, this test ensures that the builder pattern itself + // does not introduce mutability + // Assert + assertEquals("immutableId", subject.getId()); + assertEquals(StatusListCredentialSubject.TYPE_ENTRY, subject.getType()); + assertEquals("PurposeBeforeChange", subject.getStatusPurpose()); + assertEquals("ListBeforeChange", subject.getEncodedList()); + } + + @Test + void testToString() { + // Arrange + String id = "12345"; + String statusPurpose = "SomeStatusPurpose"; + String encodedList = "EncodedListData"; + // Act + String s = + StatusListCredentialSubject.builder() + .id(id) + .type(StatusListCredentialSubject.TYPE_ENTRY) + .statusPurpose(statusPurpose) + .encodedList(encodedList) + .toString(); + assertNotNull(s); + assertFalse(s.isEmpty()); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/TokenResponeTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/TokenResponeTest.java new file mode 100644 index 000000000..4e6869608 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/dto/TokenResponeTest.java @@ -0,0 +1,70 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.dto; + +import org.junit.jupiter.api.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +class TokenResponseTest { + @Test + void getAndSetAccessToken() { + // Arrange + TokenResponse tokenResponse = new TokenResponse(); + String expectedToken = "someAccessToken123"; + + // Act + tokenResponse.setAccessToken(expectedToken); + + // Assert + String actualToken = tokenResponse.getAccessToken(); + assertEquals(expectedToken, "someAccessToken123", actualToken); + } + + @Test + void setAccessTokenWithNullValue() { + // Arrange + TokenResponse tokenResponse = new TokenResponse(); + + // Act + tokenResponse.setAccessToken(null); + + // Assert + String actualToken = tokenResponse.getAccessToken(); + assertNull(actualToken); + } + + @Test + void setAccessTokenWithEmptyString() { + // Arrange + TokenResponse tokenResponse = new TokenResponse(); + String expectedToken = ""; + + // Act + tokenResponse.setAccessToken(expectedToken); + + // Assert + String actualToken = tokenResponse.getAccessToken(); + assertEquals(expectedToken, "", actualToken); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/BitSetManagerExceptionTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/BitSetManagerExceptionTest.java new file mode 100644 index 000000000..87e0dc2e6 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/BitSetManagerExceptionTest.java @@ -0,0 +1,39 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class BitSetManagerExceptionTest { + + @Test + void testConstructors() { + assertDoesNotThrow(() -> new BitSetManagerException()); + assertDoesNotThrow(() -> new BitSetManagerException("hallo")); + assertDoesNotThrow(() -> new BitSetManagerException("hallo", new IllegalArgumentException())); + assertDoesNotThrow( + () -> new BitSetManagerException("hallo", new IllegalArgumentException(), false, false)); + assertDoesNotThrow(() -> new BitSetManagerException(new IllegalArgumentException())); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/RevocationServiceExceptionTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/RevocationServiceExceptionTest.java new file mode 100644 index 000000000..82be0a153 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/exception/RevocationServiceExceptionTest.java @@ -0,0 +1,36 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.exception; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class RevocationServiceExceptionTest { + @Test + void testConstructors() { + assertDoesNotThrow(() -> new RevocationServiceException("hallo")); + assertDoesNotThrow( + () -> new RevocationServiceException("hallo", new IllegalArgumentException())); + assertDoesNotThrow(() -> new RevocationServiceException(new IllegalArgumentException())); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListCredentialTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListCredentialTest.java new file mode 100644 index 000000000..53f30f4d5 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListCredentialTest.java @@ -0,0 +1,159 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.jpa; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.AutoConfigureJson; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.BPN; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@DataJpaTest +@AutoConfigureJson +class StatusListCredentialTest { + + @MockBean + ObjectMapper objectMapper; + + @Autowired + private TestEntityManager entityManager; + + private LocalValidatorFactoryBean validator; + + @BeforeEach + public void setUp() { + validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); // Initializes the validator + } + + private VerifiableCredential createVerifiableCredentialTestData(String subjectId) { + Map credentialData = new HashMap<>(); + credentialData.put("id", "urn:uuid:" + UUID.randomUUID()); + credentialData.put("issuer", "https://issuer.example.com"); + credentialData.put("issuanceDate", Instant.now().toString()); + credentialData.put("type", List.of("VerifiableCredential", "StatusListCredential")); + + Map subjectData = new HashMap<>(); + subjectData.put("id", subjectId); + subjectData.put("type", "StatusList2021Credential"); + credentialData.put("credentialSubject", subjectData); + credentialData.put("@context", VerifiableCredential.DEFAULT_CONTEXT.toString()); + + return new VerifiableCredential(credentialData); + } + + @Test + void testStatusListCredentialPersistence() { + // Arrange + VerifiableCredential credential = + createVerifiableCredentialTestData("urn:uuid:" + UUID.randomUUID()); + + StatusListCredential statusListCredential = + StatusListCredential.builder() + .id(BPN + "revocation#1") + .issuerBpn(BPN) + .credential(credential) + .build(); + + // Act + StatusListCredential persistedStatusListCredential = + entityManager.persistFlushFind(statusListCredential); + + // Assert + assertNotNull(persistedStatusListCredential); + assertEquals(BPN + "revocation#1", persistedStatusListCredential.getId()); + assertEquals(BPN, persistedStatusListCredential.getIssuerBpn()); + assertEquals(credential, persistedStatusListCredential.getCredential()); + } + + @Test + void givenInvalidId_whenSaving_thenValidationFails() { + // Arrange + StatusListCredential statusListCredential = + StatusListCredential.builder() + .issuerBpn("") // Invalid issuerId + .credential(createVerifiableCredentialTestData("urn:uuid:" + UUID.randomUUID())) + .build(); + + // Act and Assert + Set> violations = + validator.validate(statusListCredential); + assertThat(violations).isNotEmpty(); + assertThat(violations.toString()).contains("ID cannot be blank"); + } + + @Test + void givenNullCredential_whenSaving_thenThrowsException() { + // Arrange + StatusListCredential statusListCredential = + StatusListCredential.builder() + .id(BPN + "revocation#1") + .issuerBpn(BPN) + .credential(null) // Null Credential + .build(); + + // Act and Assert + Exception exception = + assertThrows( + ConstraintViolationException.class, + () -> { + entityManager.persistFlushFind(statusListCredential); + }); + String expectedMessage = "Credential cannot be null"; + assertThat(exception.getMessage()).contains(expectedMessage); + } + + @Test + void givenInvalidCredentialId_whenSaving_thenValidationFails() { + // Arrange + VerifiableCredential invalidCredential = + createVerifiableCredentialTestData(UUID.randomUUID().toString()); + + StatusListCredential statusListCredential = + StatusListCredential.builder().issuerBpn(BPN).credential(invalidCredential).build(); + + // Act and Assert + Set> violations = + validator.validate(statusListCredential); + assertThat(violations).isNotEmpty(); // Because the `credential` field validation would fail + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListIndexTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListIndexTest.java new file mode 100644 index 000000000..39c1a0358 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/jpa/StatusListIndexTest.java @@ -0,0 +1,165 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.jpa; + +import jakarta.validation.ConstraintViolation; +import org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.AutoConfigureJson; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.BPN; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(SpringExtension.class) +@AutoConfigureJson +@DataJpaTest +class StatusListIndexTest { + + @Autowired + private TestEntityManager entityManager; + + private LocalValidatorFactoryBean validator; + + @BeforeEach + public void setUp() { + validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); // Initializes the validator + } + + @Test + void whenFieldsAreValid_thenNoConstraintViolationsAndEntityPersists() { + String id = BPN + "-revocation#1"; + String issuerBpnStatus = BPN + "-revocation"; + + StatusListIndex statusListIndex = + StatusListIndex.builder() + .id(id) + .issuerBpnStatus(issuerBpnStatus) + .currentIndex("123456") // using numeric string only as index + .build(); + + Set> violations = validator.validate(statusListIndex); + assertThat(violations).isEmpty(); + + entityManager.persistAndFlush(statusListIndex); + + StatusListIndex found = entityManager.find(StatusListIndex.class, statusListIndex.getId()); + assertThat(found).isNotNull(); + assertThat(found.getIssuerBpnStatus()).isEqualTo(statusListIndex.getIssuerBpnStatus()); + assertThat(found.getCurrentIndex()).isEqualTo(statusListIndex.getCurrentIndex()); + } + + @Test + void whenIdIsBlank_thenConstraintViolationOccurs() { + StatusListIndex statusListIndex = + StatusListIndex.builder().id(" ").currentIndex("123456").build(); + Set> violations = validator.validate(statusListIndex); + + assertThat(violations).isNotEmpty(); + assertThat(violations.toString()).contains("ID cannot be blank"); + } + + @Test + void whenIssuerBpnStatusIsBlank_thenConstraintViolationOccurs() { + StatusListIndex statusListIndex = + StatusListIndex.builder() + .issuerBpnStatus(" ") + .id(BPN + "-revocation#1") + .currentIndex("123456") + .build(); + Set> violations = validator.validate(statusListIndex); + + assertThat(violations).isNotEmpty(); + assertThat(violations.toString()).contains("Issuer BPN with status cannot be blank"); + } + + @Test + void whenCurrentIndexIsBlank_thenConstraintViolationOccurs() { + StatusListIndex statusListIndex = + StatusListIndex.builder().issuerBpnStatus(TestUtil.BPN).currentIndex(" ").build(); + Set> violations = validator.validate(statusListIndex); + + assertThat(violations).isNotEmpty(); + assertThat(violations.toString()).contains("Current index cannot be blank"); + } + + @Test + void whenCurrentIndexIsNotNumeric_thenConstraintViolationOccurs() { + String id = BPN + "-revocation#1"; + String issuerBpnStatus = BPN + "-revocation"; + String wrongIndex = "indexABC"; + + StatusListIndex statusListIndex = + StatusListIndex.builder() + .id(id) + .issuerBpnStatus(issuerBpnStatus) + .currentIndex(wrongIndex) // invalid non-numeric currentIndex + .build(); + Set> violations = validator.validate(statusListIndex); + + assertThat(violations).isNotEmpty(); + assertThat(violations.toString()).contains("Current index must be numeric"); + } + + @Test + void whenSetInvalidCurrentIndex_thenIllegalArgumentExceptionIsThrown() { + // Constructing StatusListIndex using the builder pattern + // with a valid issuerId and leaving currentIndex unset initially + StatusListIndex statusListIndex = + StatusListIndex.builder().issuerBpnStatus(BPN + "-revocation").build(); + + // Now we attempt to set an invalid (non-numeric) currentIndex + // using the setCurrentIndex method which includes validation + assertThrows(IllegalArgumentException.class, () -> statusListIndex.setCurrentIndex("indexABC")); + } + + @Test + void whenFieldsExceedSizeLimit_thenConstraintViolationOccurs() { + String longIssuerBpnStatus = BPN + "-revocation1"; + String longCurrentIndex = + "12345".repeat(4); // The repeat count adjusts on the max size of Index + String id = "normalid".repeat(76); // The repeat count adjusts on the max length of ID + StatusListIndex statusListIndex = + StatusListIndex.builder() + .issuerBpnStatus(longIssuerBpnStatus) + .currentIndex(longCurrentIndex) + .id(id) + .build(); + + Set> violations = validator.validate(statusListIndex); + + assertThat(violations).isNotEmpty(); + + assertThat(violations.toString()).contains("ID cannot exceed 256 characters"); + assertThat(violations.toString()).contains("Current index cannot exceed 16 characters"); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/HttpClientServiceTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/HttpClientServiceTest.java new file mode 100644 index 000000000..01439c239 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/HttpClientServiceTest.java @@ -0,0 +1,154 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.services; + +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import org.eclipse.tractusx.managedidentitywallets.revocation.config.security.SecurityConfigProperties; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.TokenResponse; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.RestClient; + +import java.util.UUID; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.jsonResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.mockEmptyEncodedList; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.mockStatusListCredential; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.mockStatusListVC; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +class HttpClientServiceTest { + + @RegisterExtension + static WireMockExtension wm1 = + WireMockExtension.newInstance().options(wireMockConfig().dynamicPort()).build(); + + private static SecurityConfigProperties securityConfigProperties; + + private static HttpClientService httpClientService; // The service to test + + @Mock + private RestClient.Builder webClientBuilder; // Assuming RestClient is using WebClient.Builder + + @Mock + private RestClient webClient; + + @Mock + private RestClient.RequestBodyUriSpec requestBodyUriSpec; + + @Mock + private RestClient.RequestBodySpec requestBodySpec; + + @Mock + private RestClient.RequestHeadersSpec requestHeadersSpec; + + @Mock + private RestClient.ResponseSpec responseSpec; + + @BeforeAll + public static void beforeAll() { + securityConfigProperties = Mockito.mock(SecurityConfigProperties.class); + when(securityConfigProperties.publicClientId()).thenReturn("public-client-id"); + when(securityConfigProperties.clientId()).thenReturn("client-id"); + when(securityConfigProperties.tokenUrl()).thenReturn(wm1.baseUrl() + "/token"); + httpClientService = new HttpClientService(securityConfigProperties); + ReflectionTestUtils.setField(httpClientService, "miwUrl", wm1.baseUrl()); + } + + @Test + void testGetBearerToken_Success() { + String expectedToken = "mockToken"; + TokenResponse mockTokenResponse = new TokenResponse(); + mockTokenResponse.setAccessToken(expectedToken); + wm1.stubFor(post("/token").willReturn(jsonResponse(mockTokenResponse, 200))); + String token = httpClientService.getBearerToken(); + assertEquals(expectedToken, token); + } + + // 4XX HttpClientErrorException + // 5XX HttpServerErrorException + @ParameterizedTest + @ValueSource(ints = { 500, 400 }) + void testGetBearerToken_Error(int code) { + String expectedToken = "mockToken"; + TokenResponse mockTokenResponse = new TokenResponse(); + mockTokenResponse.setAccessToken(expectedToken); + wm1.stubFor(post("/token").willReturn(jsonResponse(mockTokenResponse, code))); + if (code == 400) + assertThrows(HttpClientErrorException.class, () -> httpClientService.getBearerToken()); + else assertThrows(HttpServerErrorException.class, () -> httpClientService.getBearerToken()); + } + + @Test + void testSignStatusListVC_Success() { + final var issuer = "did:web:localhost:BPNL345345345345"; + var fragment = UUID.randomUUID().toString(); + var encodedList = mockEmptyEncodedList(); + var credentialBuilder = mockStatusListVC(issuer, fragment, encodedList); + var unsignedCredential = credentialBuilder.build(); + var statusListCredential = mockStatusListCredential(issuer, credentialBuilder); + TokenResponse tokenResponse = new TokenResponse(); + tokenResponse.setAccessToken("123456"); + wm1.stubFor(post("/token").willReturn(jsonResponse(tokenResponse, 200))); + wm1.stubFor( + post("/api/credentials?revocable=false&asJwt=false") + .willReturn(jsonResponse(statusListCredential.getCredential(), 200))); + VerifiableCredential signedCredential = + assertDoesNotThrow( + () -> + httpClientService.signStatusListVC( + unsignedCredential, httpClientService.getBearerToken())); + assertThat(signedCredential).hasFieldOrProperty("proof"); + } + + @Test + void testSignStatusListVC_Error() { + wm1.stubFor(post("/api/credentials").willReturn(aResponse().withStatus(400))); + + final var issuer = "did:web:localhost:BPNL345345345345"; + var fragment = UUID.randomUUID().toString(); + var encodedList = mockEmptyEncodedList(); + var credentialBuilder = mockStatusListVC(issuer, fragment, encodedList); + var unsignedCredential = credentialBuilder.build(); + + // HttpClientErrorException extends RestClientException + assertThrows( + HttpClientErrorException.class, + () -> httpClientService.signStatusListVC(unsignedCredential, "dummy")); + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/RevocationServiceTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/RevocationServiceTest.java new file mode 100644 index 000000000..6c97acc79 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/services/RevocationServiceTest.java @@ -0,0 +1,503 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.services; + +import lombok.SneakyThrows; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.CredentialStatus; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil; +import org.eclipse.tractusx.managedidentitywallets.revocation.config.MIWSettings; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.CredentialStatusDto; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.StatusEntryDto; +import org.eclipse.tractusx.managedidentitywallets.revocation.dto.StatusListCredentialSubject; +import org.eclipse.tractusx.managedidentitywallets.revocation.exception.BitSetManagerException; +import org.eclipse.tractusx.managedidentitywallets.revocation.exception.RevocationServiceException; +import org.eclipse.tractusx.managedidentitywallets.revocation.jpa.StatusListIndex; +import org.eclipse.tractusx.managedidentitywallets.revocation.repository.StatusListCredentialRepository; +import org.eclipse.tractusx.managedidentitywallets.revocation.repository.StatusListIndexRepository; +import org.eclipse.tractusx.managedidentitywallets.revocation.utils.BitSetManager; +import org.eclipse.tractusx.ssi.lib.did.resolver.DidResolver; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialSubject; +import org.eclipse.tractusx.ssi.lib.proof.LinkedDataProofValidation; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +import java.net.URI; +import java.util.Base64; +import java.util.BitSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.BITSET_SIZE; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.BPN; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.DID; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.VC_CONTEXTS; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.decompressGzip; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.mockEmptyEncodedList; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.mockStatusListCredential; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.mockStatusListIndex; +import static org.eclipse.tractusx.managedidentitywallets.revocation.TestUtil.mockStatusListVC; +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +class RevocationServiceTest { + + private static final String CALLER_BPN = UUID.randomUUID().toString(); + + private static StatusListCredentialRepository statusListCredentialRepository; + + private static StatusListIndexRepository statusListIndexRepository; + + private static HttpClientService httpClientService; + + private static RevocationService revocationService; + + private static MIWSettings miwSettings; + + @BeforeAll + public static void beforeAll() { + statusListCredentialRepository = Mockito.mock(StatusListCredentialRepository.class); + statusListIndexRepository = Mockito.mock(StatusListIndexRepository.class); + httpClientService = Mockito.mock(HttpClientService.class); + miwSettings = new MIWSettings(VC_CONTEXTS); + httpClientService.domainUrl = "http://example.com"; + revocationService = + new RevocationService( + statusListCredentialRepository, + statusListIndexRepository, + httpClientService, + miwSettings); + } + + @BeforeEach + public void beforeEach() { + Mockito.reset(statusListCredentialRepository, statusListIndexRepository, httpClientService); + } + + + @Nested + class VerifyStatusTest { + @SneakyThrows + @Test + void shouldVerifyStatusActive() { + final var issuer = DID; + var encodedList = mockEmptyEncodedList(); + var credentialBuilder = mockStatusListVC(issuer, "1", encodedList); + var statusListCredential = mockStatusListCredential(issuer, credentialBuilder); + // 1. create status list with the credential + var statusListIndex = mockStatusListIndex(issuer, statusListCredential, "0"); + when(statusListIndex.getStatusListCredential()).thenReturn(statusListCredential); + when(statusListCredentialRepository.findById(any(String.class))) + .thenReturn(Optional.of(statusListCredential)); + CredentialStatusDto credentialStatusDto = Mockito.mock(CredentialStatusDto.class); + when(credentialStatusDto.id()) + .thenReturn( + "http://this-is-my-domain/api/v1/revocations/credentials/" + + TestUtil.extractBpnFromDid(issuer) + + "/revocation/1#0"); + when(credentialStatusDto.statusPurpose()).thenReturn("revocation"); + when(credentialStatusDto.statusListIndex()).thenReturn("0"); + when(credentialStatusDto.statusListCredential()) + .thenReturn( + "http://this-is-my-domain/api/v1/revocations/credentials/" + + TestUtil.extractBpnFromDid(issuer) + + "/revocation/1"); + when(credentialStatusDto.type()).thenReturn("StatusList2021Entry"); + + + try (MockedStatic utils = Mockito.mockStatic(LinkedDataProofValidation.class)) { + LinkedDataProofValidation mock = Mockito.mock(LinkedDataProofValidation.class); + utils.when(() -> { + LinkedDataProofValidation.newInstance(Mockito.any(DidResolver.class)); + }).thenReturn(mock); + Mockito.when(mock.verify(Mockito.any(VerifiableCredential.class))).thenReturn(true); + Map status = revocationService.verifyStatus(credentialStatusDto); + assertEquals(status.get(StringPool.STATUS), CredentialStatus.ACTIVE.getName()); + } + } + + @SneakyThrows + @Test + void shouldVerifyStatusRevoke() { + + String indexTORevoke = "0"; + final var issuer = DID; + + //set bit at index + String encodedList = mockEmptyEncodedList(); + encodedList = BitSetManager.revokeCredential(encodedList, Integer.parseInt(indexTORevoke)); + + + var credentialBuilder = mockStatusListVC(issuer, "1", encodedList); + var statusListCredential = mockStatusListCredential(issuer, credentialBuilder); + // 1. create status list with the credential + var statusListIndex = mockStatusListIndex(issuer, statusListCredential, "0"); + when(statusListIndex.getStatusListCredential()).thenReturn(statusListCredential); + when(statusListCredentialRepository.findById(any(String.class))) + .thenReturn(Optional.of(statusListCredential)); + CredentialStatusDto credentialStatusDto = Mockito.mock(CredentialStatusDto.class); + when(credentialStatusDto.id()) + .thenReturn( + "http://this-is-my-domain/api/v1/revocations/credentials/" + + TestUtil.extractBpnFromDid(issuer) + + "/revocation/1#0"); + when(credentialStatusDto.statusPurpose()).thenReturn("revocation"); + when(credentialStatusDto.statusListIndex()).thenReturn(indexTORevoke); + when(credentialStatusDto.statusListCredential()) + .thenReturn( + "http://this-is-my-domain/api/v1/revocations/credentials/" + + TestUtil.extractBpnFromDid(issuer) + + "/revocation/1"); + when(credentialStatusDto.type()).thenReturn("StatusList2021Entry"); + try (MockedStatic utils = Mockito.mockStatic(LinkedDataProofValidation.class)) { + LinkedDataProofValidation mock = Mockito.mock(LinkedDataProofValidation.class); + utils.when(() -> { + LinkedDataProofValidation.newInstance(Mockito.any(DidResolver.class)); + }).thenReturn(mock); + Mockito.when(mock.verify(Mockito.any(VerifiableCredential.class))).thenReturn(true); + Map status = revocationService.verifyStatus(credentialStatusDto); + + assertEquals(status.get(StringPool.STATUS), CredentialStatus.REVOKED.getName()); + } + } + } + + + @Nested + class RevokeTest { + + @Test + void shouldRevokeCredential() { + final var issuer = DID; + var encodedList = mockEmptyEncodedList(); + var credentialBuilder = mockStatusListVC(issuer, "1", encodedList); + var statusListCredential = mockStatusListCredential(issuer, credentialBuilder); + // 1. create status list with the credential + var statusListIndex = mockStatusListIndex(issuer, statusListCredential, "0"); + when(statusListIndex.getStatusListCredential()).thenReturn(statusListCredential); + when(statusListCredentialRepository.findById(any(String.class))) + .thenReturn(Optional.of(statusListCredential)); + CredentialStatusDto credentialStatusDto = Mockito.mock(CredentialStatusDto.class); + when(credentialStatusDto.id()) + .thenReturn( + "http://this-is-my-domain/api/v1/revocations/credentials/" + + TestUtil.extractBpnFromDid(issuer) + + "/revocation/1#0"); + when(credentialStatusDto.statusPurpose()).thenReturn("revocation"); + when(credentialStatusDto.statusListIndex()).thenReturn("0"); + when(credentialStatusDto.statusListCredential()) + .thenReturn( + "http://this-is-my-domain/api/v1/revocations/credentials/" + + TestUtil.extractBpnFromDid(issuer) + + "/revocation/1"); + when(credentialStatusDto.type()).thenReturn("StatusList2021Entry"); + assertDoesNotThrow(() -> revocationService.revoke(credentialStatusDto, "token")); + Mockito.verify(statusListCredentialRepository, Mockito.times(1)) + .saveAndFlush(statusListCredential); + ArgumentCaptor captor = + ArgumentCaptor.forClass(VerifiableCredential.class); + Mockito.verify(httpClientService) + .signStatusListVC(captor.capture(), Mockito.any(String.class)); + VerifiableCredential newList = captor.getValue(); + VerifiableCredentialSubject verifiableCredentialSubject = + newList.getCredentialSubject().get(0); + String encodedNewList = (String) verifiableCredentialSubject.get("encodedList"); + byte[] decodedNewList = Base64.getDecoder().decode(encodedNewList); + BitSet decompressedNewList = decompressGzip(decodedNewList); + byte[] decodedList = Base64.getDecoder().decode(encodedList); + BitSet decompressedList = decompressGzip(decodedList); + assertFalse(decompressedList.get(0)); + assertTrue(decompressedNewList.get(0)); + } + + @Test + void shouldThrowRevocationServiceException() { + final var issuer = DID; + var encodedList = mockEmptyEncodedList(); + var credentialBuilder = mockStatusListVC(issuer, "1", encodedList); + var statusListCredential = mockStatusListCredential(issuer, credentialBuilder); + // 1. create status list with the credential + var statusListIndex = mockStatusListIndex(issuer, statusListCredential, "0"); + when(statusListIndex.getStatusListCredential()).thenReturn(statusListCredential); + when(statusListCredentialRepository.findById(any(String.class))) + .thenReturn(Optional.of(statusListCredential)); + CredentialStatusDto credentialStatusDto = Mockito.mock(CredentialStatusDto.class); + when(credentialStatusDto.id()) + .thenReturn( + "http://this-is-my-domain/api/v1/revocations/credentials/" + + TestUtil.extractBpnFromDid(issuer) + + "/revocation/1#0"); + when(credentialStatusDto.statusPurpose()).thenReturn("revocation"); + when(credentialStatusDto.statusListIndex()).thenReturn("0"); + when(credentialStatusDto.statusListCredential()) + .thenReturn( + "http://this-is-my-domain/api/v1/revocations/credentials/" + + TestUtil.extractBpnFromDid(issuer) + + "/revocation/1"); + when(credentialStatusDto.type()).thenReturn("StatusList2021Entry"); + try (MockedStatic utilities = Mockito.mockStatic(BitSetManager.class)) { + utilities + .when(() -> BitSetManager.revokeCredential(any(String.class), any(Integer.class))) + .thenThrow(new BitSetManagerException()); + assertThrows( + RevocationServiceException.class, + () -> revocationService.revoke(credentialStatusDto, "token")); + } + } + } + + @Nested + class CreateStatusListTest { + + @Test + void shouldCreateNewStatusList() { + ReflectionTestUtils.setField(httpClientService, "domainUrl", "http://this-is-my-domain"); + StatusEntryDto mockStatus = Mockito.mock(StatusEntryDto.class); + when(mockStatus.issuerId()).thenReturn(DID); + when(mockStatus.purpose()).thenReturn("revocation"); + CredentialStatusDto credentialStatusDto = + assertDoesNotThrow(() -> revocationService.createStatusList(mockStatus, "token")); + assertEquals("revocation", credentialStatusDto.statusPurpose()); + assertEquals( + "http://this-is-my-domain/api/v1/revocations/credentials/" + BPN + "/revocation/1#0", + credentialStatusDto.id()); + assertEquals("0", credentialStatusDto.statusListIndex()); + assertEquals( + "http://this-is-my-domain/api/v1/revocations/credentials/" + BPN + "/revocation/1", + credentialStatusDto.statusListCredential()); + assertEquals(StatusListCredentialSubject.TYPE_ENTRY, credentialStatusDto.type()); + Mockito.verify(statusListIndexRepository, times(1)).save(any(StatusListIndex.class)); + } + + @Test + void shouldUpdateExistingStatusList() { + ReflectionTestUtils.setField(httpClientService, "domainUrl", "http://this-is-my-domain"); + StatusListIndex statusListIndex = + StatusListIndex.builder() + .currentIndex("0") + .id(BPN + "-revocation#1") + .issuerBpnStatus(BPN + "-revocation") + .build(); + when(statusListIndexRepository.findByIssuerBpnStatus(BPN + "-revocation")) + .thenReturn(List.of(statusListIndex)); + StatusEntryDto mockStatus = Mockito.mock(StatusEntryDto.class); + when(mockStatus.issuerId()).thenReturn(DID); + when(mockStatus.purpose()).thenReturn("revocation"); + CredentialStatusDto credentialStatusDto = + assertDoesNotThrow(() -> revocationService.createStatusList(mockStatus, "token")); + assertEquals("revocation", credentialStatusDto.statusPurpose()); + assertEquals( + "http://this-is-my-domain/api/v1/revocations/credentials/" + BPN + "/revocation/1#1", + credentialStatusDto.id()); + assertEquals("1", credentialStatusDto.statusListIndex()); + assertEquals( + "http://this-is-my-domain/api/v1/revocations/credentials/" + BPN + "/revocation/1", + credentialStatusDto.statusListCredential()); + assertEquals(StatusListCredentialSubject.TYPE_ENTRY, credentialStatusDto.type()); + Mockito.verify(statusListIndexRepository, times(1)).save(any(StatusListIndex.class)); + } + + @Test + void shouldCreateNewStatusListWhenFirstFull() { + ReflectionTestUtils.setField(httpClientService, "domainUrl", "http://this-is-my-domain"); + StatusListIndex statusListIndex = + StatusListIndex.builder() + .currentIndex(String.valueOf(BITSET_SIZE - 1)) + .id(BPN + "-revocation#1") + .issuerBpnStatus(BPN + "-revocation") + .build(); + when(statusListIndexRepository.findByIssuerBpnStatus(BPN + "-revocation")) + .thenReturn(List.of(statusListIndex)); + StatusEntryDto mockStatus = Mockito.mock(StatusEntryDto.class); + when(mockStatus.issuerId()).thenReturn(DID); + when(mockStatus.purpose()).thenReturn("revocation"); + CredentialStatusDto credentialStatusDto = + assertDoesNotThrow(() -> revocationService.createStatusList(mockStatus, "token")); + assertEquals("revocation", credentialStatusDto.statusPurpose()); + assertEquals( + "http://this-is-my-domain/api/v1/revocations/credentials/" + BPN + "/revocation/2#0", + credentialStatusDto.id()); + assertEquals("0", credentialStatusDto.statusListIndex()); + assertEquals( + "http://this-is-my-domain/api/v1/revocations/credentials/" + BPN + "/revocation/2", + credentialStatusDto.statusListCredential()); + assertEquals(StatusListCredentialSubject.TYPE_ENTRY, credentialStatusDto.type()); + Mockito.verify(statusListIndexRepository, times(1)).save(any(StatusListIndex.class)); + } + } + + @Nested + class GetStatusListCredential { + + @Test + void shouldGetList() { + final var issuer = DID; + var fragment = UUID.randomUUID().toString(); + var encodedList = mockEmptyEncodedList(); + var credentialBuilder = mockStatusListVC(issuer, fragment, encodedList); + var statusListCredential = mockStatusListCredential(issuer, credentialBuilder); + when(statusListCredentialRepository.findById(any(String.class))) + .thenReturn(Optional.of(statusListCredential)); + VerifiableCredential verifiableCredential = + assertDoesNotThrow( + () -> revocationService.getStatusListCredential(BPN, "revocation", "1")); + assertNotNull(verifiableCredential); + assertEquals( + URI.create(TestUtil.STATUS_LIST_CREDENTIAL_SUBJECT_ID), + verifiableCredential.getCredentialSubject().get(0).getId()); + assertEquals(URI.create(issuer), verifiableCredential.getIssuer()); + assertEquals(URI.create(issuer + "#" + fragment), verifiableCredential.getId()); + assertEquals(verifiableCredential.getContext(), miwSettings.vcContexts()); + } + + @Test + void shouldReturnNull() { + when(statusListCredentialRepository.findById(any(String.class))).thenReturn(Optional.empty()); + VerifiableCredential verifiableCredential = + assertDoesNotThrow(() -> revocationService.getStatusListCredential("", "", "")); + assertNull(verifiableCredential); + } + } + + @Nested + class CheckSubStringExtraction { + @Test + void shouldExtractBpnFromDid() { + assertEquals(BPN, revocationService.extractBpnFromDid(DID)); + } + + @Test + void shouldExtractIdFromURL() { + assertEquals( + "BPNL123456789000-revocation#1", + revocationService.extractIdFromURL( + "http://this-is-my-domain/api/v1/revocations/credentials/BPNL123456789000/revocation/1")); + } + + @Test + void shouldExtractIdFromURLCaseSensitive() { + assertEquals( + "BPNL123456789000-revocation#1", + revocationService.extractIdFromURL( + "http://this-is-my-domain/api/v1/revocations/credentials/bpnl123456789000/revocation/1")); + } + + @Test + void shouldExtractBpnFromURL() { + assertEquals( + BPN, + revocationService.extractBpnFromURL( + "http://this-is-my-domain/api/v1/revocations/credentials/BPNL123456789000/revocation/1")); + } + + @Test + void shouldExtractBpnFromURLCaseSensitive() { + assertEquals( + BPN, + revocationService.extractBpnFromURL( + "http://this-is-my-domain/api/v1/revocations/creDENTials/bpNl123456789000/revocation/1")); + } + } + + @Nested + class ValidateCredentialStatus { + + @Test + @DisplayName("statusPurpose is valid") + void validCredentialStatusDto() { + String statusIndex = "1"; + String statusListCredential = + "http://example.com/api/v1/revocations/credentials/" + BPN + "/revocation/1"; + String id = statusListCredential + "#" + statusIndex; + + CredentialStatusDto dto = + new CredentialStatusDto( + id, "revocation", statusIndex, statusListCredential, "StatusList2021Entry"); + + assertDoesNotThrow(() -> revocationService.validateCredentialStatus(dto)); + } + + @Test + @DisplayName("statusPurpose from dto is not matching the credential status list url") + void invalidStatusPurpose_ThrowsIllegalArgumentException() { + String statusIndex = "1"; + String invalidPurpose = "/break/"; + String statusListCredential = + "http://example.com/api/v1/revocations/credentials/" + BPN + invalidPurpose + "1"; + String id = statusListCredential + "#" + statusIndex; + + CredentialStatusDto dto = + new CredentialStatusDto( + id, "revocation", statusIndex, statusListCredential, "StatusList2021Entry"); + assertThrows( + IllegalArgumentException.class, () -> revocationService.validateCredentialStatus(dto)); + } + + @Test + @DisplayName("id url from dto is not matching the credential status list url") + void invalidId_ThrowsIllegalArgumentException() { + String statusIndex = "1"; + String statusListCredential = + "http://example.com/api/v1/revocations/credentials/" + BPN + "/revocation/1"; + String id = statusListCredential.replace(BPN, "BPN0101010101010") + "#2"; + + CredentialStatusDto dto = + new CredentialStatusDto( + id, "revocation", statusIndex, statusListCredential, "StatusList2021Entry"); + assertThrows( + IllegalArgumentException.class, () -> revocationService.validateCredentialStatus(dto)); + } + + @Test + @DisplayName("credential status index is not matching the index in the url") + void invalidStatusIndex_ThrowsIllegalArgumentException() { + String statusIndex = "1"; + String statusListCredential = + "http://example.com/api/v1/revocations/credentials/" + BPN + "/revocation/1"; + String id = statusListCredential + "#2"; + + CredentialStatusDto dto = + new CredentialStatusDto( + id, "revocation", statusIndex, statusListCredential, "StatusList2021Entry"); + assertThrows( + IllegalArgumentException.class, () -> revocationService.validateCredentialStatus(dto)); + } + } +} diff --git a/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/utils/BitSetManagerTest.java b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/utils/BitSetManagerTest.java new file mode 100644 index 000000000..67f9c8134 --- /dev/null +++ b/revocation-service/src/test/java/org/eclipse/tractusx/managedidentitywallets/revocation/utils/BitSetManagerTest.java @@ -0,0 +1,109 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.revocation.utils; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.BitSet; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BitSetManagerTest { + + @Test + void initializeEncodedListString_ReturnsValidBase64() { + String encoded = BitSetManager.initializeEncodedListString(); + assertNotNull(encoded); + // Attempt to decode to verify it's valid Base64. + assertDoesNotThrow(() -> BitSetManager.decodeFromString(encoded)); + } + + @Test + void compress_And_Decompress_ReturnsOriginalBitSet() { + BitSet originalBitSet = new BitSet(BitSetManager.BITSET_SIZE); + originalBitSet.set(100); + originalBitSet.set(10000); + + byte[] compressedBytes = BitSetManager.compress(originalBitSet); + assertNotNull(compressedBytes); + + BitSet decompressedBitSet = BitSetManager.decompress(compressedBytes); + assertNotNull(decompressedBitSet); + assertEquals(originalBitSet, decompressedBitSet); + } + + @Test + void revokeCredential_SetsBitAndReturnsUpdatedEncodedList() throws Exception { + String encodedList = BitSetManager.initializeEncodedListString(); + int indexToRevoke = 99; + + String updatedEncodedList = BitSetManager.revokeCredential(encodedList, indexToRevoke); + assertNotNull(updatedEncodedList); + + BitSet updatedBitSet = + BitSetManager.decompress(BitSetManager.decodeFromString(updatedEncodedList)); + assertTrue(updatedBitSet.get(indexToRevoke)); + } + + @Test + @Disabled("should this even be checked here?") + void revokeCredential_AlreadyRevoked_ThrowsException() { + String encodedList = BitSetManager.initializeEncodedListString(); + int indexToRevoke = 99; + + // First time revoking should be fine + assertDoesNotThrow(() -> BitSetManager.revokeCredential(encodedList, indexToRevoke)); + + // Second time revoking should throw Exception + assertThrows(Exception.class, () -> BitSetManager.revokeCredential(encodedList, indexToRevoke)); + } + + @Test + void suspendCredential_FlipsBitAndReturnsUpdatedEncodedList() { + String encodedList = BitSetManager.initializeEncodedListString(); + int indexToSuspend = 99; + + String updatedEncodedList = BitSetManager.suspendCredential(encodedList, indexToSuspend); + assertNotNull(updatedEncodedList); + + BitSet updatedBitSet = + BitSetManager.decompress(BitSetManager.decodeFromString(updatedEncodedList)); + assertTrue(updatedBitSet.get(indexToSuspend)); // Suspend should flip the bit to true + } + + @Test + void encodeToStringAndDecodeFromString_AreReversible() { + byte[] originalData = new byte[]{ 1, 2, 3, 4, 5 }; + String encoded = BitSetManager.encodeToString(originalData); + assertNotNull(encoded); + + byte[] decodedData = BitSetManager.decodeFromString(encoded); + assertNotNull(decodedData); + assertArrayEquals(originalData, decodedData); + } +} diff --git a/settings.gradle b/settings.gradle index b2cb866c2..b50d694bb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,3 +4,5 @@ rootProject.name = 'managedidentitywallets' // for example: // include '' include 'miw' +include 'revocation-service' +include 'wallet-commons' diff --git a/wallet-commons/DEPENDENCIES b/wallet-commons/DEPENDENCIES new file mode 100644 index 000000000..f4f6517e6 --- /dev/null +++ b/wallet-commons/DEPENDENCIES @@ -0,0 +1,41 @@ +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.17.2, Apache-2.0, approved, #13672 +maven/mavencentral/com.fasterxml.jackson/jackson-bom/2.17.2, Apache-2.0, approved, #14162 +maven/mavencentral/com.github.docker-java/docker-java-api/3.3.4, Apache-2.0, approved, #10346 +maven/mavencentral/com.github.docker-java/docker-java-transport-zerodep/3.3.4, Apache-2.0 AND (Apache-2.0 AND BSD-3-Clause), approved, #15251 +maven/mavencentral/com.github.docker-java/docker-java-transport/3.3.4, Apache-2.0, approved, #7942 +maven/mavencentral/io.micrometer/micrometer-commons/1.13.2, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #14826 +maven/mavencentral/io.micrometer/micrometer-observation/1.13.2, Apache-2.0, approved, #14829 +maven/mavencentral/junit/junit/4.13.2, EPL-2.0, approved, CQ23636 +maven/mavencentral/net.java.dev.jna/jna/5.13.0, Apache-2.0 AND LGPL-2.1-or-later, approved, #15196 +maven/mavencentral/org.apache.commons/commons-compress/1.24.0, Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND LicenseRef-Public-Domain, approved, #10368 +maven/mavencentral/org.apiguardian/apiguardian-api/1.1.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.hamcrest/hamcrest-core/2.2, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-3-Clause, approved, clearlydefined +maven/mavencentral/org.jacoco/org.jacoco.agent/0.8.9, EPL-2.0, approved, CQ23285 +maven/mavencentral/org.jacoco/org.jacoco.ant/0.8.9, EPL-2.0, approved, #1068 +maven/mavencentral/org.jacoco/org.jacoco.core/0.8.9, EPL-2.0, approved, CQ23283 +maven/mavencentral/org.jacoco/org.jacoco.report/0.8.9, EPL-2.0 AND Apache-2.0, approved, CQ23284 +maven/mavencentral/org.jetbrains/annotations/17.0.0, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.junit.jupiter/junit-jupiter-api/5.10.3, EPL-2.0, approved, #9714 +maven/mavencentral/org.junit.jupiter/junit-jupiter-engine/5.10.3, EPL-2.0, approved, #9711 +maven/mavencentral/org.junit.platform/junit-platform-commons/1.10.3, EPL-2.0, approved, #9715 +maven/mavencentral/org.junit.platform/junit-platform-engine/1.10.3, EPL-2.0, approved, #9709 +maven/mavencentral/org.junit/junit-bom/5.10.3, EPL-2.0, approved, #9844 +maven/mavencentral/org.opentest4j/opentest4j/1.3.0, Apache-2.0, approved, #9713 +maven/mavencentral/org.ow2.asm/asm-commons/9.5, BSD-3-Clause, approved, #7553 +maven/mavencentral/org.ow2.asm/asm-tree/9.5, BSD-3-Clause, approved, #7555 +maven/mavencentral/org.ow2.asm/asm/9.5, BSD-3-Clause, approved, #7554 +maven/mavencentral/org.projectlombok/lombok/1.18.34, MIT, approved, #15192 +maven/mavencentral/org.rnorth.duct-tape/duct-tape/1.0.8, MIT, approved, clearlydefined +maven/mavencentral/org.slf4j/slf4j-api/2.0.13, MIT, approved, #5915 +maven/mavencentral/org.springframework.boot/spring-boot-autoconfigure/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot-devtools/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework.boot/spring-boot/3.3.2, Apache-2.0, approved, clearlydefined +maven/mavencentral/org.springframework/spring-aop/6.1.11, Apache-2.0, approved, #15221 +maven/mavencentral/org.springframework/spring-beans/6.1.11, Apache-2.0, approved, #15213 +maven/mavencentral/org.springframework/spring-context/6.1.11, Apache-2.0, approved, #15261 +maven/mavencentral/org.springframework/spring-core/6.1.11, Apache-2.0 AND BSD-3-Clause, approved, #15206 +maven/mavencentral/org.springframework/spring-expression/6.1.11, Apache-2.0, approved, #15264 +maven/mavencentral/org.springframework/spring-jcl/6.1.11, Apache-2.0, approved, #15266 +maven/mavencentral/org.testcontainers/junit-jupiter/1.19.3, MIT, approved, #10344 +maven/mavencentral/org.testcontainers/testcontainers/1.19.3, Apache-2.0 AND MIT, approved, #10347 diff --git a/wallet-commons/build.gradle b/wallet-commons/build.gradle new file mode 100644 index 000000000..7b7e67722 --- /dev/null +++ b/wallet-commons/build.gradle @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2024 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 { + id 'java-library' + id 'maven-publish' +} + +dependencies { + + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-oauth2-resource-server' + implementation 'org.springframework.security:spring-security-oauth2-jose' + + + testImplementation "org.testcontainers:junit-jupiter" + testImplementation 'org.junit.jupiter:junit-jupiter-api' +} + +dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" + mavenBom("org.testcontainers:testcontainers-bom:${testContainerVersion}") + } +} + + +jar { + enabled = true + archiveClassifier = '' +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/ApplicationRole.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/ApplicationRole.java similarity index 87% rename from miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/ApplicationRole.java rename to wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/ApplicationRole.java index a534ad42c..9ef1870dc 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/ApplicationRole.java +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/ApplicationRole.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,13 +19,12 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.constant; +package org.eclipse.tractusx.managedidentitywallets.commons.constant; -public class ApplicationRole { +import lombok.experimental.UtilityClass; - private ApplicationRole() { - throw new IllegalStateException("Constant class"); - } +@UtilityClass +public class ApplicationRole { /** * The constant ROLE_VIEW_WALLETS. diff --git a/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/CredentialStatus.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/CredentialStatus.java new file mode 100644 index 000000000..faa46f84a --- /dev/null +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/CredentialStatus.java @@ -0,0 +1,46 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.commons.constant; + +import lombok.Getter; + +/** + * The enum Credential status. + */ +@Getter +public enum CredentialStatus { + + /** + * Active credential status. + */ + ACTIVE("active"), + /** + * Revoked credential status. + */ + REVOKED("revoked"); + + private final String name; + + CredentialStatus(String name) { + this.name = name; + } +} diff --git a/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/RevocationPurpose.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/RevocationPurpose.java new file mode 100644 index 000000000..794de68bf --- /dev/null +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/RevocationPurpose.java @@ -0,0 +1,45 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.commons.constant; + +import lombok.Getter; + +/** + * The enum Revocation purpose. + */ +@Getter +public enum RevocationPurpose { + /** + * revocation purpose. + */ + REVOCATION("revocation"), + /** + * Suspension purpose. + */ + SUSPENSION("suspension"); + + private final String name; + + RevocationPurpose(String name) { + this.name = name; + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/StringPool.java similarity index 55% rename from miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java rename to wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/StringPool.java index dc137b8b8..d73feea1f 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/StringPool.java @@ -19,32 +19,74 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.constant; +package org.eclipse.tractusx.managedidentitywallets.commons.constant; + +import lombok.experimental.UtilityClass; /** - * The type Application constant. + * The type String pool. */ +@UtilityClass public class StringPool { + + /** + * The constant CREDENTIAL_ID. + */ public static final String CREDENTIAL_ID = "credentialId"; + /** + * The constant VERIFIABLE_CREDENTIALS. + */ public static final String VERIFIABLE_CREDENTIALS = "verifiableCredentials"; + /** + * The constant VP. + */ public static final String VP = "vp"; + /** + * The constant VC. + */ public static final String VC = "vc"; + /** + * The constant VALID. + */ public static final String VALID = "valid"; + /** + * The constant VALIDATE_AUDIENCE. + */ public static final String VALIDATE_AUDIENCE = "validateAudience"; + /** + * The constant VALIDATE_EXPIRY_DATE. + */ public static final String VALIDATE_EXPIRY_DATE = "validateExpiryDate"; + /** + * The constant VALIDATE_JWT_EXPIRY_DATE. + */ public static final String VALIDATE_JWT_EXPIRY_DATE = "validateJWTExpiryDate"; + /** + * The constant DID_DOCUMENT. + */ public static final String DID_DOCUMENT = "didDocument"; - private StringPool() { - throw new IllegalStateException("Constant class"); - } - + /** + * The constant ISSUER_DID. + */ public static final String ISSUER_DID = "issuerDid"; + /** + * The constant HOLDER_DID. + */ public static final String HOLDER_DID = "holderDid"; + /** + * The constant HOLDER_IDENTIFIER. + */ public static final String HOLDER_IDENTIFIER = "holderIdentifier"; + /** + * The constant TYPE. + */ public static final String TYPE = "type"; + /** + * The constant ED_25519. + */ public static final String ED_25519 = "ED25519"; @@ -58,40 +100,127 @@ private StringPool() { */ public static final String BPN = "bpn"; + /** + * The constant ID. + */ public static final String ID = "id"; + /** + * The constant CLIENT_ID. + */ public static final String CLIENT_ID = "miw_private_client"; + /** + * The constant CLIENT_SECRET. + */ public static final String CLIENT_SECRET = "miw_private_client_secret"; + /** + * The constant REALM. + */ public static final String REALM = "miw_test"; - - public static final String USER_PASSWORD = "s3cr3t"; - + /** + * The constant VALID_USER_NAME. + */ public static final String VALID_USER_NAME = "valid_user"; + /** + * The constant INVALID_USER_NAME. + */ public static final String INVALID_USER_NAME = "invalid_user"; + /** + * The constant CLIENT_CREDENTIALS. + */ public static final String CLIENT_CREDENTIALS = "client_credentials"; + /** + * The constant OPENID. + */ public static final String OPENID = "openid"; + /** + * The constant BEARER_SPACE. + */ public static final String BEARER_SPACE = "Bearer "; + /** + * The constant BPN_NUMBER_REGEX. + */ public static final String BPN_NUMBER_REGEX = "^(BPN)(L|S|A)[0-9A-Z]{12}"; + /** + * The constant W3_ID_JWS_2020_V1_CONTEXT_URL. + */ public static final String W3_ID_JWS_2020_V1_CONTEXT_URL = "https://w3id.org/security/suites/jws-2020/v1"; + /** + * The constant COMA_SEPARATOR. + */ public static final String COMA_SEPARATOR = ", "; + /** + * The constant BLANK_SEPARATOR. + */ public static final String BLANK_SEPARATOR = " "; + /** + * The constant COLON_SEPARATOR. + */ public static final String COLON_SEPARATOR = ":"; + /** + * The constant UNDERSCORE. + */ public static final String UNDERSCORE = "_"; + /** + * The constant REFERENCE_KEY. + */ public static final String REFERENCE_KEY = "dummy ref key, removed once vault setup is ready"; + /** + * The constant VAULT_ACCESS_TOKEN. + */ public static final String VAULT_ACCESS_TOKEN = "dummy vault access token, removed once vault setup is ready"; + /** + * The constant PRIVATE_KEY. + */ public static final String PRIVATE_KEY = "PRIVATE KEY"; + /** + * The constant PUBLIC_KEY. + */ public static final String PUBLIC_KEY = "PUBLIC KEY"; + /** + * The constant VC_JWT_KEY. + */ public static final String VC_JWT_KEY = "jwt"; + /** + * The constant AS_JWT. + */ public static final String AS_JWT = "asJwt"; + /** + * The constant BPN_CREDENTIAL. + */ public static final String BPN_CREDENTIAL = "BpnCredential"; + + public static final String ASSERTION_METHOD = "assertionMethod"; + public static final String SERVICE_ENDPOINT = "serviceEndpoint"; + public static final String SERVICE = "service"; + public static final String SECURITY_TOKEN_SERVICE = "SecurityTokenService"; + public static final String CREDENTIAL_SERVICE = "CredentialService"; + public static final String HTTPS_SCHEME = "https://"; + public static final String BPN_NOT_FOUND = "BPN not found"; + + /** + * The constant REVOCABLE. + */ + public static final String REVOCABLE = "revocable"; + + /** + * The constant CREDENTIAL_STATUS. + */ + public static final String CREDENTIAL_STATUS = "credentialStatus"; + + /** + * The constant STATUS. + */ + public static final String STATUS = "status"; + } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/SupportedAlgorithms.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/SupportedAlgorithms.java similarity index 79% rename from miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/SupportedAlgorithms.java rename to wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/SupportedAlgorithms.java index 227fa1347..adc2295af 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/SupportedAlgorithms.java +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/SupportedAlgorithms.java @@ -19,16 +19,25 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.constant; +package org.eclipse.tractusx.managedidentitywallets.commons.constant; +/** + * The enum Supported algorithms. + */ public enum SupportedAlgorithms { + /** + * Ed 25519 supported algorithms. + */ ED25519("ED25519"), + /** + * Es 256 k supported algorithms. + */ ES256K("ES256K"); - private String value; + private final String value; - SupportedAlgorithms(String value){ + SupportedAlgorithms(String value) { this.value = value; } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/TokenValidationErrors.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/TokenValidationErrors.java similarity index 94% rename from miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/TokenValidationErrors.java rename to wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/TokenValidationErrors.java index e90b784cf..571a3cd25 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/TokenValidationErrors.java +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/constant/TokenValidationErrors.java @@ -19,7 +19,7 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.constant; +package org.eclipse.tractusx.managedidentitywallets.commons.constant; public enum TokenValidationErrors { diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/BadDataException.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/BadDataException.java similarity index 92% rename from miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/BadDataException.java rename to wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/BadDataException.java index 79cd57916..050923ee3 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/BadDataException.java +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/BadDataException.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,7 +19,7 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.exception; +package org.eclipse.tractusx.managedidentitywallets.commons.exception; /** * The type Bad data exception. diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/ForbiddenException.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/ForbiddenException.java similarity index 92% rename from miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/ForbiddenException.java rename to wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/ForbiddenException.java index 51938e2bd..818403578 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/ForbiddenException.java +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/ForbiddenException.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,7 +19,7 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.exception; +package org.eclipse.tractusx.managedidentitywallets.commons.exception; /** * The type Forbidden exception. diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/utils/TokenParsingUtils.java similarity index 57% rename from miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java rename to wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/utils/TokenParsingUtils.java index e5101f118..595866d7d 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/utils/TokenParsingUtils.java @@ -19,29 +19,51 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.utils; +package org.eclipse.tractusx.managedidentitywallets.commons.utils; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import lombok.experimental.UtilityClass; -import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.commons.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.commons.exception.BadDataException; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtClaimNames; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import java.text.ParseException; +import java.util.Map; import java.util.Optional; +import java.util.TreeMap; -import static org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.NONCE; -import static org.springframework.security.oauth2.jwt.JwtClaimNames.JTI; -import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.ACCESS_TOKEN; -import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.SCOPE; - +/** + * The type Token parsing utils. + */ @UtilityClass public class TokenParsingUtils { + /** + * The constant PARSING_TOKEN_ERROR. + */ public static final String PARSING_TOKEN_ERROR = "Could not parse jwt token"; + /** + * The constant BEARER_ACCESS_SCOPE. + */ public static final String BEARER_ACCESS_SCOPE = "bearer_access_scope"; + /** + * The constant ACCESS_TOKEN_ERROR. + */ public static final String ACCESS_TOKEN_ERROR = "Access token not present"; + /** + * Gets claims set. + * + * @param tokenParsed the token parsed + * @return the claims set + */ public static JWTClaimsSet getClaimsSet(SignedJWT tokenParsed) { try { return tokenParsed.getJWTClaimsSet(); @@ -50,6 +72,12 @@ public static JWTClaimsSet getClaimsSet(SignedJWT tokenParsed) { } } + /** + * Parse token signed jwt. + * + * @param token the token + * @return the signed jwt + */ public static SignedJWT parseToken(String token) { try { return SignedJWT.parse(token); @@ -58,6 +86,13 @@ public static SignedJWT parseToken(String token) { } } + /** + * Gets string claim. + * + * @param claimsSet the claims set + * @param name the name + * @return the string claim + */ public static String getStringClaim(JWTClaimsSet claimsSet, String name) { try { return claimsSet.getStringClaim(name); @@ -66,15 +101,27 @@ public static String getStringClaim(JWTClaimsSet claimsSet, String name) { } } + /** + * Gets access token. + * + * @param claims the claims + * @return the access token + */ public static Optional getAccessToken(JWTClaimsSet claims) { try { - String accessTokenValue = claims.getStringClaim(ACCESS_TOKEN); + String accessTokenValue = claims.getStringClaim(OAuth2ParameterNames.ACCESS_TOKEN); return accessTokenValue == null ? Optional.empty() : Optional.of(accessTokenValue); } catch (ParseException e) { throw new BadDataException(PARSING_TOKEN_ERROR, e); } } + /** + * Gets access token. + * + * @param outerToken the outer token + * @return the access token + */ public static SignedJWT getAccessToken(String outerToken) { SignedJWT jwtOuter = parseToken(outerToken); JWTClaimsSet claimsSet = getClaimsSet(jwtOuter); @@ -82,9 +129,15 @@ public static SignedJWT getAccessToken(String outerToken) { return accessToken.map(TokenParsingUtils::parseToken).orElseThrow(() -> new BadDataException(ACCESS_TOKEN_ERROR)); } + /** + * Gets scope. + * + * @param jwtClaimsSet the jwt claims set + * @return the scope + */ public static String getScope(JWTClaimsSet jwtClaimsSet) { try { - String scopes = jwtClaimsSet.getStringClaim(SCOPE); + String scopes = jwtClaimsSet.getStringClaim(OAuth2ParameterNames.SCOPE); if (scopes == null) { scopes = jwtClaimsSet.getStringClaim(BEARER_ACCESS_SCOPE); } @@ -94,19 +147,46 @@ public static String getScope(JWTClaimsSet jwtClaimsSet) { } } + /** + * Gets jti access token. + * + * @param accessToken the access token + * @return the jti access token + */ public static String getJtiAccessToken(JWT accessToken) { try { - return getStringClaim(accessToken.getJWTClaimsSet(), JTI); + return getStringClaim(accessToken.getJWTClaimsSet(), JwtClaimNames.JTI); } catch (ParseException e) { throw new BadDataException(PARSING_TOKEN_ERROR, e); } } + /** + * Gets nonce access token. + * + * @param accessToken the access token + * @return the nonce access token + */ public static String getNonceAccessToken(JWT accessToken) { try { - return accessToken.getJWTClaimsSet().getStringClaim(NONCE); + return accessToken.getJWTClaimsSet().getStringClaim(IdTokenClaimNames.NONCE); } catch (ParseException e) { throw new BadDataException(PARSING_TOKEN_ERROR, e); } } + + /** + * Gets bpn from token. + * + * @param authentication the authentication + * @return the bpn from token + */ + public static String getBPNFromToken(Authentication authentication) { + Jwt jwt = ((JwtAuthenticationToken) authentication).getToken(); + // this will misbehave if we have more then one claims with different case + // ie. BPN=123456 and bpn=789456 + Map claims = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + claims.putAll(jwt.getClaims()); + return claims.get(StringPool.BPN).toString(); + } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/Validate.java b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/utils/Validate.java similarity index 96% rename from miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/Validate.java rename to wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/utils/Validate.java index a33fb97c7..e32fe86cb 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/Validate.java +++ b/wallet-commons/src/main/java/org/eclipse/tractusx/managedidentitywallets/commons/utils/Validate.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,7 +19,7 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.utils; +package org.eclipse.tractusx.managedidentitywallets.commons.utils; import java.util.Objects; @@ -151,4 +151,4 @@ public T launch(RuntimeException e) { } return value; } -} \ No newline at end of file +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/ValidateTest.java b/wallet-commons/src/test/java/org/eclipse/tractusx/managedidentitywallets/commons/ValidateTest.java similarity index 70% rename from miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/ValidateTest.java rename to wallet-commons/src/test/java/org/eclipse/tractusx/managedidentitywallets/commons/ValidateTest.java index 9ba28ec8f..f8e704676 100644 --- a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/ValidateTest.java +++ b/wallet-commons/src/test/java/org/eclipse/tractusx/managedidentitywallets/commons/ValidateTest.java @@ -1,6 +1,6 @@ /* * ******************************************************************************* - * Copyright (c) 2021,2023 Contributors to the Eclipse Foundation + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -19,8 +19,9 @@ * ****************************************************************************** */ -package org.eclipse.tractusx.managedidentitywallets.utils; +package org.eclipse.tractusx.managedidentitywallets.commons; +import org.eclipse.tractusx.managedidentitywallets.commons.utils.Validate; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -29,25 +30,26 @@ class ValidateTest { @Test void validateTest() { - Assertions.assertThrows(RuntimeException.class, () -> Validate.isFalse(false).launch(new RuntimeException())); + RuntimeException runtimeException = new RuntimeException(); + Assertions.assertThrows(RuntimeException.class, () -> Validate.isFalse(false).launch(runtimeException)); - Assertions.assertThrows(RuntimeException.class, () -> Validate.isTrue(true).launch(new RuntimeException())); + Assertions.assertThrows(RuntimeException.class, () -> Validate.isTrue(true).launch(runtimeException)); - Assertions.assertThrows(RuntimeException.class, () -> Validate.isNull(null).launch(new RuntimeException())); + Assertions.assertThrows(RuntimeException.class, () -> Validate.isNull(null).launch(runtimeException)); - Assertions.assertThrows(RuntimeException.class, () -> Validate.isNotNull("Test").launch(new RuntimeException())); + Assertions.assertThrows(RuntimeException.class, () -> Validate.isNotNull("Test").launch(runtimeException)); - Assertions.assertThrows(RuntimeException.class, () -> Validate.value("").isNotEmpty().launch(new RuntimeException())); + Assertions.assertThrows(RuntimeException.class, () -> Validate.value("").isNotEmpty().launch(runtimeException)); - Assertions.assertDoesNotThrow(() -> Validate.isFalse(true).launch(new RuntimeException())); + Assertions.assertDoesNotThrow(() -> Validate.isFalse(true).launch(runtimeException)); - Assertions.assertDoesNotThrow(() -> Validate.isTrue(false).launch(new RuntimeException())); + Assertions.assertDoesNotThrow(() -> Validate.isTrue(false).launch(runtimeException)); - Assertions.assertDoesNotThrow(() -> Validate.isNull("").launch(new RuntimeException())); + Assertions.assertDoesNotThrow(() -> Validate.isNull("").launch(runtimeException)); - Assertions.assertDoesNotThrow(() -> Validate.isNotNull(null).launch(new RuntimeException())); + Assertions.assertDoesNotThrow(() -> Validate.isNotNull(null).launch(runtimeException)); - Assertions.assertDoesNotThrow(() -> Validate.value("Test").isNotEmpty().launch(new RuntimeException())); + Assertions.assertDoesNotThrow(() -> Validate.value("Test").isNotEmpty().launch(runtimeException)); } } diff --git a/wallet-commons/src/test/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/BadDataExceptionTest.java b/wallet-commons/src/test/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/BadDataExceptionTest.java new file mode 100644 index 000000000..feb1ba5a7 --- /dev/null +++ b/wallet-commons/src/test/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/BadDataExceptionTest.java @@ -0,0 +1,56 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.commons.exception; + + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class BadDataExceptionTest { + + public static final String ERROR_MSG = "This is a bad data exception"; + + @Test + void testConstructorWithMessage() { + String message = ERROR_MSG; + BadDataException exception = new BadDataException(message); + Assertions.assertEquals(message, exception.getMessage()); + Assertions.assertNull(exception.getCause()); + } + + @Test + void testConstructorWithMessageAndCause() { + String message = ERROR_MSG; + Throwable cause = new IllegalArgumentException("Invalid argument"); + BadDataException exception = new BadDataException(message, cause); + Assertions.assertEquals(message, exception.getMessage()); + Assertions.assertEquals(cause, exception.getCause()); + } + + @Test + void testConstructorWithCause() { + Throwable cause = new IllegalArgumentException("Invalid argument"); + BadDataException exception = new BadDataException(cause); + Assertions.assertEquals(cause.toString(), exception.getMessage()); + Assertions.assertEquals(cause, exception.getCause()); + } +} diff --git a/wallet-commons/src/test/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/ForbiddenExceptionTest.java b/wallet-commons/src/test/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/ForbiddenExceptionTest.java new file mode 100644 index 000000000..4dcfd20ae --- /dev/null +++ b/wallet-commons/src/test/java/org/eclipse/tractusx/managedidentitywallets/commons/exception/ForbiddenExceptionTest.java @@ -0,0 +1,58 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 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.managedidentitywallets.commons.exception; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ForbiddenExceptionTest { + + @Test + void testConstructorWithMessage() { + String message = "Forbidden access!"; + ForbiddenException exception = new ForbiddenException(message); + Assertions.assertEquals(message, exception.getMessage()); + } + + @Test + void testConstructorWithMessageAndCause() { + String message = "Forbidden access!"; + Throwable cause = new IllegalArgumentException("Illegal argument"); + ForbiddenException exception = new ForbiddenException(message, cause); + Assertions.assertEquals(message, exception.getMessage()); + Assertions.assertEquals(cause, exception.getCause()); + } + + @Test + void testConstructorWithCause() { + Throwable cause = new IllegalArgumentException("Illegal argument"); + ForbiddenException exception = new ForbiddenException(cause); + Assertions.assertEquals(cause, exception.getCause()); + } + + @Test + void testDefaultConstructor() { + ForbiddenException exception = new ForbiddenException(); + Assertions.assertNull(exception.getMessage()); + Assertions.assertNull(exception.getCause()); + } +}