diff --git a/.env.docker b/.env.docker
index b3cdbea01..6800a5472 100644
--- a/.env.docker
+++ b/.env.docker
@@ -26,4 +26,7 @@ BPDM_AUTH_SCOPE=openid
BPDM_AUTH_URL=https://centralidp.demo.catena-x.net/auth/realms/CX-Central/protocol/openid-connect/token
BPDM_PULL_DATA_AT_HOUR=23
+REVOCATION_URL=http://localhost:8086
+REVOCATION_CREATE_STATUS_LIST_CREDENTIAL_AT_HOUR=3
+
LOG_LEVEL_EXPOSED=INFO
diff --git a/.env.example b/.env.example
index 7d15be793..e841de870 100644
--- a/.env.example
+++ b/.env.example
@@ -1,7 +1,7 @@
APP_VERSION=
CX_BPN="bpn111"
-CX_DB_JDBC_URL="jdbc:sqlite:local.db"
-CX_DB_JDBC_DRIVER="org.sqlite.JDBC"
+CX_DB_JDBC_URL="jdbc:postgresql://localhost:5432/miwdev?user=miwdevuser&password=^cXnF61qM1kf"
+CX_DB_JDBC_DRIVER="org.postgresql.Driver"
CX_AUTH_JWKS_URL="http://localhost:8081/auth/realms/catenax/protocol/openid-connect/certs"
CX_AUTH_ISSUER_URL="http://localhost:8081/auth/realms/catenax"
CX_AUTH_REALM="catenax"
@@ -23,5 +23,8 @@ BPDM_AUTH_SCOPE="openid"
BPDM_AUTH_URL="https://centralidp.demo.catena-x.net/auth/realms/CX-Central/protocol/openid-connect/token"
BPDM_PULL_DATA_AT_HOUR="23"
+REVOCATION_URL="http://localhost:8086"
+REVOCATION_CREATE_STATUS_LIST_CREDENTIAL_AT_HOUR="3"
+
# Set to OFF in production to avoid extensive logging
LOG_LEVEL_EXPOSED=INFO
diff --git a/.github/workflows/chart-lint.yml b/.github/workflows/chart-lint.yml
new file mode 100644
index 000000000..e4f177ab0
--- /dev/null
+++ b/.github/workflows/chart-lint.yml
@@ -0,0 +1,57 @@
+name: Lint and Test Charts
+
+# Run chart linting and tests on each pull request
+on:
+ pull_request:
+ paths:
+ - 'charts/**'
+
+jobs:
+ lint-test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Set up Helm
+ uses: azure/setup-helm@v1
+ with:
+ version: v3.9.3
+
+ - name: Add Helm repos
+ run: |
+ cd charts/managed-identity-wallets
+ helm repo add bitnami https://charts.bitnami.com/bitnami
+ helm dependency update
+
+ # Setup python as a prerequisite for chart linting
+ - uses: actions/setup-python@v2
+ with:
+ python-version: 3.7
+
+ - name: Set up chart-testing
+ uses: helm/chart-testing-action@v2.3.1
+
+ - name: Run chart-testing (list-changed)
+ id: list-changed
+ run: |
+ changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }})
+ if [[ -n "$changed" ]]; then
+ echo "::set-output name=changed::true"
+ fi
+ # run chart linting
+ - name: Run chart-testing (lint)
+ run: ct lint --target-branch ${{ github.event.repository.default_branch }} --config charts/chart-testing-config.yaml
+
+ # Preparing a kind cluster to install and test charts on
+ - name: Create kind cluster
+ uses: helm/kind-action@v1.4.0
+ if: steps.list-changed.outputs.changed == 'true'
+
+ # install the chart to the kind cluster and run helm test
+ # define charts to test with the --charts parameter
+ - name: Run chart-testing (install)
+ run: ct install --charts charts/managed-identity-wallets --config charts/chart-testing-config.yaml
+ if: steps.list-changed.outputs.changed == 'true'
diff --git a/.github/workflows/chart-releaser.yaml b/.github/workflows/chart-releaser.yaml
new file mode 100644
index 000000000..77e1ae80e
--- /dev/null
+++ b/.github/workflows/chart-releaser.yaml
@@ -0,0 +1,37 @@
+name: Release - Helm Charts
+
+on:
+ push:
+ paths:
+ - 'charts/**'
+ branches:
+ - main
+jobs:
+ release:
+ # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
+ # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
+ permissions:
+ contents: write
+ runs-on: ubuntu-latest
+
+ steps:
+ # fetch-depth: 0 is required to determine differences in chart(s)
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Configure Git
+ run: |
+ git config user.name "$GITHUB_ACTOR"
+ git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
+
+ - name: Install Helm
+ uses: azure/setup-helm@v3
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Run chart-releaser
+ uses: helm/chart-releaser-action@v1.4.1
+ env:
+ CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
\ No newline at end of file
diff --git a/.github/workflows/kics.yml b/.github/workflows/kics.yml
index 5d89aeb84..71f535ec5 100644
--- a/.github/workflows/kics.yml
+++ b/.github/workflows/kics.yml
@@ -63,6 +63,7 @@ jobs:
# GITHUB_TOKEN enables this github action to access github API and post comments in a pull request
# token: ${{ secrets.GITHUB_TOKEN }}
# enable_comments: true
+ exclude_paths: "docs/openapi_v200.json"
# Upload findings to GitHub Advanced Security Dashboard
- name: Upload SARIF file for GitHub Advanced Security Dashboard
diff --git a/.github/workflows/service-build.yaml b/.github/workflows/service-build.yaml
index e5b65a514..0583a26da 100644
--- a/.github/workflows/service-build.yaml
+++ b/.github/workflows/service-build.yaml
@@ -99,5 +99,5 @@ jobs:
with:
context: .
push: true
- tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest${{ env.TAG_SUFFIX }}, ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.APP_VERSION }}.${{ env.SHORT_SHA }}
+ tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest${{ env.TAG_SUFFIX }}, ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.APP_VERSION }}.${{ env.SHORT_SHA }}, ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.APP_VERSION }}${{ env.TAG_SUFFIX }}
labels: ${{ steps.meta.outputs.labels }}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..ccc6c9951
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,16 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [2.1.1] - 2022-10-07
+
+### Added
+- Wallet handling via CRUD
+- Verifiable Credentials and Verifiable Presentation handling via CRUD
+- Revocation of Verifiable Credentials
+- DID document and Service Endpoint handling via CRUD
+- Business Partner data integration from external BPDM service and Verifiable Credentials
diff --git a/DEPENDENCIES b/DEPENDENCIES
index 22918d4ba..a296e91b4 100644
--- a/DEPENDENCIES
+++ b/DEPENDENCIES
@@ -6,7 +6,7 @@ maven/mavencentral/com.apicatalog/titanium-json-ld/1.0.0, Apache-2.0, approved,
maven/mavencentral/com.apicatalog/titanium-json-ld/1.1.0, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.auth0/java-jwt/3.13.0, MIT, approved, clearlydefined
maven/mavencentral/com.auth0/jwks-rsa/0.17.0, MIT, approved, #3117
-maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.13.1, Apache-2.0, approved, CQ24135
+maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.13.3, Apache-2.0, approved, CQ24135
maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.12.5, Apache-2.0, approved, CQ23845
maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.13.1, Apache-2.0, approved, #2133
maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.11.0, Apache-2.0, approved, CQ23093
@@ -19,6 +19,7 @@ maven/mavencentral/com.github.kagkarlsson/db-scheduler/11.2, Apache-2.0, approve
maven/mavencentral/com.github.kagkarlsson/micro-jdbc/0.3, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.github.peteroupc/numbers/1.7.4, CC0-1.0, approved, CQ22895
maven/mavencentral/com.google.code.findbugs/jsr305/3.0.2, Apache-2.0, approved, #20
+maven/mavencentral/com.google.code.gson/gson/2.9.1, Apache-2.0, approved, CQ24148
maven/mavencentral/com.google.errorprone/error_prone_annotations/2.3.4, Apache-2.0, approved, #807
maven/mavencentral/com.google.guava/failureaccess/1.0.1, Apache-2.0, approved, CQ22654
maven/mavencentral/com.google.guava/guava/30.0-jre, Apache-2.0, approved, clearlydefined
@@ -26,6 +27,10 @@ maven/mavencentral/com.google.guava/listenablefuture/9999.0-empty-to-avoid-confl
maven/mavencentral/com.google.j2objc/j2objc-annotations/1.3, Apache-2.0, approved, CQ21195
maven/mavencentral/com.googlecode.json-simple/json-simple/1.1.1, Apache-2.0, approved, CQ9858
maven/mavencentral/com.sabnf/apg/1.1.0, BSD-2-Clause, approved, #3114
+maven/mavencentral/com.squareup.okhttp3/logging-interceptor/4.10.0, Apache-2.0, approved, clearlydefined
+maven/mavencentral/com.squareup.okhttp3/okhttp/4.10.0, Apache-2.0 AND MPL-2.0, approved, #3057
+maven/mavencentral/com.squareup.okio/okio-jvm/3.0.0, Apache-2.0, approved, clearlydefined
+maven/mavencentral/com.squareup.okio/okio/3.0.0, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.typesafe/config/1.4.1, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.upokecenter/cbor/4.4.3, CC0-1.0, approved, #254
maven/mavencentral/commons-codec/commons-codec/1.11, Apache-2.0 AND BSD-3-Clause, approved, CQ15971
@@ -97,12 +102,15 @@ maven/mavencentral/io.netty/netty-transport-native-epoll/4.1.74.Final, Apache-2.
maven/mavencentral/io.netty/netty-transport-native-kqueue/4.1.74.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926
maven/mavencentral/io.netty/netty-transport-native-unix-common/4.1.74.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926
maven/mavencentral/io.netty/netty-transport/4.1.74.Final, Apache-2.0 AND BSD-3-Clause AND MIT, approved, CQ20926
+maven/mavencentral/io.projectreactor/reactor-core/3.4.22, Apache-2.0, approved, clearlydefined
maven/mavencentral/io.setl/rdf-urdna/1.1, Apache-2.0, approved, clearlydefined
maven/mavencentral/junit/junit/4.10, CPL-1.0, approved, CQ5958
maven/mavencentral/junit/junit/4.13.2, EPL-2.0, approved, CQ23636
maven/mavencentral/net.java.dev.jna/jna-platform/5.9.0, Apache-2.0 OR LGPL-2.1-or-later, approved, #3118
maven/mavencentral/net.java.dev.jna/jna/5.6.0, Apache-2.0 AND LGPL-2.1-or-later, approved, CQ22391
maven/mavencentral/net.java.dev.jna/jna/5.9.0, Apache-2.0 AND LGPL-2.1-or-later, approved, #3110
+maven/mavencentral/network.idu.acapy/aries-client-python/0.7.29, Apache-2.0, approved, clearlydefined
+maven/mavencentral/org.apache.commons/commons-lang3/3.12.0, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.apache.httpcomponents/httpasyncclient/4.1.5, Apache-2.0, approved, CQ13506
maven/mavencentral/org.apache.httpcomponents/httpclient/4.5.13, Apache-2.0 AND LicenseRef-Public-Domain, approved, CQ23527
maven/mavencentral/org.apache.httpcomponents/httpcore-nio/4.4.15, Apache-2.0, approved, CQ13509
@@ -200,7 +208,8 @@ maven/mavencentral/org.ow2.asm/asm-analysis/9.2, BSD-3-Clause, approved, clearly
maven/mavencentral/org.ow2.asm/asm-commons/9.2, BSD-3-Clause, approved, clearlydefined
maven/mavencentral/org.ow2.asm/asm-tree/9.2, BSD-3-Clause, approved, clearlydefined
maven/mavencentral/org.ow2.asm/asm/9.2, BSD-3-Clause, approved, CQ23635
-maven/mavencentral/org.postgresql/postgresql/42.4.0, BSD-2-Clause, approved, #3112
+maven/mavencentral/org.postgresql/postgresql/42.4.1, BSD-2-Clause, approved, #3112
+maven/mavencentral/org.reactivestreams/reactive-streams/1.0.4, CC0-1.0, approved, CQ16332
maven/mavencentral/org.slf4j/slf4j-api/1.7.25, MIT, approved, CQ13368
maven/mavencentral/org.slf4j/slf4j-api/1.7.30, MIT, approved, CQ13368
maven/mavencentral/org.slf4j/slf4j-api/1.7.32, MIT, approved, CQ13368
diff --git a/Dockerfile b/Dockerfile
index ee674b8f0..60e76cb4d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM amazoncorretto:17-alpine
+FROM eclipse-temurin:19-jre-alpine
EXPOSE 8080:8080
# run as non-root user
RUN addgroup -g 1001 -S user && adduser -u 1001 -S -s /bin/false -G user user
diff --git a/README.md b/README.md
index c4a4b2c45..b485f29e6 100644
--- a/README.md
+++ b/README.md
@@ -1,417 +1,441 @@
-# Catena-X Core Managed Identity Wallets
+# Managed Identity Wallets
This repository is part of the overarching Catena-X project, and more specifically
developed within the Catena-X Core Agile Release Train.
-The Managed Identity Wallets service implements the Self-Sovereign-Identity (SSI)
+The Managed Identity Wallets (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.
Technically this project is developed using the [ktor](https://ktor.io) Microservices
framework and thus the Kotlin language. It is using [gradle](https://gradle.org/) as
-build system.
-
-# Table of contents
-
-1. [Introduction](#introduction)
-2. [Used Technologies](#usedtechnologies)
-3. [Local Deployment Toolstack](#deploymentwithIntellij)
-4. [Steps for initial lokal Deployment and Wallet Creation](#initialDeploymentandWalletCreation)
-5. [Building with gradle](#buildingWithGradle)
-6. [Running locally with gradle (MacOS)](#runningLocallyWithGradle)
- 1. [Under IntelliJ](#underIntelliJ)
-7. [Building and running the Docker image](#buildingAndRunningTheDockerImage)
-8. [Environment variable setup](#environmentVariableSetup)
-9. [Local development environment](#localDevelopmentEnvironment)
- 1. [Aca-Py Docker Image](#acapyDockerImage)
- 2. [Start up Docker Containers for Postgres, Keycloak and AcaPy](#startupDockerContainers)
- 3. [IntelliJ Development Setup](#intellijDevelopmentSetup)
- 4. [Initial Wallet Setup](#initialWalletSetup)
-10. [Testing GitHub actions locally](#testingGitHubActionsLocally)
-11. [Setting up progresql database](#settingUpPostgresSqlDatabase)
-12. [Generate DID from Seed](#generateDIDFromSEED)
-13. [Tests](#tests)
-12. [Dashboard](#dashboard)
-13. [Future](#future)
-14. [Further Notes](#furtherNotes)
-15. [Helm Setup and Auto Deployment](#helmSetupAndAutoDeployment)
-
-## Used technologies in this Project
-
-- ACA-Py (Aries Cloud Agent Python) https://github.com/hyperledger/aries-cloudagent-python
- * specifially the multi-tenant feature https://github.com/hyperledger/aries-cloudagent-python/blob/main/Multitenancy.md)
- * and the JSON-LD credential https://github.com/hyperledger/aries-cloudagent-python/blob/main/JsonLdCredentials.md)
-- Hyperledger Indy https://hyperledger-indy.readthedocs.io/en/latest/
-- Ktor Framework https://ktor.io/
-
-## Local Deployment Toolstack
-
-- Intellij - https://www.jetbrains.com/de-de/idea/download/
-- Postman - https://www.postman.com/downloads/
-- Docker - https://www.docker.com/products/docker-desktop/
-- DBeaver - https://dbeaver.io/
-- Gradle - https://gradle.org/install/
-
-## Steps for initial lokal Deployment and Wallet Creation
-
-1. Clone the Github Repository - https://github.com/eclipse-tractusx/managed-identity-wallets.git
-2. (Optional) Clone the [Aca-Py Docker Image](#acapyDockerImage)
-3. Copy .env.example and rename to dev.env see section [IntelliJ Development Setup](#intellijDevelopmentSetup)
-4. Start Docker containers of Keycloak, Acapy and Postgres, see section [Startup Docker Containers](#startupDockerContainers)
-5. Setup Postgres Connection in DBeaver with Credentials -postgres, -cx_password on port 5432
- 1. Add the postgres settings to dev.env and comment out the h2-settings also in section
- 2. Create miwdev Database with following commands:
- ```
- CREATE DATABASE miwdev;
- CREATE ROLE miwdevuser WITH LOGIN NOSUPERUSER INHERIT CREATEDB NOCREATEROLE NOREPLICATION PASSWORD '^cXnF61qM1kf';
- GRANT CONNECT ON DATABASE miwdev TO miwdevuser;
+build system. To store the wallets and communicate with an external ledger MIW is using
+[Aries Cloud Agent Python](https://github.com/hyperledger/aries-cloudagent-python) with
+it's [multi-tenant feature](https://github.com/hyperledger/aries-cloudagent-python/blob/main/Multitenancy.md)
+and [JSON-LD credential](https://github.com/hyperledger/aries-cloudagent-python/blob/main/JsonLdCredentials.md)
+To support credential revocation MIW is using the revocation service within the
+[GXFS Notarization API/Service](https://gitlab.com/gaia-x/data-infrastructure-federation-services/not/notarization-service/-/tree/main/services/revocation)
+
+> **Warning**
+> This is not yet ready for production usage, as
+> [Aries Cloud Agent Python](https://github.com/hyperledger/aries-cloudagent-python)
+> does not support `did:indy` resolution yet. This disclaimer will be removed,
+> once it is available.
+
+# Developer Documentation
+
+To run MIW locally, this section describes the tooling as well as
+the local development setup.
+## Tooling
+
+Following tools the MIW development team used successfully:
+
+| Area | Tool | Download Link | Comment |
+|-------------|--------------------|------------------|-------------|
+| IDE | IntelliJ | https://www.jetbrains.com/idea/download/ | Additionally the [envfile plugin](https://plugins.jetbrains.com/plugin/7861-envfile) is suggested |
+| | Visual Studio Code | https://code.visualstudio.com/download | Test with version 1.71.2, additionally Git, Kotlin, Kubernetes plugins are suggested |
+| Build | Gradle | https://gradle.org/install/ | Tested with version 7.3.3 |
+| Runtime | Docker Desktop | https://www.docker.com/products/docker-desktop/ | |
+| | Rancher Desktop | https://rancherdesktop.io | Tested with version 1.5.1, and Docker cli version `Docker version 20.10.17-rd, build c2e4e01` and Docker Compose cli version `Docker Compose version v2.6.1` |
+| API Testing | Postman | https://www.postman.com/downloads/ | Tested with version 9.31.0 |
+| Database | DBeaver | https://dbeaver.io/ | Tested with version 22.2.0.202209051344 |
+
+## Environment Variables
+
+Please see the file `.env.example` for the environment examples that are used
+below. Here a few hints on how to set it up:
+
+| Key | Type | Description |
+|---------------------------|--------|-------------|
+| `CX_DB_JDBC_URL` | URL | database connection string, most commonly postgreSQL is used |
+| `CX_DB_JDBC_DRIVER` | URL | database driver to use, most commonly postgreSQL is used |
+| `CX_AUTH_JWKS_URL` | URL | IAM certs url |
+| `CX_AUTH_ISSUER_URL` | URL | IAM token issuer url |
+| `CX_AUTH_REALM` | String | IAM realm |
+| `CX_AUTH_ROLE_MAPPINGS` | String | IAM role mapping |
+| `CX_AUTH_RESOURCE_ID` | String | IAM resource id |
+| `CX_AUTH_CLIENT_ID` | String | IAM client id |
+| `CX_AUTH_CLIENT_SECRET` | String | It can be extracted from keycloak under *realms* >*catenax* > *clients* > *ManagedIdentityWallets* > *credentials* |
+| `APP_VERSION` | String | application version, this should be in-line with the version in the deployment |
+| `ACAPY_API_ADMIN_URL` | String | admin url of ACA-Py |
+| `ACAPY_ADMIN_API_KEY` | String | admin api key of ACA-Py endpoints |
+| `ACAPY_NETWORK_IDENTIFIER`| String | Hyperledger Indy name space |
+| `CX_BPN` | String | BPN of the base wallet, this wallet is the first to create |
+| `BPDM_DATAPOOL_URL` | String | BPDM data pool API endpoint |
+| `BPDM_AUTH_CLIENT_ID` | String | client id for accessing the BPDM data pool endpoint |
+| `BPDM_AUTH_CLIENT_SECRET` | String | client secret for accessing the BPDM data pool endpoint |
+| `BPDM_AUTH_GRANT_TYPE` | String | grant type for accessing the BPDM data pool endpoint |
+| `BPDM_AUTH_SCOPE` | String | openid scope for accessing the BPDM data pool endpoint |
+| `BPDM_AUTH_URL` | String | IAM url to get the access token for BPDM data pool endpoint |
+| `BPDM_PULL_DATA_AT_HOUR` | String | At which hour (24-hour clock) the cron job should pull the data from the BPDM data pool |
+| `REVOCATION_URL` | String | URL of the revocation service |
+| `REVOCATION_CREATE_STATUS_LIST_CREDENTIAL_AT_HOUR` | String | At which hour (24-hour clock) the cron job should issue/update status-list credentials |
+
+## Local Development Setup
+
+To get a full development environment up (first with a in-memory database)
+run following these steps:
+
+1. Clone the GitHub repository
+
+ ```bash
+ git clone https://github.com/catenax-ng/product-core-managed-identity-wallets.git
+ cd product-core-managed-identity-wallets
```
-
- Then following environment settings in your local environment file (potentially
- named `dev.env`) can be used:
-
+
+1. Copy over the `.env.example` to `dev.env`
+
+
+ ```bash
+ cp .env.example dev.env
```
- CX_DB_JDBC_URL="jdbc:postgresql://localhost:5432/miwdev?user=miwdevuser&password=^cXnF61qM1kf"
- CX_DB_JDBC_DRIVER="org.postgresql.Driver"
+
+1. Start the supporting containers for postgreSQL (database), keycloak (identity
+management), ACA-Py (ledger communication) and revocation service (credential
+revocation handling)
+
+ ```bash
+ cd dev-assets/dev-containers
+ docker compose up -d
```
-6. Run `Application.kt` in IntelliJ (with `dev.env` as configuration) or in your IDE or run it on the command line (on MacOS: `set -a; source dev.env; set +a` and `./gradlew run`)
-7. Start Postman and add the environment and the collection from ./dev-assets/
- 1. In the added environment make sure that the client_id and client_secret are correct. Check the steps in [start up Docker containers](#startupDockerContainers) to get the current values of the client id and secret
- 2. In the body of *Create wallet in Managed Identity Wallets*, change the `bpn` value to your `CX_BPN` from your env file
- 1. ![Change the BPN name](docs/images/ChangeBpnName.png "Adjusting the BPN Name")
- 3. Execute the request and note down your `did` and `verKey` from the response
- 1. ![Create wallet response](docs/images/CreateWalletResponse.png "Wallet creation response")
-8. Register public DID
- 1. Register your DID from your Wallet at https://indy-test.idu.network/ with "Register from DID"
- 1. ![Public DID registration](docs/images/PublicDIDRegister.png "Public DID registration")
- 2. Register your DID with Managed Identity Wallets with a POST to `/api/wallets//public` and as body the ver key
- `{ "verKey": "verification key from creation" }`
-9. Now you have created your own Wallet and published your DID to the Ledger, you can retrieve the list of wallets in Postman via the *Get wallets from Managed Identity Wallets*
-## Building with gradle
+ You can stop the containers via `docker compose down -v`
-To install gradle just follow [the official guide](https://gradle.org/install/), e.g. on MacOS homebrew can be used:
+1. Run the MIW service from the project rootfolder via (on MacOS)
-```
-brew install gradle
-```
+ ```bash
+ cd ../../
+ set -a; source dev.env; set +a
+ ./gradlew run
+ ```
-Building then works with
+ or respectively run `Application.kt` within in your IDE (using `dev.env` as configuration).
-```
-gradle build
-```
+1. :tada: **First milestone reached the MIW service is up and running!**
-Or, as we also use gradle in the CI/CD pipeline, the gradle wrapper can be used
+ Suggested next step is to use the postgreSQL database to have persistent storage
+ across starts, this can be done via changing following variables in `dev.env`
+ (assuming the standard port for postgreSQL 5432 is available).
-```
-./gradlew build
-```
+ | Key | Value |
+ |-------------------|-----------------|
+ | CX_DB_JDBC_URL | `jdbc:postgresql://localhost:5432/miwdev?user=miwdevuser&password=cx_password` |
+ | CX_DB_JDBC_DRIVER | `org.postgresql.Driver` |
-In the following the `gradle` commands are using the gradle wrapper `gradlew`.
+ Then restart the service via `./gradlew run`
-## Running locally with gradle (MacOS)
+## Advanced Development Setup
-Copy the file `.env.example` and rename it to `dev.env`
+With the following steps you can explore the API and create your first wallet
-```
-set -a; source dev.env; set +a
-./gradlew run
-```
-### Under Intellij
+1. Start Postman and add the environment and the collection from ./dev-assets/
+ 1. In the added environment make sure that the client_id and client_secret are the same as in your `dev.env` configuration.
+ 2. In the body of *Create wallet in Managed Identity Wallets*, change the `bpn` value to your `CX_BPN` from your env file
-Download the Intellij envFile Plugin, copy the file `.env.example` and rename it to `dev.env`
+ ![Change the BPN name](docs/images/ChangeBpnName.png "Adjusting the BPN Name")
-## Building and running the Docker image
+ 3. Execute the request and note down your `did` and `verKey` from the response
+
+ ![Create wallet response](docs/images/CreateWalletResponse.png "Wallet creation response")
-Based on the [official documentation](https://ktor.io/docs/docker.html#getting-the-application-ready)
-below the steps to build and run this service via Docker.
+1. Register public DID
+ 1. Register your DID from your Wallet at https://indy-test.idu.network/ with "Register from DID"
-First step is to create the distribution of the application (in this case using Gradle):
+ ![Public DID registration](docs/images/PublicDIDRegister.png "Public DID registration")
-```
-./gradlew installDist
-```
+ 2. Register your DID with Managed Identity Wallets with a POST to `/api/wallets//public` and as body the ver key
+ `{ "verKey": "verification key from creation" }`
-Next step is to build and tag the Docker image:
+1. Issue Status-List Credential by sending a POST request to `/api/credentials/revocations/statusListCredentialRefresh`. This step is required because the Credential can only be issued after the DID is registiered on Ledger and set to Public
-```
-docker build -t catena-x/managed-identity-wallets: .
-```
+1. :tada: **Second milestone reached: Your own wallet!**
-Finally, start the image (please make sure that there are no quotes around the
-values in the env file):
+Now you have achieved the following:
-```
-docker run --env-file .env.docker -p 8080:8080 catena-x/managed-identity-wallets:
-```
+* set up the development environment to run it from source
+* created your own wallet and published your DID to the ledger
+* you can retrieve the list of wallets in Postman via the *Get wallets from Managed Identity Wallets*
-## Environment variable setup
+## Setup Summary
-Please see the file `.env.example` for the environment examples that are used
-below. Here a few hints on how to set it up:
+| Service | URL | Description |
+|-----------------------|-------------------------|-------------|
+| postgreSQL database | port 5432 on `localhost`| within the Docker Compose setup |
+| Keycloak | http://localhost:8081/ | within the Docker Compose setup, username: `admin` and password: `catena`, client id: `ManagedIdentityWallets` and client secret can be found under the Clients > ManagedIdentityWallets > Credentials |
+| revocation service | http://localhost:8086 | within the Docker Compose setup |
+| ACA-Py | http://localhost:10000 | within the Docker Compose setup |
+| MIW service | http://localhost:8080/ | |
-1. `CX_DB_JDBC_URL`: enter the database url, default is `jdbc:h2:mem:miwdev;DB_CLOSE_DELAY=-1;`
-2. `CX_DB_JDBC_DRIVER`: enter the driver, default is `org.h2.Driver`
-3. `CX_AUTH_JWKS_URL`: enter the keycloak certs url, e.g. `http://localhost:8081/auth/realms/catenax/protocol/openid-connect/certs`
-4. `CX_AUTH_ISSUER_URL`: enter the token issue, e.g. `http://localhost:8081/auth/realms/catenax`
-5. `CX_AUTH_REALM`: specify the realm, e.g. `catenax`
-6. `CX_AUTH_ROLE_MAPPINGS`: specify the expected role mappings within the token, e.g. `create_wallets:add_wallets,view_wallets:view_wallets,update_wallets:update_wallets,delete_wallets:delete_wallets,view_wallet:view_wallet,update_wallet:update_wallet`
-7. `CX_AUTH_RESOURCE_ID`: specify the resource id e.g. `ManagedIdentityWallets`
-8. `CX_AUTH_CLIENT_ID`: specify the expected client id, e.g. `ManagedIdentityWallets`
-9. `CX_AUTH_CLIENT_SECRET`: specify the client secret. It can be extracted from keycloak under `realms - catenax - clients - ManagedIdentityWallets - credentials`
-10. `APP_VERSION`: specify the application version, e.g. `0.0.10` note that github actions replace the value before the helm deployment
-11. `ACAPY_API_ADMIN_URL`: specify the admin url of Aca-Py, e.g. `http://localhost:11000`
-12. `ACAPY_LEDGER_URL`: specify the indy ledger url for registeration, e.g.`https://indy-test.idu.network/register`
-13. `ACAPY_NETWORK_IDENTIFIER`: specify the name space of indy ledger, e.g. `local:test`
-14. `ACAPY_ADMIN_API_KEY`: specify the admin api key of Aca-Py enpoints, e.g. `Hj23iQUsstG!dde`
-15. `CX_BPN`: specify the bpn of the catenaX wallet, e.g. `Bpn111` This wallet should be the first wallet to create.
-15. `BPDM_DATAPOOL_URL`: specify the base data pool API endpoint of the `BPDM` e.g. `https://catenax-bpdm-int.demo.catena-x.net`
-15. `BPDM_AUTH_CLIENT_ID`: specify the expected client id
-15. `BPDM_AUTH_CLIENT_SECRET=`: specify the expected client secret
-15. `BPDM_AUTH_GRANT_TYPE`: specify the expected grant type e.g. `client_credentials`
-15. `BPDM_AUTH_SCOPE`: specify the expected scope e.g. `openid`
-15. `BPDM_AUTH_URL`: specify the url to get the access token of `BPDM` e.g. `https://centralidp.demo.catena-x.net/auth/realms/CX-Central/protocol/openid-connect/token`
-15. `BPDM_PULL_DATA_AT_HOUR`: specify at which hour (24-hour clock) the cron job should pull the data from the `BPDM` e.g. `23`
-
-## Local development environment
-
-To resemble the staging and production system as much as possible also on the
-local machine, an external Postgresql database should be used instead of
-the default included h2 in-memory database. Additionally the authentication and authorization could be done via
-[keycloak](https://www.keycloak.org).
-
-There are two ways to set up the local environment:
-1. *Run from source*: using keycloak and postgres as stand-alone docker containers and running the managed identity wallets service via gradle, or
-2. *Run in Kubernetes*: packaging all of the services and run them on a local kubernetes cluster
-
-![Development Environment Setup Options](docs/images/DevEnvSetupOptions.png "Development Environment Setup Options")
-
-### Preperation of Aca-Py Docker Image
-
-Building the Aca-Py image is necessary for both setup options:
-You can either use the image `bcgovimages/aries-cloudagent:py36-1.16-1_0.7.4` or build your own image following the steps:
-* clone the repository `git clone https://github.com/hyperledger/aries-cloudagent-python.git`
-* navigate to the repository `cd aries-cloudagent-python`
-* currently tested with commit `0.7.4` from June, 30, 2022
-* run `git checkout 0.7.4`
-* run `docker build -t acapy:0.7.4 -f ./docker/Dockerfile.run .`
-* change the used image for `cx_acapy` in `dev-assets/dev-containers/docker-compose.yml`
+# Administrator Documentation
-### Preparation of Managed Identity Wallet Docker Image
+## Manual Keycloak Configuration
-Building the service image is necessary for both setup options, it is recommended
-to use as version tag the version specified in `gradle.properties`:
+Within the development setup the Keycloak is initially prepared with the
+values in `./dev-assets/dev-containers/keycloak`. The realm could also be
+manually added and configured at http://localhost:8081 via the "Add realm"
+button. It can be for example named `catenax`. Also add an additional client,
+e.g. named `ManagedIdentityWallets` with *valid redirect url* set to
+`http://localhost:8080/*`. The roles
+ * add_wallets
+ * view_wallets
+ * update_wallets
+ * delete_wallets
+ * view_wallet
+ * update_wallet
+can be added under *Clients > ManagedIdentityWallets > Roles* and then
+assigned to the client using *Clients > ManagedIdentityWallets > Client Scopes*
+*> Service Account Roles > Client Roles > ManagedIdentityWallets*. The
+available scopes/roles are:
-```
-./gradlew installDist
-docker build -t catena-x/managed-identity-wallets: .
-```
+1. Role `add_wallets` to create a new wallet
-### Option 1: Run from source
+1. Role `view_wallets`:
+ * to get a list of all wallets
+ * to retrieve one wallet by its identifier
+ * to validate a Verifiable Presentation
+ * to get all stored Verifiable Credentials
-Starting up Docker Containers for Postgres, Keycloak and AcaPy via following steps:
+1. Role `update_wallets` for the following actions:
+ * to store Verifiable Credential
+ * to set the wallet DID to public on chain
+ * to issue a Verifiable Credential
+ * to issue a Verifiable Presentation
+ * to add, update and delete service endpoint of DIDs
+ * to trigger the update of Business Partner Data of all existing wallets
+
+1. Role `delete_wallets` to remove a wallet
-* navigate to `./dev-assets/dev-containers`
-* run `docker-compose up -d` (or `docker compose up -d`, depdending on the installation) to start a Postgresql database and Keycloak instance and the AcaPy Service as Docker containers
-* If the used Indy ledger `--genesis-url https://indy-test.idu.network/genesis \` is write-restricted to endorsers or higher roles, the DID and its VerKey must be registered manually before starting AcaPy. To generate a new DID with a given seed see the section - [Generate DID from Seed](#generateDIDFromSEED)
-* To setup the Postgresql database in the application please see the section below - [Setting up progresql database](#settingUpPostgresSqlDatabase), for the database
-* The keycloak configuration are imported from `./dev-assets/dev-containers/keycloak` in the docker compose file.
-* Keycloak is reachable at `http://localhost:8081/` with `username: admin` and `password: catena`,
- the client id ist `ManagedIdentityWallets` and client secret can be found under the `Clients - ManagedIdentityWallets - Credentials`
-* The new realm of keycloak could also be manually added and configured at http://localhost:8081 via the "Add realm" button. It can be for example named `catenax`. Also add an additional client, e.g. named `ManagedIdentityWallets` with *valid redirect url* set to `http://localhost:8080/*`. The following Roles `add_wallets,view_wallets,update_wallets,delete_wallets,view_wallet,update_wallet` can be added under `Clients - ManagedIdentityWallets - Roles` and then assigned to the client using `Clients - ManagedIdentityWallets - Client Scopes - Service Account Roles - Client Roles - ManagedIdentityWallets`. Additionaly a Token mapper can to be created under `Clients - ManagedIdentityWallets - Mappers - create` with the following configuration:
-```
-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.
+1. 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 Presentation
-Finally run the managed identity wallets service via
+1. Role `update_wallet` requires the BPN of Caller and it can be used:
+ * to issue Verifiable Credentials (The BPN of issuer will be checked)
+ * to issue Verifiable Presentations (The BPN of holder will be checked)
+ * to store Verifiable Credentials (The BPN of holder will be checked)
+ * to trigger Business Partner Data update for its own BPN
-```
-./gradlew run
-```
+Additionally a Token mapper can to be created under *Clients* >
+*ManagedIdentityWallets* > *Mappers* > *create* with the following
+configuration (using as example `BPNL000000001`):
-or respectively in your IDE.
-### Option 2: Run in Kubernetes
+| 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 |
-*Work in progress*
+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.
-1. Create a namespace
+## Manual Database Configuration
-Using as example `managed-identity-wallets`:
+Within the development setup databases are initially prepared as needed, in the
+cloud deployment that is done via init containers. The MIW and the ACA-Py
+service are setting up the required tables on the first start. For MIW this is
+done within the `src/main/.../managedidentitywallets/plugins/Persistence.kt` database
+setup:
```
-kubectl create namespace managed-identity-wallets
+SchemaUtils.createMissingTablesAndColumns(Wallets, VerifiableCredentials, SchedulerTasks)
```
-2. Create relevant secrets
+The tables of the **Revocation Service** are added manually to the database using the
+SQL script at `./dev-asset/dev-containers/revocation/V1.0.0__Create_DB.sql`
-Altogether four secrets are needed
-* catenax-managed-identity-wallets-secrets
-* catenax-managed-identity-wallets-acapy-secrets. If the Indy ledger is write-restricted, the DID of the used seed must be registered manually before starting AcaPy.
-* postgres-acapy-secret-config
-* postgres-managed-identity-wallets-secret-config
+## Local docker deployment
-Create these with following commands, after replacing the placeholders:
+First step is to create the distribution of the application:
+```bash
+./gradlew installDist
```
-kubectl -n managed-identity-wallets create secret generic catenax-managed-identity-wallets-secrets \
- --from-literal=cx-db-jdbc-url='jdbc:postgresql://:5432/miwdev?user=miwdevuser&password=' \
- --from-literal=cx-auth-client-id='ManagedIdentityWallets' \
- --from-literal=cx-auth-client-secret=''
-
-kubectl -n managed-identity-wallets create secret generic catenax-managed-identity-wallets-acapy-secrets \
- --from-literal=acapy-wallet-key='' \
- --from-literal=acapy-agent-wallet-seed='' \
- --from-literal=acapy-jwt-secret='' \
- --from-literal=acapy-db-account='postgres' \
- --from-literal=acapy-db-password='' \
- --from-literal=acapy-db-admin='postgres' \
- --from-literal=acapy-db-admin-password='' \
- --from-literal=acapy-admin-api-key=''
-
-kubectl -n managed-identity-wallets create secret generic postgres-acapy-secret-config \
---from-literal=password='' \
---from-literal=postgres-password='' \
---from-literal=user='postgres'
-
-kubectl -n managed-identity-wallets create secret generic postgres-managed-identity-wallets-secret-config \
---from-literal=password='' \
---from-literal=postgres-password='' \
---from-literal=user='postgres'
-```
-
-3. Install a new deployment via helm
-Run following command to use the base values as well as the predefined values for local deployment:
+Next step is to build and tag the Docker image, replacing the
+`` with the app version:
```
-helm install managed-identity-wallets ./helm/managed-identity-wallets/ -n managed-identity-wallets -f ./helm/managed-identity-wallets/values.yaml -f ./helm/managed-identity-wallets/values-local.yaml
+docker build -t catena-x/managed-identity-wallets: .
```
-4. Expose via loadbalancer
+Finally, start the image (please make sure that there are no quotes around the
+values in the env file):
```
-kubectl -n managed-identity-wallets apply -f dev-assets/kube-local-lb.yaml
+docker run --env-file .env.docker -p 8080:8080 catena-x/managed-identity-wallets:
```
-### IntelliJ Development Setup
+## Deployment on Kubernetes
-To run and develop using IntelliJ IDE:
-* open the IntelliJ IDE and import the project
-* create file `dev.env` and copy the values from `.env.example`
-* install the plugin `Env File` https://plugins.jetbrains.com/plugin/7861-envfile
+*Work in progress*
-Later you can run `Application.kt` after adding the `dev.env` to the Run/Debug configuration
+1. Create a namespace
-### Initial Wallet Setup
+ Using as example `managed-identity-wallets`:
-* Create the Catena-X wallet using the value stored in `CX_BPN` as BPN
-* Register the DID of Catena-X Wallet and its VerKey on the ledger [Register from DID](https://indy-test.idu.network/) as Endorser
-* Assign the DID to public manually by sending a POST request `/api/wallets//public` and as body the ver key
- `{ "verKey": "verification key from creation" }`
+ ```
+ kubectl create namespace managed-identity-wallets
+ ```
-## Testing GitHub actions locally
+1. Create relevant secrets
-Using [act](https://github.com/nektos/act) it is possible to test GitHub actions
-locally. To run it needs a secrets file, which could be derived on `.env.example`,
-see the section above.
+ Altogether four secrets are needed
+ * catenax-managed-identity-wallets-secrets
+ * catenax-managed-identity-wallets-acapy-secrets
+ * postgres-acapy-secret-config
+ * postgres-managed-identity-wallets-secret-config
-```
-act --secret-file .env
-```
-## Setting up progresql database
+ Create these with following commands, after replacing the placeholders:
-Based on the [documentation](https://docs.microsoft.com/en-us/azure/postgresql/howto-create-users)
-provided by Mirosoft following SQL needs to be executed to setup initial the database:
+ ```
+ kubectl -n managed-identity-wallets create secret generic catenax-managed-identity-wallets-secrets \
+ --from-literal=cx-db-jdbc-url='jdbc:postgresql://:5432/miwdev?user=miwdevuser&password=' \
+ --from-literal=cx-auth-client-id='ManagedIdentityWallets' \
+ --from-literal=cx-auth-client-secret=''
+
+ kubectl -n managed-identity-wallets create secret generic catenax-managed-identity-wallets-acapy-secrets \
+ --from-literal=acapy-wallet-key='' \
+ --from-literal=acapy-agent-wallet-seed='' \
+ --from-literal=acapy-jwt-secret='' \
+ --from-literal=acapy-db-account='postgres' \
+ --from-literal=acapy-db-password='' \
+ --from-literal=acapy-db-admin='postgres' \
+ --from-literal=acapy-db-admin-password='' \
+ --from-literal=acapy-admin-api-key=''
+
+ kubectl -n managed-identity-wallets create secret generic postgres-acapy-secret-config \
+ --from-literal=password='' \
+ --from-literal=postgres-password='' \
+ --from-literal=user='postgres'
+
+ kubectl -n managed-identity-wallets create secret generic postgres-managed-identity-wallets-secret-config \
+ --from-literal=password='' \
+ --from-literal=postgres-password='' \
+ --from-literal=user='postgres'
+ ```
-```
-CREATE DATABASE miwdev;
-CREATE ROLE miwdevuser WITH LOGIN NOSUPERUSER INHERIT CREATEDB NOCREATEROLE NOREPLICATION PASSWORD '^cXnF61qM1kf';
-GRANT CONNECT ON DATABASE miwdev TO miwdevuser;
-```
+1. If the Indy ledger is write-restricted, the DID of the used seed
+ must be registered manually before starting ACA-Py.
-Then following environment settings in your local environment file (potentially
-named `dev.env`) can be used:
+1. Install a new deployment via helm
-```
-CX_DB_JDBC_URL="jdbc:postgresql://localhost:5432/miwdev?user=miwdevuser&password=^cXnF61qM1kf"
-CX_DB_JDBC_DRIVER="org.postgresql.Driver"
-```
+ Run following command to use the base values as well as the predefined values for local deployment:
+ ```
+ helm install managed-identity-wallets ./helm/managed-identity-wallets/ -n managed-identity-wallets -f ./helm/managed-identity-wallets/values.yaml -f ./helm/managed-identity-wallets/values-local.yaml
+ ```
+4. Expose via loadbalancer
-## Scopes
-The Available Scopes/Roles are:
+ ```
+ kubectl -n managed-identity-wallets apply -f dev-assets/kube-local-lb.yaml
+ ```
-1. Role `add_wallets` to create a new wallet
+5. To check the current deployment and version run `helm list -n `. Example output:
-1. Role `view_wallets`:
- * to get a list of all wallets
- * to retrieve one wallet by its identifier
- * to validate a Verifiable Presentation
- * to get all stored Verifiable Credentials
+ ```
+ NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
+ cx-miw ingress-miw 1 2022-02-24 08:51:39.864930557 +0000 UTC deployed catenax-managed-identity-wallets-0.1.0 0.0.5
+ ```
-1. Role `update_wallets` for the following actions:
- * to store Verifiable Credential
- * to set the wallet DID to public on chain
- * to issue a Verifiable Credential
- * to issue a Verifiable Presentation
- * to add, update and delete service endpoint of DIDs
- * to trigger the update of Business Partner Data of all existing wallets
-
-1. Role `delete_wallets` to remove a wallet
+# End Users
-1. 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 Presentation
+See OpenAPI documentation, which is automatically created from
+the source and available on each deployment at the `/docs` endpoint
+(e.g. locally at http://localhost:8080/docs). An export of the JSON
+document can be also found in [docs/openapi_v200.json](docs/openapi_v200.json).
-1. Role `update_wallet` requires the BPN of Caller and it can be used:
- * to issue Verifiable Credentials (The BPN of issuer will be checked)
- * to issue Verifiable Presentations (The BPN of holder will be checked)
- * to store Verifiable Credentials (The BPN of holder will be checked)
- * to trigger Business Partner Data update for its own BPN
+# Further Guides
+
+In this section there are advanced cases (e.g. building your own ACA-Py image)
+described.
+
+## Preparation of ACA-Py Docker Image
+
+ACA-Py can be used via the official image at `bcgovimages/aries-cloudagent:py36-1.16-1_0.7.4`
+or build your own image following the steps:
+* clone the repository `git clone https://github.com/hyperledger/aries-cloudagent-python.git`
+* navigate to the repository `cd aries-cloudagent-python`
+* currently tested with commit `0.7.4` from June, 30, 2022
+* run `git checkout 0.7.4`
+* run `docker build -t acapy:0.7.4 -f ./docker/Dockerfile.run .`
+* change the used image for `cx_acapy` in `dev-assets/dev-containers/docker-compose.yml`
+
+## Integrate with an write-restricted Indy Ledger
+
+If the used Indy ledger (see parameter `--genesis-url https://indy-test.idu.network/genesis`)
+is write-restricted to endorsers or higher roles, the DID and its VerKey must be registered
+manually before starting ACA-Py.
+
+The [Indy CLI](https://hyperledger-indy.readthedocs.io/projects/sdk/en/latest/docs/design/001-cli/README.html)
+in Docker using the [docker-file](https://github.com/hyperledger/indy-sdk/blob/main/cli/cli.dockerfile)
+can be used to generate a new DID from a given seed. However, it does not show the
+complete `VerKey`, check this [Issue](https://github.com/hyperledger/indy-sdk/issues/2553).
+Therefore, the easiest way to generate a DID is currently to start ACA-Py with a given seed.
-## Generate DID and get its VerKey
-The [Indy CLI](https://hyperledger-indy.readthedocs.io/projects/sdk/en/latest/docs/design/001-cli/README.html) in Docker using the [docker-file](https://github.com/hyperledger/indy-sdk/blob/main/cli/cli.dockerfile) can be used to generate a new DID from a given seed. However, it does not show the complete VerKey, check this [Issue](https://github.com/hyperledger/indy-sdk/issues/2553). Therefore, the easiest way to generate a DID is currently to start AcaPy with a given seed.
* Navigate to `./dev-assets/generate-did-from-seed`
- * Generate a random seed that has 32 characters. If the use of an offline secure secret/password generator is not possible, then these guidelines must be followed:
+ * Generate a random seed that has 32 characters. If the use of an offline secure secret/password
+ generator is not possible, then these guidelines must be followed:
* No repeat of characters or strings
* No patterns or use of dictionary words
* The use of uppercase and lowercase letters - as well as numbers and allowed symbols
* No personal preferences like names or phone numbers
* Set the seed as an enviroment variable e.g. `export SEED=312931k4h15989pqwpou129412i214dk`
- * Run the script generateDidFromSeed script with `./generateDidFromSeed.sh` which starts the AcaPy container and shows the printout of the DID and VerKey from its logs in the console like the following
+ * Run the script generateDidFromSeed script with `./generateDidFromSeed.sh` which starts the
+ ACA-Py container and shows the printout of the `DID` and `VerKey` from its logs in the console
+ like the following
```
- 2022-08-12 08:08:13,888 indy.did DEBUG get_my_did_with_meta: <<< res: '{"did":"Hw2eFhr3KcZw5JcRW45KNc","verkey":"AEErMofs7DcJT636pocN2RiEHgTLoF4Mpj6heFXwtb3q","tempVerkey":null,"metadata":null}'
+ 2022-08-12 08:08:13,888 indy.did DEBUG get_my_did_with_meta: <<< res: '{"did":"HW2eFhr3KcZw5JcRW45KNc","verkey":"aEErMofs7DcJT636pocN2RiEHgTLoF4Mpj6heFXwtb3q","tempVerkey":null,"metadata":null}'
```
- * If the script did not stop the container, the command `docker-compose down -v` can stop and delete it manually
+ * If the script did not stop the container, the command `docker compose down -v` can stop and delete it manually
-## Tests
+## Testing GitHub actions locally
-### Unit Tests
+Using [act](https://github.com/nektos/act) it is possible to test GitHub actions
+locally. To run it needs a secrets file, which could be derived on `.env.example`,
+see the section above.
+
+```
+act --secret-file .env
+```
- ./gradlew test
+## Test Coverage
-### Test Coverage
-Jacoco is used to generate the coverage report. The report generation and the coverage verification are automatically executed after tests.
+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/`
+The generated HTML report can be found under `jacoco-report/html/`
-To generate the report run the command `./gradlew jacocoTestReport`
+To generate the report run the command
-To check the coverage run the command `./gradlew jacocoTestCoverageVerification`. Currently the minimum is 80% (INSTRUCTIONS)
+```
+./gradlew jacocoTestReport
+```
+
+To check the coverage run the command
+
+```
+./gradlew jacocoTestCoverageVerification
+```
-Files to be excluded from the coverage calculation can be set in the `gradle.properties` using a comma-separated list of files or directories with possible wildcards as the value for the property `coverage_excludes`. The files in `models` and `entities` should be excluded as long as they don't have any logic. The services that are mocked in unit tests must be excluded. Also their interfaces need to be excluded because they have a `companion object` that is used to create those services. Files like `Application.kt` which are tested or simulated indirectly for example using `withTestApplication` should also be excluded.
+Currently the minimum is 80%
+
+Files to be excluded from the coverage calculation can be set in
+`gradle.properties` using a comma-separated list of files or directories
+with possible wildcards as the value for the property `coverage_excludes`.
+The files in `models` and `entities` should be excluded as long as they
+don't have any logic. The services that are mocked in unit tests must be
+excluded. Also their interfaces need to be excluded because they have a
+`companion object` that is used to create those services. Files like
+`Application.kt` which are tested or simulated indirectly for example
+using `withTestApplication` should also be excluded.
## Dashboard
@@ -438,74 +462,3 @@ yarn build
rm -rf ../static/*
cp -r dist/* ../static
```
-
-## Future
-
-Potentially following libraries and frameworks could be added in future
-
-* [HikariCP](https://github.com/brettwooldridge/HikariCP) for connection pooling
-* [Koin](https://github.com/InsertKoinIO/koin) for dependency injection
-
-------
-
-# Further notes
-
-Deployment to be adjusted to the ArgoCD deployment, below notes are just for reference
-
-## Helm Setup and Auto Deployment
-The Helm setup is configured under `helm/managed-identity-wallets` and used by `github-actions` for auto deployment. Before pushing to the `develop` branch, please check if the version of the `gradle.properties` need to be updated, the Aca-Py image is uploaded as described [section](##Aca-Py_Build_and_ Upload_Image) and the secret files and `values-staging.yaml` sill accurate.
-
-* To check the current deployment and version run `helm list -n `. Example output:
-```
-NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
-cx-miw ingress-miw 1 2022-02-24 08:51:39.864930557 +0000 UTC deployed catenax-managed-identity-wallets-0.1.0 0.0.5
-```
-
-The deployment requires also a secret file `catenax-managed-identity-wallets-secrets` that include the following data:
-1. `cx-db-jdbc-url` (includes password/credentials for DB access)
-1. `cx-auth-client-id`
-1. `cx-auth-client-secret`
-
-To add a secret file to the namespace in the cluster:
-* login to AKS
-* either import them using a file `kubectl -n create secret generic catenax-managed-identity-wallets-secrets --from-file `
-* or run the following command after replaceing the placeholders
-```
- kubectl -n create secret generic catenax-managed-identity-wallets-secrets \
- --from-literal=cx-db-jdbc-url='' \
- --from-literal=cx-auth-client-id='' \
- --from-literal=cx-auth-client-secret=''
-```
-
-Aca-py will be deployed and connected to a postgres database pod in the same namespace. The postgres database is deployed using the following [instructions](https://www.sumologic.com/blog/kubernetes-deploy-postgres/) The used files can be found under `dev-assets/acapy-postgres` without adding a Service. The IP of the acapy-postgres pod should be updated in the `values-staging.yaml` whenever the postgres pod is changed
-
-The deployment of AcaPy instance requires also a secret file `catenax-managed-identity-wallets-acapy-secrets` that include the following data:
-1. `acapy-wallet-key` the key of the base wallet
-1. `acapy-agent-wallet-seed` the seed of the base wallet
-1. `acapy-jwt-secret` the jwt secret for the tokens
-1. `acapy-db-account` postgres account
-1. `acapy-db-password` postgres password
-1. `acapy-db-admin` postgres admin
-1. `acapy-db-admin-password` postgres admin password
-1. `acapy-admin-api-key` the admin api key used by the managed identity wallets and acapy instance
-```
-kubectl -n create secret generic catenax-managed-identity-wallets-acapy-secrets \
- --from-literal=acapy-wallet-key='' \
- --from-literal=acapy-agent-wallet-seed='' \
- --from-literal=acapy-jwt-secret='' \
- --from-literal=acapy-db-account='' \
- --from-literal=acapy-db-password='' \
- --from-literal=acapy-db-admin='' \
- --from-literal=acapy-db-admin-password='' \
- --from-literal=acapy-admin-api-key=''
-```
-
-* To check if the secrets stored correctly run `kubectl -n get secret/catenax-managed-identity-wallets-secrets -o yaml`
-* To check if the secrets stored correctly run `kubectl -n get secret/catenax-managed-identity-wallets-acapy-secrets -o yaml`
-
-Currently the ORM Exposed is creating the tables if they don't exist yet, done
-within the `Persistence.kt` database setup:
-
-```
-SchemaUtils.createMissingTablesAndColumns(Companies, Wallets, VerifiableCredentials)
-```
\ No newline at end of file
diff --git a/SelfManagedWallets.md b/SelfManagedWallets.md
new file mode 100644
index 000000000..a1e4334a5
--- /dev/null
+++ b/SelfManagedWallets.md
@@ -0,0 +1,46 @@
+## Interaction with self managed wallet
+
+- Interaction with self managed wallet involves:
+ - Establish connection with self managed wallet as defined in [ARIES RFC 0023](https://github.com/hyperledger/aries-rfcs/tree/25464a5c8f8a17b14edaa4310393df6094ace7b0/features/0023-did-exchange)
+ - Issue credential to self managed wallet as defined in [Aries RFC 0453](https://github.com/hyperledger/aries-rfcs/tree/cd27fc64aa2805f756a118043d7c880354353047/features/0453-issue-credential-v2)
+ - Request presentation from self managed wallet as defined in [Aries RFC 0454](https://github.com/hyperledger/aries-rfcs/tree/eace815c3e8598d4a8dd7881d8c731fdb2bcc0aa/features/0454-present-proof-v2)
+
+- Current limitation:
+ - Self managed wallets can only interact with the Catena-X wallet
+ - Request presentation from self managed wallet is not implemented yet
+ - Credential revocation is not supported for credentials issued to self managed wallet
+
+### Register, Establish Connection and Issue Membership and Bpn Credential
+A self managed wallet can be registered on the MIW by giving the `bpn`, `did`, `name`, and an optional `webhookUrl` to inform the requester when the connection reaches the state `Completed` and the Membership and BPN credentials are issued.
+
+The following instruction will be executed when a self managed wallet is registered:
+ - Establish connection between Catena-X wallet DID and the given DID of the self managed wallet
+ - Store the connection Id in database
+ - If webhookUrl exist then store it with the request Id of the connection in database
+ - Set the state of connection and webhook to `Request``
+ - When the self managed wallet accepts the connection, then the built websocket connected to Acapy will trigger a function to perform the following steps:
+ - Set the connection state to `Completed`
+ - If the WebhookUrl exist then send the information to the stored url
+ - Trigger the creation of the Membership and BPN credentials which sends a `Credential Offer` to the self managed wallet
+ - When the self managed wallet accepts the offer, then the two credentials will be issued by the Catena X wallet.
+
+
+### Issue Verifiable Credential for Self Managed Wallet
+A credential can be sent to the self managed wallet by calling the `issuance-flow` endpoint: This endpoint takes required information to construct the credential, also an optional webhookUrl as input. This call will trigger the application to send a `Credential Offer` to the self managed wallet. When the `Credential Offer` get accepted by the self managed wallet the managed wallet will issue a credential and send it. When the credential is accepted and stored by the self managed wallet the MIW will then set the state to `Done`. If the webhookUrl exist a notification will be sent.
+
+### Request Verifiable Presentation from Self Managed Wallet
+ not implemented yet!
+
+### Local Test Steps:
+1. Follow the steps in `Steps for initial local deployment and wallet Creation` section in the `README.md` file
+1. Import a new postman collection `Test Acapy - Self Managed Wallet.postman_collection.json` from `./dev-asset`
+1. Run `Test-Acapy-SelfManagedWallet/Get Connections` and make sure there are no connections. If there are any please delete them using `Remove Connection`
+1. From `Custodian Sample` collection run `Register self managed wallets`
+1. Run `Test-Acapy-SelfManagedWallet/Get Connections` and copy the `connection_Id` e.g. `716e678c-f329-4baa-be4d-3c68f004a0ef`
+1. Run `Test-Acapy-SelfManagedWallet/Accept Connection` after replacing the connection id in the path e.g. `http://localhost:11001/didexchange/716e678c-f329-4baa-be4d-3c68f004a0ef/accept-request`
+1. The Self Managed Wallet will trigger the AcaPy of MIW and the MIW will change the state of the connection to `Completed` and issue 2 Verifiable Credential Offers. Those can be verified by looking at the database
+1. To Accept the BPN Credential by the self managed wallet run `Get Records` after changing the connection Id e.g. `http://localhost:11001/issue-credential-2.0/records?connection_id=716e678c-f329-4baa-be4d-3c68f004a0ef`
+1. Search for the BPN Credential and then copy its crednetial exchange id `cred_ex-id` e.g. `e55a3a77-d0bb-43d3-a7a3-0f7003798fc0`
+1. To Accept the credential offer and send a request run `Test-Acapy-SelfManagedWallet/Send Credential Request` after replacing the cred_ex_id e.g. `http://localhost:11001/issue-credential-2.0/records/e55a3a77-d0bb-43d3-a7a3-0f7003798fc0/send-request`. This step will trigger the AcaPy of MIW to issue a signed credential.
+1. To Store the credential run `Test-Acapy-SelfManagedWallet/Store Credential` after replacing the cred_ex_id in the path and giving a unique id for the credential in the body e.g. `http://localhost:11001/issue-credential-2.0/records/e55a3a77-d0bb-43d3-a7a3-0f7003798fc0/store` with body `{"credential_id": "12345678-9999-43d3-a7a3-111111111111" }`. This will trigger AcaPy to set the credential status of DONE and MIW to send a notification to the webhook, that is given with the wallet registration.
+1. To Send another Credential Offer you can call `Custodian Sample/Issuance-flow` and repeat the steps 8 to 11
diff --git a/build.gradle.kts b/build.gradle.kts
index f8473c962..b049439c0 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -59,7 +59,7 @@ dependencies {
implementation("io.bkbn:kompendium-core:$kompendium_version")
implementation("io.bkbn:kompendium-auth:$kompendium_version")
- implementation("org.postgresql:postgresql:42.4.0")
+ implementation("org.postgresql:postgresql:42.4.1")
implementation("org.xerial:sqlite-jdbc:3.36.0.3")
// for now: using kotlinx.serialization
@@ -73,6 +73,7 @@ dependencies {
implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version")
implementation("decentralized-identity:did-common-java:1.0.0")
+ implementation("network.idu.acapy:aries-client-python:0.7.29")
// https://mvnrepository.com/artifact/com.github.kagkarlsson/db-scheduler
implementation("com.github.kagkarlsson:db-scheduler:11.2")
diff --git a/dev-assets/Custodian Sample.postman_collection.json b/dev-assets/Custodian Sample.postman_collection.json
index 51330f471..7bd5ba402 100644
--- a/dev-assets/Custodian Sample.postman_collection.json
+++ b/dev-assets/Custodian Sample.postman_collection.json
@@ -78,7 +78,7 @@
"header": [],
"body": {
"mode": "raw",
- "raw": "{\n \"name\": \"acapy_BPNL000000001_name\",\n \"bpn\": \"BPNL000000001\"\n}",
+ "raw": "{\n \"name\": \"acapy_bpn222_name\",\n \"bpn\": \"bpn222\"\n}",
"options": {
"raw": {
"language": "json"
@@ -142,14 +142,14 @@
"method": "GET",
"header": [],
"url": {
- "raw": "{{custodian_url}}/api/wallets/BPNL000000001?withCredentials=true",
+ "raw": "{{custodian_url}}/api/wallets/bpn222?withCredentials=true",
"host": [
"{{custodian_url}}"
],
"path": [
"api",
"wallets",
- "BPNL000000001"
+ "bpn222"
],
"query": [
{
@@ -177,14 +177,14 @@
"method": "DELETE",
"header": [],
"url": {
- "raw": "{{custodian_url}}/api/wallets/did:indy:local:test:GoVWzSKLChMM91MGpw4LAi",
+ "raw": "{{custodian_url}}/api/wallets/TestX1",
"host": [
"{{custodian_url}}"
],
"path": [
"api",
"wallets",
- "did:indy:local:test:GoVWzSKLChMM91MGpw4LAi"
+ "TestX1"
]
}
},
@@ -206,14 +206,90 @@
"method": "GET",
"header": [],
"url": {
- "raw": "{{custodian_url}}/api/didDocuments/BPNL000000001",
+ "raw": "{{custodian_url}}/api/didDocuments/bpn222",
"host": [
"{{custodian_url}}"
],
"path": [
"api",
"didDocuments",
- "BPNL000000001"
+ "bpn222"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Register self managed wallets",
+ "request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{access_token}}",
+ "type": "string"
+ }
+ ]
+ },
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"bpn\": \"TestX1\",\n \"did\": \"did:sov:7rB93fLvW5kgujZ4E57ZxL\",\n \"name\": \"TestX1\",\n \"webhookUrl\": \"ME_WEB_HOOK\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{custodian_url}}/api/wallets/self-managed-wallets",
+ "host": [
+ "{{custodian_url}}"
+ ],
+ "path": [
+ "api",
+ "wallets",
+ "self-managed-wallets"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Issuance flow (currently available for catena X to issue creds for self managed wallets)",
+ "request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{access_token}}",
+ "type": "string"
+ }
+ ]
+ },
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://raw.githubusercontent.com/catenax-ng/product-core-schemas/main/businessPartnerData\"\n ],\n \"id\": \"http://example.edu/credentials/3739141414\",\n \"type\": [\n \"BpnCredential\",\n \"VerifiableCredential\"\n ],\n \"issuerIdentifier\": \"bpn111\",\n \"issuanceDate\": \"2021-06-16T18:56:59Z\",\n \"expirationDate\": \"2026-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"type\": [\n \"BpnCredential\"\n ],\n \"bpn\": \"NEWNEWTestTest\",\n \"id\": \"did:sov:7rB93fLvW5kgujZ4E57ZxL\"\n },\n \"holderIdentifier\": \"did:sov:7rB93fLvW5kgujZ4E57ZxL\",\n \"isRevocable\": false\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{custodian_url}}/api/credentials/issuance-flow",
+ "host": [
+ "{{custodian_url}}"
+ ],
+ "path": [
+ "api",
+ "credentials",
+ "issuance-flow"
]
}
},
@@ -236,7 +312,7 @@
"header": [],
"body": {
"mode": "raw",
- "raw": "{\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\"\n ],\n \"id\": \"http://example.edu/credentials/3888\",\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuerIdentifier\": \"bpn111\",\n \"issuanceDate\": \"2000-06-16T18:56:59Z\",\n \"expirationDate\": \"2999-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"TestAfterQuestion\",\n \"familyName\": \"Student\",\n \"degree\": {\n \"type\": \"Master1\",\n \"degreeType\": \"Undergraduate2\",\n \"name\": \"Master of Test11\"\n },\n \"college\": \"Test2\"\n },\n \"holderIdentifier\": \"bpn222\"\n}",
+ "raw": "{\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\"\n ],\n \"id\": \"http://example.edu/credentials/3888\",\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuerIdentifier\": \"bpn111\",\n \"issuanceDate\": \"2000-06-16T18:56:59Z\",\n \"expirationDate\": \"2999-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"TestAfterQuestion\",\n \"familyName\": \"Student5\",\n \"degree\": {\n \"typeeee\": \"Master1\",\n \"degreeType\": \"Undergraduate2\",\n \"name\": \"Master of Test11\"\n },\n \"college\": \"Test2\"\n },\n \"holderIdentifier\": \"bpn222\",\n \"isRevocable\": true\n}",
"options": {
"raw": {
"language": "json"
@@ -244,13 +320,19 @@
}
},
"url": {
- "raw": "{{custodian_url}}/api/credentials",
+ "raw": "{{custodian_url}}/api/credentials?isRevocable=true",
"host": [
"{{custodian_url}}"
],
"path": [
"api",
"credentials"
+ ],
+ "query": [
+ {
+ "key": "isRevocable",
+ "value": "true"
+ }
]
}
},
@@ -273,7 +355,7 @@
"header": [],
"body": {
"mode": "raw",
- "raw": "{\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\"\n ],\n \"id\": \"http://example.edu/credentials/3739\",\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuanceDate\": \"2021-06-16T18:56:59Z\",\n \"expirationDate\": \"2026-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"Sally\",\n \"familyName\": \"Student\",\n \"degree\": {\n \"type\": \"Master\",\n \"degreeType\": \"Undergraduate\",\n \"name\": \"Master of Test\"\n },\n \"college\": \"Test\"\n },\n \"holderIdentifier\": \"bpn555\"\n}",
+ "raw": "{\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\"\n ],\n \"id\": \"http://example.edu/credentials/3739\",\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuanceDate\": \"2021-06-16T18:56:59Z\",\n \"expirationDate\": \"2026-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"Sally\",\n \"familyName\": \"Student\",\n \"degree\": {\n \"type\": \"Master\",\n \"degreeType\": \"Undergraduate\",\n \"name\": \"Master of Test\"\n },\n \"college\": \"Test\"\n },\n \"holderIdentifier\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ\",\n \"isRevocable\": true\n}",
"options": {
"raw": {
"language": "json"
@@ -294,6 +376,94 @@
},
"response": []
},
+ {
+ "name": "Revoke Credential",
+ "request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{access_token}}",
+ "type": "string"
+ }
+ ]
+ },
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"id\": \"http://example.edu/credentials/3888\",\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\",\n \"https://w3id.org/vc/status-list/2021/v1\"\n ],\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuer\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ\",\n \"issuanceDate\": \"2000-06-16T18:56:59Z\",\n \"expirationDate\": \"2999-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"TestAfterQuestion\",\n \"familyName\": \"Student5\",\n \"degree\": {\n \"typeeee\": \"Master1\",\n \"degreeType\": \"Undergraduate2\",\n \"name\": \"Master of Test11\"\n },\n \"college\": \"Test2\",\n \"id\": \"did:sov:HR6kiZSiUHfcW2tkGxtDKk\"\n },\n \"credentialStatus\": {\n \"id\": \"http://localhost:8080/api/credentials/status/2476607d-b00b-44f8-9c43-62f3bedd621b#1\",\n \"type\": \"StatusList2021Entry\",\n \"statusPurpose\": \"revocation\",\n \"statusListIndex\": \"1\",\n \"statusListCredential\": \"http://localhost:8080/api/credentials/status/2476607d-b00b-44f8-9c43-62f3bedd621b\"\n },\n \"proof\": {\n \"type\": \"Ed25519Signature2018\",\n \"created\": \"2022-09-16T15:13:26Z\",\n \"proofPurpose\": \"assertionMethod\",\n \"verificationMethod\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ#key-1\",\n \"jws\": \"eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..7qfg_wZPBxCF_Ua254ll7dIxmaS42sFXJgfMb95_NByCLbur6PyLbhqCGuGQttdu0lVbjawvCrkvj7YEFxGyCQ\"\n }\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{custodian_url}}/api/credentials/revocations",
+ "host": [
+ "{{custodian_url}}"
+ ],
+ "path": [
+ "api",
+ "credentials",
+ "revocations"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update Revocation Lists",
+ "request": {
+ "auth": {
+ "type": "bearer",
+ "bearer": [
+ {
+ "key": "token",
+ "value": "{{access_token}}",
+ "type": "string"
+ }
+ ]
+ },
+ "method": "POST",
+ "header": [],
+ "url": {
+ "raw": "{{custodian_url}}/api/credentials/revocations/statusListCredentialRefresh",
+ "host": [
+ "{{custodian_url}}"
+ ],
+ "path": [
+ "api",
+ "credentials",
+ "revocations",
+ "statusListCredentialRefresh"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Get Status-List Credential (using revocation-list-name-of-wallet)",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "{{custodian_url}}/api/credentials/status/5c145c85-8fcb-42d4-893c-d19a55581e00",
+ "host": [
+ "{{custodian_url}}"
+ ],
+ "path": [
+ "api",
+ "credentials",
+ "status",
+ "5c145c85-8fcb-42d4-893c-d19a55581e00"
+ ]
+ }
+ },
+ "response": []
+ },
{
"name": "Issue Presentations",
"request": {
@@ -311,7 +481,7 @@
"header": [],
"body": {
"mode": "raw",
- "raw": "{\n \"holderIdentifier\": \"bpn222\",\n \"verifiableCredentials\": [\n {\n \"id\": \"http://example.edu/credentials/3888\",\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\"\n ],\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuer\": \"did:indy:local:test:XMcRfSUkkQK38p6CCjHZz6\",\n \"issuanceDate\": \"2000-06-16T18:56:59Z\",\n \"expirationDate\": \"2999-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"TestAfterQuestion\",\n \"familyName\": \"Student\",\n \"degree\": {\n \"type\": \"Master1\",\n \"degreeType\": \"Undergraduate2\",\n \"name\": \"Master of Test11\"\n },\n \"college\": \"Test2\",\n \"id\": \"did:indy:local:test:Y1m8DFWdkqfgahTkLtW9NQ\"\n },\n \"proof\": {\n \"type\": \"Ed25519Signature2018\",\n \"created\": \"2022-08-22T09:02:33Z\",\n \"proofPurpose\": \"assertionMethod\",\n \"verificationMethod\": \"did:indy:local:test:XMcRfSUkkQK38p6CCjHZz6#key-1\",\n \"jws\": \"eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..G3uG6ZuQrcyoUQrs9lLZoohlXgm2-t7C6Q4bx1QCM1ZZC22SnVifV7DjNTHkA3kcsKLaKbSmkuqje0pxD8JfCw\"\n }\n }\n ]\n}",
+ "raw": "{\n \"holderIdentifier\": \"bpn111\",\n \"verifiableCredentials\": [\n {\n \"id\": \"http://example.edu/credentials/3888\",\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\",\n \"https://w3id.org/vc/status-list/2021/v1\"\n ],\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuer\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ\",\n \"issuanceDate\": \"2000-06-16T18:56:59Z\",\n \"expirationDate\": \"2999-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"TestAfterQuestion\",\n \"familyName\": \"Student5\",\n \"degree\": {\n \"typeeee\": \"Master1\",\n \"degreeType\": \"Undergraduate2\",\n \"name\": \"Master of Test11\"\n },\n \"college\": \"Test2\",\n \"id\": \"did:sov:HR6kiZSiUHfcW2tkGxtDKk\"\n },\n \"credentialStatus\": {\n \"id\": \"http://localhost:8080/api/credentials/status/2476607d-b00b-44f8-9c43-62f3bedd621b#1\",\n \"type\": \"StatusList2021Entry\",\n \"statusPurpose\": \"revocation\",\n \"statusListIndex\": \"1\",\n \"statusListCredential\": \"http://localhost:8080/api/credentials/status/2476607d-b00b-44f8-9c43-62f3bedd621b\"\n },\n \"proof\": {\n \"type\": \"Ed25519Signature2018\",\n \"created\": \"2022-09-16T15:13:26Z\",\n \"proofPurpose\": \"assertionMethod\",\n \"verificationMethod\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ#key-1\",\n \"jws\": \"eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..7qfg_wZPBxCF_Ua254ll7dIxmaS42sFXJgfMb95_NByCLbur6PyLbhqCGuGQttdu0lVbjawvCrkvj7YEFxGyCQ\"\n }\n }\n ]\n}",
"options": {
"raw": {
"language": "json"
@@ -319,7 +489,7 @@
}
},
"url": {
- "raw": "{{custodian_url}}/api/presentations?withCredentialsValidation=false&withCredentialsDateValidation=false",
+ "raw": "{{custodian_url}}/api/presentations?withCredentialsValidation=true&withCredentialsDateValidation=true&withRevocationValidation=true",
"host": [
"{{custodian_url}}"
],
@@ -330,11 +500,15 @@
"query": [
{
"key": "withCredentialsValidation",
- "value": "false"
+ "value": "true"
},
{
"key": "withCredentialsDateValidation",
- "value": "false"
+ "value": "true"
+ },
+ {
+ "key": "withRevocationValidation",
+ "value": "true"
}
]
}
@@ -358,7 +532,7 @@
"header": [],
"body": {
"mode": "raw",
- "raw": "{\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\"\n ],\n \"id\": \"3110bc8e-5470-4356-a341-aae80dfc38c5\",\n \"type\": [\n \"VerifiablePresentation\"\n ],\n \"holder\": \"did:indy:local:test:JPbsf8GpUYiavsK95SGpge\",\n \"verifiableCredential\": [\n {\n \"id\": \"http://example.edu/credentials/3888\",\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\"\n ],\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuer\": \"did:indy:local:test:JPbsf8GpUYiavsK95SGpge\",\n \"issuanceDate\": \"2025-06-16T18:56:59Z\",\n \"expirationDate\": \"2026-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"TestAfterQuestion\",\n \"familyName\": \"Student\",\n \"degree\": {\n \"type\": \"Master1\",\n \"degreeType\": \"Undergraduate2\",\n \"name\": \"Master of Test11\"\n },\n \"college\": \"Test2\",\n \"id\": \"did:indy:local:test:JPbsf8GpUYiavsK95SGpge\"\n },\n \"proof\": {\n \"type\": \"Ed25519Signature2018\",\n \"created\": \"2022-07-15T09:37:53Z\",\n \"proofPurpose\": \"assertionMethod\",\n \"verificationMethod\": \"did:indy:local:test:JPbsf8GpUYiavsK95SGpge#key-1\",\n \"jws\": \"eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..83LdD5thKn4GAMmSam7PWT9_TFROnSAyFU6nknqc46dQZfnA-y7fZTlvn_80P5zY8l7et0RaJHqbn9WFXN-IDw\"\n }\n }\n ],\n \"proof\": {\n \"type\": \"Ed25519Signature2018\",\n \"created\": \"2022-07-15T09:38:11Z\",\n \"proofPurpose\": \"assertionMethod\",\n \"verificationMethod\": \"did:indy:local:test:JPbsf8GpUYiavsK95SGpge#key-1\",\n \"jws\": \"eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..d2BfdTxbdNw-b7zKEz0LMM9WDrFskJIQ-7fOvEfUFpG0gk8jvWZmekb5Yfb2W_6rNKXiBlTRVn66t_kDoRPDCQ\"\n }\n}",
+ "raw": "{\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\"\n ],\n \"id\": \"e74ddd4d-c6c2-49c9-83ef-826f0daa5c1e\",\n \"type\": [\n \"VerifiablePresentation\"\n ],\n \"holder\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ\",\n \"verifiableCredential\": [\n {\n \"id\": \"http://example.edu/credentials/3888\",\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\",\n \"https://w3id.org/vc/status-list/2021/v1\"\n ],\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuer\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ\",\n \"issuanceDate\": \"2000-06-16T18:56:59Z\",\n \"expirationDate\": \"2999-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"TestAfterQuestion\",\n \"familyName\": \"Student5\",\n \"degree\": {\n \"typeeee\": \"Master1\",\n \"degreeType\": \"Undergraduate2\",\n \"name\": \"Master of Test11\"\n },\n \"college\": \"Test2\",\n \"id\": \"did:sov:HR6kiZSiUHfcW2tkGxtDKk\"\n },\n \"credentialStatus\": {\n \"id\": \"http://localhost:8080/api/credentials/status/2476607d-b00b-44f8-9c43-62f3bedd621b#1\",\n \"type\": \"StatusList2021Entry\",\n \"statusPurpose\": \"revocation\",\n \"statusListIndex\": \"1\",\n \"statusListCredential\": \"http://localhost:8080/api/credentials/status/2476607d-b00b-44f8-9c43-62f3bedd621b\"\n },\n \"proof\": {\n \"type\": \"Ed25519Signature2018\",\n \"created\": \"2022-09-16T15:13:26Z\",\n \"proofPurpose\": \"assertionMethod\",\n \"verificationMethod\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ#key-1\",\n \"jws\": \"eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..7qfg_wZPBxCF_Ua254ll7dIxmaS42sFXJgfMb95_NByCLbur6PyLbhqCGuGQttdu0lVbjawvCrkvj7YEFxGyCQ\"\n }\n }\n ],\n \"proof\": {\n \"type\": \"Ed25519Signature2018\",\n \"created\": \"2022-09-16T15:15:29Z\",\n \"proofPurpose\": \"assertionMethod\",\n \"verificationMethod\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ#key-1\",\n \"jws\": \"eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..Ev3ctb0z9des1wos3nfVUcAgGi3ek3cBsFk5BAILAV8tXt1pnK9Vp8JeYse45egnn3C_iQ2A2bD_4VLWupSLAg\"\n }\n}",
"options": {
"raw": {
"language": "json"
@@ -366,7 +540,7 @@
}
},
"url": {
- "raw": "{{custodian_url}}/api/presentations/validation?withDateValidation=false",
+ "raw": "{{custodian_url}}/api/presentations/validation?withDateValidation=false&withRevocationValidation=true",
"host": [
"{{custodian_url}}"
],
@@ -379,6 +553,10 @@
{
"key": "withDateValidation",
"value": "false"
+ },
+ {
+ "key": "withRevocationValidation",
+ "value": "true"
}
]
}
@@ -402,7 +580,7 @@
"header": [],
"body": {
"mode": "raw",
- "raw": "{\n \"id\": \"http://example.edu/credentials/3666\",\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\"\n ],\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuer\": \"did:indy:local:test:JPbsf8GpUYiavsK95SGpge\",\n \"issuanceDate\": \"2025-06-16T18:56:59Z\",\n \"expirationDate\": \"2026-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"TestAfterQuestion\",\n \"familyName\": \"Student\",\n \"degree\": {\n \"type\": \"Master1\",\n \"degreeType\": \"Undergraduate2\",\n \"name\": \"Master of Test11\"\n },\n \"college\": \"Test2\",\n \"id\": \"did:indy:local:test:M6Mis1fZKuhEw71GNY3TAb\"\n },\n \"proof\": {\n \"type\": \"Ed25519Signature2018\",\n \"created\": \"2022-07-15T09:35:59Z\",\n \"proofPurpose\": \"assertionMethod\",\n \"verificationMethod\": \"did:indy:local:test:JPbsf8GpUYiavsK95SGpge#key-1\",\n \"jws\": \"eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..4mFcySYFNAV6Bif6OqHeGqhQZ1kPMbq5FbOjurbIBIyYnQyRICa1b7RB_nxfz9fdP7WYxthTVnaWiXs2WbpzBQ\"\n }\n}",
+ "raw": "{\n \"id\": \"http://example.edu/credentials/3888\",\n \"@context\": [\n \"https://www.w3.org/2018/credentials/v1\",\n \"https://www.w3.org/2018/credentials/examples/v1\",\n \"https://w3id.org/vc/status-list/2021/v1\"\n ],\n \"type\": [\n \"University-Degree-Credential\",\n \"VerifiableCredential\"\n ],\n \"issuer\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ\",\n \"issuanceDate\": \"2000-06-16T18:56:59Z\",\n \"expirationDate\": \"2999-06-17T18:56:59Z\",\n \"credentialSubject\": {\n \"givenName\": \"TestAfterQuestion\",\n \"familyName\": \"Student5\",\n \"degree\": {\n \"typeeee\": \"Master1\",\n \"degreeType\": \"Undergraduate2\",\n \"name\": \"Master of Test11\"\n },\n \"college\": \"Test2\",\n \"id\": \"did:sov:HR6kiZSiUHfcW2tkGxtDKk\"\n },\n \"credentialStatus\": {\n \"id\": \"http://localhost:8080/api/credentials/status/2476607d-b00b-44f8-9c43-62f3bedd621b#1\",\n \"type\": \"StatusList2021Entry\",\n \"statusPurpose\": \"revocation\",\n \"statusListIndex\": \"1\",\n \"statusListCredential\": \"http://localhost:8080/api/credentials/status/2476607d-b00b-44f8-9c43-62f3bedd621b\"\n },\n \"proof\": {\n \"type\": \"Ed25519Signature2018\",\n \"created\": \"2022-09-16T15:13:26Z\",\n \"proofPurpose\": \"assertionMethod\",\n \"verificationMethod\": \"did:sov:MzoR91L1YAmXdPXXp97SRJ#key-1\",\n \"jws\": \"eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..7qfg_wZPBxCF_Ua254ll7dIxmaS42sFXJgfMb95_NByCLbur6PyLbhqCGuGQttdu0lVbjawvCrkvj7YEFxGyCQ\"\n }\n}",
"options": {
"raw": {
"language": "json"
@@ -410,14 +588,14 @@
}
},
"url": {
- "raw": "{{custodian_url}}/api/wallets/bpn111/credentials",
+ "raw": "{{custodian_url}}/api/wallets/bpn222/credentials",
"host": [
"{{custodian_url}}"
],
"path": [
"api",
"wallets",
- "bpn111",
+ "bpn222",
"credentials"
]
}
@@ -440,7 +618,7 @@
"method": "GET",
"header": [],
"url": {
- "raw": "{{custodian_url}}/api/credentials",
+ "raw": "{{custodian_url}}/api/credentials?issuerIdentifier=bpn111",
"host": [
"{{custodian_url}}"
],
@@ -466,8 +644,7 @@
},
{
"key": "issuerIdentifier",
- "value": "bpn555",
- "disabled": true
+ "value": "bpn111"
}
]
}
@@ -530,7 +707,7 @@
"header": [],
"body": {
"mode": "raw",
- "raw": "{\n \"type\": \"linked_domains\",\n \"serviceEndpoint\": \"https://myhost:7712\"\n}",
+ "raw": "{\n \"type\": \"did-communication\",\n \"serviceEndpoint\": \"http://cx_acapy:8000/\"\n}",
"options": {
"raw": {
"language": "json"
@@ -538,7 +715,7 @@
}
},
"url": {
- "raw": "{{custodian_url}}/api/didDocuments/bpn111/services/linked_domains",
+ "raw": "{{custodian_url}}/api/didDocuments/bpn111/services/did-communication",
"host": [
"{{custodian_url}}"
],
@@ -547,7 +724,7 @@
"didDocuments",
"bpn111",
"services",
- "linked_domains"
+ "did-communication"
]
}
},
@@ -618,14 +795,14 @@
}
},
"url": {
- "raw": "{{custodian_url}}/api/wallets/BPNL000000000000/public",
+ "raw": "{{custodian_url}}/api/wallets/bpn111/public",
"host": [
"{{custodian_url}}"
],
"path": [
"api",
"wallets",
- "BPNL000000000000",
+ "bpn111",
"public"
]
}
@@ -675,39 +852,6 @@
}
},
"response": []
- },
- {
- "name": "New Request",
- "protocolProfileBehavior": {
- "disableBodyPruning": true
- },
- "request": {
- "method": "GET",
- "header": [],
- "body": {
- "mode": "raw",
- "raw": "{\n \"reqSignature\": {},\n \"txn\": {\n \"data\": {\n \"data\": {\n \"alias\": \"Node1\",\n \"blskey\": \"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba\",\n \"blskey_pop\": \"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1\",\n \"client_ip\": \"40.89.175.63\",\n \"client_port\": 9702,\n \"node_ip\": \"40.89.175.63\",\n \"node_port\": 9701,\n \"services\": [\n \"VALIDATOR\"\n ]\n },\n \"dest\": \"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv\"\n },\n \"metadata\": {\n \"from\": \"Th7MpTaRZVRYnPiabds81Y\"\n },\n \"type\": \"0\"\n },\n \"txnMetadata\": {\n \"seqNo\": 1,\n \"txnId\": \"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62\"\n },\n \"ver\": \"1\"\n}\n {\n \"reqSignature\": {},\n \"txn\": {\n \"data\": {\n \"data\": {\n \"alias\": \"Node2\",\n \"blskey\": \"37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk\",\n \"blskey_pop\": \"Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5\",\n \"client_ip\": \"40.89.175.63\",\n \"client_port\": 9704,\n \"node_ip\": \"40.89.175.63\",\n \"node_port\": 9703,\n \"services\": [\n \"VALIDATOR\"\n ]\n },\n \"dest\": \"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb\"\n },\n \"metadata\": {\n \"from\": \"EbP4aYNeTHL6q385GuVpRV\"\n },\n \"type\": \"0\"\n },\n \"txnMetadata\": {\n \"seqNo\": 2,\n \"txnId\": \"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc\"\n },\n \"ver\": \"1\"\n}\n {\n \"reqSignature\": {},\n \"txn\": {\n \"data\": {\n \"data\": {\n \"alias\": \"Node3\",\n \"blskey\": \"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5\",\n \"blskey_pop\": \"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh\",\n \"client_ip\": \"40.89.175.63\",\n \"client_port\": 9706,\n \"node_ip\": \"40.89.175.63\",\n \"node_port\": 9705,\n \"services\": [\n \"VALIDATOR\"\n ]\n },\n \"dest\": \"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya\"\n },\n \"metadata\": {\n \"from\": \"4cU41vWW82ArfxJxHkzXPG\"\n },\n \"type\": \"0\"\n },\n \"txnMetadata\": {\n \"seqNo\": 3,\n \"txnId\": \"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4\"\n },\n \"ver\": \"1\"\n}\n {\n \"reqSignature\": {},\n \"txn\": {\n \"data\": {\n \"data\": {\n \"alias\": \"Node4\",\n \"blskey\": \"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw\",\n \"blskey_pop\": \"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP\",\n \"client_ip\": \"40.89.175.63\",\n \"client_port\": 9708,\n \"node_ip\": \"40.89.175.63\",\n \"node_port\": 9707,\n \"services\": [\n \"VALIDATOR\"\n ]\n },\n \"dest\": \"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA\"\n },\n \"metadata\": {\n \"from\": \"TWwCRQRZ2ZHMJFn9TzLp7W\"\n },\n \"type\": \"0\"\n },\n \"txnMetadata\": {\n \"seqNo\": 4,\n \"txnId\": \"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008\"\n },\n \"ver\": \"1\"\n}",
- "options": {
- "raw": {
- "language": "json"
- }
- }
- },
- "url": {
- "raw": "http://localhost:9092/1.0/identifiers/did:indy:idunion:test:AZW9NgcDfBriWAVkGgTxi9",
- "protocol": "http",
- "host": [
- "localhost"
- ],
- "port": "9092",
- "path": [
- "1.0",
- "identifiers",
- "did:indy:idunion:test:AZW9NgcDfBriWAVkGgTxi9"
- ]
- }
- },
- "response": []
}
]
}
\ No newline at end of file
diff --git a/dev-assets/Test-Acapy-SelfManagedWallet.postman_collection.json b/dev-assets/Test-Acapy-SelfManagedWallet.postman_collection.json
new file mode 100644
index 000000000..a353f4887
--- /dev/null
+++ b/dev-assets/Test-Acapy-SelfManagedWallet.postman_collection.json
@@ -0,0 +1,344 @@
+{
+ "info": {
+ "_postman_id": "ca9fc822-6f4a-4a2e-a37a-90b186bd714b",
+ "name": "Test-Acapy-SelfManagedWallet",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "Get Connections",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "url": {
+ "raw": "http://localhost:11001/connections",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "connections"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": "",
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Accept Connection (replace connection id)",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "url": {
+ "raw": "http://localhost:11001/didexchange/e842a370-d6bc-4230-9085-a6ed0f5b4e6b/accept-request",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "didexchange",
+ "e842a370-d6bc-4230-9085-a6ed0f5b4e6b",
+ "accept-request"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Get Records (replace connection id)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "url": {
+ "raw": "http://localhost:11001/issue-credential-2.0/records?connection_id=e842a370-d6bc-4230-9085-a6ed0f5b4e6b",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "issue-credential-2.0",
+ "records"
+ ],
+ "query": [
+ {
+ "key": "connection_id",
+ "value": "e842a370-d6bc-4230-9085-a6ed0f5b4e6b"
+ },
+ {
+ "key": "",
+ "value": "",
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Record (replace with cred_ex_id)",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "url": {
+ "raw": "http://localhost:11001/issue-credential-2.0/records/cc12a037-fcbc-4c4d-a82f-ee0c01965b52",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "issue-credential-2.0",
+ "records",
+ "cc12a037-fcbc-4c4d-a82f-ee0c01965b52"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": "",
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Send Credential Request (replace cred_ex_id)",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"holder_did\": \"did:sov:7rB93fLvW5kgujZ4E57ZxL\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://localhost:11001/issue-credential-2.0/records/917538c2-eef3-4141-be8f-177df7622a29/send-request",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "issue-credential-2.0",
+ "records",
+ "917538c2-eef3-4141-be8f-177df7622a29",
+ "send-request"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Store Credential (replace cred_ex_id and credential_id)",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"credential_id\": \"917538c2-eef3-4141-be8f-177df7622a29\"\n}"
+ },
+ "url": {
+ "raw": "http://localhost:11001/issue-credential-2.0/records/917538c2-eef3-4141-be8f-177df7622a29/store",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "issue-credential-2.0",
+ "records",
+ "917538c2-eef3-4141-be8f-177df7622a29",
+ "store"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Get Credential (replace credential id)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "url": {
+ "raw": "http://localhost:11001/credential/w3c/917538c2-eef3-4141-be8f-177df7622a29",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "credential",
+ "w3c",
+ "917538c2-eef3-4141-be8f-177df7622a29"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": "",
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Remove Connection (replace connection id)",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "url": {
+ "raw": "http://localhost:11001/connections/61be1bae-471b-4fa0-b9ff-662d365d16c9",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "connections",
+ "61be1bae-471b-4fa0-b9ff-662d365d16c9"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": "",
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Get All Credentials",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "http://localhost:11001/credentials/w3c",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "credentials",
+ "w3c"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Credential (replace with record_id of credential)",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-API-KEY",
+ "value": "Hj23iQUsstG!dde",
+ "type": "default"
+ }
+ ],
+ "url": {
+ "raw": "http://localhost:11001/credential/w3c/cc12a037-fcbc-4c4d-a82f-ee0c01965b52",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "11001",
+ "path": [
+ "credential",
+ "w3c",
+ "cc12a037-fcbc-4c4d-a82f-ee0c01965b52"
+ ],
+ "query": [
+ {
+ "key": "",
+ "value": "",
+ "disabled": true
+ }
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/dev-assets/dev-containers/db.Dockerfile b/dev-assets/dev-containers/db.Dockerfile
new file mode 100644
index 000000000..f7faa8e1e
--- /dev/null
+++ b/dev-assets/dev-containers/db.Dockerfile
@@ -0,0 +1,2 @@
+FROM postgres:14.2-alpine
+COPY revocation/V1.0.0__Create_DB.sql /docker-entrypoint-initdb.d/
diff --git a/dev-assets/dev-containers/docker-compose.yml b/dev-assets/dev-containers/docker-compose.yml
index d27fe7d02..78925d47c 100644
--- a/dev-assets/dev-containers/docker-compose.yml
+++ b/dev-assets/dev-containers/docker-compose.yml
@@ -2,7 +2,9 @@ version: '3'
services:
cx_postgres:
- image: postgres:14.2
+ build:
+ context: .
+ dockerfile: db.Dockerfile
container_name: cx_postgres
environment:
POSTGRES_USER: postgres
@@ -42,7 +44,7 @@ services:
command: [
"-c",
"aca-py start \
- -e http://localhost:8000/ \
+ -e http://cx_acapy:8000/ \
--auto-provision \
--inbound-transport http '0.0.0.0' 8000 \
--outbound-transport http \
@@ -54,7 +56,7 @@ services:
--wallet-storage-config '{\"url\":\"cx_postgres:5432\",\"max_connections\":5}'
--wallet-storage-creds '{\"account\":\"postgres\",\"password\":\"cx_password\",\"admin_account\":\"postgres\",\"admin_password\":\"cx_password\"}'
--seed 00000000000000000000000111111119 \
- --genesis-url https://indy-test.idu.network/genesis \
+ --genesis-url http://dev.greenlight.bcovrin.vonx.io/genesis \
--label CatenaXIssuer \
--admin-api-key Hj23iQUsstG!dde \
--auto-ping-connection \
@@ -64,5 +66,52 @@ services:
--log-level DEBUG",
]
+ test_acapy:
+ image: bcgovimages/aries-cloudagent:py36-1.16-1_0.7.4
+ container_name: test_acapy
+ ports:
+ - "11001:11001"
+ - "8001:8001"
+ entrypoint: /bin/bash
+ command: [
+ "-c",
+ "aca-py start \
+ -e http://test_acapy:8001/ \
+ --auto-provision \
+ --inbound-transport http '0.0.0.0' 8001 \
+ --outbound-transport http \
+ --admin '0.0.0.0' 11001 \
+ --wallet-name TestWallet \
+ --wallet-type indy \
+ --wallet-key issuerKeySecret19 \
+ --seed 04001600008020004050050111111119 \
+ --genesis-url http://dev.greenlight.bcovrin.vonx.io/genesis \
+ --label TestWallet \
+ --admin-api-key Hj23iQUsstG!dde \
+ --auto-ping-connection \
+ --jwt-secret jwtSecret19 \
+ --public-invites \
+ --log-level DEBUG",
+ ]
+
+ cx_revocation_service:
+ image: registry.gitlab.com/gaia-x/data-infrastructure-federation-services/not/notarization-service/revocation:1.0.0-SNAPSHOT-quarkus-2.10.2.Final-java17
+ container_name: cx_revocation_service
+ ports:
+ - "8086:8086"
+ depends_on:
+ - cx_postgres
+ environment:
+ QUARKUS_DATASOURCE_JDBC_URL: jdbc:postgresql://cx_postgres:5432/postgres?user=postgres&password=cx_password
+ QUARKUS_HTTP_PORT: 8086
+ QUARKUS_HTTP_ACCESS_LOG_ENABLED: "true"
+ REVOCATION_BASE_URL: http://localhost:8080/api/credentials/
+ REVOCATION_MIN_ISSUE_INTERVAL: 2
+ QUARKUS_REST_CLIENT_SSI_ISSUANCE_API_URL: "${SSI_SERVICE_URL:-http://host.docker.internal:8080}"
+ WAIT_HOSTS: "cx_postgres:5432"
+ WAIT_HOSTS_TIMEOUT: "300"
+ WAIT_SLEEP_INTERVAL: "5"
+ WAIT_HOST_CONNECT_TIMEOUT: "3"
+
volumes:
postgres-data:
diff --git a/dev-assets/dev-containers/revocation/V1.0.0__Create_DB.sql b/dev-assets/dev-containers/revocation/V1.0.0__Create_DB.sql
new file mode 100644
index 000000000..7938763d5
--- /dev/null
+++ b/dev-assets/dev-containers/revocation/V1.0.0__Create_DB.sql
@@ -0,0 +1,31 @@
+CREATE TABLE lists (
+ list_name character varying(255),
+ profile_name character varying(255),
+ encoded_list character varying(1048576) NULL,
+ list_credential character varying(1048576) NULL,
+ last_update timestamp with time zone NOT NULL DEFAULT '1970-01-01T00:00:00.00Z',
+ PRIMARY KEY (list_name),
+ UNIQUE (profile_name)
+);
+
+CREATE TABLE entry_counter (
+ list_name character varying(255) NOT NULL,
+ last_idx bigint NOT NULL,
+ PRIMARY KEY (list_name),
+ FOREIGN KEY (list_name) REFERENCES lists (list_name)
+);
+
+CREATE TABLE list_entry (
+ list_name character varying(255),
+ index bigint,
+ created_at timestamp with time zone NOT NULL DEFAULT now(),
+ revoked boolean NOT NULL DEFAULT false,
+ revoked_at timestamp with time zone NULL,
+ processed_to_list boolean NOT NULL DEFAULT false,
+ PRIMARY KEY (list_name, index),
+ FOREIGN KEY (list_name) REFERENCES lists (list_name)
+);
+
+
+CREATE INDEX idx_list_entry_processed ON list_entry (list_name, processed_to_list, revoked)
+ WHERE processed_to_list IS false AND revoked IS true;
diff --git a/docs/Architecture.md b/docs/Architecture.md
new file mode 100644
index 000000000..919453eff
--- /dev/null
+++ b/docs/Architecture.md
@@ -0,0 +1,273 @@
+# Managed Identity Wallet ARC42 Documentation
+
+- [Managed Identity Wallet ARC42 Documentation](#managed-identity-wallet-arc42-documentation)
+- [Introduction and Goals](#introduction-and-goals)
+ - [Requirements Overview](#requirements-overview)
+ - [Quality Goals](#quality-goals)
+ - [Stakeholders](#stakeholders)
+- [Architecture Constraints](#architecture-constraints)
+- [System Scope and Context](#system-scope-and-context)
+ - [Business Context](#business-context)
+ - [Technical Context](#technical-context)
+- [Solution Strategy](#solution-strategy)
+- [Building Block View](#building-block-view)
+ - [Whitebox Overall System](#whitebox-overall-system)
+- [Runtime View](#runtime-view)
+ - [Create Managed Wallet for Legal Entity](#create-managed-wallet-for-legal-entity)
+ - [Update Business Partner Data for Managed Wallet](#update-business-partner-data-for-managed-wallet)
+ - [Issue Credential and Presentation for Managed Wallet](#issue-credential-and-presentation-for-managed-wallet)
+ - [Register Self-Managed Wallet](#register-self-managed-wallet)
+ - [Issue Credential Flow](#issue-credential-flow)
+ - [Permission Handling](#permission-handling)
+- [Deployment View](#deployment-view)
+- [Cross-cutting Concepts](#cross-cutting-concepts)
+- [Design Decisions](#design-decisions)
+ - [Selection of DID method](#selection-of-did-method)
+ - [Usage of ACA-Py versus other framework versus own implementation](#usage-of-aca-py-versus-other-framework-versus-own-implementation)
+ - [Concept and Implementation of Credential Revocation](#concept-and-implementation-of-credential-revocation)
+ - [Concept and Implementation of Interaction with Self-Managed Wallets](#concept-and-implementation-of-interaction-with-self-managed-wallets)
+- [Quality Requirements](#quality-requirements)
+- [Risks and Technical Debts](#risks-and-technical-debts)
+- [Glossary](#glossary)
+
+
+Introduction and Goals
+======================
+
+To align the identity, authentication and data exchange of CX participants with the open and decentralized concepts within [GAIA-X](https://www.gxfs.eu/specifications/), especially self-sovereign identities, every legal entity associated to a BPNL number should have the possibility to also get a [W3C compliant DID](https://www.w3.org/TR/did-core/) (Decentralized Identifier). Due to the lack of production-ready SSI infrastructure and slow adoption on the market, this is in a first step achieved by providing a managed wallet (also called "Custodian") with a private/public key pair and related DID for a legal entity along with the onboarding to CX. This wallet can then be used via the Managed Identity Wallet API by other CX services or applications such as the Self Description Hub or the EDC to issue and retrieve [verifiable credentials](https://www.w3.org/TR/vc-data-model/) and create verifiable presentations on behalf of a certain legal entity as part of CX processes and use cases. In later steps, the same DID and credentials can be transferred to an external wallet managed by the legal entity itself, allowing self-sovereign data management and communication. Alternatively, a company can already "bring its own DID" upon onboarding and register it as a self-managed wallet for later interactions with CX managed wallets for credential and presentation exchange.
+
+Requirements Overview
+---------------------
+
+The basic requirements for the Managed Identity Wallet can be summarised as follows:
+
+* Establish a Catena-X DID and associated wallet as trust anchor for issuing CX related verifiable credentials
+* Link each BPNL to a unique DID distinguishing two cases
+ * managed: create and register a new DID and managed wallet, and allow the owners or administrators of the BPNL to access the related wallet and credentials via a REST API
+ * self-managed: connect an existing DID and external wallet provided by the owner or administrator of the BPNL in order to issue CX related verifiable credentials to and request presentations from the related DID
+* Allow other Catena-X components as well as DID owners to issue and store verifiable credentials
+* Allow other Catena-X components as well as DID owners to create and validate verifiable credentials
+* Issue and revoke verifiable credentials for Catena-X related data such as the BPN, membership status, business partner data (addresses, bank data etc.) according to the onboarding and change processes
+
+Quality Goals
+-------------
+
+| Title of Goal | Description and Reason |
+| ------------- | ---------------------- |
+| Security | The Managed Identity Wallet Service must be highly secure in terms of storage and access control, since it handles sensitive private key and claim information. |
+| Scalability | The Managed Identity Wallet Service must scale to the expected amount of participants without significant decrease of execution time. |
+| Maintainability | The Managed Identity Wallet Service should be structured in a modular way and re-use existing (open-source) components, frameworks and libraries where possible, so it can be extended and maintained in an efficient way. |
+
+Stakeholders
+------------
+
+The key stakeholders of the component are:
+
+* Catena-X Operating Company: to be compliant with Catena-X SSI concepts, especially for verifiable presentations in self descriptions
+* EDC operators: to be able to use SSI based identity and access management for the communication between data consumer and provider
+
+Architecture Constraints
+========================
+
+The architecture of the Managed Identity Wallet Service follows the general principles, guidelines and boundaries from the Catena-X project.
+
+System Scope and Context
+========================
+
+The Managed Identity Wallet Service is primarily used as an internal service within other Catena-X service or related components like EDC, but can also directly connect peer-to-peer with other DID agents.
+
+Business Context
+----------------
+
+![MIW Business Context](./diagrams/MIW-Business-Context.drawio.png)
+
+Technical Context
+-----------------
+
+![MIW Technical Context](./diagrams/MIW-Technical-Context.drawio.png)
+
+Solution Strategy
+=================
+
+The Managed Identity Wallet is implemented as an independent REST API service using the following technology stack:
+
+* Ktor Framework (Kotlin) on JDK 17
+* PostgreSQL database
+* [ACA-Py](https://github.com/hyperledger/aries-cloudagent-python) multi-tenant Hyperledger Aries agent (third-party Docker image, Python-based implementation)
+* GXFS Notarization API Revocation service (third-party Docker image, Quarkus-based implementation)
+* Gradle build
+* Dockerized setup for Kubernetes with Helm
+
+Building Block View
+===================
+
+Whitebox Overall System
+-----------------------
+
+The service consists of a main API implementation, connected to its own PostgreSQL database, a multi-tenancy enabled ACA-Py agent with its own PostgreSQL database, and an internal Revocation Service..
+
+![MIW Building Blocks](./diagrams/MIW-Building-Blocks.drawio.png)
+
+The API part is internally structured in different packages and classes for the API routes, most logic is contained in service classes abstracted by interfaces:
+
+* Routes:
+ * Did Document: DID resolution and management
+ * VC: issuance and revocation of verifiable credentials
+ * VP: issuance and validation of verifiable presentations
+ * Wallet: management (CRUD) of identity wallets
+* Services:
+ * AcaPyService: Abstraction of calls and response handling of ACA-Py
+ * Aries Event Handler: Processing of incoming Aries DID-Comm messages
+ * Business Partner Data Service: Abstraction of calls and response handling of BPDM
+ * RevocationService: Abstraction of calls and response handling of the revocation service
+ * WalletService: General wallet management and orchestration logic
+ * WebhookService: Abstraction of external webhook handling (webhooks called by the MIW as callback)
+
+Runtime View
+============
+
+The currently released API specification and documentation (INT environment) can be found under [https://managed-identity-wallets.int.demo.catena-x.net/docs](https://managed-identity-wallets.int.demo.catena-x.net/docs).
+
+In general, the API covers the following functionality:
+
+* Create, remove and retrieve managed wallets
+* Create or update business partner data related credentials
+* Manage DID document of the DID of a managed wallet (currently supports only adding or updating service endpoints)
+* Generate and store verifiable credentials issued by a particular identifier of a managed wallet or the Catena-X platform issuer
+* Create a verifiable presentation for a given list of VCs and presenter (as identifier of a managed wallet)
+* Validate a given verifiable presentation (convenience functionality, currently only supports Indy DIDs as issuers or holders)
+* Register self-managed wallets based on an existing DID
+* Trigger and handle the issue credential Aries flow for the Catena-X wallet as the issuer
+
+In the following, the most relevant operations are described as sequence diagrams.
+
+_Note that for managed wallets, VCs and VPs are only generated in an ephemeral way or stored in the own data store of the API, not by using Hyperledger Aries flows and storing them in the agent data._
+
+### Create Managed Wallet for Legal Entity
+
+![](./diagrams/MIW-Sequence-Create-Wallet.drawio.png)
+
+### Update Business Partner Data for Managed Wallet
+
+![](./diagrams/MIW-Sequence-Update-BP-Data.drawio.png)
+
+
+
+### Issue Credential and Presentation for Managed Wallet
+
+![](./diagrams/MIW-Sequence-Issue-VC-VP.drawio.png)
+
+### Register Self-Managed Wallet
+
+![](./diagrams/MIW-Sequence-Register-Self-ManagedWallet.drawio.png)
+
+### Issue Credential Flow
+
+![](./diagrams/MIW-Sequence-Issue-Credential-Flow.drawio.png)
+
+### Permission Handling
+
+For the API access, technical users are authenticated based on bearer tokens (JWT) issued by the Catena-X Keycloak. Each API operation specifies, which scopes/roles are required in order to be allowed to execute this operations, additionally the BPN associated to a user (available as a claim in the JWT) is considered to restrict access to the DID or wallet of the legal entity the user belongs to.
+
+For details on the permissions see the README section on scopes ([https://github.com/eclipse-tractusx/managed-identity-wallets#scopes-](https://github.com/eclipse-tractusx/managed-identity-wallets#scopes-)) as well as the statements about permissions in the API doc ([https://managed-identity-wallets.int.demo.catena-x.net/docs](https://managed-identity-wallets.int.demo.catena-x.net/docs)).
+
+Deployment View
+===============
+
+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: [https://github.com/eclipse-tractusx/managed-identity-wallets](https://github.com/eclipse-tractusx/managed-identity-wallets)
+
+The following diagram provides an overview of the deployment structure in Kubernetes (AKS).
+
+![](./diagrams/MIW-Deployment-AKS.drawio.png)
+
+
+
+Cross-cutting Concepts
+======================
+
+The main driver behind the Managed Identity Wallet Service was the compliance and compatibility with W3C SSI standards als in relation to GAIA-X principles. The solution is based on and uses a couple of standards or re-usable open-source components that can considered of overarching concern:
+
+* W3C Decentralized Identifiers (DIDs) [https://www.w3.org/TR/did-core/](https://www.w3.org/TR/did-core/)
+* W3C Verifiable Credential Data Model [https://www.w3.org/TR/vc-data-model/](https://www.w3.org/TR/vc-data-model/)
+* W3C Status List 2021 [https://w3c-ccg.github.io/vc-status-list-2021/](https://w3c-ccg.github.io/vc-status-list-2021/)
+* Linux Foundation Hyperledger Aries ([https://github.com/hyperledger/aries](https://github.com/hyperledger/aries)) incorporating the DIF DIDComm messaging standard ([https://identity.foundation/didcomm-messaging/spec/](https://identity.foundation/didcomm-messaging/spec/))
+* Hyperledger Aries Cloud Agent Python (ACA-Py, [https://github.com/hyperledger/aries-cloudagent-python](https://github.com/hyperledger/aries-cloudagent-python))
+* GAIA-X Federated Services (GXFS) Notarization API Revocation Service reference implementation ([https://gitlab.com/gaia-x/data-infrastructure-federation-services/not/notarization-service/-/tree/main/services/revocation](https://gitlab.com/gaia-x/data-infrastructure-federation-services/not/notarization-service/-/tree/main/services/revocation))
+
+Design Decisions
+================
+
+Decisions were made on several aspects as part of the sprint work in further development of the Managed Identity Wallet Service.
+
+### Selection of DID method
+
+Summary: decision was towards Indy DID (did:sov and future did:indy) due to the compatibility to the IDUnion network and most available Aries agent implementations such as the [Business Partner Agent](https://github.com/hyperledger-labs/business-partner-agent). In general, the solution should be open to support other DID methods for resolution and as the holder or presenter of credentials in the future.
+
+### Usage of ACA-Py versus other framework versus own implementation
+
+Summary: The evaluation revealed that ACA-Py can be used with certain limitations since it meets the basic requirements. We decided to use it to save implementation time, and address the limitations within the ACA-Py community, possibly by contributing extensions or fixes in the future.
+
+### Concept and Implementation of Credential Revocation
+
+Summary: Given the standardisation activities in W3C and the already available implementation from the GXFS Notarization Service, the recommendation is to use the Status List 2021 approach. For the implementation, the revocation service in the GXFS Notarization Service should be re-used if possible, ideally without any source code dependencies using the published Docker image ([https://gitlab.com/gaia-x/data-infrastructure-federation-services/not/notarization-service/container\_registry/3120998](https://gitlab.com/gaia-x/data-infrastructure-federation-services/not/notarization-service/container_registry/3120998)).
+
+### Concept and Implementation of Interaction with Self-Managed Wallets
+
+Summary: Since Hyperledger Aries based on DID-Comm seems to evolve into an industry standard for VC and VP interoperability (e.g. also in GAIA-X specifications) and there are already working implementations (most known ACA-Py that we also use in the MIW), it is the preferred option.
+
+Quality Requirements
+====================
+
+The Managed Identity Wallet sticks to the Quality Gate requirements of the overall Catena-X project where relevant:
+
+* 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
+
+Risks and Technical Debts
+=========================
+
+| Topic | Description and Explanation | Recommendation |
+| ----- | --------------------------- | -------------- |
+| Indy DID: Lacking support in Indy SDK and thus ACA-Py, needed to use did:sov for now | The W3C compliant DID method specification for Indy ([https://hyperledger.github.io/indy-did-method/](https://hyperledger.github.io/indy-did-method/)) is still fairly new and not yet fully implemented in major clients. That is why we had to use the Sovrin DID ([https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html](https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html)) for now, which is not fully W3C compliant and does not contain a network identifier to distinguish different ledgers (e.g. testnet and mainnet) in the DID. | The implementation should be adjusted to did:indy as soon as ACA-Py releases support for this. |
+| Availability of mainnet | I it envisioned to use the IDUnion mainnet as the Indy ledger for a productive solution. This mainnet is still under construction and not yet available for public use, there is also no committed target date, expectations are towards end of 2022, which would be sufficient for the timeline of Catena-X. | Escalate to IDUnion contacts when it becomes clear that the expected date of general availability can not be met. |
+| Lack of development resources and budget: open issues from implementation of integration with self-managed wallets | - Interaction with a self-managed wallet must still be configured and tested in the cloud setup
- Request presentation from self-managed wallets is not implemented
- Issued verifiable credentials to self-managed wallets do not support revocation yet
- The interaction is only possible with the Catena-X wallet (currently, there is no requirement to do that for other wallets, though)
|
+
+
+
+Glossary
+========
+
+| Term | Description |
+| ---- | ----------- |
+| Gaia-X | Gaia-X represents the next generation of data infrastructure ecosystem: an open, transparent, and secure digital ecosystem, where data and services can be made available, collated and shared in an environment of trust. ([more](https://gaia-x.eu/what-is-gaia-x/about-gaia-x/))
+| Catena-X Portal / Onboarding Process | The portal is used for the registration/onboarding process in Catena-X and includes the login, user management and the initial registration.
+| Decentralized Identifier (DID) | Decentralized Identifiers are a new type of identifiers that enables verifiable, decentralized digital identity. ([more](https://www.w3.org/TR/did-core/))
+| Claim, Verifiable Credential | An assertion made about a subject by an issuer. ([more](https://www.w3.org/TR/vc-data-model/))
+| Holder, Subject | Is the user that holds the verifiable credentials, usually the subject. ([more](https://www.w3.org/TR/vc-data-model/))
+| Issuer | Is an instance that can issue verifiable credentials. ([more](https://www.w3.org/TR/vc-data-model/))
+| Verifiable Presentation | The expression of a subset of one's persona is called a verifiable presentation. ([more](https://www.w3.org/TR/vc-data-model/))
+| Verifier | Is an instance that verifies the verifiable credentials of the holder. ([more](https://www.w3.org/TR/vc-data-model/))
+| Credential revocation | Is the process that an issuers marks a previously issued, valid, and non-expired credential as revoked and no longer valid
+| DIDComm / DID-Comm | Is a messaging format to securely communicate between DIDs, typically using agents. ([more](https://identity.foundation/didcomm-messaging/spec/))
+| Hyperledger Aries | Is a set of standard protocols (=workflows and message formats) and tools to allow meaningful interactions between DIDs, adding semantics to the basic DIDComm messaging ([more](https://github.com/hyperledger/aries))
+| Managed vs. Self-Managed (Identity) Wallet | An identity wallet holds the private key and credentials related to one particular DID. A managed wallet means that the wallet is operated by someone else than the owner of the DID and made accessible via an additional management interface such as the Managed Identity Wallet Service. A self-managed wallet means the owner of the DID has direct control on the wallet, ideally also hosting and operating it on own premises.
+
+
+
+* * *
\ No newline at end of file
diff --git a/docs/diagrams/MIW-Building-Blocks.drawio.png b/docs/diagrams/MIW-Building-Blocks.drawio.png
new file mode 100644
index 000000000..c59a4535b
Binary files /dev/null and b/docs/diagrams/MIW-Building-Blocks.drawio.png differ
diff --git a/docs/diagrams/MIW-Business-Context.drawio.png b/docs/diagrams/MIW-Business-Context.drawio.png
new file mode 100644
index 000000000..a1857ed8f
Binary files /dev/null and b/docs/diagrams/MIW-Business-Context.drawio.png differ
diff --git a/docs/diagrams/MIW-Deployment-AKS.drawio.png b/docs/diagrams/MIW-Deployment-AKS.drawio.png
new file mode 100644
index 000000000..c73cb20c6
Binary files /dev/null and b/docs/diagrams/MIW-Deployment-AKS.drawio.png differ
diff --git a/docs/diagrams/MIW-Sequence-Create-Wallet.drawio.png b/docs/diagrams/MIW-Sequence-Create-Wallet.drawio.png
new file mode 100644
index 000000000..3b2772795
Binary files /dev/null and b/docs/diagrams/MIW-Sequence-Create-Wallet.drawio.png differ
diff --git a/docs/diagrams/MIW-Sequence-Issue-Credential-Flow.drawio.png b/docs/diagrams/MIW-Sequence-Issue-Credential-Flow.drawio.png
new file mode 100644
index 000000000..d1fd349cd
Binary files /dev/null and b/docs/diagrams/MIW-Sequence-Issue-Credential-Flow.drawio.png differ
diff --git a/docs/diagrams/MIW-Sequence-Issue-VC-VP.drawio.png b/docs/diagrams/MIW-Sequence-Issue-VC-VP.drawio.png
new file mode 100644
index 000000000..086f16746
Binary files /dev/null and b/docs/diagrams/MIW-Sequence-Issue-VC-VP.drawio.png differ
diff --git a/docs/diagrams/MIW-Sequence-Register-Self-ManagedWallet.drawio.png b/docs/diagrams/MIW-Sequence-Register-Self-ManagedWallet.drawio.png
new file mode 100644
index 000000000..227c43fbd
Binary files /dev/null and b/docs/diagrams/MIW-Sequence-Register-Self-ManagedWallet.drawio.png differ
diff --git a/docs/diagrams/MIW-Sequence-Update-BP-Data.drawio.png b/docs/diagrams/MIW-Sequence-Update-BP-Data.drawio.png
new file mode 100644
index 000000000..d0b6c0bfa
Binary files /dev/null and b/docs/diagrams/MIW-Sequence-Update-BP-Data.drawio.png differ
diff --git a/docs/diagrams/MIW-Technical-Context.drawio.png b/docs/diagrams/MIW-Technical-Context.drawio.png
new file mode 100644
index 000000000..1bea047d0
Binary files /dev/null and b/docs/diagrams/MIW-Technical-Context.drawio.png differ
diff --git a/docs/openapi_v200.json b/docs/openapi_v200.json
new file mode 100644
index 000000000..aedcb2145
--- /dev/null
+++ b/docs/openapi_v200.json
@@ -0,0 +1,3700 @@
+{
+ "openapi": "3.0.3",
+ "info": {
+ "title": "Catena-X Core Managed Identity Wallets API",
+ "version": "2.0.0",
+ "description": "Catena-X Core Managed Identity Wallets API",
+ "termsOfService": "https://www.catena-x.net/",
+ "contact": {
+ "name": "Catena-X Core Agile Release Train",
+ "url": "https://www.catena-x.net/",
+ "email": "info@catena-x.net"
+ },
+ "license": {
+ "name": "Apache 2.0",
+ "url": "https://github.com/catenax-ng/product-core-managed-identity-wallets/blob/develop/LICENSE"
+ }
+ },
+ "servers": [
+ {
+ "url": "http://localhost:8080",
+ "description": "Local Dev Environment"
+ }
+ ],
+ "paths": {
+ "/api/wallets": {
+ "get": {
+ "tags": [
+ "Wallets"
+ ],
+ "summary": "List of wallets",
+ "description": "Permission: **view_wallets**\n\nRetrieve list of registered wallets",
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "List of wallets",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/WalletDto"
+ },
+ "maxItems": 999,
+ "type": "array"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ },
+ "post": {
+ "tags": [
+ "Wallets"
+ ],
+ "summary": "Create wallet",
+ "description": "Permission: **add_wallets**\n\nCreate a wallet and store it ",
+ "parameters": [],
+ "requestBody": {
+ "description": "wallet to create",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/WalletCreateDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "bpn": "name",
+ "name": "bpn"
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Wallet was successfully created",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/WalletDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "name": "name",
+ "bpn": "bpn",
+ "did": "did",
+ "verKey": "verkey",
+ "createdAt": "2022-09-17T14:31:47.056108748",
+ "vcs": []
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "409": {
+ "description": "The request could not be completed due to a conflict.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/wallets/{identifier}": {
+ "get": {
+ "tags": [
+ "Wallets"
+ ],
+ "summary": "Retrieve wallet by identifier",
+ "description": "Permission: **view_wallets** OR **view_wallet** (The BPN of Wallet to retrieve must equal the BPN of caller)\n\nRetrieve single wallet by identifier, with or without its credentials",
+ "parameters": [
+ {
+ "name": "identifier",
+ "in": "path",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "did": {
+ "value": "did:example:0123"
+ },
+ "bpn": {
+ "value": "bpn123"
+ }
+ }
+ },
+ {
+ "name": "withCredentials",
+ "in": "query",
+ "schema": {
+ "type": "boolean"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "withCredentials": {
+ "value": "false"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The wallet",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/WalletDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "name": "name",
+ "bpn": "bpn",
+ "did": "did",
+ "createdAt": "2022-09-17T14:31:47.056263746",
+ "vcs": []
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "The required entity does not exists",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ },
+ "delete": {
+ "tags": [
+ "Wallets"
+ ],
+ "summary": "Remove wallet",
+ "description": "Permission: **delete_wallets**\n\nRemove hosted wallet",
+ "parameters": [],
+ "responses": {
+ "200": {
+ "description": "Wallet successfully removed!",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SuccessResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "Wallet successfully removed!"
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "The required entity does not exists",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/wallets/{identifier}/credentials": {
+ "post": {
+ "tags": [
+ "Wallets"
+ ],
+ "summary": "Store Verifiable Credential",
+ "description": "Permission: **update_wallets** OR **update_wallet** (The BPN of wallet to extract credentials from must equal BPN of caller)\n\nStore a verifiable credential in the wallet of the given identifier",
+ "parameters": [
+ {
+ "name": "identifier",
+ "in": "path",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "did": {
+ "value": "did:exp:123"
+ },
+ "bpn": {
+ "value": "BPN123"
+ }
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "The verifiable credential to be stored",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/IssuedVerifiableCredentialRequestDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "http://example.edu/credentials/3732",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "credentialStatus": {
+ "id": "http://example.edu/api/credentials/status/test#3",
+ "type": "StatusList2021Entry",
+ "statusPurpose": "revocation",
+ "statusListIndex": "3",
+ "statusListCredential": "http://example.edu/api/credentials/status/test"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Success message",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SuccessResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "Credential with id http://example.edu/credentials/3732has been successfully stored"
+ }
+ }
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "The required entity does not exists",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/wallets/{identifier}/public": {
+ "post": {
+ "tags": [
+ "Wallets"
+ ],
+ "summary": "Register on Public Chain",
+ "description": "Permission: **update_wallets**\n\nRegister wallet DID on the public chain, endpoint only available for the base wallet",
+ "parameters": [
+ {
+ "name": "identifier",
+ "in": "path",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "did": {
+ "value": "did:exp:123"
+ },
+ "bpn": {
+ "value": "BPN123"
+ }
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "VerKey",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerKeyDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "verKey": "VERIFICATION_KEY_AFTER_CREATION"
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "Success message",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SuccessResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "Wallet has been successfully registered on chain"
+ }
+ }
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "The required entity does not exists",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/businessPartnerDataRefresh": {
+ "post": {
+ "tags": [
+ "BusinessPartnerData"
+ ],
+ "summary": "Pull business partner data from BPDM and issue or update verifiable credentials",
+ "description": "Permission: **update_wallets** OR **update_wallet** (The BPN of wallet to update must equal BPN of caller) \n\nPull business partner data from BPDM and issueor update related verifiable credentials. To update a specific wallet give its identifier as a query parameter.",
+ "parameters": [
+ {
+ "name": "identifier",
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "nullable": true
+ },
+ "required": false,
+ "deprecated": false,
+ "examples": {
+ "did": {
+ "value": "did:example:0123"
+ },
+ "bpn": {
+ "value": "bpn123"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "202": {
+ "description": "Empty response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/didDocuments/{identifier}": {
+ "get": {
+ "tags": [
+ "DIDDocument"
+ ],
+ "summary": "Resolve DID Document",
+ "description": "Resolve the DID document for a given DID or BPN",
+ "parameters": [
+ {
+ "name": "identifier",
+ "in": "path",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "did": {
+ "value": "did:exp:123"
+ },
+ "bpn": {
+ "value": "BPN123"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The resolved DID Document",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DidDocumentDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "@context": [
+ "https://www.w3.org/ns/did/v1"
+ ],
+ "controller": [
+ "123",
+ "1231"
+ ],
+ "verificationMethod": [
+ {
+ "id": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "type": "Ed25519VerificationKey2018",
+ "controller": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "publicKeyBase58": "FyfKP2HvTKqDZQzvyL38yXH7bExmwofxHf2NR5BrcGf1"
+ }
+ ],
+ "service": [
+ {
+ "id": "did:example:123#edv",
+ "type": "ServiceEndpointProxyService",
+ "serviceEndpoint": "https://myservice.com/myendpoint"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "The required entity does not exists",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/didDocuments/{identifier}/services": {
+ "post": {
+ "tags": [
+ "DIDDocument"
+ ],
+ "summary": "Add New Service Endpoint",
+ "description": "Permission: **update_wallets**\n\nAdd a new service endpoint to the DID Document",
+ "parameters": [
+ {
+ "name": "identifier",
+ "in": "path",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "did": {
+ "value": "did:exp:123"
+ },
+ "bpn": {
+ "value": "BPN123"
+ }
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "The Service endpoint",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DidServiceDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "did:example:123#edv",
+ "type": "ServiceEndpointProxyService",
+ "serviceEndpoint": "https://myservice.com/myendpoint"
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "The resolved DID Document after adding the new Service",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DidDocumentDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "@context": [
+ "https://www.w3.org/ns/did/v1"
+ ],
+ "controller": [
+ "123",
+ "1231"
+ ],
+ "verificationMethod": [
+ {
+ "id": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "type": "Ed25519VerificationKey2018",
+ "controller": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "publicKeyBase58": "FyfKP2HvTKqDZQzvyL38yXH7bExmwofxHf2NR5BrcGf1"
+ }
+ ],
+ "service": [
+ {
+ "id": "did:example:123#edv",
+ "type": "ServiceEndpointProxyService",
+ "serviceEndpoint": "https://myservice.com/myendpoint"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "The required entity does not exists",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/didDocuments/{identifier}/services/{id}": {
+ "put": {
+ "tags": [
+ "DIDDocument"
+ ],
+ "summary": "Update an existing Service Endpoint",
+ "description": "Permission: **update_wallets**\n\nUpdate the service endpoint in the DID Document based on its id",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "id": {
+ "value": "did:example:123#edv"
+ }
+ }
+ },
+ {
+ "name": "identifier",
+ "in": "path",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "did": {
+ "value": "did:exp:123"
+ },
+ "bpn": {
+ "value": "BPN123"
+ }
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "The updated service endpoint data",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DidServiceUpdateRequestDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "type": "ServiceEndpointProxyService",
+ "serviceEndpoint": "https://myservice.com/myendpoint"
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "The resolved DID Document after the updating the Service",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DidDocumentDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "@context": [
+ "https://www.w3.org/ns/did/v1"
+ ],
+ "controller": [
+ "123",
+ "1231"
+ ],
+ "verificationMethod": [
+ {
+ "id": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "type": "Ed25519VerificationKey2018",
+ "controller": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "publicKeyBase58": "FyfKP2HvTKqDZQzvyL38yXH7bExmwofxHf2NR5BrcGf1"
+ }
+ ],
+ "service": [
+ {
+ "id": "did:example:123#edv",
+ "type": "ServiceEndpointProxyService",
+ "serviceEndpoint": "https://myservice.com/myendpoint"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "The required entity does not exists",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ },
+ "delete": {
+ "tags": [
+ "DIDDocument"
+ ],
+ "summary": "Remove Service Endpoint",
+ "description": "Permission: **update_wallets**\n\nRemove service endpoint in DID Document based on its id",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "id": {
+ "value": "did:example:123#edv"
+ }
+ }
+ },
+ {
+ "name": "identifier",
+ "in": "path",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "did": {
+ "value": "did:exp:123"
+ },
+ "bpn": {
+ "value": "BPN123"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The resolved DID Document after removing the service",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DidDocumentDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "@context": [
+ "https://www.w3.org/ns/did/v1"
+ ],
+ "controller": "test",
+ "verificationMethod": [
+ {
+ "id": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "type": "Ed25519VerificationKey2018",
+ "controller": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "publicKeyBase58": "FyfKP2HvTKqDZQzvyL38yXH7bExmwofxHf2NR5BrcGf1"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "The required entity does not exists",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/credentials": {
+ "get": {
+ "tags": [
+ "VerifiableCredentials"
+ ],
+ "summary": "Query Verifiable Credentials",
+ "description": "Permission: **view_wallets** OR **view_wallet** (The BPN of holderIdentifier must equal BPN of caller)\n\nSearch verifiable credentials with filter criteria",
+ "parameters": [
+ {
+ "name": "holderIdentifier",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "holderIdentifierDid": {
+ "value": "did:example:4567"
+ },
+ "holderIdentifierBPN": {
+ "value": "BPN4567"
+ }
+ }
+ },
+ {
+ "name": "id",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "id": {
+ "value": "http://example.edu/credentials/3732"
+ }
+ }
+ },
+ {
+ "name": "issuerIdentifier",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "issuerIdentifierDid": {
+ "value": "did:example:0123"
+ },
+ "issuerIdentifierBPN": {
+ "value": "BPN0123"
+ }
+ }
+ },
+ {
+ "name": "type",
+ "in": "query",
+ "schema": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "type": {
+ "value": "['University-Degree-Credential']"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The list of verifiable credentials matching the query, empty if no match found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "items": {
+ "$ref": "#/components/schemas/VerifiableCredentialDto"
+ },
+ "maxItems": 999,
+ "type": "array"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ },
+ "post": {
+ "tags": [
+ "VerifiableCredentials"
+ ],
+ "summary": "Issue Verifiable Credential",
+ "description": "Permission: **update_wallets** OR **update_wallet** (The BPN of the issuer of the Verifiable Credential must equal BPN of caller)\n\nIssue a verifiable credential with a given issuer DID",
+ "parameters": [],
+ "requestBody": {
+ "description": "The verifiable credential input data",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifiableCredentialRequestDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "http://example.edu/credentials/3732",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuerIdentifier": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "holderIdentifier": "did:example:492edf208",
+ "isRevocable": true
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "The created Verifiable Credential",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifiableCredentialDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "http://example.edu/credentials/3732",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "credentialStatus": {
+ "id": "https://example.com/credentials/status/3#94567",
+ "type": "StatusList2021Entry",
+ "statusPurpose": "revocation",
+ "statusListIndex": "94567",
+ "statusListCredential": "https://example.com/credentials/status/3"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/credentials/issuer": {
+ "post": {
+ "tags": [
+ "VerifiableCredentials"
+ ],
+ "summary": "Issue a Verifiable Credential with Catena-X platform issuer",
+ "description": "Permission: **update_wallets** OR **update_wallet** (The BPN of Catena-X wallet must equal BPN of caller)\n\nIssue a verifiable credential by Catena-X wallet",
+ "parameters": [],
+ "requestBody": {
+ "description": "The verifiable credential input",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifiableCredentialRequestWithoutIssuerDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "http://example.edu/credentials/3732",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "holderIdentifier": "did:example:492edf208",
+ "isRevocable": true
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "The created Verifiable Credential",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifiableCredentialDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "http://example.edu/credentials/3732",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "credentialStatus": {
+ "id": "https://example.com/credentials/status/3#94567",
+ "type": "StatusList2021Entry",
+ "statusPurpose": "revocation",
+ "statusListIndex": "94567",
+ "statusListCredential": "https://example.com/credentials/status/3"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/credentials/revocations": {
+ "post": {
+ "tags": [
+ "VerifiableCredentials"
+ ],
+ "summary": "Revoke issued Verifiable Credential",
+ "description": "Permission: **update_wallets** OR **update_wallet** (The BPN of the issuer of the Verifiable Credential must equal BPN of caller)\n\nRevoke issued Verifiable Credential by issuer",
+ "parameters": [],
+ "requestBody": {
+ "description": "The signed verifiable credential",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifiableCredentialDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "http://example.edu/credentials/3732",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "credentialStatus": {
+ "id": "https://example.com/credentials/status/3#94567",
+ "type": "StatusList2021Entry",
+ "statusPurpose": "revocation",
+ "statusListIndex": "94567",
+ "statusListCredential": "https://example.com/credentials/status/3"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "202": {
+ "description": "Empty response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/credentials/revocations/statusListCredentialRefresh": {
+ "post": {
+ "tags": [
+ "VerifiableCredentials"
+ ],
+ "summary": "Re-issue the Status-List Credential for all or given wallet",
+ "description": "Permission: **update_wallets** OR**update_wallet** (The BPN of wallet to update must equal BPN of caller) \n\nRe-issue the Status-List Credential for all registered wallet",
+ "parameters": [
+ {
+ "name": "force",
+ "in": "query",
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "nullable": true
+ },
+ "required": false,
+ "deprecated": false
+ },
+ {
+ "name": "identifier",
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "nullable": true
+ },
+ "required": false,
+ "deprecated": false,
+ "examples": {
+ "identifier": {
+ "value": "BPN0001"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "202": {
+ "description": "Empty response body",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/credentials/status/{listName}": {
+ "get": {
+ "tags": [
+ "VerifiableCredentials"
+ ],
+ "summary": "Query Status-List Credentials",
+ "description": "Get the Status-List Credential for a given listName",
+ "parameters": [
+ {
+ "name": "listName",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ },
+ "required": true,
+ "deprecated": false,
+ "examples": {
+ "listName": {
+ "value": "5cb9ce19-9a10-48fe-bfa6-384632b89dc3"
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "The Verifiable Credential",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifiableCredentialDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "https://example.com/api/credentials/status/5c145c85-8fcb-42d4-893c-d19a55581e00",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://w3id.org/vc/status-list/2021/v1"
+ ],
+ "type": [
+ "VerifiableCredential",
+ "StatusList2021Credential"
+ ],
+ "issuer": "did:indy:local:test:Ae49DuXZy2PLBjSL9W2V2i",
+ "issuanceDate": "2022-08-31T07:19:36Z",
+ "credentialSubject": {
+ "id": "https://example.com/api/credentials/status/5c145c85-8fcb-42d4-893c-d19a55581e00#list",
+ "type": "StatusList2021",
+ "statusPurpose": "revocation",
+ "encodedList": "H4sIAAAAAAAAAO3BIQEAAAACIAf4f68zLEADAAAAAAAAAAAAAAAAAAAAvA3HJiyHAEAAAA=="
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-08-31T07:19:42Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:indy:local:test:Ae49DuXZy2PLBjSL9W2V2i#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0FB66o-WAn8W4qnNK0NsHBFMJj_ZM42ADdbwYO-P8oGywaYWeBPZylgD35AV2-CR0b5Hs8uDq0EIn8iHycjmBQ"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/presentations": {
+ "post": {
+ "tags": [
+ "VerifiablePresentations"
+ ],
+ "summary": "Create Verifiable Presentation",
+ "description": "Permission: **update_wallets** OR **update_wallet** (The BPN of the issuer of the Verifiable Presentation must equal to BPN of caller)\n\nCreate a verifiable presentation from a list of verifiable credentials, signed by the holder",
+ "parameters": [
+ {
+ "name": "withCredentialsDateValidation",
+ "in": "query",
+ "schema": {
+ "type": "boolean",
+ "default": true
+ },
+ "required": false,
+ "deprecated": false,
+ "examples": {
+ "withCredentialsDateValidation": {
+ "value": "false"
+ }
+ }
+ },
+ {
+ "name": "withCredentialsValidation",
+ "in": "query",
+ "schema": {
+ "type": "boolean",
+ "default": true
+ },
+ "required": false,
+ "deprecated": false,
+ "examples": {
+ "withCredentialsValidation": {
+ "value": "false"
+ }
+ }
+ },
+ {
+ "name": "withRevocationValidation",
+ "in": "query",
+ "schema": {
+ "type": "boolean",
+ "default": true
+ },
+ "required": false,
+ "deprecated": false,
+ "examples": {
+ "withRevocationValidation": {
+ "value": "false"
+ }
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "The verifiable presentation input data",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifiablePresentationRequestDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "holderIdentifier": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "verifiableCredentials": [
+ {
+ "id": "http://example.edu/credentials/333",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#keys-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "The created verifiable presentation",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifiablePresentationDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3732",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/api/presentations/validation": {
+ "post": {
+ "tags": [
+ "VerifiablePresentations"
+ ],
+ "summary": "Validate Verifiable Presentation",
+ "description": "Permission: **view_wallets** OR **view_wallet**\n\nValidate Verifiable Presentation with all included credentials",
+ "parameters": [
+ {
+ "name": "withDateValidation",
+ "in": "query",
+ "schema": {
+ "type": "boolean",
+ "default": false,
+ "nullable": true
+ },
+ "required": false,
+ "deprecated": false,
+ "examples": {
+ "withDateValidation": {
+ "value": "false"
+ }
+ }
+ },
+ {
+ "name": "withRevocationValidation",
+ "in": "query",
+ "schema": {
+ "type": "boolean",
+ "default": true
+ },
+ "required": false,
+ "deprecated": false,
+ "examples": {
+ "withRevocationValidation": {
+ "value": "false"
+ }
+ }
+ }
+ ],
+ "requestBody": {
+ "description": "The verifiable presentation to validate",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifiablePresentationDto"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3732",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "description": "The verification value",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/VerifyResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "valid": true,
+ "vp": {
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3732",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential, VerifiableCredential"
+ ],
+ "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ "issuanceDate": "2019-06-16T18:56:59Z",
+ "expirationDate": "2019-06-17T18:56:59Z",
+ "credentialSubject": {
+ "college": "Test-University"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2021-11-17T22:20:27Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
+ "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "The request could not be completed due to a forbidden access.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The request could not be completed due to a failed authorization.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ },
+ "/list-credential/{profileName}/issue": {
+ "post": {
+ "tags": [
+ "VerifiableCredentials"
+ ],
+ "summary": "Issue a List Status credential",
+ "description": "This endpoint is called by the revocation service to issue a list status credential for a given profileName",
+ "parameters": [],
+ "requestBody": {
+ "description": "The subject of the status list credential",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ListCredentialRequestData"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "id": "uuid-of-list",
+ "subject": {
+ "id": "https://example.com/status/3#list",
+ "type": "StatusList2021",
+ "statusPurpose": "revocation",
+ "encodedList": "H4sIAAAAAAAAA-3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA"
+ }
+ }
+ }
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "description": "The created verifiable credential",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "string"
+ },
+ "examples": {
+ "demo": {
+ "value": "credential-as-string"
+ }
+ }
+ }
+ }
+ },
+ "422": {
+ "description": "The input can not be processed due to semantic mismatches",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "The input does not comply to the syntax requirements",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ },
+ "examples": {
+ "demo": {
+ "value": {
+ "message": "reason",
+ "error": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "Unexpected error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ExceptionResponse"
+ }
+ }
+ }
+ }
+ },
+ "deprecated": false
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "WalletDto": {
+ "properties": {
+ "bpn": {
+ "type": "string"
+ },
+ "createdAt": {
+ "$ref": "#/components/schemas/LocalDateTime"
+ },
+ "did": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "revocationListName": {
+ "type": "string",
+ "nullable": true
+ },
+ "vcs": {
+ "items": {
+ "$ref": "#/components/schemas/VerifiableCredentialDto"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "verKey": {
+ "type": "string",
+ "nullable": true
+ }
+ },
+ "required": [
+ "name",
+ "bpn",
+ "did",
+ "createdAt",
+ "vcs"
+ ],
+ "type": "object"
+ },
+ "LocalDateTime": {
+ "properties": {
+ "date": {
+ "$ref": "#/components/schemas/LocalDate"
+ },
+ "time": {
+ "$ref": "#/components/schemas/LocalTime"
+ }
+ },
+ "type": "object"
+ },
+ "LocalDate": {
+ "properties": {
+ "year": {
+ "format": "int32",
+ "type": "integer"
+ },
+ "month": {
+ "$ref": "#/components/schemas/Short"
+ },
+ "day": {
+ "$ref": "#/components/schemas/Short"
+ }
+ },
+ "type": "object"
+ },
+ "Short": {
+ "properties": {},
+ "type": "object"
+ },
+ "LocalTime": {
+ "properties": {
+ "hour": {
+ "$ref": "#/components/schemas/Short"
+ },
+ "minute": {
+ "$ref": "#/components/schemas/Short"
+ },
+ "second": {
+ "$ref": "#/components/schemas/Short"
+ },
+ "nano": {
+ "format": "int32",
+ "type": "integer"
+ }
+ },
+ "type": "object"
+ },
+ "Byte": {
+ "properties": {},
+ "type": "object"
+ },
+ "VerifiableCredentialDto": {
+ "properties": {
+ "@context": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "credentialStatus": {
+ "$ref": "#/components/schemas/CredentialStatus"
+ },
+ "credentialSubject": {
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "type": "object"
+ },
+ "expirationDate": {
+ "type": "string",
+ "nullable": true
+ },
+ "id": {
+ "type": "string",
+ "nullable": true
+ },
+ "issuanceDate": {
+ "type": "string"
+ },
+ "issuer": {
+ "type": "string"
+ },
+ "proof": {
+ "$ref": "#/components/schemas/LdProofDto"
+ },
+ "type": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ }
+ },
+ "required": [
+ "@context",
+ "type",
+ "issuer",
+ "issuanceDate",
+ "credentialSubject"
+ ],
+ "type": "object"
+ },
+ "CredentialStatus": {
+ "properties": {
+ "credentialType": {
+ "type": "string"
+ },
+ "index": {
+ "type": "string"
+ },
+ "listUrl": {
+ "type": "string"
+ },
+ "statusId": {
+ "type": "string"
+ },
+ "statusPurpose": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "statusId",
+ "index",
+ "listUrl"
+ ],
+ "type": "object"
+ },
+ "Any": {
+ "properties": {},
+ "type": "object"
+ },
+ "LdProofDto": {
+ "properties": {
+ "challenge": {
+ "type": "string",
+ "nullable": true
+ },
+ "created": {
+ "type": "string"
+ },
+ "creator": {
+ "type": "string",
+ "nullable": true
+ },
+ "domain": {
+ "type": "string",
+ "nullable": true
+ },
+ "jws": {
+ "type": "string",
+ "nullable": true
+ },
+ "nonce": {
+ "type": "string",
+ "nullable": true
+ },
+ "proofPurpose": {
+ "type": "string"
+ },
+ "proofValue": {
+ "type": "string",
+ "nullable": true
+ },
+ "type": {
+ "type": "string"
+ },
+ "verificationMethod": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "created",
+ "proofPurpose",
+ "verificationMethod"
+ ],
+ "type": "object"
+ },
+ "ExceptionResponse": {
+ "properties": {
+ "error": {
+ "type": "boolean"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "message"
+ ],
+ "type": "object"
+ },
+ "WalletCreateDto": {
+ "properties": {
+ "bpn": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "bpn",
+ "name"
+ ],
+ "type": "object"
+ },
+ "SuccessResponse": {
+ "properties": {
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "message"
+ ],
+ "type": "object"
+ },
+ "IssuedVerifiableCredentialRequestDto": {
+ "properties": {
+ "@context": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "CredentialStatus": {
+ "$ref": "#/components/schemas/CredentialStatus"
+ },
+ "credentialSubject": {
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "type": "object"
+ },
+ "expirationDate": {
+ "type": "string",
+ "nullable": true
+ },
+ "id": {
+ "type": "string",
+ "nullable": true
+ },
+ "issuanceDate": {
+ "type": "string"
+ },
+ "issuer": {
+ "type": "string"
+ },
+ "proof": {
+ "$ref": "#/components/schemas/LdProofDto"
+ },
+ "type": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ }
+ },
+ "required": [
+ "@context",
+ "type",
+ "issuer",
+ "issuanceDate",
+ "credentialSubject",
+ "CredentialStatus",
+ "proof"
+ ],
+ "type": "object"
+ },
+ "VerKeyDto": {
+ "properties": {
+ "verKey": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "verKey"
+ ],
+ "type": "object"
+ },
+ "DidDocumentDto": {
+ "properties": {
+ "alsoKnownAs": {
+ "type": "string",
+ "nullable": true
+ },
+ "assertionMethodVerificationMethods": {
+ "items": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "authenticationVerificationMethods": {
+ "items": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "capabilityDelegationVerificationMethods": {
+ "items": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "capabilityInvocationVerificationMethods": {
+ "items": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "context": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "controller": {
+ "$ref": "#/components/schemas/Short"
+ },
+ "id": {
+ "type": "string"
+ },
+ "keyAgreementVerificationMethods": {
+ "items": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "services": {
+ "items": {
+ "$ref": "#/components/schemas/DidServiceDto"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "verificationMethods": {
+ "items": {
+ "$ref": "#/components/schemas/DidVerificationMethodDto"
+ },
+ "maxItems": 999,
+ "type": "array"
+ }
+ },
+ "required": [
+ "id",
+ "context"
+ ],
+ "type": "object"
+ },
+ "DidServiceDto": {
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "priority": {
+ "format": "int32",
+ "type": "integer",
+ "nullable": true
+ },
+ "recipientKeys": {
+ "items": {
+ "type": "string",
+ "nullable": true
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "routingKeys": {
+ "items": {
+ "type": "string",
+ "nullable": true
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "serviceEndpoint": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "type",
+ "serviceEndpoint"
+ ],
+ "type": "object"
+ },
+ "DidVerificationMethodDto": {
+ "properties": {
+ "controller": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "publicKeyBase58": {
+ "type": "string",
+ "nullable": true
+ },
+ "publicKeyBase64": {
+ "type": "string",
+ "nullable": true
+ },
+ "publicKeyHex": {
+ "type": "string",
+ "nullable": true
+ },
+ "publicKeyJwk": {
+ "$ref": "#/components/schemas/PublicKeyJwkDto"
+ },
+ "publicKeyMultibase": {
+ "type": "string",
+ "nullable": true
+ },
+ "publicKeyPem": {
+ "type": "string",
+ "nullable": true
+ },
+ "type": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "type",
+ "controller"
+ ],
+ "type": "object"
+ },
+ "PublicKeyJwkDto": {
+ "properties": {
+ "additionalAttributes": {
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "type": "object"
+ },
+ "alg": {
+ "type": "string",
+ "nullable": true
+ },
+ "crv": {
+ "type": "string",
+ "nullable": true
+ },
+ "keyOps": {
+ "items": {
+ "type": "string",
+ "nullable": true
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "kid": {
+ "type": "string",
+ "nullable": true
+ },
+ "kty": {
+ "type": "string"
+ },
+ "use": {
+ "type": "string",
+ "nullable": true
+ },
+ "x": {
+ "type": "string",
+ "nullable": true
+ },
+ "y": {
+ "type": "string",
+ "nullable": true
+ }
+ },
+ "required": [
+ "kty"
+ ],
+ "type": "object"
+ },
+ "DidServiceUpdateRequestDto": {
+ "properties": {
+ "serviceEndpoint": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "serviceEndpoint"
+ ],
+ "type": "object"
+ },
+ "VerifiableCredentialRequestDto": {
+ "properties": {
+ "@context": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "credentialSubject": {
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "type": "object"
+ },
+ "expirationDate": {
+ "type": "string",
+ "nullable": true
+ },
+ "holderIdentifier": {
+ "type": "string",
+ "nullable": true
+ },
+ "id": {
+ "type": "string",
+ "nullable": true
+ },
+ "isRevocable": {
+ "type": "boolean"
+ },
+ "issuanceDate": {
+ "type": "string",
+ "nullable": true
+ },
+ "issuerIdentifier": {
+ "type": "string"
+ },
+ "type": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ }
+ },
+ "required": [
+ "@context",
+ "type",
+ "issuerIdentifier",
+ "issuanceDate",
+ "credentialSubject"
+ ],
+ "type": "object"
+ },
+ "VerifiableCredentialRequestWithoutIssuerDto": {
+ "properties": {
+ "@context": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "credentialSubject": {
+ "additionalProperties": {
+ "$ref": "#/components/schemas/Any"
+ },
+ "type": "object"
+ },
+ "expirationDate": {
+ "type": "string",
+ "nullable": true
+ },
+ "holderIdentifier": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string",
+ "nullable": true
+ },
+ "isRevocable": {
+ "type": "boolean"
+ },
+ "issuanceDate": {
+ "type": "string",
+ "nullable": true
+ },
+ "type": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ }
+ },
+ "required": [
+ "@context",
+ "type",
+ "issuanceDate",
+ "credentialSubject",
+ "holderIdentifier"
+ ],
+ "type": "object"
+ },
+ "VerifiablePresentationRequestDto": {
+ "properties": {
+ "holderIdentifier": {
+ "type": "string"
+ },
+ "verifiableCredentials": {
+ "items": {
+ "$ref": "#/components/schemas/VerifiableCredentialDto"
+ },
+ "maxItems": 999,
+ "type": "array"
+ }
+ },
+ "required": [
+ "holderIdentifier",
+ "verifiableCredentials"
+ ],
+ "type": "object"
+ },
+ "VerifiablePresentationDto": {
+ "properties": {
+ "@context": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "holder": {
+ "type": "string",
+ "nullable": true
+ },
+ "id": {
+ "type": "string",
+ "nullable": true
+ },
+ "proof": {
+ "$ref": "#/components/schemas/LdProofDto"
+ },
+ "type": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 999,
+ "type": "array"
+ },
+ "verifiableCredential": {
+ "items": {
+ "$ref": "#/components/schemas/VerifiableCredentialDto"
+ },
+ "maxItems": 999,
+ "type": "array"
+ }
+ },
+ "required": [
+ "@context",
+ "type"
+ ],
+ "type": "object"
+ },
+ "VerifyResponse": {
+ "properties": {
+ "error": {
+ "type": "string",
+ "nullable": true
+ },
+ "valid": {
+ "type": "boolean"
+ },
+ "vp": {
+ "$ref": "#/components/schemas/VerifiablePresentationDto"
+ }
+ },
+ "required": [
+ "valid"
+ ],
+ "type": "object"
+ },
+ "ListCredentialRequestData": {
+ "properties": {
+ "listId": {
+ "type": "string",
+ "nullable": true
+ },
+ "subject": {
+ "$ref": "#/components/schemas/ListCredentialSubject"
+ }
+ },
+ "required": [
+ "subject"
+ ],
+ "type": "object"
+ },
+ "ListCredentialSubject": {
+ "properties": {
+ "credentialId": {
+ "type": "string"
+ },
+ "credentialType": {
+ "type": "string"
+ },
+ "encodedList": {
+ "type": "string"
+ },
+ "statusPurpose": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "credentialId",
+ "encodedList"
+ ],
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "auth-token": {
+ "bearerFormat": "JWT",
+ "type": "http",
+ "scheme": "bearer"
+ }
+ }
+ },
+ "security": [
+ {
+ "Bearer": [ "auth-token" ]
+ }
+ ],
+ "tags": []
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 9b8a2138a..286fb9f05 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -4,5 +4,5 @@ logback_version=1.2.9
kotlin.code.style=official
kompendium_version=2.3.5
exposed_version=0.38.2
-version=0.4.0
-coverage_excludes=**/models/**,**/entities/**,**/Application*,**/services/IWalletService*,**/services/IAcaPyService*,**/services/AcaPyService*,**/services/IBusinessPartnerDataService*,**/services/BusinessPartnerDataServiceImpl*
\ No newline at end of file
+version=2.1.1
+coverage_excludes=**/models/**,**/entities/**,**/Application*,**/services/IWalletService*,**/services/IAcaPyService*,**/services/AcaPyService*,**/services/IBusinessPartnerDataService*,**/services/BusinessPartnerDataServiceImpl*,**/services/IRevocationService*,**/services/RevocationService*
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/Application.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/Application.kt
index 54816107b..6f544ce94 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/Application.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/Application.kt
@@ -23,14 +23,15 @@ import io.ktor.application.*
import io.ktor.features.*
import org.eclipse.tractusx.managedidentitywallets.models.*
import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.WalletAndAcaPyConfig
+import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.ConnectionRepository
import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.CredentialRepository
import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.WalletRepository
+import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.WebhookRepository
import org.eclipse.tractusx.managedidentitywallets.plugins.*
import org.eclipse.tractusx.managedidentitywallets.routes.appRoutes
-import org.eclipse.tractusx.managedidentitywallets.services.IBusinessPartnerDataService
-import org.eclipse.tractusx.managedidentitywallets.services.IWalletService
-import org.eclipse.tractusx.managedidentitywallets.services.UtilsService
+import org.eclipse.tractusx.managedidentitywallets.services.*
+import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.LoggerFactory
@@ -40,6 +41,8 @@ object Services {
lateinit var businessPartnerDataService: IBusinessPartnerDataService
lateinit var walletService: IWalletService
lateinit var utilsService: UtilsService
+ lateinit var revocationService: IRevocationService
+ lateinit var webhookService: IWebhookService
}
fun Application.module(testing: Boolean = false) {
@@ -52,7 +55,9 @@ fun Application.module(testing: Boolean = false) {
configureSockets()
configureSerialization()
- install(DefaultHeaders)
+ install(DefaultHeaders) {
+ header("X-Frame-Options", "DENY")
+ }
// for debugging
install(CallLogging)
@@ -66,14 +71,29 @@ fun Application.module(testing: Boolean = false) {
val walletRepository = WalletRepository()
val credRepository = CredentialRepository()
+ val connectionRepository = ConnectionRepository()
+ val webhookRepository = WebhookRepository()
+
+ val baseWalletBpn = environment.config.property("wallet.baseWalletBpn").getString()
val acaPyConfig = WalletAndAcaPyConfig(
apiAdminUrl = environment.config.property("acapy.apiAdminUrl").getString(),
networkIdentifier = environment.config.property("acapy.networkIdentifier").getString(),
adminApiKey = environment.config.property("acapy.adminApiKey").getString(),
- baseWalletBpn = environment.config.property("wallet.baseWalletBpn").getString()
+ baseWalletBpn = baseWalletBpn
)
val utilsService = UtilsService(networkIdentifier = acaPyConfig.networkIdentifier)
- val walletService = IWalletService.createWithAcaPyService(acaPyConfig, walletRepository, credRepository, utilsService)
+ val revocationUrl = environment.config.property("revocation.baseUrl").getString()
+ val revocationService = IRevocationService.createRevocationService(revocationUrl)
+ val webhookService = IWebhookService.createWebhookService(webhookRepository)
+ val walletService = IWalletService.createWithAcaPyService(
+ acaPyConfig,
+ walletRepository,
+ credRepository,
+ utilsService,
+ revocationService,
+ webhookService,
+ connectionRepository
+ )
val bpdmConfig = BPDMConfig(
url = environment.config.property("bpdm.datapoolUrl").getString(),
tokenUrl = environment.config.property("bpdm.authUrl").getString(),
@@ -87,10 +107,21 @@ fun Application.module(testing: Boolean = false) {
Services.businessPartnerDataService = businessPartnerDataService
Services.walletService = walletService
Services.utilsService = utilsService
+ Services.revocationService = revocationService
+ Services.webhookService = webhookService
+
configureRouting(walletService)
- appRoutes(walletService, businessPartnerDataService)
+ appRoutes(walletService, businessPartnerDataService, revocationService, utilsService)
configurePersistence()
configureJobs()
+
+ val wallets = transaction {
+ walletService.getAll()
+ }
+
+ if (wallets.isNotEmpty() && wallets.stream().anyMatch{ wallet -> wallet.bpn == baseWalletBpn }) {
+ walletService.subscribeForAriesWS()
+ }
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/Internal.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/Internal.kt
index e6f6f42bb..0cd3b28cb 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/Internal.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/Internal.kt
@@ -44,6 +44,8 @@ class ForbiddenException(message: String? = "empty message") : Exception(message
class AuthorizationException(message: String? = "empty message") : Exception(message)
+class InternalServerErrorException(message: String? = "empty message") : Exception(message)
+
val semanticallyInvalidInputException = ExceptionInfo(
responseType = typeOf(),
description = "The input can not be processed due to semantic mismatches",
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletCreateDto.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletCreateDto.kt
index a4623a5f4..2db53b1ad 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletCreateDto.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletCreateDto.kt
@@ -27,4 +27,18 @@ data class WalletCreateDto(val bpn: String, val name: String) {
require(bpn.isNotBlank()) { "Field 'bpn' is required not to be blank, but it was blank" }
require(name.isNotBlank()) { "Field 'name' is required not to be blank, but it was blank" }
}
-}
\ No newline at end of file
+}
+
+@Serializable
+data class SelfManagedWalletCreateDto(
+ val bpn: String,
+ val name: String,
+ val did: String,
+ val webhookUrl: String? = null
+) {
+ init {
+ require(bpn.isNotBlank()) { "Field 'bpn' is required not to be blank, but it was blank" }
+ require(name.isNotBlank()) { "Field 'name' is required not to be blank, but it was blank" }
+ require(did.isNotBlank()) { "Field 'did' is required not to be blank, but it was blank" }
+ }
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletDto.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletDto.kt
index d00c46db6..f8a845814 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletDto.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletDto.kt
@@ -35,7 +35,9 @@ data class WalletDto(
val did: String,
val verKey: String? = null,
@Serializable(with = LocalDateTimeAsStringSerializer::class) val createdAt: LocalDateTime,
- val vcs: List
+ val vcs: List,
+ val revocationListName: String? = null,
+ val pendingMembershipIssuance: Boolean
) {
init {
@@ -73,3 +75,19 @@ data class WalletDtoParameter(
data class VerKeyDto(
val verKey: String
)
+
+@Serializable
+data class SelfManagedWalletResultDto(
+ val name: String,
+ val bpn: String,
+ val did: String,
+ @Serializable(with = LocalDateTimeAsStringSerializer::class) val createdAt: LocalDateTime,
+)
+
+@Serializable
+data class ConnectionDto(
+ val connectionId: String,
+ val theirDid: String,
+ val myDid: String,
+ val state: String
+)
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletExtendedData.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletExtendedData.kt
index 1e17da209..3da3e07c7 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletExtendedData.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/WalletExtendedData.kt
@@ -27,7 +27,9 @@ data class WalletExtendedData(
val name: String,
val bpn: String,
val did: String,
- var walletId: String,
- var walletKey: String,
- var walletToken: String
+ var walletId: String?,
+ var walletKey: String?,
+ var walletToken: String?,
+ var revocationListName: String?,
+ var pendingMembershipIssuance: Boolean
)
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/JsonLdContexts.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/JsonLdContexts.kt
index 86ea816e6..0c771f078 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/JsonLdContexts.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/JsonLdContexts.kt
@@ -22,6 +22,7 @@ package org.eclipse.tractusx.managedidentitywallets.models.ssi
class JsonLdContexts {
companion object {
val JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1 = "https://www.w3.org/2018/credentials/v1"
+ val JSONLD_CONTEXT_W3C_STATUS_LIST_2021_V1 = "https://w3id.org/vc/status-list/2021/v1"
val JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1 = "https://www.w3.org/2018/credentials/examples/v1"
val JSONLD_CONTEXT_BPD_CREDENTIALS = "https://raw.githubusercontent.com/catenax-ng/product-core-schemas/main/businessPartnerData"
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/VerifiableCredentialDto.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/VerifiableCredentialDto.kt
index 32a071bea..fcc8fedcf 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/VerifiableCredentialDto.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/VerifiableCredentialDto.kt
@@ -47,6 +47,8 @@ data class VerifiableCredentialDto(
val expirationDate: String? = null, // In Rfc3339
@Field(description = "The Credential Subject including the DID of the Subject", name = "credentialSubject")
val credentialSubject: Map,
+ @Field(description = "The Credential Status if the credential is revocable", name = "credentialStatus")
+ val credentialStatus: CredentialStatus? = null,
@Field(description = "The Proof generated by the Issuer for issued Credentials", name = "proof")
val proof: LdProofDto? = null
)
@@ -84,6 +86,8 @@ data class IssuedVerifiableCredentialRequestDto(
val expirationDate: String? = null, // In Rfc3339
@Field(description = "The Credential Subject including the DID of the Subject", name = "credentialSubject")
val credentialSubject: Map,
+ @Field(description = "The Credential Status including the revocation related Information", name = "CredentialStatus")
+ val credentialStatus: CredentialStatus?,
@Field(description = "The Proof generated by the Issuer", name = "proof")
val proof: LdProofDto
)
@@ -105,9 +109,13 @@ data class VerifiableCredentialRequestDto(
val expirationDate: String? = null, // In Rfc3339
@Field(description = "The Credential Subject representing the payload", name = "credentialSubject")
val credentialSubject: Map,
- @Field(description = "The DID or BPN of holder, the DID will be automatically set as the id attribute in the credential subject",
+ @Field(description = "The Identifier of the holder, It Should be a DID or BPN. " +
+ "This value will be ignored if the credential subject has an id, " +
+ "otherwise it will be set as the id attribute in the credential subject",
name = "holderIdentifier")
- val holderIdentifier: String,
+ val holderIdentifier: String?= null,
+ @Field(description = "Flag whether credential is revocable or not. Default true", name = "isRevocable")
+ val isRevocable: Boolean = true
)
@Serializable
@@ -128,4 +136,122 @@ data class VerifiableCredentialRequestWithoutIssuerDto(
@Field(description = "The DID or BPN of holder, the DID will be automatically set as the id attribute in the credential subject",
name = "holderIdentifier")
val holderIdentifier: String,
+ @Field(description = "Flag whether credential is revocable or not. Default true", name = "isRevocable")
+ val isRevocable: Boolean = true
+)
+
+@Serializable
+data class CredentialStatus (
+ @SerialName("id") @JsonProperty("id") var statusId: String,
+ @SerialName("type") @JsonProperty("type") var credentialType: String = "StatusList2021Entry",
+ @SerialName("statusPurpose") @JsonProperty("statusPurpose") var statusPurpose: String = "revocation",
+ @SerialName("statusListIndex") @JsonProperty("statusListIndex") var index: String,
+ @SerialName("statusListCredential") @JsonProperty("statusListCredential") var listUrl: String,
+) {
+ companion object {
+ const val CREDENTIAL_TYPE = "StatusList2021Entry"
+ const val STATUS_PURPOSE = "revocation"
+ }
+}
+
+@Serializable
+data class ListCredentialRequestData (
+ @SerialName("id") @JsonProperty("id") var listId: String? = null,
+ var subject: ListCredentialSubject
+)
+
+@Serializable
+data class ListCredentialSubject (
+ @SerialName("id") @JsonProperty("id") var credentialId: String,
+ @SerialName("type") @JsonProperty("type") var credentialType: String = "StatusList2021",
+ var statusPurpose: String = "revocation",
+ var encodedList: String
+) {
+ companion object {
+ const val CREDENTIAL_TYPE_KEY = "credentialType"
+ const val CREDENTIAL_TYPE = "StatusList2021"
+ const val STATUS_PURPOSE_KEY = "statusPurpose"
+ const val STATUS_PURPOSE = "revocation"
+ const val ENCODED_LIST_KEY = "encodedList"
+ }
+}
+
+@Serializable
+data class ListNameParameter(
+ @Param(type = ParamType.QUERY)
+ @Field(description = "The revocation listName", name = "listName")
+ val listName: String,
+)
+
+@Serializable
+data class StatusListRefreshParameters(
+ @Param(type = ParamType.QUERY)
+ @Field(description = "The DID or BPN of the wallet whose revocation list should be refreshed", name = "identifier")
+ val identifier: String? = null,
+ @Param(type = ParamType.QUERY)
+ @Field(description = "Force an update. Default is false", name = "force")
+ val force: Boolean? = false,
+)
+
+@Serializable
+data class VerifiableCredentialIssuanceFlowRequestDto (
+ @Field(description = "The ID of credential as String (URI compatible)", name = "id")
+ val id: String? = null,
+ @JsonProperty("@context") @SerialName("@context")
+ @Field(description = "List of Contexts", name = "@context")
+ val context: List,
+ @Field(description = "List of Credential Types", name = "type")
+ val type: List,
+ @Field(description = "The DID or BPN of Issuer", name = "issuerIdentifier")
+ val issuerIdentifier: String,
+ @Field(description = "The Issuance Date as String in Rfc3339 format, if null, the current time is used", name = "issuanceDate")
+ var issuanceDate: String?, // In Rfc3339
+ @Field(description = "The Expiration Date as String in Rfc3339 format", name = "expirationDate")
+ val expirationDate: String? = null, // In Rfc3339
+ @Field(description = "The Credential Subject representing the payload", name = "credentialSubject")
+ val credentialSubject: Map,
+ @Field(description = "The Identifier of the holder, It Should be a DID or BPN. " +
+ "This value will be ignored if the credential subject has an id, " +
+ "otherwise it will be set as the id attribute in the credential subject",
+ name = "holderIdentifier")
+ val holderIdentifier: String?= null,
+ @Field(description = "Flag whether credential is revocable or not. Default true", name = "isRevocable")
+ val isRevocable: Boolean = true,
+ @Field(description = "The URL to be used as webhook to inform the requester when the issuance flow is done", name = "webhookUrl")
+ val webhookUrl: String? = null,
+
+) {
+ fun toInternalVerifiableCredentialIssuanceFlowRequest(): VerifiableCredentialIssuanceFlowRequest {
+ return VerifiableCredentialIssuanceFlowRequest(
+ id = this.id,
+ context = this.context,
+ type = this.type,
+ issuerIdentifier = this.issuerIdentifier,
+ issuanceDate = this.issuanceDate,
+ expirationDate = this.expirationDate,
+ credentialSubject = this.credentialSubject,
+ holderIdentifier = this.holderIdentifier,
+ isRevocable = this.isRevocable,
+ webhookUrl = this.webhookUrl,
+ credentialStatus = null,
+ connectionId = null
+ )
+ }
+}
+
+@Serializable
+data class VerifiableCredentialIssuanceFlowRequest(
+ val id: String? = null,
+ @JsonProperty("@context") @SerialName("@context")
+ val context: List,
+ val type: List,
+ val issuerIdentifier: String,
+ var issuanceDate: String?, // In Rfc3339
+ val expirationDate: String? = null, // In Rfc3339
+ val credentialSubject: Map,
+ val credentialStatus: CredentialStatus?,
+ val holderIdentifier: String?= null,
+ val isRevocable: Boolean = true,
+ val webhookUrl: String? = null,
+ val connectionId: String?= null
)
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/VerifiablePresentationDto.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/VerifiablePresentationDto.kt
index 1b9a35179..260357eb2 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/VerifiablePresentationDto.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/VerifiablePresentationDto.kt
@@ -62,7 +62,13 @@ data class WithDateValidation(
description = "Flag whether issuance and expiration date of all credentials should be validated",
name = "withDateValidation"
)
- val withDateValidation: Boolean? = false
+ val withDateValidation: Boolean? = false,
+ @Param(type = ParamType.QUERY)
+ @Field(
+ description = "Optional flag to check if any of the verifiable credentials has been revoked. Default is true",
+ name = "withRevocationValidation"
+ )
+ val withRevocationValidation: Boolean = true
)
@Serializable
@@ -79,5 +85,11 @@ data class VerifiablePresentationIssuanceParameter(
"Default is true. If `withCredentialsValidation` is false then this value will be ignored.",
name = "withCredentialsDateValidation"
)
- val withCredentialsDateValidation: Boolean = true
+ val withCredentialsDateValidation: Boolean = true,
+ @Param(type = ParamType.QUERY)
+ @Field(
+ description = "Optional flag to check if any of the verifiable credentials has been revoked. Default is true",
+ name = "withRevocationValidation"
+ )
+ val withRevocationValidation: Boolean = true
)
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/acapy/AcaPyDto.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/acapy/AcaPyDto.kt
index 54976e818..44222437d 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/acapy/AcaPyDto.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/models/ssi/acapy/AcaPyDto.kt
@@ -211,3 +211,9 @@ data class DidEndpointWithType(
val endpoint: String,
@JsonProperty("endpoint_type") @SerialName("endpoint_type") val endpointType: String
)
+
+@Serializable
+data class CredentialOfferResponse(
+ @JsonProperty("credential_offer") @SerialName("credential_offer") val credentialOffer: String,
+ @JsonProperty("threadId") @SerialName("threadId") val threadId: String
+)
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/ConnectionDao.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/ConnectionDao.kt
new file mode 100644
index 000000000..06d88ba25
--- /dev/null
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/ConnectionDao.kt
@@ -0,0 +1,40 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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.persistence.entities
+
+import org.jetbrains.exposed.dao.Entity
+import org.jetbrains.exposed.dao.EntityClass
+import org.jetbrains.exposed.dao.id.EntityID
+import org.jetbrains.exposed.dao.id.IntIdTable
+
+object Connections : IntIdTable("connections") {
+ val connectionId = varchar("connection_id", 4096).uniqueIndex("connectionId")
+ val theirDid = varchar("their_did", 4096)
+ val myDid = varchar("my_did", 4096)
+ val state = varchar("state", 4096)
+}
+
+class Connection(id: EntityID) : Entity(id) {
+ companion object : EntityClass(Connections)
+ var connectionId by Connections.connectionId
+ var theirDid by Connections.theirDid
+ var myDid by Connections.myDid
+ var state by Connections.state
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/WalletDao.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/WalletDao.kt
index 139677f9e..3dacf15e3 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/WalletDao.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/WalletDao.kt
@@ -32,9 +32,12 @@ object Wallets : IntIdTable("wallets") {
var bpn = varchar("bpn", 36).uniqueIndex("bpn")
val did = varchar("did", 4096).uniqueIndex("did")
- val walletId = varchar("wallet_id", 4096)
- val walletKey = varchar("wallet_key", 4096)
- val walletToken = varchar("wallet_token", 4096)
+ val walletId = varchar("wallet_id", 4096).nullable()
+ val walletKey = varchar("wallet_key", 4096).nullable()
+ val walletToken = varchar("wallet_token", 4096).nullable()
+
+ val revocationListName = varchar("revocation_list_name", 4096).nullable()
+ val pendingMembershipIssuance = bool("pending_membership_issuance").default(false)
}
class Wallet(id: EntityID) : Entity(id) {
@@ -49,4 +52,8 @@ class Wallet(id: EntityID) : Entity(id) {
var walletId by Wallets.walletId
var walletKey by Wallets.walletKey
var walletToken by Wallets.walletToken
+
+ var revocationListName by Wallets.revocationListName
+
+ var pendingMembershipIssuance by Wallets.pendingMembershipIssuance
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/WebhookDao.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/WebhookDao.kt
new file mode 100644
index 000000000..820349696
--- /dev/null
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/entities/WebhookDao.kt
@@ -0,0 +1,38 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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.persistence.entities
+
+import org.jetbrains.exposed.dao.Entity
+import org.jetbrains.exposed.dao.EntityClass
+import org.jetbrains.exposed.dao.id.EntityID
+import org.jetbrains.exposed.dao.id.IntIdTable
+
+object Webhooks : IntIdTable("webhooks") {
+ val threadId = varchar("thread_id", 4096).uniqueIndex("threadId")
+ val webhookUrl = varchar("webhook_url", 4096)
+ val state = varchar("state", 4096)
+}
+
+class Webhook(id: EntityID) : Entity(id) {
+ companion object : EntityClass(Webhooks)
+ var threadId by Webhooks.threadId
+ var webhookUrl by Webhooks.webhookUrl
+ var state by Webhooks.state
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/ConnectionRepository.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/ConnectionRepository.kt
new file mode 100644
index 000000000..de6dd9ed2
--- /dev/null
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/ConnectionRepository.kt
@@ -0,0 +1,92 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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.persistence.repositories
+
+import org.eclipse.tractusx.managedidentitywallets.models.ConnectionDto
+import org.eclipse.tractusx.managedidentitywallets.models.NotFoundException
+import org.eclipse.tractusx.managedidentitywallets.persistence.entities.*
+import org.hyperledger.aries.api.connection.ConnectionRecord
+import org.hyperledger.aries.api.connection.ConnectionState
+import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.transactions.transaction
+
+class ConnectionRepository {
+
+ fun getAll(): List = transaction { Connection.all().toList() }
+
+ fun get(
+ connectionId: String,
+ ): Connection = Connection.find { Connections.connectionId eq connectionId }
+ .firstOrNull()
+ ?: throw NotFoundException("Connection with id $connectionId not found")
+
+ fun add(
+ connectionOwnerDid: String,
+ connectionTargetDid: String,
+ connectionRecord: ConnectionRecord
+ ): Connection {
+ return Connection.new {
+ connectionId = connectionRecord.connectionId
+ theirDid = connectionTargetDid
+ myDid = connectionOwnerDid
+ state = connectionRecord.state.toString()
+ }
+ }
+
+ fun updateConnectionState(connectionId: String, connectionState: ConnectionState) {
+ get(connectionId).apply {
+ state = connectionState.name
+ }
+ }
+
+ fun deleteConnections(did: String): Boolean {
+ Connections.deleteWhere {
+ (Connections.myDid eq did) or (Connections.theirDid eq did)
+ }
+ return true
+ }
+
+ fun deleteConnection(connectionId: String): Boolean {
+ get(connectionId).delete()
+ return true
+ }
+
+ fun getConnections(
+ myDid: String?,
+ theirDid: String?
+ ): List = transaction {
+ val query = Connections.selectAll()
+ myDid?.let {
+ query.andWhere { Connections.myDid eq it }
+ }
+ theirDid?.let {
+ query.andWhere { Connections.theirDid eq it }
+ }
+ query.toList().map {
+ ConnectionDto(
+ theirDid = it[Connections.theirDid],
+ myDid = it[Connections.myDid],
+ connectionId = it[Connections.connectionId],
+ state = it[Connections.state]
+ )
+ }
+ }
+
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/WalletRepository.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/WalletRepository.kt
index 512f46ab3..39f1c840a 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/WalletRepository.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/WalletRepository.kt
@@ -19,10 +19,7 @@
package org.eclipse.tractusx.managedidentitywallets.persistence.repositories
-import org.eclipse.tractusx.managedidentitywallets.models.ConflictException
-import org.eclipse.tractusx.managedidentitywallets.models.NotFoundException
-import org.eclipse.tractusx.managedidentitywallets.models.WalletExtendedData
-import org.eclipse.tractusx.managedidentitywallets.models.WalletDto
+import org.eclipse.tractusx.managedidentitywallets.models.*
import org.eclipse.tractusx.managedidentitywallets.models.ssi.VerifiableCredentialDto
import org.eclipse.tractusx.managedidentitywallets.persistence.entities.*
import org.jetbrains.exposed.sql.or
@@ -37,16 +34,30 @@ class WalletRepository {
fun getWallet(identifier: String): Wallet {
return Wallet.find { (Wallets.did eq identifier) or (Wallets.bpn eq identifier) }
.firstOrNull()
- ?: throw NotFoundException("Wallet with identifier $identifier not found")
+ ?: throw NotFoundException("Wallet with given identifier not found")
}
@Throws(ConflictException::class)
fun checkWalletAlreadyExists(identifier: String) {
if (!Wallet.find { (Wallets.did eq identifier) or (Wallets.bpn eq identifier) }.empty()) {
- throw ConflictException("Wallet with identifier $identifier already exists!")
+ throw ConflictException("Wallet with given identifier already exists!")
}
}
+ @Throws(NotFoundException::class, UnprocessableEntityException::class)
+ fun getSelfManagedWalletOrThrow(identifier: String): Wallet {
+ return transaction {
+ val wallet = Wallet.find { (Wallets.did eq identifier) or (Wallets.bpn eq identifier) }.firstOrNull()
+ if (wallet == null) {
+ throw NotFoundException("Wallet with given identifier not found")
+ } else if (!wallet.walletId.isNullOrBlank()) {
+ throw UnprocessableEntityException("The Wallet with given identifier is not a self managed wallet")
+ } else {
+ wallet
+ }
+ }
+ }
+
fun addWallet(wallet: WalletExtendedData): Wallet {
// no VCs are added in this step, they will come in through the business partner data service
return Wallet.new {
@@ -57,6 +68,8 @@ class WalletRepository {
walletKey = wallet.walletKey
walletToken = wallet.walletToken
createdAt = LocalDateTime.now()
+ revocationListName = wallet.revocationListName
+ pendingMembershipIssuance = wallet.pendingMembershipIssuance
}
}
@@ -65,11 +78,20 @@ class WalletRepository {
return true
}
+ fun updatePending(did: String, isPending: Boolean) {
+ getWallet(did).apply {
+ pendingMembershipIssuance = isPending
+ }
+ }
+
fun toObject(entity: Wallet): WalletDto = entity.run {
- WalletDto(name, bpn, did, null, createdAt, emptyList().toMutableList())
+ WalletDto(name, bpn, did, null, createdAt,
+ emptyList().toMutableList(), revocationListName,
+ pendingMembershipIssuance)
}
fun toWalletCompleteDataObject(entity: Wallet): WalletExtendedData = entity.run {
- WalletExtendedData(id.value, name, bpn, did, walletId, walletKey, walletToken)
+ WalletExtendedData(id.value, name, bpn, did, walletId, walletKey,
+ walletToken, revocationListName, pendingMembershipIssuance)
}
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/WebhookRepository.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/WebhookRepository.kt
new file mode 100644
index 000000000..1e76d125b
--- /dev/null
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/persistence/repositories/WebhookRepository.kt
@@ -0,0 +1,63 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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.persistence.repositories
+
+import org.eclipse.tractusx.managedidentitywallets.models.NotFoundException
+import org.eclipse.tractusx.managedidentitywallets.persistence.entities.*
+import org.jetbrains.exposed.sql.transactions.transaction
+
+class WebhookRepository {
+
+ fun getAll(): List = transaction { Webhook.all().toList() }
+
+ fun get(
+ threadId: String,
+ ): Webhook = Webhook.find { Webhooks.threadId eq threadId }
+ .firstOrNull()
+ ?: throw NotFoundException("Webhook with threadId $threadId not found")
+
+ fun getOrNull(
+ threadId: String,
+ ): Webhook? = Webhook.find { Webhooks.threadId eq threadId }.firstOrNull()
+
+ fun deleteWebhook(threadId: String): Boolean {
+ get(threadId).delete()
+ return true
+ }
+
+ fun add(
+ webhookThreadId: String,
+ url: String,
+ stateOfRequest: String
+ ): Webhook {
+ return Webhook.new {
+ threadId = webhookThreadId
+ webhookUrl = url
+ state = stateOfRequest
+ }
+ }
+
+ fun updateStateOfWebhook(webhookThreadId: String, stateOfRequest: String) {
+ get(webhookThreadId).apply {
+ state = stateOfRequest
+ }
+ }
+
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/Persistence.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/Persistence.kt
index 4c53391ae..347c0d998 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/Persistence.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/Persistence.kt
@@ -20,21 +20,24 @@
package org.eclipse.tractusx.managedidentitywallets.plugins
import io.ktor.application.*
-import org.eclipse.tractusx.managedidentitywallets.persistence.entities.SchedulerTasks
+import org.eclipse.tractusx.managedidentitywallets.persistence.entities.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
-import org.eclipse.tractusx.managedidentitywallets.persistence.entities.VerifiableCredentials
-import org.eclipse.tractusx.managedidentitywallets.persistence.entities.Wallets
-
fun Application.configurePersistence() {
val jdbcUrl = environment.config.property("db.jdbcUrl").getString()
val jdbcDriver = environment.config.property("db.jdbcDriver").getString()
Database.connect(jdbcUrl, driver = jdbcDriver)
transaction {
// Create missing tables
- SchemaUtils.createMissingTablesAndColumns(Wallets, VerifiableCredentials, SchedulerTasks)
+ SchemaUtils.createMissingTablesAndColumns(
+ Wallets,
+ VerifiableCredentials,
+ SchedulerTasks,
+ Connections,
+ Webhooks
+ )
commit()
}
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/Scheduler.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/Scheduler.kt
index 4cdc34028..1f949a739 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/Scheduler.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/Scheduler.kt
@@ -36,17 +36,27 @@ fun Application.configureJobs() {
val jdbcUrl = environment.config.property("db.jdbcUrl").getString()
val pullDataAtHour = environment.config.property("bpdm.pullDataAtHour").getString().toInt()
+ val createCredentialsAtHour = environment.config.property("revocation.createStatusListCredentialAtHour").getString().toInt()
val bpdmUpdate: RecurringTask = Tasks.recurring("bpdm-update",
// Spring Scheduled tasks (second, minute, hour, day of month, month, day(s) of week)
Schedules.cron("0 0 $pullDataAtHour * * *"))
- .execute { inst, ctx ->
- runJobPayload()
+ .execute { _, _ ->
+ runPullDataAndUpdateCatenaXCredentialJobPayload()
+ }
+
+ val updateRevocationList: RecurringTask = Tasks.recurring("revocation-list-update",
+ // Spring Scheduled tasks (second, minute, hour, day of month, month, day(s) of week)
+ Schedules.cron("0 0 $createCredentialsAtHour * * *"))
+ .execute { _, _ ->
+ runIssueStatusListCredentialsJobPayload()
}
val scheduler: Scheduler = Scheduler
.create(initDatabase(jdbcUrl))
- .startTasks(bpdmUpdate)
+ //TODO: replace after fixing the Business Partner Data requests
+ //.startTasks(bpdmUpdate, updateRevocationList)
+ .startTasks(updateRevocationList)
.pollingInterval(Duration.ofHours(1))
.registerShutdownHook()
.threads(3)
@@ -71,8 +81,14 @@ fun initDatabase(jdbcUrl: String): DataSource {
}
}
-fun runJobPayload() {
+fun runPullDataAndUpdateCatenaXCredentialJobPayload() {
runBlocking {
Services.businessPartnerDataService.pullDataAndUpdateCatenaXCredentialsAsync()
}
}
+
+fun runIssueStatusListCredentialsJobPayload() {
+ runBlocking {
+ Services.revocationService.issueStatusListCredentials()
+ }
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/StatusPages.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/StatusPages.kt
index 9f93e5d88..d1fdc5a8c 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/StatusPages.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/plugins/StatusPages.kt
@@ -50,5 +50,8 @@ fun Application.configureStatusPages() {
exception { cause ->
call.respond(HttpStatusCode.Unauthorized, ExceptionResponse(cause.message!!))
}
+ exception { cause ->
+ call.respond(HttpStatusCode.InternalServerError, ExceptionResponse(cause.message!!))
+ }
}
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/AppRoutes.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/AppRoutes.kt
index 0074d64d9..b1a6115a2 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/AppRoutes.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/AppRoutes.kt
@@ -19,12 +19,32 @@
package org.eclipse.tractusx.managedidentitywallets.routes
+import io.bkbn.kompendium.core.Notarized.notarizedPost
+import io.bkbn.kompendium.core.metadata.ParameterExample
+import io.bkbn.kompendium.core.metadata.RequestInfo
+import io.bkbn.kompendium.core.metadata.ResponseInfo
+import io.bkbn.kompendium.core.metadata.method.PostInfo
import io.ktor.application.*
+import io.ktor.http.*
+import io.ktor.request.*
+import io.ktor.response.*
import io.ktor.routing.*
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import org.eclipse.tractusx.managedidentitywallets.models.*
+import org.eclipse.tractusx.managedidentitywallets.models.BadRequestException
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.ListCredentialRequestData
import org.eclipse.tractusx.managedidentitywallets.services.IBusinessPartnerDataService
+import org.eclipse.tractusx.managedidentitywallets.services.IRevocationService
import org.eclipse.tractusx.managedidentitywallets.services.IWalletService
+import org.eclipse.tractusx.managedidentitywallets.services.UtilsService
-fun Application.appRoutes(walletService: IWalletService, businessPartnerDataService: IBusinessPartnerDataService) {
+fun Application.appRoutes(
+ walletService: IWalletService,
+ businessPartnerDataService: IBusinessPartnerDataService,
+ revocationService: IRevocationService,
+ utilsService: UtilsService
+) {
routing {
route("/api") {
@@ -32,9 +52,41 @@ fun Application.appRoutes(walletService: IWalletService, businessPartnerDataServ
walletRoutes(walletService, businessPartnerDataService)
businessPartnerDataRoutes(businessPartnerDataService)
didDocRoutes(walletService)
- vcRoutes(walletService)
+ vcRoutes(walletService, revocationService, utilsService)
vpRoutes(walletService)
}
+
+ // Used by the revocation service to issue Status-List Credential
+ route("/list-credential/{profileName}/issue") {
+ notarizedPost(
+ PostInfo(
+ summary = "Issue a List Status credential",
+ description = "This endpoint is called by the revocation service to issue a list status credential for a given profileName",
+ parameterExamples = setOf(
+ ParameterExample("profileName", "profileName", "Ae49DuXZy2PLBjSL9W2V2i"),
+ ),
+ requestInfo = RequestInfo(
+ description = "The subject of the status list credential",
+ examples = listCredentialRequestData
+ ),
+ responseInfo = ResponseInfo(
+ status = HttpStatusCode.Created,
+ description = "The created verifiable credential",
+ examples = mapOf("demo" to "credential-as-string")
+ ),
+ canThrow = setOf(
+ semanticallyInvalidInputException, syntacticallyInvalidInputException
+ ),
+ tags = setOf("VerifiableCredentials")
+ )
+ ) {
+ val profileName = call.parameters["profileName"] ?: throw BadRequestException("Missing or malformed profileName")
+ val listCredentialRequestData = call.receive()
+ val verifiableCredentialDto = walletService.issueStatusListCredential(profileName, listCredentialRequestData)
+ call.respond(HttpStatusCode.Created, Json.encodeToString(verifiableCredentialDto))
+ }
+ }
+
}
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/VcRoutes.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/VcRoutes.kt
index 367453a02..83b246565 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/VcRoutes.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/VcRoutes.kt
@@ -34,16 +34,21 @@ import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import org.eclipse.tractusx.managedidentitywallets.Services
-import org.eclipse.tractusx.managedidentitywallets.models.forbiddenException
+import org.eclipse.tractusx.managedidentitywallets.models.*
+import org.eclipse.tractusx.managedidentitywallets.models.BadRequestException
-import org.eclipse.tractusx.managedidentitywallets.models.semanticallyInvalidInputException
import org.eclipse.tractusx.managedidentitywallets.models.ssi.*
import org.eclipse.tractusx.managedidentitywallets.models.ssi.JsonLdContexts
-import org.eclipse.tractusx.managedidentitywallets.models.syntacticallyInvalidInputException
-import org.eclipse.tractusx.managedidentitywallets.models.unauthorizedException
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.CredentialOfferResponse
+import org.eclipse.tractusx.managedidentitywallets.services.IRevocationService
import org.eclipse.tractusx.managedidentitywallets.services.IWalletService
+import org.eclipse.tractusx.managedidentitywallets.services.UtilsService
-fun Route.vcRoutes(walletService: IWalletService) {
+fun Route.vcRoutes(
+ walletService: IWalletService,
+ revocationService: IRevocationService,
+ utilsService: UtilsService
+) {
route("/credentials") {
@@ -149,7 +154,141 @@ fun Route.vcRoutes(walletService: IWalletService) {
}
}
}
+
+ route("/issuance-flow") {
+ notarizedAuthenticate(AuthorizationHandler.JWT_AUTH_TOKEN) {
+ notarizedPost(
+ PostInfo(
+ summary = "Issue credential flow according to Aries RFC 0453",
+ description = "Permission: " +
+ "**${AuthorizationHandler.getPermissionOfRole(AuthorizationHandler.ROLE_UPDATE_WALLETS)}** OR " +
+ "**${AuthorizationHandler.getPermissionOfRole(AuthorizationHandler.ROLE_UPDATE_WALLET)}** " +
+ "(The BPN of Catena-X wallet must equal BPN of caller)\n" +
+ "\nTrigger an issue credential flow according to Aries RFC 0453 from the issuer to the holder. " +
+ "Issuer must be a DID managed by the MIW",
+ requestInfo = RequestInfo(
+ description = "The verifiable credential input",
+ examples = verifiableCredentialIssuanceFlowRequestDtoExample
+ ),
+ responseInfo = ResponseInfo(
+ status = HttpStatusCode.Created,
+ description = "The credential Offer as String",
+ examples = credentialOfferResponseExample
+ ),
+ canThrow = setOf(semanticallyInvalidInputException, syntacticallyInvalidInputException,
+ forbiddenException, unauthorizedException),
+ tags = setOf("VerifiableCredentials")
+ )
+ ) {
+ val verifiableCredentialRequestDto = call.receive()
+ AuthorizationHandler.checkHasRightsToUpdateWallet(call, Services.walletService.getCatenaXBpn())
+ val vc = verifiableCredentialRequestDto.toInternalVerifiableCredentialIssuanceFlowRequest()
+ call.respond(
+ HttpStatusCode.Created,
+ walletService.triggerCredentialIssuanceFlow(vc)
+ )
+ }
+ }
+ }
+
+ route("/revocations") {
+ notarizedAuthenticate(AuthorizationHandler.JWT_AUTH_TOKEN) {
+ notarizedPost(
+ PostInfo(
+ summary = "Revoke issued Verifiable Credential",
+ description = "Permission: " +
+ "**${AuthorizationHandler.getPermissionOfRole(AuthorizationHandler.ROLE_UPDATE_WALLETS)}** OR " +
+ "**${AuthorizationHandler.getPermissionOfRole(AuthorizationHandler.ROLE_UPDATE_WALLET)}** " +
+ "(The BPN of the issuer of the Verifiable Credential must equal BPN of caller)\n" +
+ "\nRevoke issued Verifiable Credential by issuer",
+ requestInfo = RequestInfo(
+ description = "The signed verifiable credential",
+ examples = signedVerifiableCredentialDtoExample
+ ),
+ responseInfo = ResponseInfo(
+ status = HttpStatusCode.Accepted,
+ description = "Empty response body"
+ ),
+ canThrow = setOf(semanticallyInvalidInputException, syntacticallyInvalidInputException,
+ forbiddenException, unauthorizedException),
+ tags = setOf("VerifiableCredentials")
+ )
+ ) {
+ val verifiableCredentialDto = call.receive()
+ AuthorizationHandler.checkHasRightsToUpdateWallet(call, verifiableCredentialDto.issuer)
+ walletService.revokeVerifiableCredential(verifiableCredentialDto)
+ call.respond(HttpStatusCode.Accepted)
+ }
+ }
+
+ route("/statusListCredentialRefresh") {
+ notarizedAuthenticate(AuthorizationHandler.JWT_AUTH_TOKEN) {
+ notarizedPost(
+ PostInfo(
+ summary = "Re-issue the Status-List Credential for all or given wallet",
+ description = "Permission: " +
+ "**${AuthorizationHandler.getPermissionOfRole(AuthorizationHandler.ROLE_UPDATE_WALLETS)}** OR" +
+ "**${AuthorizationHandler.getPermissionOfRole(AuthorizationHandler.ROLE_UPDATE_WALLET)}** " +
+ "(The BPN of wallet to update must equal BPN of caller) \n" +
+ "\nRe-issue the Status-List Credential for all registered wallet",
+ parameterExamples = setOf(
+ ParameterExample("identifier", "identifier", "BPN0001")
+ ),
+ requestInfo = null,
+ responseInfo = ResponseInfo(
+ status = HttpStatusCode.Accepted,
+ description = "Empty response body",
+ ),
+ canThrow = setOf(semanticallyInvalidInputException, syntacticallyInvalidInputException,
+ forbiddenException, unauthorizedException),
+ tags = setOf("VerifiableCredentials")
+ )
+ ) {
+ val identifier = call.request.queryParameters["identifier"]
+ AuthorizationHandler.checkHasRightsToUpdateWallet(call, identifier)
+
+ val force: Boolean = if (!call.request.queryParameters["force"].isNullOrBlank()) {
+ call.request.queryParameters["force"].toBoolean()
+ } else { false }
+
+ if (identifier.isNullOrBlank()) {
+ revocationService.issueStatusListCredentials()
+ } else {
+ val wallet = walletService.getWallet(identifier, false)
+ revocationService.issueStatusListCredentials(
+ profileName = utilsService.getIdentifierOfDid(did = wallet.did),
+ force = force
+ )
+ }
+ call.respond(HttpStatusCode.Accepted)
+ }
+ }
+ }
+ }
+
+ // Public endpoint to get Status-List Credential
+ route("/status/{listName}") {
+ notarizedGet(
+ GetInfo(
+ summary = "Query Status-List Credentials",
+ description = "Get the Status-List Credential for a given listName",
+ parameterExamples = setOf(
+ ParameterExample("listName", "listName", "5cb9ce19-9a10-48fe-bfa6-384632b89dc3"),
+ ),
+ responseInfo = ResponseInfo(
+ status = HttpStatusCode.OK,
+ description = "The Verifiable Credential",
+ examples = statusListCredentialExample
+ ),
+ tags = setOf("VerifiableCredentials"),
+ )
+ ) {
+ val listName = call.parameters["listName"] ?: throw BadRequestException("Missing or malformed listName")
+ call.respond(HttpStatusCode.OK, revocationService.getStatusListCredentialOfManagedWallet(listName))
+ }
+ }
}
+
}
val verifiableCredentialRequestDtoExample = mapOf(
@@ -195,12 +334,82 @@ val signedVerifiableCredentialDtoExample = mapOf(
issuanceDate = "2019-06-16T18:56:59Z",
expirationDate = "2019-06-17T18:56:59Z",
credentialSubject = mapOf("college" to "Test-University"),
+ credentialStatus = CredentialStatus(
+ statusId = "https://example.com/credentials/status/3#94567",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "revocation",
+ index = "94567",
+ listUrl = "https://example.com/credentials/status/3"
+ ),
proof = LdProofDto(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "did:example:76e12ec712ebc6f1c221ebfeb1f#keys-1",
+ verificationMethod = "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
)
+
+val listCredentialRequestData = mapOf(
+ "demo" to ListCredentialRequestData(
+ listId = "uuid-of-list",
+ subject = ListCredentialSubject (
+ credentialId = "https://example.com/status/3#list",
+ credentialType = "StatusList2021",
+ statusPurpose = "revocation",
+ encodedList = "H4sIAAAAAAAAA-3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA"
+ )
+ )
+)
+
+val statusListCredentialExample = mapOf(
+ "demo" to VerifiableCredentialDto(
+ id = "https://example.com/api/credentials/status/5c145c85-8fcb-42d4-893c-d19a55581e00",
+ context = listOf("https://www.w3.org/2018/credentials/v1", "https://w3id.org/vc/status-list/2021/v1"),
+ type = listOf( "VerifiableCredential", "StatusList2021Credential"),
+ issuer = "did:indy:local:test:Ae49DuXZy2PLBjSL9W2V2i",
+ issuanceDate = "2022-08-31T07:19:36Z",
+ credentialSubject = mapOf(
+ "id" to "https://example.com/api/credentials/status/5c145c85-8fcb-42d4-893c-d19a55581e00#list",
+ "type" to "StatusList2021",
+ "statusPurpose" to "revocation",
+ "encodedList" to "H4sIAAAAAAAAAO3BIQEAAAACIAf4f68zLEADAAAAAAAAAAAAAAAAAAAAvA3HJiyHAEAAAA=="
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2022-08-31T07:19:42Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "did:indy:local:test:Ae49DuXZy2PLBjSL9W2V2i#key-1",
+ jws = "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0FB66o-WAn8W4qnNK0NsHBFMJj_ZM42ADdbwYO-P8oGywaYWeBPZylgD35AV2-CR0b5Hs8uDq0EIn8iHycjmBQ"
+ )
+ )
+)
+
+val verifiableCredentialIssuanceFlowRequestDtoExample = mapOf(
+ "demo" to VerifiableCredentialIssuanceFlowRequestDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuerIdentifier = "did:example:76e12ec712ebc6f1c221ebfeb1f",
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University"),
+ holderIdentifier = "did:example:492edf208",
+ webhookUrl = "http://example.com/webhooks"
+ )
+)
+
+val credentialOfferAsString = """
+{"credential": {"@context": ["https://www.w3.org/2018/credentials/v1", "https://raw.githubusercontent.com/catenax-ng/product-core-schemas/main/businessPartnerData"], "type": ["BpnCredential", "VerifiableCredential"], "issuer": "did:sov:HsfwvUFcZkAcxDa2kASMr7", "issuanceDate": "2021-06-16T18:56:59Z", "credentialSubject": {"type": ["BpnCredential"], "bpn": "NEWNEWTestTest", "id": "did:sov:7rB93fLvW5kgujZ4E57ZxL"}}, "options": {"proofType": "Ed25519Signature2018"}}
+""".trimIndent()
+
+val credentialOfferResponseExample = mapOf(
+ "demo" to CredentialOfferResponse(
+ credentialOffer = credentialOfferAsString,
+ threadId = "2ewqe-qwe24-eqweqwrqwr-rwqrqwr"
+ )
+)
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/VpRoutes.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/VpRoutes.kt
index 781596f04..fd54878a5 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/VpRoutes.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/VpRoutes.kt
@@ -59,6 +59,11 @@ fun Route.vpRoutes(walletService: IWalletService) {
"withCredentialsDateValidation",
"withCredentialsDateValidation",
"false"
+ ),
+ ParameterExample(
+ "withRevocationValidation",
+ "withRevocationValidation",
+ "false"
)
),
requestInfo = RequestInfo(
@@ -86,12 +91,17 @@ fun Route.vpRoutes(walletService: IWalletService) {
call.request.queryParameters["withCredentialsDateValidation"].toBoolean()
} else { true }
+ val withRevocationValidation = if (call.request.queryParameters["withRevocationValidation"] != null) {
+ call.request.queryParameters["withRevocationValidation"].toBoolean()
+ } else { true }
+
call.respond(
HttpStatusCode.Created,
walletService.issuePresentation(
verifiableCredentialDto,
withCredentialsValidation,
- withCredentialsDateValidation
+ withCredentialsDateValidation,
+ withRevocationValidation
)
)
}
@@ -107,7 +117,8 @@ fun Route.vpRoutes(walletService: IWalletService) {
"**${AuthorizationHandler.getPermissionOfRole(AuthorizationHandler.ROLE_VIEW_WALLET)}**\n" +
"\nValidate Verifiable Presentation with all included credentials",
parameterExamples = setOf(
- ParameterExample("withDateValidation", "withDateValidation", "false")
+ ParameterExample("withDateValidation", "withDateValidation", "false"),
+ ParameterExample("withRevocationValidation", "withRevocationValidation", "false")
),
requestInfo = RequestInfo(
description = "The verifiable presentation to validate",
@@ -132,9 +143,13 @@ fun Route.vpRoutes(walletService: IWalletService) {
val withDateValidation = if (call.request.queryParameters["withDateValidation"] != null) {
call.request.queryParameters["withDateValidation"].toBoolean()
} else { false }
+ val withRevocationValidation = if (call.request.queryParameters["withRevocationValidation"] != null) {
+ call.request.queryParameters["withRevocationValidation"].toBoolean()
+ } else { true }
val verifyResponse = walletService.verifyVerifiablePresentation(
vpDto = verifiablePresentation,
- withDateValidation = withDateValidation
+ withDateValidation = withDateValidation,
+ withRevocationValidation = withRevocationValidation
)
call.respond(HttpStatusCode.OK, verifyResponse)
}
@@ -191,7 +206,7 @@ val verifiablePresentationResponseDtoExample = mapOf(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "did:example:76e12ec712ebc6f1c221ebfeb1f#keys-1",
+ verificationMethod = "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
@@ -200,7 +215,7 @@ val verifiablePresentationResponseDtoExample = mapOf(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "did:example:76e12ec712ebc6f1c221ebfeb1f#keys-1",
+ verificationMethod = "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/WalletRoutes.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/WalletRoutes.kt
index 6a85615a0..9dde98c95 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/WalletRoutes.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/routes/WalletRoutes.kt
@@ -46,7 +46,7 @@ import org.jetbrains.exposed.exceptions.ExposedSQLException
import java.time.LocalDateTime
-fun Route.walletRoutes(walletService: IWalletService, businessPartnerDataService: IBusinessPartnerDataService) {
+fun Route.walletRoutes(walletService: IWalletService,businessPartnerDataService: IBusinessPartnerDataService) {
route("/wallets") {
@@ -123,6 +123,35 @@ fun Route.walletRoutes(walletService: IWalletService, businessPartnerDataService
}
}
+ route("/self-managed-wallets") {
+ notarizedAuthenticate(AuthorizationHandler.JWT_AUTH_TOKEN) {
+ notarizedPost(
+ PostInfo(
+ summary = "Register and Establish Initial Connection with Partners",
+ description = "Permission: " +
+ "**${AuthorizationHandler.getPermissionOfRole(AuthorizationHandler.ROLE_CREATE_WALLETS)}**\n" +
+ "\n Register self managed wallet and establish the initial connection with CatenaX. " +
+ "Also issue their membership and BPN credentials",
+ requestInfo = RequestInfo(
+ description = "Register self managed wallet, establish a connection and issue membership and BPN credentials",
+ examples = selfManagedWalletCreateDtoExample
+ ),
+ responseInfo = ResponseInfo(
+ status = HttpStatusCode.Created,
+ description = "The request was able send a connection request to the DID",
+ ),
+ canThrow = setOf(notFoundException, syntacticallyInvalidInputException),
+ )
+ ) {
+ val selfManagedWalletCreateDto = call.receive()
+ return@notarizedPost call.respond(
+ HttpStatusCode.Created,
+ walletService.registerSelfManagedWalletAndBuildConnection(selfManagedWalletCreateDto)
+ )
+ }
+ }
+ }
+
route("/{identifier}") {
notarizedAuthenticate(AuthorizationHandler.JWT_AUTH_TOKEN) {
@@ -325,11 +354,18 @@ val issuedVerifiableCredentialRequestDtoExample = mapOf(
issuanceDate = "2019-06-16T18:56:59Z",
expirationDate = "2019-06-17T18:56:59Z",
credentialSubject = mapOf("college" to "Test-University"),
+ credentialStatus = CredentialStatus(
+ statusId = "http://example.edu/api/credentials/status/test#3",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "revocation",
+ index= "3",
+ listUrl= "http://example.edu/api/credentials/status/test"
+ ),
proof = LdProofDto(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "did:example:76e12ec712ebc6f1c221ebfeb1f#keys-1",
+ verificationMethod = "did:example:76e12ec712ebc6f1c221ebfeb1f#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
@@ -342,7 +378,8 @@ val walletDtoWithVerKeyExample = mapOf(
"did",
"verkey",
LocalDateTime.now(),
- emptyList().toMutableList()
+ vcs = emptyList().toMutableList(),
+ pendingMembershipIssuance = false
)
)
@@ -353,7 +390,8 @@ val walletDtoExample = mapOf(
"did",
null,
LocalDateTime.now(),
- emptyList().toMutableList()
+ vcs = emptyList().toMutableList(),
+ pendingMembershipIssuance = false
)
)
@@ -363,3 +401,11 @@ val walletCreateDtoExample = mapOf(
"bpn"
)
)
+
+val selfManagedWalletCreateDtoExample = mapOf(
+ "demo" to SelfManagedWalletCreateDto(
+ name ="name",
+ bpn = "bpn",
+ did = "did",
+ )
+)
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/AcaPyService.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/AcaPyService.kt
index 243a01f1f..f64f120d9 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/AcaPyService.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/AcaPyService.kt
@@ -25,8 +25,21 @@ import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
+import org.eclipse.tractusx.managedidentitywallets.Services
import org.eclipse.tractusx.managedidentitywallets.models.*
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.VerifiableCredentialIssuanceFlowRequest
import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.*
+import org.hyperledger.aries.AriesClient
+import org.hyperledger.aries.AriesWebSocketClient
+import org.hyperledger.aries.api.connection.ConnectionRecord
+import org.hyperledger.aries.api.did_exchange.DidExchangeCreateRequestFilter
+import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState
+import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord
+import org.hyperledger.aries.api.issue_credential_v2.V2CredentialExchangeFree
+import org.hyperledger.aries.api.jsonld.ProofType
+import org.hyperledger.aries.api.jsonld.VerifiableCredential
+import org.hyperledger.aries.config.GsonConfig
+import java.util.*
class AcaPyService(
private val acaPyConfig: WalletAndAcaPyConfig,
@@ -79,7 +92,7 @@ class AcaPyService(
url("${acaPyConfig.apiAdminUrl}/multitenancy/wallet/${walletData.walletId}/remove")
headers.append("X-API-Key", acaPyConfig.adminApiKey)
contentType(ContentType.Application.Json)
- body = WalletKey(walletData.walletKey)
+ body = WalletKey(walletData.walletKey!!)
}
}
@@ -168,4 +181,108 @@ class AcaPyService(
}
}
+ override suspend fun deleteConnection(connectionId: String, token: String) {
+ client.delete {
+ url("${acaPyConfig.apiAdminUrl}/connections/$connectionId")
+ headers.append(HttpHeaders.Authorization, "Bearer $token")
+ headers.append("X-API-Key", acaPyConfig.adminApiKey)
+ accept(ContentType.Application.Json)
+ }
+ }
+
+ override fun subscribeForWebSocket(subscriberWallet: WalletExtendedData) {
+ val wsUrl = acaPyConfig.apiAdminUrl
+ .replace("http", "ws")
+ .plus("/ws")
+
+ AriesWebSocketClient
+ .builder()
+ .bearerToken(subscriberWallet.walletToken)
+ .url(wsUrl)
+ .apiKey(acaPyConfig.adminApiKey)
+ .walletId(subscriberWallet.walletId)
+ .handler(
+ BaseWalletAriesEventHandler(
+ Services.businessPartnerDataService,
+ Services.walletService,
+ Services.webhookService
+ )
+ )
+ .build()
+ }
+
+ override suspend fun getAcapyClient(walletToken: String): AriesClient {
+ return AriesClient
+ .builder()
+ .url(acaPyConfig.apiAdminUrl)
+ .apiKey(acaPyConfig.adminApiKey)
+ .bearerToken(walletToken)
+ .build()
+ }
+
+ override suspend fun connect(
+ selfManagedWalletCreateDto: SelfManagedWalletCreateDto,
+ token: String
+ ): ConnectionRecord {
+ val ariesClient = getAcapyClient(token)
+ val didOfPartner = utilsService.replaceNetworkIdentifierWithSov(selfManagedWalletCreateDto.did)
+ val pendingCon: Optional = ariesClient.didExchangeCreateRequest(
+ DidExchangeCreateRequestFilter
+ .builder()
+ .theirPublicDid(didOfPartner)
+ .alias(selfManagedWalletCreateDto.name)
+ .usePublicDid(true)
+ .build()
+ )
+
+ return if (pendingCon.isPresent) {
+ pendingCon.get()
+ } else {
+ throw InternalServerErrorException("Connection Request Failed")
+ }
+ }
+
+ override suspend fun issuanceFlowCredentialSend(
+ token: String,
+ vc: VerifiableCredentialIssuanceFlowRequest
+ ): V20CredExRecord {
+ val ariesClient = getAcapyClient(token)
+
+ val credential = VerifiableCredential.builder()
+ .context(vc.context)
+ .credentialSubject(GsonConfig.defaultConfig().toJsonTree(vc.credentialSubject).asJsonObject)
+ .issuanceDate(vc.issuanceDate)
+ .issuer(vc.issuerIdentifier)
+ .type(vc.type)
+ .build()
+
+ val credentialRequest = V2CredentialExchangeFree.builder()
+ .connectionId(UUID.fromString(vc.connectionId))
+ .filter(
+ V2CredentialExchangeFree.V20CredFilter.builder()
+ .ldProof(
+ V2CredentialExchangeFree.LDProofVCDetail.builder()
+ .credential(credential)
+ .options(
+ V2CredentialExchangeFree.LDProofVCDetailOptions.builder()
+ .proofType(ProofType.Ed25519Signature2018)
+ .build()
+ )
+ .build()
+ )
+ .build()
+ ).build()
+
+ val vc2CredExRecord: Optional = ariesClient.issueCredentialV2Send(credentialRequest)
+ if (vc2CredExRecord.isPresent) {
+ if (vc2CredExRecord.get().state == CredentialExchangeState.OFFER_SENT) {
+ return vc2CredExRecord.get()
+ } else {
+ throw InternalServerErrorException("Credential Record is not in OFFER_SENT state. " +
+ "current state: ${vc2CredExRecord.get().state}")
+ }
+ }
+ throw InternalServerErrorException("Failed to issue credential: $credential")
+ }
+
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/AcaPyWalletServiceImpl.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/AcaPyWalletServiceImpl.kt
index 2c3c3967e..5b6479c0e 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/AcaPyWalletServiceImpl.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/AcaPyWalletServiceImpl.kt
@@ -20,14 +20,19 @@
package org.eclipse.tractusx.managedidentitywallets.services
import foundation.identity.jsonld.JsonLDUtils
+import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
-import org.eclipse.tractusx.managedidentitywallets.Services
import org.eclipse.tractusx.managedidentitywallets.models.*
import org.eclipse.tractusx.managedidentitywallets.models.ssi.*
import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.*
+import org.eclipse.tractusx.managedidentitywallets.persistence.entities.Connection
+import org.eclipse.tractusx.managedidentitywallets.persistence.entities.Wallet
+import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.ConnectionRepository
import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.CredentialRepository
import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.WalletRepository
+import org.hyperledger.aries.api.connection.ConnectionState
+import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState
import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.LoggerFactory
import java.time.Instant
@@ -37,10 +42,12 @@ class AcaPyWalletServiceImpl(
private val acaPyService: IAcaPyService,
private val walletRepository: WalletRepository,
private val credentialRepository: CredentialRepository,
- private val utilsService: UtilsService
+ private val utilsService: UtilsService,
+ private val revocationService: IRevocationService,
+ private val webhookService: IWebhookService,
+ private val connectionRepository: ConnectionRepository
) : IWalletService {
- private val networkIdentifier = acaPyService.getWalletAndAcaPyConfig().networkIdentifier
private val baseWalletBpn = acaPyService.getWalletAndAcaPyConfig().baseWalletBpn
companion object {
@@ -52,24 +59,25 @@ class AcaPyWalletServiceImpl(
return transaction {
val extractedWallet = walletRepository.getWallet(identifier)
val walletDto = walletRepository.toObject(extractedWallet)
- if (withCredentials) {
- val credentials = getCredentials(
+ val credentials: List = if (withCredentials) {
+ getCredentials(
null,
holderIdentifier = walletDto.did,
null,
null
)
- WalletDto(
- walletDto.name,
- walletDto.bpn,
- walletDto.did,
- walletDto.verKey,
- walletDto.createdAt,
- credentials
- )
- } else {
- walletDto
- }
+ } else { listOf() }
+
+ WalletDto(
+ name = walletDto.name,
+ bpn = walletDto.bpn,
+ did = walletDto.did,
+ verKey = walletDto.verKey,
+ createdAt = walletDto.createdAt,
+ vcs = credentials,
+ revocationListName = walletDto.revocationListName,
+ pendingMembershipIssuance = walletDto.pendingMembershipIssuance
+ )
}
}
@@ -92,14 +100,14 @@ class AcaPyWalletServiceImpl(
override suspend fun registerBaseWallet(verKey: String): Boolean {
log.debug("Register base wallet with bpn $baseWalletBpn and key $verKey")
val catenaXWallet = getWalletExtendedInformation(baseWalletBpn)
- val shortDid = catenaXWallet.did.substring(("did:indy:$networkIdentifier:").length)
+ val shortDid = utilsService.getIdentifierOfDid(catenaXWallet.did)
// Register DID with public DID on ledger
acaPyService.assignDidToPublic(
shortDid,
- catenaXWallet.walletToken
+ catenaXWallet.walletToken!!
)
-
+ subscribeForAriesWS()
return true
}
@@ -124,31 +132,47 @@ class AcaPyWalletServiceImpl(
createdSubWalletDto.token
)
- // For Catena-X Wallet
+ // For Catena-X Wallet
// 1. The DID will be registered externally with endorser role.
// 2. The Assign to public will be triggered manually
if (!isCatenaXWallet(walletCreateDto.bpn)) {
registerSubWalletUsingCatenaXWallet(walletCreateDto, createdDid)
}
+
+ // create revocation list
+ val revocationListName = revocationService.registerList(createdDid.result.did, issueCredential = false)
+
val walletToCreate = WalletExtendedData(
name = walletCreateDto.name,
bpn = walletCreateDto.bpn,
- did = "did:indy:${networkIdentifier}:${createdDid.result.did}",
+ did = "${utilsService.getDidMethodPrefixWithNetworkIdentifier()}${createdDid.result.did}",
walletId = createdSubWalletDto.walletId,
walletKey = subWalletToCreate.walletKey,
- walletToken = createdSubWalletDto.token
+ walletToken = createdSubWalletDto.token,
+ revocationListName = revocationListName,
+ pendingMembershipIssuance = true
)
val storedWallet = transaction {
val createdWalletData = walletRepository.addWallet(walletToCreate)
walletRepository.toObject(createdWalletData)
}
+ // For Catena-X Wallet The DID is not registered yet and therefore a credential cannot be issued!
+ if (!isCatenaXWallet(walletCreateDto.bpn)) {
+ revocationService.issueStatusListCredentials(
+ profileName = utilsService.getIdentifierOfDid(storedWallet.did),
+ force = true
+ )
+ }
+
return WalletDto(
storedWallet.name,
storedWallet.bpn,
storedWallet.did,
createdDid.result.verkey,
storedWallet.createdAt,
- storedWallet.vcs
+ storedWallet.vcs,
+ storedWallet.revocationListName,
+ storedWallet.pendingMembershipIssuance
)
}
@@ -162,18 +186,81 @@ class AcaPyWalletServiceImpl(
verkey = createdDid.result.verkey,
role = "NONE"
),
- catenaXWallet.walletToken
+ catenaXWallet.walletToken!!
)
}
+ override suspend fun registerSelfManagedWalletAndBuildConnection(
+ selfManagedWalletCreateDto: SelfManagedWalletCreateDto
+ ): SelfManagedWalletResultDto {
+
+ utilsService.checkIndyDid(selfManagedWalletCreateDto.did)
+ val catenaXWallet = getWalletExtendedInformation(baseWalletBpn)
+
+ transaction {
+ walletRepository.checkWalletAlreadyExists(selfManagedWalletCreateDto.did)
+ }
+
+ val connectionRecord = acaPyService.connect(selfManagedWalletCreateDto, catenaXWallet.walletToken!!)
+
+ val walletToCreate = WalletExtendedData(
+ name = selfManagedWalletCreateDto.name,
+ bpn = selfManagedWalletCreateDto.bpn,
+ did = selfManagedWalletCreateDto.did,
+ pendingMembershipIssuance = true,
+ walletId = null,
+ walletKey = null,
+ walletToken = null,
+ revocationListName = null
+ )
+
+ return transaction {
+ val createdWalletData = walletRepository.addWallet(walletToCreate)
+ val connectionOwnerWallet = walletRepository.getWallet(catenaXWallet.did)
+ connectionRepository.add(
+ connectionOwnerDid = connectionOwnerWallet.did,
+ connectionTargetDid = selfManagedWalletCreateDto.did,
+ connectionRecord = connectionRecord
+ )
+ if (!selfManagedWalletCreateDto.webhookUrl.isNullOrBlank()) {
+ webhookService.addWebhook(
+ // The ConnectionRecord has no threadId. Therefore, the requestId as an Identifier.
+ threadId = connectionRecord.requestId,
+ url = selfManagedWalletCreateDto.webhookUrl,
+ state = ConnectionState.REQUEST.name
+ )
+ }
+ SelfManagedWalletResultDto(
+ createdWalletData.name,
+ createdWalletData.bpn,
+ createdWalletData.did,
+ createdWalletData.createdAt
+ )
+ }
+ }
+
override suspend fun deleteWallet(identifier: String): Boolean {
log.debug("Delete Wallet with identifier $identifier")
val walletData = getWalletExtendedInformation(identifier)
+ val isSelfManagedWallet = walletData.walletId.isNullOrBlank()
transaction {
+ val connections = connectionRepository.getConnections(null, walletData.did)
+ connections.forEach {
+ val connectionOwnerWallet = getWalletExtendedInformation(it.myDid)
+ runBlocking {
+ acaPyService.deleteConnection(
+ connectionId = it.connectionId,
+ token = connectionOwnerWallet.walletToken!!
+ )
+ }
+ }
credentialRepository.deleteCredentialsOfWallet(walletId = walletData.id!!)
+ connectionRepository.deleteConnections(walletData.did)
walletRepository.deleteWallet(identifier)
}
- acaPyService.deleteSubWallet(walletData)
+ if (!isSelfManagedWallet) {
+ acaPyService.deleteSubWallet(walletData)
+ }
return true
}
@@ -208,7 +295,6 @@ class AcaPyWalletServiceImpl(
override suspend fun issueCatenaXCredential(
vcCatenaXRequest: VerifiableCredentialRequestWithoutIssuerDto
): VerifiableCredentialDto {
- log.debug("Issue CatenaX Credential $vcCatenaXRequest")
val verifiableCredentialRequestDto = VerifiableCredentialRequestDto(
id = vcCatenaXRequest.id,
context = vcCatenaXRequest.context,
@@ -218,6 +304,7 @@ class AcaPyWalletServiceImpl(
expirationDate = vcCatenaXRequest.expirationDate,
credentialSubject = vcCatenaXRequest.credentialSubject,
holderIdentifier = vcCatenaXRequest.holderIdentifier,
+ isRevocable = vcCatenaXRequest.isRevocable
)
return issueCredential(verifiableCredentialRequestDto)
}
@@ -225,38 +312,38 @@ class AcaPyWalletServiceImpl(
override suspend fun issueCredential(vcRequest: VerifiableCredentialRequestDto): VerifiableCredentialDto {
log.debug("Issue Credential $vcRequest")
val issuerWalletData = getWalletExtendedInformation(vcRequest.issuerIdentifier)
- val holderWalletData = getWalletExtendedInformation(vcRequest.holderIdentifier)
-
val issuerDid = issuerWalletData.did
- val holderDid = holderWalletData.did
val credentialSubject = vcRequest.credentialSubject.toMutableMap()
- if (credentialSubject["id"] != null || credentialSubject["id"] != holderDid) {
- credentialSubject["id"] = holderDid
+ // If the ID of Credential Subject exist then ignore the given holderIdentifier.
+ if (!credentialSubject.containsKey("id") && !vcRequest.holderIdentifier.isNullOrBlank()) {
+ credentialSubject["id"] = try {
+ val holderWalletData = getWalletExtendedInformation(vcRequest.holderIdentifier)
+ holderWalletData.did
+ } catch (e: NotFoundException) {
+ vcRequest.holderIdentifier
+ }
+ }
+
+ val context = vcRequest.context.toMutableList()
+ var credentialStatus: CredentialStatus? = null
+ if (vcRequest.isRevocable) {
+ credentialStatus = revocationService.addStatusEntry(utilsService.getIdentifierOfDid(issuerDid))
+ context.add(JsonLdContexts.JSONLD_CONTEXT_W3C_STATUS_LIST_2021_V1)
}
val verificationMethod = getVerificationMethod(issuerDid, 0)
val convertedDatetime: Date = Date.from(Instant.now())
- val issuanceDate = vcRequest.issuanceDate ?: JsonLDUtils.dateToString(convertedDatetime)
- val signRequest: SignRequest = SignRequest(
- SignDoc(
- credential = VerifiableCredentialDto(
- id = vcRequest.id,
- context = vcRequest.context,
- type = vcRequest.type,
- issuer = issuerDid,
- issuanceDate = issuanceDate,
- credentialSubject = credentialSubject,
- expirationDate = vcRequest.expirationDate
- ),
- options = SignOptions(
- proofPurpose = "assertionMethod",
- type = "Ed25519Signature2018",
- verificationMethod = utilsService.replaceSovWithNetworkIdentifier(verificationMethod.id)
- )
- ),
- verkey = getVerificationKey(verificationMethod, VerificationKeyType.PUBLIC_KEY_BASE58.toString())
+ val verifiableCredentialToSign = VerifiableCredentialDto(
+ id = vcRequest.id,
+ context = context,
+ type = vcRequest.type,
+ issuer = issuerDid,
+ issuanceDate = vcRequest.issuanceDate ?: JsonLDUtils.dateToString(convertedDatetime),
+ credentialSubject = credentialSubject,
+ credentialStatus = credentialStatus,
+ expirationDate = vcRequest.expirationDate
)
- val signedVcResultAsJsonString = acaPyService.signJsonLd(signRequest, issuerWalletData.walletToken)
- val signedVcResult: SignCredentialResponse = Json.decodeFromString(signedVcResultAsJsonString)
+ val signedVcResult: SignCredentialResponse =
+ signVerifiableCredential(verifiableCredentialToSign, verificationMethod, issuerWalletData)
if (signedVcResult.signedDoc != null) {
return signedVcResult.signedDoc
}
@@ -269,12 +356,12 @@ class AcaPyWalletServiceImpl(
val modifiedDid: String
if (utilsService.isDID(identifier)) {
val catenaXWallet = getWalletExtendedInformation(baseWalletBpn)
- token = catenaXWallet.walletToken
+ token = catenaXWallet.walletToken!!
utilsService.checkIndyDid(identifier)
modifiedDid = utilsService.replaceNetworkIdentifierWithSov(identifier)
} else {
val walletData = getWalletExtendedInformation(identifier)
- token = walletData.walletToken
+ token = walletData.walletToken!!
modifiedDid = utilsService.replaceNetworkIdentifierWithSov(walletData.did)
}
val didDocResult = acaPyService.resolveDidDoc(modifiedDid, token)
@@ -305,16 +392,17 @@ class AcaPyWalletServiceImpl(
override suspend fun issuePresentation(
vpRequest: VerifiablePresentationRequestDto,
withCredentialsValidation: Boolean,
- withCredentialsDateValidation: Boolean
+ withCredentialsDateValidation: Boolean,
+ withRevocationValidation: Boolean
): VerifiablePresentationDto {
log.debug("Issue Presentation $vpRequest")
val holderWalletData = getWalletExtendedInformation(vpRequest.holderIdentifier)
val holderDid = holderWalletData.did
- val token = holderWalletData.walletToken
+ val token = holderWalletData.walletToken!!
val verificationMethod = getVerificationMethod(vpRequest.holderIdentifier, 0)
if (withCredentialsValidation) {
vpRequest.verifiableCredentials.forEach {
- validateVerifiableCredential(it, withCredentialsDateValidation, token)
+ validateVerifiableCredential(it, withCredentialsDateValidation, withRevocationValidation, token)
}
}
val signRequest: SignRequest = SignRequest(
@@ -363,7 +451,7 @@ class AcaPyWalletServiceImpl(
endpoint = serviceDto.serviceEndpoint,
endpointType = utilsService.mapServiceTypeToEnum(serviceDto.type)
),
- walletData.walletToken
+ walletData.walletToken!!
)
return resolveDocument(walletData.did)
}
@@ -390,7 +478,7 @@ class AcaPyWalletServiceImpl(
endpoint = serviceUpdateRequestDto.serviceEndpoint,
endpointType = utilsService.mapServiceTypeToEnum(serviceUpdateRequestDto.type)
),
- walletData.walletToken
+ walletData.walletToken!!
)
found = true
}
@@ -430,15 +518,18 @@ class AcaPyWalletServiceImpl(
override fun getCatenaXBpn(): String = baseWalletBpn
- override suspend fun verifyVerifiablePresentation(vpDto: VerifiablePresentationDto,
- withDateValidation: Boolean): VerifyResponse {
+ override suspend fun verifyVerifiablePresentation(
+ vpDto: VerifiablePresentationDto,
+ withDateValidation: Boolean,
+ withRevocationValidation: Boolean
+ ): VerifyResponse {
val catenaXWallet = getWalletExtendedInformation(baseWalletBpn)
- validateVerifiablePresentation(vpDto, catenaXWallet.walletToken)
+ validateVerifiablePresentation(vpDto, catenaXWallet.walletToken!!)
val listOfVerifiableCredentials = vpDto.verifiableCredential
if (!listOfVerifiableCredentials.isNullOrEmpty()) {
listOfVerifiableCredentials.forEach {
- validateVerifiableCredential(it, withDateValidation, catenaXWallet.walletToken)
+ validateVerifiableCredential(it, withDateValidation, withRevocationValidation, catenaXWallet.walletToken!!)
}
}
return VerifyResponse(error = null, valid = true, vp = vpDto)
@@ -459,6 +550,7 @@ class AcaPyWalletServiceImpl(
private suspend fun validateVerifiableCredential(
vc: VerifiableCredentialDto,
withDateValidation: Boolean,
+ withRevocationCheck: Boolean,
walletToken: String
) {
if (withDateValidation) {
@@ -476,6 +568,9 @@ class AcaPyWalletServiceImpl(
if (vc.proof == null) {
throw UnprocessableEntityException("Cannot verify verifiable credential ${vc.id} due to missing proof")
}
+ if (withRevocationCheck) {
+ validateRevocation(vc, walletToken)
+ }
val verifyReq = VerifyRequest(
signedDoc = vc,
verkey = getVerKeyOfVerificationMethodId(vc.issuer, vc.proof.verificationMethod)
@@ -549,4 +644,217 @@ class AcaPyWalletServiceImpl(
}
}
+ private suspend fun validateRevocation(vc: VerifiableCredentialDto, walletToken: String) {
+ // Credential is not revocable
+ if (vc.credentialStatus == null) {
+ return
+ }
+ verifyPropertiesOfCredentialStatus(vc.id, vc.credentialStatus)
+
+ val statusListVerifiableCredential = revocationService.getStatusListCredentialOfUrl(vc.credentialStatus.listUrl)
+
+ if (statusListVerifiableCredential.issuer != vc.issuer) {
+ throw UnprocessableEntityException("Cannot verify revocation: " +
+ "The issuer of the given credential ${vc.id} is not the issuer of the StatusListCredential")
+ }
+
+ val listCredentialSubjectAsMap = statusListVerifiableCredential.credentialSubject
+
+ if (listCredentialSubjectAsMap.containsKey(ListCredentialSubject.CREDENTIAL_TYPE_KEY) &&
+ listCredentialSubjectAsMap[ListCredentialSubject.CREDENTIAL_TYPE_KEY] != ListCredentialSubject.CREDENTIAL_TYPE) {
+ throw UnprocessableEntityException("Cannot verify revocation status of credential ${vc.id} " +
+ "due to wrong type of extracted StatusList Credential")
+ }
+ if (listCredentialSubjectAsMap.containsKey(ListCredentialSubject.STATUS_PURPOSE_KEY) &&
+ listCredentialSubjectAsMap[ListCredentialSubject.STATUS_PURPOSE_KEY] != ListCredentialSubject.STATUS_PURPOSE) {
+ throw UnprocessableEntityException("Cannot verify revocation status of credential ${vc.id} " +
+ "due to wrong statusPurpose of extracted StatusList Credential")
+ }
+ if (listCredentialSubjectAsMap.containsKey(ListCredentialSubject.ENCODED_LIST_KEY) &&
+ listCredentialSubjectAsMap[ListCredentialSubject.ENCODED_LIST_KEY].toString().isBlank()) {
+ throw UnprocessableEntityException("Cannot verify revocation status of credential ${vc.id} " +
+ "due to empty or null encodedList of extracted StatusList Credential")
+ }
+
+ validateVerifiableCredential(vc = statusListVerifiableCredential,
+ withDateValidation = true,
+ withRevocationCheck = false,
+ walletToken = walletToken
+ )
+
+ val bitSet = utilsService.decodeBitset(listCredentialSubjectAsMap["encodedList"] as String)
+ if (bitSet.get(Integer.valueOf(vc.credentialStatus.index))) {
+ throw UnprocessableEntityException("The credential ${vc.id} has been revoked!")
+ }
+ }
+
+ override suspend fun issueStatusListCredential(
+ profileName: String,
+ listCredentialRequestData: ListCredentialRequestData
+ ): VerifiableCredentialDto {
+ val issuerDid = "${utilsService.getDidMethodPrefixWithNetworkIdentifier()}$profileName"
+ val verifiableCredentialRequestDto = VerifiableCredentialRequestDto(
+ id = listCredentialRequestData.listId,
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_STATUS_LIST_2021_V1
+ ),
+ type = listOf("VerifiableCredential", "StatusList2021Credential"),
+ issuerIdentifier = issuerDid,
+ credentialSubject = mapOf(
+ "id" to listCredentialRequestData.subject.credentialId,
+ "type" to "StatusList2021",
+ "statusPurpose" to "revocation",
+ "encodedList" to listCredentialRequestData.subject.encodedList
+ ),
+ issuanceDate = JsonLDUtils.dateToString(Date.from(Instant.now())),
+ isRevocable = false
+ )
+ return issueCredential(verifiableCredentialRequestDto)
+ }
+
+ private suspend fun signVerifiableCredential(
+ verifiableCredentialToSign: VerifiableCredentialDto,
+ verificationMethod: DidVerificationMethodDto,
+ issuerWalletData: WalletExtendedData
+ ): SignCredentialResponse {
+ val signRequest: SignRequest = SignRequest(
+ SignDoc(
+ credential = verifiableCredentialToSign,
+ options = SignOptions(
+ proofPurpose = "assertionMethod",
+ type = "Ed25519Signature2018",
+ verificationMethod = utilsService.replaceSovWithNetworkIdentifier(verificationMethod.id)
+ )
+ ),
+ verkey = getVerificationKey(verificationMethod, VerificationKeyType.PUBLIC_KEY_BASE58.toString())
+ )
+ val signedVcResultAsJsonString = acaPyService.signJsonLd(signRequest, issuerWalletData.walletToken!!)
+ return Json.decodeFromString(signedVcResultAsJsonString)
+ }
+
+ override suspend fun revokeVerifiableCredential(vc: VerifiableCredentialDto) {
+ val issuerDid = vc.issuer
+ val walletOfIssuer = getWalletExtendedInformation(issuerDid)
+
+ if (vc.credentialStatus == null) {
+ throw UnprocessableEntityException("The given verifiable credential is not revocable!")
+ }
+ verifyPropertiesOfCredentialStatus(vc.id, vc.credentialStatus)
+
+ validateVerifiableCredential(vc,
+ withDateValidation = false, withRevocationCheck = false, walletOfIssuer.walletToken!!)
+
+ val profileName = utilsService.getIdentifierOfDid(walletOfIssuer.did)
+ revocationService.revoke(
+ profileName = profileName,
+ indexOfCredential = vc.credentialStatus.index.toLong()
+ )
+ revocationService.issueStatusListCredentials(profileName, true)
+ }
+
+ override fun updateConnectionState(connectionId: String, state: ConnectionState) {
+ connectionRepository.updateConnectionState(connectionId, state)
+ }
+
+ override fun setPartnerMembershipIssued(walletDto: WalletDto) {
+ return walletRepository.updatePending(
+ did = walletDto.did,
+ isPending = false
+ )
+ }
+
+ override fun getConnection(connectionId: String): Connection {
+ return connectionRepository.get(connectionId)
+ }
+
+ override fun subscribeForAriesWS() {
+ val baseWallet = getWalletExtendedInformation(baseWalletBpn)
+ acaPyService.subscribeForWebSocket(baseWallet)
+ }
+
+ override suspend fun triggerCredentialIssuanceFlow(
+ vc: VerifiableCredentialIssuanceFlowRequest
+ ): CredentialOfferResponse {
+ val catenaXWallet = getWalletExtendedInformation(baseWalletBpn)
+ if (vc.issuerIdentifier != catenaXWallet.did && vc.issuerIdentifier != catenaXWallet.bpn) {
+ throw UnprocessableEntityException("The Issuance Flow supports only the CatenaX wallet as issuer")
+ }
+
+ // Check the Holder
+ val credentialSubject = vc.credentialSubject.toMutableMap()
+ val holderWallet: Wallet = if (credentialSubject.containsKey("id")) {
+ val wallet = walletRepository.getSelfManagedWalletOrThrow(credentialSubject["id"] as String)
+ credentialSubject["id"] = utilsService.replaceNetworkIdentifierWithSov(wallet.did)
+ wallet
+ } else if (!vc.holderIdentifier.isNullOrBlank()) {
+ val wallet = walletRepository.getSelfManagedWalletOrThrow(vc.holderIdentifier)
+ credentialSubject["id"] = utilsService.replaceNetworkIdentifierWithSov(wallet.did)
+ wallet
+ } else {
+ throw UnprocessableEntityException("The credential subject id aka. Holder is not defined")
+ }
+
+ // Check connections
+ val connection = getConnections(catenaXWallet.did, holderWallet.did).firstOrNull()
+ if (connection == null || connection.state != ConnectionState.COMPLETED.name) {
+ throw InternalServerErrorException("Invalid connection between ${catenaXWallet.did} and ${holderWallet.did}")
+ }
+
+ val vcContext: List = if (vc.isRevocable) {
+ val mutableContexts = vc.context.toMutableList()
+ mutableContexts.add(JsonLdContexts.JSONLD_CONTEXT_W3C_STATUS_LIST_2021_V1)
+ mutableContexts
+ } else { vc.context }
+ val vcAcapyRequest = VerifiableCredentialIssuanceFlowRequest(
+ id = vc.id ,
+ context = vcContext,
+ type = vc.type,
+ issuerIdentifier = utilsService.replaceNetworkIdentifierWithSov(catenaXWallet.did),
+ issuanceDate = vc.issuanceDate,
+ expirationDate = vc.expirationDate,
+ credentialSubject = credentialSubject,
+ credentialStatus = null, // currently, not supported in issuance flow
+ webhookUrl = vc.webhookUrl,
+ connectionId = connection.connectionId
+ )
+
+ val v20CredExRecord = acaPyService.issuanceFlowCredentialSend(
+ token = catenaXWallet.walletToken!!,
+ vc = vcAcapyRequest
+ )
+
+ transaction {
+ if (!vc.webhookUrl.isNullOrBlank()) {
+ webhookService.addWebhook(v20CredExRecord.threadId, vc.webhookUrl, CredentialExchangeState.OFFER_SENT.name)
+ }
+ }
+ return CredentialOfferResponse(
+ credentialOffer = String(Base64.getDecoder().decode(v20CredExRecord.credOffer.offersAttach[0].data.base64), Charsets.UTF_8),
+ threadId = v20CredExRecord.threadId
+ )
+ }
+
+ private fun getConnections(myDid: String?, theirDid: String?): List {
+ return connectionRepository.getConnections(myDid, theirDid)
+ }
+
+ private fun verifyPropertiesOfCredentialStatus(
+ credentialId: String? = "",
+ credentialStatus: CredentialStatus
+ ) {
+ if (credentialStatus.credentialType != CredentialStatus.CREDENTIAL_TYPE) {
+ throw UnprocessableEntityException("Credential with Id $credentialId has invalid credential status 'Type'")
+ }
+ if (credentialStatus.statusPurpose != CredentialStatus.STATUS_PURPOSE) {
+ throw UnprocessableEntityException("Credential with Id $credentialId has invalid 'statusPurpose'")
+ }
+ if (credentialStatus.index.isBlank() || credentialStatus.index.toLong() < 0) {
+ throw UnprocessableEntityException("Credential with Id $credentialId has invalid 'statusListIndex'")
+ }
+ if (credentialStatus.listUrl.isBlank()) {
+ throw UnprocessableEntityException("Credential with Id $credentialId has invalid 'statusListCredential'")
+ }
+ }
+
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/BaseWalletAriesEventHandler.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/BaseWalletAriesEventHandler.kt
new file mode 100644
index 000000000..947580a95
--- /dev/null
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/BaseWalletAriesEventHandler.kt
@@ -0,0 +1,110 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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.services
+
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import org.eclipse.tractusx.managedidentitywallets.models.WalletDto
+import org.hyperledger.aries.api.connection.ConnectionRecord
+import org.hyperledger.aries.api.connection.ConnectionState
+import org.hyperledger.aries.api.issue_credential_v1.CredentialExchangeState
+import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord
+import org.hyperledger.aries.webhook.TenantAwareEventHandler
+import org.jetbrains.exposed.sql.transactions.transaction
+
+class BaseWalletAriesEventHandler(
+ private val businessPartnerDataService: IBusinessPartnerDataService,
+ private val walletService: IWalletService,
+ private val webhookService: IWebhookService
+): TenantAwareEventHandler() {
+
+ override fun handleConnection(walletId: String, connection: ConnectionRecord) {
+ super.handleConnection(walletId, connection)
+
+ when(connection.state) {
+ ConnectionState.COMPLETED -> {
+ val pairOfWebhookUrlAndWallet = updateConnectionStateAndSendWebhook(connection)
+ val webhookUrl: String? = pairOfWebhookUrlAndWallet.first
+ val walletOfConnectionTarget: WalletDto = pairOfWebhookUrlAndWallet.second
+ if (walletOfConnectionTarget.pendingMembershipIssuance) {
+ GlobalScope.launch {
+ val success = businessPartnerDataService
+ .issueAndSendCatenaXCredentialsForSelfManagedWalletsAsync(
+ targetWallet = walletOfConnectionTarget,
+ connectionId = connection.connectionId,
+ webhookUrl = webhookUrl
+ ).await()
+ if (success) {
+ transaction { walletService.setPartnerMembershipIssued(walletOfConnectionTarget) }
+ }
+ }
+ }
+ }
+ ConnectionState.ABANDONED,
+ ConnectionState.ERROR -> {
+ updateConnectionStateAndSendWebhook(connection)
+ }
+ else -> { return }
+ }
+ }
+
+ private fun updateConnectionStateAndSendWebhook(connection: ConnectionRecord): Pair {
+ var webhookUrl: String? = null
+ val walletOfConnectionTarget: WalletDto = transaction {
+ val webhook = webhookService.getWebhookByThreadId(connection.requestId)
+ if (webhook != null) {
+ webhookService.sendWebhookConnectionMessage(webhook.threadId, connection)
+ webhookService.updateStateOfWebhook(webhook.threadId, connection.state.name)
+ webhookUrl = webhook.webhookUrl
+ }
+ val storedConnection = walletService.getConnection(connection.connectionId)
+ walletService.updateConnectionState(storedConnection.connectionId, connection.state)
+ walletService.getWallet(storedConnection.theirDid, false)
+ }
+ return Pair(webhookUrl, walletOfConnectionTarget)
+ }
+
+ override fun handleCredentialV2(walletId: String?, v20Credential: V20CredExRecord?) {
+ super.handleCredentialV2(walletId, v20Credential)
+ if (v20Credential != null) {
+ val threadId = v20Credential.threadId
+ when(v20Credential.state) {
+ CredentialExchangeState.CREDENTIAL_ISSUED -> {
+ transaction {
+ if (webhookService.getWebhookByThreadId(threadId) != null) {
+ webhookService.updateStateOfWebhook(threadId, v20Credential.state.name)
+ }
+ }
+ }
+ CredentialExchangeState.DONE,
+ CredentialExchangeState.ABANDONED,
+ CredentialExchangeState.DECLINED -> {
+ transaction {
+ if (webhookService.getWebhookByThreadId(threadId) != null) {
+ webhookService.sendWebhookCredentialMessage(threadId, v20Credential)
+ webhookService.updateStateOfWebhook(threadId, v20Credential.state.name)
+ }
+ }
+ }
+ else -> { return }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/BusinessPartnerDataServiceImpl.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/BusinessPartnerDataServiceImpl.kt
index fc6bc9616..899d1e8fd 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/BusinessPartnerDataServiceImpl.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/BusinessPartnerDataServiceImpl.kt
@@ -36,9 +36,11 @@ import org.slf4j.LoggerFactory
import java.time.Instant
import java.util.*
-class BusinessPartnerDataServiceImpl(private val walletService: IWalletService,
- private val bpdmConfig: BPDMConfig,
- private val client: HttpClient): IBusinessPartnerDataService {
+class BusinessPartnerDataServiceImpl(
+ private val walletService: IWalletService,
+ private val bpdmConfig: BPDMConfig,
+ private val client: HttpClient): IBusinessPartnerDataService {
+
companion object {
private val log = LoggerFactory.getLogger(this::class.java)
}
@@ -204,6 +206,52 @@ class BusinessPartnerDataServiceImpl(private val walletService: IWalletService,
}
}
+ override suspend fun issueAndSendCatenaXCredentialsForSelfManagedWalletsAsync(
+ targetWallet: WalletDto,
+ connectionId: String,
+ webhookUrl: String?
+ ): Deferred =
+ GlobalScope.async {
+ val bpn = targetWallet.bpn
+ val membershipVC = prepareMembershipCredential(bpn)
+ val bpnVC = prepareBpnCredentials(bpn)
+ val catenaXWallet = walletService.getWallet(walletService.getCatenaXBpn())
+ val membershipVCIssuanceFlowRequest = VerifiableCredentialIssuanceFlowRequest(
+ id = membershipVC.id,
+ context = membershipVC.context,
+ type = membershipVC.type,
+ issuanceDate = membershipVC.issuanceDate,
+ issuerIdentifier = catenaXWallet.did,
+ expirationDate = membershipVC.expirationDate,
+ credentialSubject = membershipVC.credentialSubject,
+ credentialStatus = null,
+ holderIdentifier = membershipVC.holderIdentifier,
+ isRevocable = membershipVC.isRevocable,
+ webhookUrl = webhookUrl,
+ connectionId = connectionId
+ )
+ val bpnVCIssuanceFlowRequest = VerifiableCredentialIssuanceFlowRequest(
+ id = bpnVC.id,
+ context = bpnVC.context,
+ type = bpnVC.type,
+ issuanceDate = bpnVC.issuanceDate,
+ issuerIdentifier = catenaXWallet.did,
+ expirationDate = bpnVC.expirationDate,
+ credentialSubject = bpnVC.credentialSubject,
+ credentialStatus = null,
+ holderIdentifier = bpnVC.holderIdentifier,
+ isRevocable = bpnVC.isRevocable,
+ webhookUrl = webhookUrl,
+ connectionId = connectionId
+ )
+ //TODO The AcaPy java library does not support credential status
+ walletService.triggerCredentialIssuanceFlow(membershipVCIssuanceFlowRequest)
+ walletService.triggerCredentialIssuanceFlow(bpnVCIssuanceFlowRequest)
+ true
+ }
+
+ // ============== Private ==============
+
private fun prepareNamesCredential(bpn: String, name: ExtendedMultiPurposeDto): VerifiableCredentialRequestWithoutIssuerDto {
val currentDateAsString = JsonLDUtils.dateToString(Date.from(Instant.now()))
return VerifiableCredentialRequestWithoutIssuerDto(
@@ -229,7 +277,8 @@ class BusinessPartnerDataServiceImpl(private val walletService: IWalletService,
name = name.language.name
)
),
- holderIdentifier = bpn
+ holderIdentifier = bpn,
+ isRevocable = true
)
}
@@ -255,7 +304,8 @@ class BusinessPartnerDataServiceImpl(private val walletService: IWalletService,
"language" to legalForm.language,
"categories" to legalForm.categories
),
- holderIdentifier = bpn
+ holderIdentifier = bpn,
+ isRevocable = true
)
}
@@ -285,7 +335,8 @@ class BusinessPartnerDataServiceImpl(private val walletService: IWalletService,
"nationalBankAccountIdentifier" to bankAccount.nationalBankAccountIdentifier,
"nationalBankIdentifier" to bankAccount.nationalBankIdentifier
),
- holderIdentifier = bpn
+ holderIdentifier = bpn,
+ isRevocable = true
)
}
@@ -406,31 +457,43 @@ class BusinessPartnerDataServiceImpl(private val walletService: IWalletService,
type = listOf(JsonLdTypes.ADDRESS_TYPE, JsonLdTypes.CREDENTIAL_TYPE),
issuanceDate = currentDateAsString,
credentialSubject = credSubject,
- holderIdentifier = bpn
+ holderIdentifier = bpn,
+ isRevocable = true
)
}
- private fun prepareMembershipCredential(bpn: String): VerifiableCredentialRequestWithoutIssuerDto {
+ private fun prepareMembershipCredential(
+ bpn: String
+ ): VerifiableCredentialRequestWithoutIssuerDto {
val currentDateAsString = JsonLDUtils.dateToString(Date.from(Instant.now()))
+ val credentialSubject = mutableMapOf(
+ "type" to listOf(JsonLdTypes.MEMBERSHIP_TYPE),
+ "memberOf" to "Catena-X",
+ "status" to "Active",
+ "startTime" to currentDateAsString
+ )
+ val contexts = mutableListOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_BPD_CREDENTIALS
+ )
return VerifiableCredentialRequestWithoutIssuerDto(
id = UUID.randomUUID().toString(),
- context = listOf(
- JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
- JsonLdContexts.JSONLD_CONTEXT_BPD_CREDENTIALS
- ),
+ context = contexts,
type = listOf(JsonLdTypes.MEMBERSHIP_TYPE, JsonLdTypes.CREDENTIAL_TYPE),
issuanceDate = currentDateAsString,
- credentialSubject = mapOf(
- "type" to listOf(JsonLdTypes.MEMBERSHIP_TYPE),
- "memberOf" to "Catena-X",
- "status" to "Active",
- "startTime" to currentDateAsString
- ),
- holderIdentifier = bpn
+ credentialSubject = credentialSubject,
+ holderIdentifier = bpn,
+ isRevocable = true
)
}
- private fun prepareBpnCredentials(bpn: String): VerifiableCredentialRequestWithoutIssuerDto {
+ private fun prepareBpnCredentials(
+ bpn: String
+ ): VerifiableCredentialRequestWithoutIssuerDto {
+ val credentialSubject = mutableMapOf(
+ "type" to listOf(JsonLdTypes.BPN_TYPE),
+ "bpn" to bpn
+ )
return VerifiableCredentialRequestWithoutIssuerDto(
id = UUID.randomUUID().toString(),
context = listOf(
@@ -439,11 +502,9 @@ class BusinessPartnerDataServiceImpl(private val walletService: IWalletService,
),
type = listOf(JsonLdTypes.BPN_TYPE, JsonLdTypes.CREDENTIAL_TYPE),
issuanceDate = JsonLDUtils.dateToString(Date.from(Instant.now())),
- credentialSubject = mapOf(
- "type" to listOf(JsonLdTypes.BPN_TYPE),
- "bpn" to bpn
- ),
- holderIdentifier = bpn
+ credentialSubject = credentialSubject,
+ holderIdentifier = bpn,
+ isRevocable = false // THE BPN Credential is not Revocable!
)
}
@@ -459,6 +520,7 @@ class BusinessPartnerDataServiceImpl(private val walletService: IWalletService,
issuanceDate = vcDto.issuanceDate,
expirationDate = vcDto.expirationDate,
credentialSubject = vcDto.credentialSubject,
+ credentialStatus = vcDto.credentialStatus,
proof = vcDto.proof
)
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IAcaPyService.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IAcaPyService.kt
index 7101800f1..8c2d224b4 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IAcaPyService.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IAcaPyService.kt
@@ -21,7 +21,11 @@ package org.eclipse.tractusx.managedidentitywallets.services
import io.ktor.client.*
import org.eclipse.tractusx.managedidentitywallets.models.*
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.VerifiableCredentialIssuanceFlowRequest
import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.*
+import org.hyperledger.aries.AriesClient
+import org.hyperledger.aries.api.connection.ConnectionRecord
+import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord
interface IAcaPyService {
@@ -49,6 +53,22 @@ interface IAcaPyService {
suspend fun updateService(serviceEndPoint: DidEndpointWithType, token: String)
+ fun subscribeForWebSocket(subscriberWallet: WalletExtendedData)
+
+ suspend fun getAcapyClient(walletToken: String): AriesClient
+
+ suspend fun connect(
+ selfManagedWalletCreateDto: SelfManagedWalletCreateDto,
+ token: String
+ ): ConnectionRecord
+
+ suspend fun issuanceFlowCredentialSend(
+ token: String,
+ vc: VerifiableCredentialIssuanceFlowRequest
+ ): V20CredExRecord
+
+ suspend fun deleteConnection(connectionId: String, token: String)
+
companion object {
fun create(
walletAndAcaPyConfig: WalletAndAcaPyConfig,
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IBusinessPartnerDataService.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IBusinessPartnerDataService.kt
index 87ffa0640..e60c26cc8 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IBusinessPartnerDataService.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IBusinessPartnerDataService.kt
@@ -24,6 +24,7 @@ import io.ktor.client.*
import io.ktor.client.features.logging.*
import io.ktor.client.features.observer.*
import org.eclipse.tractusx.managedidentitywallets.models.BPDMConfig
+import org.eclipse.tractusx.managedidentitywallets.models.WalletDto
import org.slf4j.LoggerFactory
interface IBusinessPartnerDataService {
@@ -36,6 +37,12 @@ interface IBusinessPartnerDataService {
data: T? = null
): Deferred
+ suspend fun issueAndSendCatenaXCredentialsForSelfManagedWalletsAsync(
+ targetWallet: WalletDto,
+ connectionId: String,
+ webhookUrl: String? = null
+ ): Deferred
+
companion object {
private val log = LoggerFactory.getLogger(this::class.java)
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IRevocationService.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IRevocationService.kt
new file mode 100644
index 000000000..cad7affe4
--- /dev/null
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IRevocationService.kt
@@ -0,0 +1,74 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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.services
+
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.databind.SerializationFeature
+import io.ktor.client.*
+import io.ktor.client.features.json.*
+import io.ktor.client.features.observer.*
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.CredentialStatus
+import io.ktor.client.features.logging.*
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.VerifiableCredentialDto
+import org.slf4j.LoggerFactory
+
+interface IRevocationService {
+
+ suspend fun registerList(profileName: String, issueCredential: Boolean): String
+
+ suspend fun addStatusEntry(profileName: String): CredentialStatus
+
+ suspend fun getStatusListCredentialOfManagedWallet(listName: String): VerifiableCredentialDto
+
+ suspend fun getStatusListCredentialOfUrl(statusListUrl: String): VerifiableCredentialDto
+
+ suspend fun revoke(profileName: String, indexOfCredential: Long)
+
+ suspend fun issueStatusListCredentials(profileName: String? = null, force: Boolean? = false)
+
+ companion object {
+ private val log = LoggerFactory.getLogger(this::class.java)
+
+ fun createRevocationService(revocationUrl: String): IRevocationService {
+ return RevocationServiceImpl(
+ revocationUrl,
+ HttpClient {
+ expectSuccess = true
+ install(ResponseObserver) {
+ onResponse { response ->
+ log.debug("HTTP status: ${response.status.value}")
+ log.debug("HTTP description: ${response.status.description}")
+ }
+ }
+ install(Logging) {
+ logger = Logger.DEFAULT
+ level = LogLevel.BODY
+ }
+ install(JsonFeature) {
+ serializer = JacksonSerializer {
+ enable(SerializationFeature.INDENT_OUTPUT)
+ serializationConfig.defaultPrettyPrinter
+ setSerializationInclusion(JsonInclude.Include.NON_NULL)
+ }
+ }
+ })
+ }
+ }
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IWalletService.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IWalletService.kt
index 474c7172c..eb81ea9a7 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IWalletService.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IWalletService.kt
@@ -29,10 +29,16 @@ import io.ktor.client.features.observer.*
import io.ktor.client.statement.*
import org.eclipse.tractusx.managedidentitywallets.models.*
import org.eclipse.tractusx.managedidentitywallets.models.ssi.*
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.CredentialOfferResponse
import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.VerifyResponse
import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.WalletAndAcaPyConfig
+import org.eclipse.tractusx.managedidentitywallets.persistence.entities.Connection
+import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.ConnectionRepository
import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.CredentialRepository
import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.WalletRepository
+import org.hyperledger.aries.api.connection.ConnectionState
+import org.hyperledger.aries.api.issue_credential_v2.V20CredOffer
+import org.slf4j.LoggerFactory
interface IWalletService {
@@ -50,13 +56,23 @@ interface IWalletService {
suspend fun createWallet(walletCreateDto: WalletCreateDto): WalletDto
+ suspend fun registerSelfManagedWalletAndBuildConnection(
+ selfManagedWalletCreateDto: SelfManagedWalletCreateDto
+ ): SelfManagedWalletResultDto
+
suspend fun deleteWallet(identifier: String): Boolean
fun storeCredential(identifier: String, issuedCredential: IssuedVerifiableCredentialRequestDto): Boolean
suspend fun issueCredential(vcRequest: VerifiableCredentialRequestDto): VerifiableCredentialDto
- suspend fun issueCatenaXCredential(vcCatenaXRequest: VerifiableCredentialRequestWithoutIssuerDto): VerifiableCredentialDto
+ suspend fun issueCatenaXCredential(
+ vcCatenaXRequest: VerifiableCredentialRequestWithoutIssuerDto
+ ): VerifiableCredentialDto
+
+ suspend fun triggerCredentialIssuanceFlow(
+ vc: VerifiableCredentialIssuanceFlowRequest
+ ): CredentialOfferResponse
suspend fun resolveDocument(identifier: String): DidDocumentDto
@@ -65,7 +81,8 @@ interface IWalletService {
suspend fun issuePresentation(
vpRequest: VerifiablePresentationRequestDto,
withCredentialsValidation: Boolean,
- withCredentialsDateValidation: Boolean
+ withCredentialsDateValidation: Boolean,
+ withRevocationValidation: Boolean
): VerifiablePresentationDto
fun getCredentials(
@@ -89,15 +106,38 @@ interface IWalletService {
fun getCatenaXBpn(): String
- suspend fun verifyVerifiablePresentation(vpDto: VerifiablePresentationDto,
- withDateValidation: Boolean = false): VerifyResponse
+ suspend fun verifyVerifiablePresentation(
+ vpDto: VerifiablePresentationDto,
+ withDateValidation: Boolean = false,
+ withRevocationValidation: Boolean
+ ): VerifyResponse
+
+ suspend fun issueStatusListCredential(
+ profileName: String,
+ listCredentialRequestData: ListCredentialRequestData
+ ): VerifiableCredentialDto
+
+ suspend fun revokeVerifiableCredential(vc: VerifiableCredentialDto)
+
+ fun setPartnerMembershipIssued(walletDto: WalletDto)
+
+ fun updateConnectionState(connectionId: String, state: ConnectionState)
+
+ fun getConnection(connectionId: String): Connection
+
+ fun subscribeForAriesWS()
companion object {
+ private val log = LoggerFactory.getLogger(this::class.java)
+
fun createWithAcaPyService(
walletAndAcaPyConfig: WalletAndAcaPyConfig,
walletRepository: WalletRepository,
credentialRepository: CredentialRepository,
- utilsService: UtilsService
+ utilsService: UtilsService,
+ revocationService: IRevocationService,
+ webhookService: IWebhookService,
+ connectionRepository: ConnectionRepository
): IWalletService {
val acaPyService = IAcaPyService.create(
walletAndAcaPyConfig = walletAndAcaPyConfig,
@@ -106,8 +146,8 @@ interface IWalletService {
expectSuccess = true
install(ResponseObserver) {
onResponse { response ->
- println("HTTP status: ${response.status.value}")
- println("HTTP description: ${response.status.description}")
+ log.debug("HTTP status: ${response.status.value}")
+ log.debug("HTTP description: ${response.status.description}")
}
}
HttpResponseValidator {
@@ -150,7 +190,8 @@ interface IWalletService {
}
}
)
- return AcaPyWalletServiceImpl(acaPyService, walletRepository, credentialRepository, utilsService)
+ return AcaPyWalletServiceImpl(acaPyService, walletRepository, credentialRepository,
+ utilsService, revocationService, webhookService, connectionRepository)
}
}
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IWebhookService.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IWebhookService.kt
new file mode 100644
index 000000000..763b05305
--- /dev/null
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/IWebhookService.kt
@@ -0,0 +1,69 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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.services
+
+import io.ktor.client.*
+import io.ktor.client.features.logging.*
+import io.ktor.client.features.observer.*
+import org.eclipse.tractusx.managedidentitywallets.persistence.entities.Webhook
+import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.WebhookRepository
+import org.hyperledger.aries.api.connection.ConnectionRecord
+import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord
+import org.slf4j.LoggerFactory
+
+interface IWebhookService {
+
+ fun addWebhook(threadId: String, url:String, state: String)
+
+ fun getWebhookByThreadId(threadId: String?): Webhook?
+
+ fun sendWebhookConnectionMessage(url: String, connection: ConnectionRecord): Boolean
+
+ fun sendWebhookCredentialMessage(url: String, v20CredExRecord: V20CredExRecord): Boolean
+
+ fun sendWebhookPresentationMessage(url: String): Boolean
+
+ fun updateStateOfWebhook(threadId: String, state: String)
+
+ companion object {
+ private val log = LoggerFactory.getLogger(this::class.java)
+
+ fun createWebhookService(
+ webhookRepository: WebhookRepository
+ ): IWebhookService {
+ return WebhookServiceImpl(
+ webhookRepository,
+ HttpClient {
+ expectSuccess = false // must be set to false to handle thrown error if the access token has expired
+ install(ResponseObserver) {
+ onResponse { response ->
+ log.debug("HTTP status: ${response.status.value}")
+ log.debug("HTTP description: ${response.status.description}")
+ }
+ }
+ install(Logging) {
+ logger = Logger.DEFAULT
+ level = LogLevel.BODY
+ }
+ }
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/RevocationServiceImpl.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/RevocationServiceImpl.kt
new file mode 100644
index 000000000..05cae57fd
--- /dev/null
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/RevocationServiceImpl.kt
@@ -0,0 +1,112 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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.services
+
+import io.ktor.client.*
+import io.ktor.client.features.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import org.eclipse.tractusx.managedidentitywallets.models.NotFoundException
+import org.eclipse.tractusx.managedidentitywallets.models.UnprocessableEntityException
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.CredentialStatus
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.VerifiableCredentialDto
+
+class RevocationServiceImpl(
+ private val revocationUrl: String,
+ private val client: HttpClient
+ ): IRevocationService {
+
+ override suspend fun registerList(profileName: String, issueCredential: Boolean): String {
+ val httpResponse: HttpResponse = client.post {
+ url("$revocationUrl/management/lists?profile=$profileName&issue-list-credential=$issueCredential")
+ accept(ContentType.Any)
+ }
+ return httpResponse.readText()
+ }
+
+ override suspend fun addStatusEntry(profileName: String): CredentialStatus {
+ var httpResponse: HttpResponse = client.config {
+ expectSuccess = false
+ }.post {
+ url("$revocationUrl/management/lists/$profileName/entry")
+ accept(ContentType.Application.Json)
+ contentType(ContentType.Application.Json)
+ }
+ if (httpResponse.status == HttpStatusCode.NotFound) {
+ // create and retry to implement self healing if list for the bpn was not created yet
+ registerList(profileName, false)
+ httpResponse = client.post {
+ url("$revocationUrl/management/lists/$profileName/entry")
+ accept(ContentType.Application.Json)
+ contentType(ContentType.Application.Json)
+ }
+ } else if (httpResponse.status.value >= 300) {
+ throw UnprocessableEntityException(httpResponse.readText())
+ }
+ return Json.decodeFromString(httpResponse.readText())
+ }
+
+ override suspend fun getStatusListCredentialOfManagedWallet(listName: String): VerifiableCredentialDto {
+ return getStatusListCredentialOfUrl("$revocationUrl/status/$listName")
+ }
+
+ override suspend fun getStatusListCredentialOfUrl(statusListUrl: String): VerifiableCredentialDto {
+ return try {
+ val httpResponse: HttpResponse = client.get {
+ url(statusListUrl)
+ accept(ContentType.Application.Json)
+ contentType(ContentType.Application.Json)
+ }
+ Json.decodeFromString(httpResponse.readText())
+ } catch (e: Exception) {
+ if (!e.message.isNullOrBlank() && e.message!!.contains("404 Not Found")) {
+ throw NotFoundException("The StatusList credential is not found!")
+ }
+ throw UnprocessableEntityException("The StatusList credential is not available due to the Error: ${e.message}")
+ }
+ }
+
+ override suspend fun revoke(profileName: String, indexOfCredential: Long) {
+ client.delete {
+ url("$revocationUrl/management/lists/$profileName/entry/$indexOfCredential")
+ accept(ContentType.Application.Json)
+ contentType(ContentType.Application.Json)
+ }
+ }
+
+ override suspend fun issueStatusListCredentials(profileName: String?, force: Boolean?) {
+ if (profileName.isNullOrBlank()) {
+ client.post {
+ url("$revocationUrl/management/lists/issue-credentials")
+ accept(ContentType.Application.Json)
+ }
+ } else {
+ client.post {
+ url("$revocationUrl/management/lists/issue-credential/$profileName?force=$force")
+ accept(ContentType.Application.Json)
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/UtilsService.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/UtilsService.kt
index a17edfbfb..959f8552f 100644
--- a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/UtilsService.kt
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/UtilsService.kt
@@ -21,7 +21,10 @@ package org.eclipse.tractusx.managedidentitywallets.services
import org.eclipse.tractusx.managedidentitywallets.models.*
import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.*
+import java.io.ByteArrayInputStream
import java.security.SecureRandom
+import java.util.*
+import java.util.zip.GZIPInputStream
class UtilsService(private val networkIdentifier: String) {
@@ -55,16 +58,48 @@ class UtilsService(private val networkIdentifier: String) {
}
}
- fun replaceSovWithNetworkIdentifier(input: String): String =
- input.replace(":sov:", ":indy:$networkIdentifier:")
+ fun getDidMethodPrefixWithNetworkIdentifier(): String {
+ //TODO replace implementation when indy is supported by AcaPy
+ //return "did:indy:$networkIdentifier:"
+ return "did:sov:"
+ }
+
+ fun getOldDidMethodPrefixWithNetworkIdentifier(): String {
+ //TODO replace implementation when indy is supported by AcaPy
+ return "did:indy:$networkIdentifier:"
+ }
- fun replaceNetworkIdentifierWithSov(input: String): String =
- input.replace(":indy:$networkIdentifier:", ":sov:")
+ fun replaceSovWithNetworkIdentifier(input: String): String {
+ //TODO check if this method is needed when indy is supported by AcaPy
+ //input.replace(":sov:", ":indy:$networkIdentifier:")
+ return input
+ }
+
+ fun replaceNetworkIdentifierWithSov(input: String): String {
+ //TODO check if this method is needed when indy is supported by AcaPy
+ // replacing always because of AcaPys limitations
+ return input.replace(":indy:$networkIdentifier:", ":sov:")
+ }
fun checkIndyDid(did: String) {
- val regex = """did:indy:$networkIdentifier:.[^-\s]{16,}${'$'}""".toRegex()
+ // allow old and new DID methods to accomodate migrated scenarios
+ val regex = """(${getDidMethodPrefixWithNetworkIdentifier()}|${getOldDidMethodPrefixWithNetworkIdentifier()}).[^-\s]{16,}${'$'}""".toRegex()
if (!regex.matches(did)) {
- throw UnprocessableEntityException("The DID must be a valid and supported Indy DID")
+ throw UnprocessableEntityException("The DID must be a valid and supported DID: ${getDidMethodPrefixWithNetworkIdentifier()} or ${getOldDidMethodPrefixWithNetworkIdentifier()}")
+ }
+ }
+
+ fun decodeBitset(encoded: String): BitSet {
+ val unzipped = decodeBytes(encoded)
+ return BitSet.valueOf(unzipped)
+ }
+
+ private fun decodeBytes(encoded: String): ByteArray {
+ val rawBytes = Base64.getDecoder().decode(encoded)
+ return ByteArrayInputStream(rawBytes).run {
+ GZIPInputStream(this).use {
+ it.readBytes()
+ }
}
}
diff --git a/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/WebhookServiceImpl.kt b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/WebhookServiceImpl.kt
new file mode 100644
index 000000000..bcfcfbd7c
--- /dev/null
+++ b/src/main/kotlin/org/eclipse/tractusx/managedidentitywallets/services/WebhookServiceImpl.kt
@@ -0,0 +1,87 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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.services
+
+import io.ktor.client.*
+import io.ktor.client.request.*
+import io.ktor.http.*
+import kotlinx.coroutines.runBlocking
+import org.eclipse.tractusx.managedidentitywallets.persistence.entities.Webhook
+import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.WebhookRepository
+import org.hyperledger.aries.api.connection.ConnectionRecord
+import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord
+import org.jetbrains.exposed.sql.transactions.transaction
+
+class WebhookServiceImpl(
+ private val webhookRepository: WebhookRepository,
+ private val client: HttpClient
+) : IWebhookService {
+
+ override fun updateStateOfWebhook(threadId: String, state: String) {
+ webhookRepository.updateStateOfWebhook(threadId, state)
+ }
+
+ override fun addWebhook(threadId: String, url: String, state: String) {
+ webhookRepository.add(
+ webhookThreadId = threadId,
+ url = url,
+ stateOfRequest = state
+ )
+ }
+
+ override fun getWebhookByThreadId(threadId: String?): Webhook? {
+ if (!threadId.isNullOrBlank()) {
+ return transaction { webhookRepository.getOrNull(threadId) }
+ }
+ return null
+ }
+
+ override fun sendWebhookConnectionMessage(url: String, connection: ConnectionRecord): Boolean {
+ return runBlocking {
+ sendWebhookMessage(url, connection)
+ }
+ }
+
+ override fun sendWebhookCredentialMessage(url: String, v20CredExRecord: V20CredExRecord): Boolean {
+ return runBlocking {
+ sendWebhookMessage(url, v20CredExRecord)
+ }
+ }
+
+ override fun sendWebhookPresentationMessage(url: String): Boolean {
+ return runBlocking {
+ sendWebhookMessage(url, "empty")
+ }
+ }
+
+ private suspend fun sendWebhookMessage(url: String, messageBody: Any): Boolean {
+ return try {
+ client.post {
+ url(url)
+ accept(ContentType.Application.Json)
+ contentType(ContentType.Application.Json)
+ body = messageBody
+ }
+ true
+ } catch (e: Exception) {
+ false
+ }
+ }
+}
diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf
index 1ebc9cd8d..dc874f754 100644
--- a/src/main/resources/application.conf
+++ b/src/main/resources/application.conf
@@ -48,3 +48,8 @@ bpdm {
authUrl=${BPDM_AUTH_URL}
pullDataAtHour=${BPDM_PULL_DATA_AT_HOUR}
}
+
+revocation {
+ baseUrl=${REVOCATION_URL}
+ createStatusListCredentialAtHour=${REVOCATION_CREATE_STATUS_LIST_CREDENTIAL_AT_HOUR}
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/AcaPyMockedService.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/AcaPyMockedService.kt
index 7c337a97a..c3bbecf01 100644
--- a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/AcaPyMockedService.kt
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/AcaPyMockedService.kt
@@ -24,16 +24,15 @@ import org.eclipse.tractusx.managedidentitywallets.models.ssi.*
import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.*
import org.eclipse.tractusx.managedidentitywallets.services.IAcaPyService
+import org.hyperledger.acy_py.generated.model.AttachDecorator
+import org.hyperledger.acy_py.generated.model.AttachDecoratorData
+import org.hyperledger.aries.AriesClient
+import org.hyperledger.aries.api.connection.ConnectionRecord
+import org.hyperledger.aries.api.connection.ConnectionState
+import org.hyperledger.aries.api.issue_credential_v2.V20CredExRecord
+import org.hyperledger.aries.api.issue_credential_v2.V20CredOffer
import java.security.SecureRandom
-object SingletonTestData {
- lateinit var baseWalletDID: String
- lateinit var baseWalletVerKey: String
- lateinit var signCredentialResponse: String
- var isValidVerifiableCredential: Boolean = true
- var isValidVerifiablePresentation: Boolean = true
-}
-
class AcaPyMockedService(val baseWalletBpn: String,
val networkIdentifier: String): IAcaPyService {
@@ -152,20 +151,20 @@ class AcaPyMockedService(val baseWalletBpn: String,
context = emptyList(),
verificationMethods = listOf(
DidVerificationMethodDto(
- id = "did:indy:${getWalletAndAcaPyConfig().networkIdentifier}:${getIdentifierOfDid(did)}#key-1",
+ id = "${getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(did)}#key-1",
type = "Ed25519VerificationKey2018",
- controller = "did:indy:${getWalletAndAcaPyConfig().networkIdentifier}:${getIdentifierOfDid(did)}",
+ controller = "${getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(did)}",
publicKeyBase58= "${didToVerKey[key]}"
)
),
services = listOf(
DidServiceDto(
- id = "did:indy:${getWalletAndAcaPyConfig().networkIdentifier}:${getIdentifierOfDid(did)}#did-communication",
+ id = "${getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(did)}#did-communication",
type = "did-communication",
serviceEndpoint = "http://localhost:8000/",
),
DidServiceDto(
- id = "did:indy:${getWalletAndAcaPyConfig().networkIdentifier}:${getIdentifierOfDid(did)}#linked_domains",
+ id = "${getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(did)}#linked_domains",
type = "linked_domains",
serviceEndpoint = "https://myhost:1111",
)
@@ -183,15 +182,15 @@ class AcaPyMockedService(val baseWalletBpn: String,
context = emptyList(),
verificationMethods = listOf(
DidVerificationMethodDto(
- id = "did:indy:${getWalletAndAcaPyConfig().networkIdentifier}:${getIdentifierOfDid(did)}#key-1",
+ id = "${getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(did)}#key-1",
type = "Ed25519VerificationKey2018",
- controller = "did:indy:${getWalletAndAcaPyConfig().networkIdentifier}:${getIdentifierOfDid(did)}",
+ controller = "${getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(did)}",
publicKeyBase58= "${SingletonTestData.baseWalletVerKey}"
)
),
services = listOf(
DidServiceDto(
- id = "did:indy:${getWalletAndAcaPyConfig().networkIdentifier}:${getIdentifierOfDid(did)}#did-communication",
+ id = "${getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(did)}#did-communication",
type = "did-communication",
serviceEndpoint = "http://localhost:8000/",
)
@@ -206,7 +205,7 @@ class AcaPyMockedService(val baseWalletBpn: String,
context = emptyList(),
services = listOf(
DidServiceDto(
- id = "did:indy:${getWalletAndAcaPyConfig().networkIdentifier}:${getIdentifierOfDid(did)}#did-communication",
+ id = "${getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(did)}#did-communication",
type = "did-communication",
serviceEndpoint = "http://localhost:8000/",
)
@@ -218,6 +217,44 @@ class AcaPyMockedService(val baseWalletBpn: String,
override suspend fun updateService(serviceEndPoint: DidEndpointWithType, token: String) {}
+ override fun subscribeForWebSocket(subscriberWallet: WalletExtendedData) { }
+
+ override suspend fun getAcapyClient(walletToken: String): AriesClient {
+ TODO("Not yet implemented")
+ }
+
+ override suspend fun connect(
+ selfManagedWalletCreateDto: SelfManagedWalletCreateDto,
+ token: String
+ ): ConnectionRecord {
+ val connReq = ConnectionRecord()
+ connReq.connectionId = SingletonTestData.connectionId
+ connReq.theirDid = "${getDidMethodPrefixWithNetworkIdentifier()}:..."
+ connReq.myDid = SingletonTestData.baseWalletDID
+ connReq.state = ConnectionState.REQUEST
+ connReq.requestId = SingletonTestData.threadId
+ return connReq
+ }
+
+ override suspend fun issuanceFlowCredentialSend(
+ token: String,
+ vc: VerifiableCredentialIssuanceFlowRequest
+ ): V20CredExRecord {
+ val attachDecoratorData = AttachDecoratorData()
+ attachDecoratorData.base64 = "Y3JlZGVudGlhbA=="
+ val attach = AttachDecorator()
+ attach.data = attachDecoratorData
+ val offerAttach = listOf(attach)
+ val credOffer = V20CredOffer()
+ val credExRecord = V20CredExRecord()
+ credExRecord.credOffer = credOffer
+ credExRecord.credOffer.offersAttach = offerAttach
+ credExRecord.threadId = SingletonTestData.threadId
+ return credExRecord
+ }
+
+ override suspend fun deleteConnection(connectionId: String, token: String) { return }
+
private fun createRandomString(): String {
return (1..25)
.map { SecureRandom().nextInt(charPool.size) }
@@ -229,4 +266,8 @@ class AcaPyMockedService(val baseWalletBpn: String,
val elementsOfDid: List = did.split(":")
return elementsOfDid[elementsOfDid.size - 1]
}
+
+ private fun getDidMethodPrefixWithNetworkIdentifier(): String {
+ return SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()
+ }
}
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/ApplicationTest.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/ApplicationTest.kt
index e221106b8..c41ac4890 100644
--- a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/ApplicationTest.kt
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/ApplicationTest.kt
@@ -113,12 +113,14 @@ class ApplicationTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
configureJobs()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
assertTrue(true)
}
@@ -132,11 +134,13 @@ class ApplicationTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
handleRequest(HttpMethod.Get, "/").apply {
assertEquals(HttpStatusCode.OK, response.status())
@@ -178,11 +182,13 @@ class ApplicationTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
assertFails {
@@ -263,11 +269,13 @@ class ApplicationTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
// view wallets with single view token should not work
@@ -415,7 +423,7 @@ class ApplicationTest {
"University-Degree-Credential",
"VerifiableCredential"
],
- "issuer": "did:indy:local:test:43Arq24V9uQFPKHuDb7TC5",
+ "issuer": "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}43Arq24V9uQFPKHuDb7TC5",
"issuanceDate": "2019-06-16T18:56:59Z",
"expirationDate": "2019-06-17T18:56:59Z",
"credentialSubject": {
@@ -427,13 +435,13 @@ class ApplicationTest {
"name": "Master of Science and Arts"
},
"college": "Stuttgart",
- "id": "did:indy:local:test:QZakhgHUUAowUbhgZ9PZLD"
+ "id": "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}QZakhgHUUAowUbhgZ9PZLD"
},
"proof": {
"type": "Ed25519Signature2018",
"created": "2022-03-24T09:34:02Z",
"proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:43Arq24V9uQFPKHuDb7TC5#key-1",
+ "verificationMethod": "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}43Arq24V9uQFPKHuDb7TC5#key-1",
"jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..LvCQ4TWhFHOkwMzvrx-TxHovoaCLPlK2taHxQUtUOp0Uc_jYbjL3XgVR2u6jVMvGIdPt4gs-VZb49f7GuiXFDA"
}
}
@@ -465,7 +473,7 @@ class ApplicationTest {
"University-Degree-Credential",
"VerifiableCredential"
],
- "issuer": "did:indy:local:test:43Arq24V9uQFPKHuDb7TC5",
+ "issuer": "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}43Arq24V9uQFPKHuDb7TC5",
"issuanceDate": "2019-06-16T18:56:59Z",
"expirationDate": "2019-06-17T18:56:59Z",
"credentialSubject": {
@@ -477,13 +485,13 @@ class ApplicationTest {
"name": "Master of Science and Arts"
},
"college": "Stuttgart",
- "id": "did:indy:local:test:QZakhgHUUAowUbhgZ9PZLD"
+ "id": "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}QZakhgHUUAowUbhgZ9PZLD"
},
"proof": {
"type": "Ed25519Signature2018",
"created": "2022-03-24T09:34:02Z",
"proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:43Arq24V9uQFPKHuDb7TC5#key-1",
+ "verificationMethod": "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}43Arq24V9uQFPKHuDb7TC5#key-1",
"jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..LvCQ4TWhFHOkwMzvrx-TxHovoaCLPlK2taHxQUtUOp0Uc_jYbjL3XgVR2u6jVMvGIdPt4gs-VZb49f7GuiXFDA"
}
}
@@ -531,7 +539,7 @@ class ApplicationTest {
"type": "Ed25519Signature2018",
"created": "2022-07-15T09:35:59Z",
"proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:JPbsf8GpUYiavsK95SGpge#key-1",
+ "verificationMethod": "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}JPbsf8GpUYiavsK95SGpge#key-1",
"jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..4mFcySYFNAV6Bif6OqHeGqhQZ1kPMbq5FbOjurbIBIyYnQyRICa1b7RB_nxfz9fdP7WYxthTVnaWiXs2WbpzBQ"
}
}
@@ -575,7 +583,7 @@ class ApplicationTest {
"type": "Ed25519Signature2018",
"created": "2022-07-15T09:35:59Z",
"proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:JPbsf8GpUYiavsK95SGpge#key-1",
+ "verificationMethod": "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}JPbsf8GpUYiavsK95SGpge#key-1",
"jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..4mFcySYFNAV6Bif6OqHeGqhQZ1kPMbq5FbOjurbIBIyYnQyRICa1b7RB_nxfz9fdP7WYxthTVnaWiXs2WbpzBQ"
}
}
@@ -620,7 +628,7 @@ class ApplicationTest {
"type": "Ed25519Signature2018",
"created": "2022-07-15T09:35:59Z",
"proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:JPbsf8GpUYiavsK95SGpge#key-1",
+ "verificationMethod": "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}JPbsf8GpUYiavsK95SGpge#key-1",
"jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..4mFcySYFNAV6Bif6OqHeGqhQZ1kPMbq5FbOjurbIBIyYnQyRICa1b7RB_nxfz9fdP7WYxthTVnaWiXs2WbpzBQ"
}
}
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/BusinessPartnerDataMockedService.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/BusinessPartnerDataMockedService.kt
index e930f42e2..24a612df6 100644
--- a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/BusinessPartnerDataMockedService.kt
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/BusinessPartnerDataMockedService.kt
@@ -21,6 +21,7 @@ package org.eclipse.tractusx.managedidentitywallets
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
+import org.eclipse.tractusx.managedidentitywallets.models.WalletDto
import org.eclipse.tractusx.managedidentitywallets.services.IBusinessPartnerDataService
class BusinessPartnerDataMockedService: IBusinessPartnerDataService {
@@ -35,4 +36,12 @@ class BusinessPartnerDataMockedService: IBusinessPartnerDataService {
return CompletableDeferred(true)
}
+ override suspend fun issueAndSendCatenaXCredentialsForSelfManagedWalletsAsync(
+ targetWallet: WalletDto,
+ connectionId: String,
+ webhookUrl: String?
+ ): Deferred {
+ TODO("Not yet implemented")
+ }
+
}
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/BusinessPartnerDataTest.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/BusinessPartnerDataTest.kt
index a3284eec2..e3b1515f5 100644
--- a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/BusinessPartnerDataTest.kt
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/BusinessPartnerDataTest.kt
@@ -51,11 +51,12 @@ class BusinessPartnerDataTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
}) {
handleRequest(HttpMethod.Post, "/api/businessPartnerDataRefresh") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
@@ -75,7 +76,7 @@ class BusinessPartnerDataTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
}) {
val businessPartnerDataAsJson =
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/CredentialsTest.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/CredentialsTest.kt
index 226000a50..d1e2b498f 100644
--- a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/CredentialsTest.kt
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/CredentialsTest.kt
@@ -17,22 +17,19 @@
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/
-package org.eclipse.tractusx.managedidentitywallet
+package org.eclipse.tractusx.managedidentitywallets
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
-import org.eclipse.tractusx.managedidentitywallets.EnvironmentTestSetup
-import org.eclipse.tractusx.managedidentitywallets.Services
-import org.eclipse.tractusx.managedidentitywallets.SingletonTestData
-import org.eclipse.tractusx.managedidentitywallets.TestServer
import org.eclipse.tractusx.managedidentitywallets.models.StoreVerifiableCredentialParameter
import org.eclipse.tractusx.managedidentitywallets.models.WalletCreateDto
import org.eclipse.tractusx.managedidentitywallets.models.WalletDto
import org.eclipse.tractusx.managedidentitywallets.models.ssi.*
import org.eclipse.tractusx.managedidentitywallets.plugins.*
import org.eclipse.tractusx.managedidentitywallets.routes.appRoutes
+import java.io.File
import kotlin.test.*
@kotlinx.serialization.ExperimentalSerializationApi
@@ -50,6 +47,11 @@ class CredentialsTest {
server.stop(1000, 10000)
}
+ @AfterTest
+ fun cleanSingletonTestData() {
+ SingletonTestData.cleanSingletonTestData()
+ }
+
@Test
fun testGetAndStoreVerifiableCredentials() {
withTestApplication({
@@ -58,12 +60,14 @@ class CredentialsTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
configureStatusPages()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
// programmatically add a wallet
val walletDto: WalletDto
@@ -80,41 +84,10 @@ class CredentialsTest {
credentials = EnvironmentTestSetup.walletService.getCredentials(
null,null,null,null)
assertTrue { credentials.isEmpty() }
- val vcAsString ="""
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D",
- "issuanceDate": "2021-06-16T18:56:59Z",
- "expirationDate": "2026-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master",
- "degreeType": "Undergraduate",
- "name": "Master of Test"
- },
- "college": "Test",
- "id": "${walletDto.did}"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:13:16Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
- }
- }
- """.trimIndent()
+ //TODO replace issuerDid inside vcWithReplaceableSubjectId when did indy method is supported by AcaPy
+ val vcAsString: String = File("./src/test/resources/credentials-test-data/vcWithReplaceableSubjectId.json")
+ .readText(Charsets.UTF_8).replace("", walletDto.did)
val storeVerifiableCredentialParameter = StoreVerifiableCredentialParameter(EnvironmentTestSetup.DEFAULT_BPN)
assertEquals(EnvironmentTestSetup.DEFAULT_BPN, storeVerifiableCredentialParameter.identifier)
@@ -127,40 +100,9 @@ class CredentialsTest {
assertEquals(HttpStatusCode.Created, response.status())
}
- val vcWithWrongSubjectAsString ="""
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D",
- "issuanceDate": "2021-06-16T18:56:59Z",
- "expirationDate": "2026-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master",
- "degreeType": "Undergraduate",
- "name": "Master of Test"
- },
- "college": "Test",
- "id": "did:indy:local:test:NotEqualWalletDID"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:13:16Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
- }
- }
- """.trimIndent()
+ val vcWithWrongSubjectAsString: String = File("./src/test/resources/credentials-test-data/vcWithReplaceableSubjectId.json")
+ .readText(Charsets.UTF_8).replace("",
+ "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}NotEqualWalletDID")
handleRequest(HttpMethod.Post, "/api/wallets/${EnvironmentTestSetup.DEFAULT_BPN}/credentials") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
@@ -197,17 +139,20 @@ class CredentialsTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
configureStatusPages()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
// programmatically add a wallet
- val walletDto: WalletDto
runBlocking {
- walletDto = EnvironmentTestSetup.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ val walletDto = EnvironmentTestSetup.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ SingletonTestData.baseWalletVerKey = walletDto.verKey!!
+ SingletonTestData.baseWalletDID = walletDto.did
}
val verifiableCredentialRequest = VerifiableCredentialRequestDto(
context = listOf(
@@ -216,14 +161,14 @@ class CredentialsTest {
),
id = "http://example.edu/credentials/3732",
type = listOf("University-Degree-Credential, VerifiableCredential"),
- issuerIdentifier = walletDto.did,
+ issuerIdentifier = SingletonTestData.baseWalletDID,
issuanceDate = "2019-06-16T18:56:59Z",
expirationDate = "2019-06-17T18:56:59Z",
credentialSubject = mapOf("college" to "Test-University"),
- holderIdentifier = walletDto.did
+ holderIdentifier = SingletonTestData.baseWalletDID,
+ isRevocable = true
)
- SingletonTestData.baseWalletVerKey = walletDto.verKey!!
- SingletonTestData.baseWalletDID = walletDto.did
+
val signedCred = Json.encodeToString(
VerifiableCredentialDto.serializer(),
VerifiableCredentialDto(
@@ -233,15 +178,15 @@ class CredentialsTest {
),
id = "http://example.edu/credentials/3732",
type = listOf("University-Degree-Credential, VerifiableCredential"),
- issuer = walletDto.did,
+ issuer = SingletonTestData.baseWalletDID,
issuanceDate = "2019-06-16T18:56:59Z",
expirationDate = "2019-06-17T18:56:59Z",
- credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialSubject = mapOf("college" to "Test-University", "id" to SingletonTestData.baseWalletDID),
proof = LdProofDto(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "${walletDto.did}#keys-1",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
@@ -254,16 +199,217 @@ class CredentialsTest {
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
setBody(
Json.encodeToString(
- VerifiableCredentialRequestDto.serializer(),
- verifiableCredentialRequest,
- ))
+ VerifiableCredentialRequestDto.serializer(),
+ verifiableCredentialRequest,
+ )
+ )
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+
+ // No Holder identifier , only subject ID
+ val verifiableCredentialRequestNoHolderButWithSubjectId = VerifiableCredentialRequestDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuerIdentifier = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to SingletonTestData.baseWalletDID),
+ )
+ handleRequest(HttpMethod.Post, "/api/credentials") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiableCredentialRequestDto.serializer(),
+ verifiableCredentialRequestNoHolderButWithSubjectId,
+ )
+ )
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+
+ // The Holder identifier is not a managed wallet
+ val verifiableCredentialRequestWithRandomHolder = VerifiableCredentialRequestDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuerIdentifier = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University"),
+ holderIdentifier = "Random-Value"
+ )
+ val signedCredWithRandomHolder = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to "Random-Value"),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+ SingletonTestData.signCredentialResponse = """{ "signed_doc": $signedCredWithRandomHolder }"""
+ handleRequest(HttpMethod.Post, "/api/credentials") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiableCredentialRequestDto.serializer(),
+ verifiableCredentialRequestWithRandomHolder,
+ )
+ )
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+
+ // No Holder identifier and no subject ID
+ val signedCredWithoutSubjectId = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University"),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#keys-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+ SingletonTestData.signCredentialResponse = """{ "signed_doc": $signedCredWithoutSubjectId }"""
+ val verifiableCredentialRequestNoHolderNoSubjectId = VerifiableCredentialRequestDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuerIdentifier = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University"),
+ )
+ handleRequest(HttpMethod.Post, "/api/credentials") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiableCredentialRequestDto.serializer(),
+ verifiableCredentialRequestNoHolderNoSubjectId,
+ )
+ )
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+
+ handleRequest(HttpMethod.Post, "/api/credentials") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiableCredentialRequestDto.serializer(),
+ verifiableCredentialRequest,
+ ))
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+
+ // change did of test wallet
+ val originalDID = SingletonTestData.baseWalletDID
+ val replacedDID = SingletonTestData.baseWalletDID.replace(
+ Services.utilsService.getDidMethodPrefixWithNetworkIdentifier(),
+ Services.utilsService.getOldDidMethodPrefixWithNetworkIdentifier()
+ )
+ EnvironmentTestSetup.replaceWalletDid(originalDID, replacedDID)
+ SingletonTestData.baseWalletDID = replacedDID
+
+ // try to issue a credential
+ val signedCredWithoutSubjectIdReplacedDid = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University"),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#keys-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+ SingletonTestData.signCredentialResponse = """{ "signed_doc": $signedCredWithoutSubjectIdReplacedDid }"""
+ val verifiableCredentialRequestNoHolderNoSubjectIdReplacedDid = VerifiableCredentialRequestDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuerIdentifier = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University"),
+ )
+ handleRequest(HttpMethod.Post, "/api/credentials") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiableCredentialRequestDto.serializer(),
+ verifiableCredentialRequestNoHolderNoSubjectIdReplacedDid,
+ )
+ )
}.apply {
assertEquals(HttpStatusCode.Created, response.status())
}
- SingletonTestData.baseWalletVerKey = ""
- SingletonTestData.baseWalletDID = ""
- SingletonTestData.signCredentialResponse = ""
+ // change it back
+ EnvironmentTestSetup.replaceWalletDid(SingletonTestData.baseWalletDID, originalDID)
+ SingletonTestData.baseWalletDID = originalDID
runBlocking {
EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN)
@@ -279,17 +425,20 @@ class CredentialsTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
configureStatusPages()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
// programmatically add a wallet
- val walletDto: WalletDto
runBlocking {
- walletDto = EnvironmentTestSetup.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ val walletDto = EnvironmentTestSetup.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ SingletonTestData.baseWalletVerKey = walletDto.verKey!!
+ SingletonTestData.baseWalletDID = walletDto.did
}
val verifiableCredentialRequest = VerifiableCredentialRequestWithoutIssuerDto(
context = listOf(
@@ -301,10 +450,9 @@ class CredentialsTest {
issuanceDate = "2019-06-16T18:56:59Z",
expirationDate = "2019-06-17T18:56:59Z",
credentialSubject = mapOf("college" to "Test-University"),
- holderIdentifier = walletDto.did
+ holderIdentifier = SingletonTestData.baseWalletDID
)
- SingletonTestData.baseWalletVerKey = walletDto.verKey!!
- SingletonTestData.baseWalletDID = walletDto.did
+
val signedCred = Json.encodeToString(
VerifiableCredentialDto.serializer(),
VerifiableCredentialDto(
@@ -314,15 +462,15 @@ class CredentialsTest {
),
id = "http://example.edu/credentials/3732",
type = listOf("University-Degree-Credential, VerifiableCredential"),
- issuer = walletDto.did,
+ issuer = SingletonTestData.baseWalletDID,
issuanceDate = "2019-06-16T18:56:59Z",
expirationDate = "2019-06-17T18:56:59Z",
- credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialSubject = mapOf("college" to "Test-University", "id" to SingletonTestData.baseWalletDID),
proof = LdProofDto(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "${walletDto.did}#keys-1",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
@@ -340,9 +488,29 @@ class CredentialsTest {
}.apply {
assertEquals(HttpStatusCode.Created, response.status())
}
- SingletonTestData.baseWalletVerKey = ""
- SingletonTestData.baseWalletDID = ""
- SingletonTestData.signCredentialResponse = ""
+ val verifiableCredentialRequestIrrevocable = VerifiableCredentialRequestWithoutIssuerDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University"),
+ holderIdentifier = SingletonTestData.baseWalletDID
+ )
+ handleRequest(HttpMethod.Post, "/api/credentials/issuer") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiableCredentialRequestWithoutIssuerDto.serializer(),
+ verifiableCredentialRequestIrrevocable))
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
runBlocking {
EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN)
@@ -350,4 +518,502 @@ class CredentialsTest {
}
}
+ @Test
+ fun testIssueAndRevokeCredential() {
+ withTestApplication({
+ EnvironmentTestSetup.setupEnvironment(environment)
+ configurePersistence()
+ configureOpenAPI()
+ configureSecurity()
+ configureRouting(EnvironmentTestSetup.walletService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
+ configureSerialization()
+ configureStatusPages()
+ Services.walletService = EnvironmentTestSetup.walletService
+ Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
+ Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
+ }) {
+ // programmatically add a wallet
+ val walletDto: WalletDto
+ runBlocking {
+ walletDto = EnvironmentTestSetup.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ }
+
+ SingletonTestData.baseWalletVerKey = walletDto.verKey!!
+ SingletonTestData.baseWalletDID = walletDto.did
+ SingletonTestData.revocationListName = walletDto.revocationListName!!
+ SingletonTestData.credentialIndex = 0
+ val signedCred = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = walletDto.did,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialStatus = CredentialStatus(
+ statusId = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}#${SingletonTestData.credentialIndex}",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "revocation",
+ index = SingletonTestData.credentialIndex.toString(),
+ listUrl = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}"
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${walletDto.did}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+ SingletonTestData.signCredentialResponse = """{ "signed_doc": $signedCred }"""
+ SingletonTestData.isValidVerifiableCredential = true
+ SingletonTestData.credentialIndex = 1
+ // revoke credential
+ handleRequest(HttpMethod.Post, "/api/credentials/revocations") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(signedCred)
+ }.apply {
+ assertEquals(HttpStatusCode.Accepted, response.status())
+ }
+
+ val signedIrrevocableCredential = VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = walletDto.did,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${walletDto.did}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+
+ handleRequest(HttpMethod.Post, "/api/credentials/revocations") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(Json.encodeToString(VerifiableCredentialDto.serializer(), signedIrrevocableCredential))
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("The given verifiable credential is not revocable!"))
+ }
+
+ val signedCredWithInvalidStatusType = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = walletDto.did,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialStatus = CredentialStatus(
+ statusId = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}#${SingletonTestData.credentialIndex}",
+ credentialType = "Wrong-status-type",
+ statusPurpose = "revocation",
+ index = "-1",
+ listUrl = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}"
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${walletDto.did}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+
+ handleRequest(HttpMethod.Post, "/api/credentials/revocations") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(signedCredWithInvalidStatusType)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("has invalid credential status 'Type'"))
+ }
+
+ val signedCredWithInvalidStatusPurpose = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = walletDto.did,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialStatus = CredentialStatus(
+ statusId = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}#${SingletonTestData.credentialIndex}",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "wrong-purpose",
+ index = "1",
+ listUrl = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}"
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${walletDto.did}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+
+ handleRequest(HttpMethod.Post, "/api/credentials/revocations") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(signedCredWithInvalidStatusPurpose)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("has invalid 'statusPurpose'"))
+ }
+
+ val signedCredWithInvalidIndex = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = walletDto.did,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialStatus = CredentialStatus(
+ statusId = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}#${SingletonTestData.credentialIndex}",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "revocation",
+ index = "-1",
+ listUrl = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}"
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${walletDto.did}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+
+ handleRequest(HttpMethod.Post, "/api/credentials/revocations") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(signedCredWithInvalidIndex)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("has invalid 'statusListIndex'"))
+ }
+
+ val signedCredWithInvalidStatusListUrl = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = walletDto.did,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialStatus = CredentialStatus(
+ statusId = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}#${SingletonTestData.credentialIndex}",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "revocation",
+ index = "3",
+ listUrl = " "
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${walletDto.did}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+
+ handleRequest(HttpMethod.Post, "/api/credentials/revocations") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(signedCredWithInvalidStatusListUrl)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("has invalid 'statusListCredential'"))
+ }
+
+ val signedCredWithInvalidStatusListUrlWithoutCredentialId = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = walletDto.did,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2019-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialStatus = CredentialStatus(
+ statusId = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}#${SingletonTestData.credentialIndex}",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "revocation",
+ index = " ",
+ listUrl = " "
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${walletDto.did}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+
+ handleRequest(HttpMethod.Post, "/api/credentials/revocations") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(signedCredWithInvalidStatusListUrlWithoutCredentialId)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("Credential with Id null has invalid 'statusListIndex'"))
+ }
+
+ runBlocking {
+ EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN)
+ }
+ }
+ }
+
+ @Test
+ fun testIssueStatusListCredential() {
+ withTestApplication({
+ EnvironmentTestSetup.setupEnvironment(environment)
+ configurePersistence()
+ configureOpenAPI()
+ configureSecurity()
+ configureRouting(EnvironmentTestSetup.walletService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
+ configureSerialization()
+ configureStatusPages()
+ Services.walletService = EnvironmentTestSetup.walletService
+ Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
+ Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ }) {
+ // programmatically add a wallet
+ val walletDto: WalletDto
+ runBlocking {
+ walletDto = EnvironmentTestSetup.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ }
+
+ SingletonTestData.baseWalletVerKey = walletDto.verKey!!
+ SingletonTestData.baseWalletDID = walletDto.did
+ SingletonTestData.revocationListName = walletDto.revocationListName!!
+ SingletonTestData.credentialIndex = 0
+
+ val signedCred = Json.encodeToString(
+ VerifiableCredentialDto.serializer(),
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("StatusList2021Credential, VerifiableCredential"),
+ issuer = walletDto.did,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ credentialSubject = mapOf(
+ "id" to "https://example.com/status/${Services.utilsService.getIdentifierOfDid(walletDto.did)}#list",
+ "type" to "StatusList2021",
+ "statusPurpose" to "revocation",
+ "encodedList" to "H4sIAAAAAAAAA-3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA"
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${walletDto.did}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+ SingletonTestData.signCredentialResponse = """{ "signed_doc": $signedCred }"""
+ SingletonTestData.isValidVerifiableCredential = true
+ val listCredentialRequestData = ListCredentialRequestData(
+ listId = "uuid-of-list",
+ subject = ListCredentialSubject (
+ credentialId = "https://example.com/status/3#list",
+ credentialType = "StatusList2021",
+ statusPurpose = "revocation",
+ encodedList = "H4sIAAAAAAAAA-3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA"
+ )
+ )
+
+ handleRequest(
+ HttpMethod.Post, "/list-credential/${Services.utilsService.getIdentifierOfDid(walletDto.did)}/issue") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(Json.encodeToString(ListCredentialRequestData.serializer(), listCredentialRequestData))
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+
+ runBlocking {
+ EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN)
+ }
+ }
+ }
+
+ @Test
+ fun testGetStatusListCredential() {
+ withTestApplication({
+ EnvironmentTestSetup.setupEnvironment(environment)
+ configurePersistence()
+ configureOpenAPI()
+ configureSecurity()
+ configureRouting(EnvironmentTestSetup.walletService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
+ configureSerialization()
+ configureStatusPages()
+ Services.walletService = EnvironmentTestSetup.walletService
+ Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
+ Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
+ }) {
+ // programmatically add a wallet
+ val walletDto: WalletDto
+ runBlocking {
+ walletDto = EnvironmentTestSetup.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ }
+
+ SingletonTestData.baseWalletVerKey = walletDto.verKey!!
+ SingletonTestData.baseWalletDID = walletDto.did
+ SingletonTestData.revocationListName = walletDto.revocationListName!!
+
+ handleRequest(
+ HttpMethod.Get, "/api/credentials/status/${walletDto.revocationListName}") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ }.apply {
+ assertEquals(HttpStatusCode.OK, response.status())
+ val credential = Json.decodeFromString(VerifiableCredentialDto.serializer(), response.content!!)
+ assertEquals(
+ "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}#list",
+ credential.credentialSubject["id"]
+ )
+ }
+
+ // without listName
+ handleRequest(
+ HttpMethod.Get, "/api/credentials/status/") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ }.apply {
+ assertEquals(HttpStatusCode.NotFound, response.status())
+ }
+
+ runBlocking {
+ EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN)
+ }
+ }
+ }
+
+ @Test
+ fun testIssueAndUpdateStatusListCredential() {
+ withTestApplication({
+ EnvironmentTestSetup.setupEnvironment(environment)
+ configurePersistence()
+ configureOpenAPI()
+ configureSecurity()
+ configureRouting(EnvironmentTestSetup.walletService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
+ configureSerialization()
+ configureStatusPages()
+ Services.walletService = EnvironmentTestSetup.walletService
+ Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
+ Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ }) {
+ // programmatically add a wallet
+ var walletDto: WalletDto
+ runBlocking {
+ walletDto = EnvironmentTestSetup.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ }
+
+ handleRequest(
+ HttpMethod.Post, "/api/credentials/revocations/statusListCredentialRefresh") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ }.apply {
+ assertEquals(HttpStatusCode.Accepted, response.status())
+ }
+
+ handleRequest(
+ HttpMethod.Post, "/api/credentials/revocations/statusListCredentialRefresh?identifier=${walletDto.did}&force=false") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ }.apply {
+ assertEquals(HttpStatusCode.Accepted, response.status())
+ }
+
+ handleRequest(
+ HttpMethod.Post, "/api/credentials/revocations/statusListCredentialRefresh?identifier=${walletDto.did}&force=true") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ }.apply {
+ assertEquals(HttpStatusCode.Accepted, response.status())
+ }
+
+ handleRequest(
+ HttpMethod.Get, "/api/credentials/status/listName=${walletDto.revocationListName}") {
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ }.apply {
+ assertEquals(HttpStatusCode.OK, response.status())
+ }
+
+ runBlocking {
+ EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN)
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/DidDocTest.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/DidDocTest.kt
index 0791a33b8..e547d0915 100644
--- a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/DidDocTest.kt
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/DidDocTest.kt
@@ -53,12 +53,14 @@ class DidDocTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
configureStatusPages()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
// programmatically add a wallet
val walletDto: WalletDto
@@ -79,21 +81,21 @@ class DidDocTest {
assertEquals(HttpStatusCode.OK, response.status())
}
- val notValidDidMethod = "did:local:indy:test:XMcRfSUkkQK38p6CCjHZz6"
+ val notValidDidMethod = "did:local:test:test:XMcRfSUkkQK38p6CCjHZz6"
handleRequest(HttpMethod.Get, "/api/didDocuments/$notValidDidMethod") {
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
}.apply {
assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
}
- val notValidDidMethodShortId = "did:indy:${EnvironmentTestSetup.NETWORK_ID}:XMcRfSU"
+ val notValidDidMethodShortId = "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}XMcRfSU"
handleRequest(HttpMethod.Get, "/api/didDocuments/$notValidDidMethodShortId") {
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
}.apply {
assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
}
- val notSupportedDidMethod = "did:sov:XMcRfSUkkQK38p6CCjHZz6"
+ val notSupportedDidMethod = "did:wrong:network:XMcRfSUkkQK38p6CCjHZz6"
handleRequest(HttpMethod.Get, "/api/didDocuments/$notSupportedDidMethod") {
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
}.apply {
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/EnvironmentTestSetup.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/EnvironmentTestSetup.kt
index b77a2b84b..a7eeff69a 100644
--- a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/EnvironmentTestSetup.kt
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/EnvironmentTestSetup.kt
@@ -22,23 +22,42 @@ package org.eclipse.tractusx.managedidentitywallets
import io.ktor.application.*
import io.ktor.config.*
+import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.ConnectionRepository
import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.CredentialRepository
import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.WalletRepository
+import org.eclipse.tractusx.managedidentitywallets.persistence.repositories.WebhookRepository
import org.eclipse.tractusx.managedidentitywallets.routes.AuthorizationHandler
import org.eclipse.tractusx.managedidentitywallets.services.AcaPyWalletServiceImpl
+import org.eclipse.tractusx.managedidentitywallets.services.IWebhookService
import org.eclipse.tractusx.managedidentitywallets.services.UtilsService
+
+import org.jetbrains.exposed.sql.transactions.transaction
+
import java.sql.DriverManager
+import java.util.*
object EnvironmentTestSetup {
const val DEFAULT_BPN = "BPNL00000"
const val EXTRA_TEST_BPN = "BPNL0Test"
const val NETWORK_ID = "local:test"
+ const val NONE_REVOKED_ENCODED_LIST = "H4sIAAAAAAAAAO3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAIC3AYbSVKsAQAAA"
+ const val ZERO_THIRD_REVOKED_ENCODED_LIST ="H4sIAAAAAAAAAO3BIQEAAAACIKv/DzvDAjQAAAAAAAAAAAAAAAAAAADA2wBHo2oBAEAAAA=="
private val walletRepository = WalletRepository()
private val credentialRepository = CredentialRepository()
+ val connectionRepository = ConnectionRepository()
+ val webhookRepository = WebhookRepository()
+
private val acaPyMockedService = AcaPyMockedService(DEFAULT_BPN, NETWORK_ID)
+ val revocationMockedService = RevocationMockedService(NETWORK_ID)
+ val webhookService = IWebhookService.createWebhookService(webhookRepository)
val utilsService = UtilsService(NETWORK_ID)
- val walletService = AcaPyWalletServiceImpl(acaPyMockedService, walletRepository, credentialRepository, utilsService)
+
+ val walletService = AcaPyWalletServiceImpl(
+ acaPyMockedService, walletRepository,
+ credentialRepository, utilsService, revocationMockedService,
+ webhookService, connectionRepository
+ )
val bpdService = BusinessPartnerDataMockedService()
val EMPTY_ROLES_TOKEN = JwtConfigTest.makeToken(listOf())
@@ -60,7 +79,6 @@ object EnvironmentTestSetup {
), EXTRA_TEST_BPN
)
-
fun setupEnvironment(environment: ApplicationEnvironment) {
val jdbcUrl = System.getenv("CX_DB_JDBC_URL") ?: "jdbc:sqlite:file:test?mode=memory&cache=shared"
(environment.config as MapApplicationConfig).apply {
@@ -88,6 +106,9 @@ object EnvironmentTestSetup {
put("bpdm.pullDataAtHour", System.getenv("BPDM_PULL_DATA_AT_HOUR") ?: "23")
put("bpdm.datapoolUrl", System.getenv("BPDM_DATAPOOL_URL") ?: "http://0.0.0.0:8080")
+
+ put("revocation.baseUrl", System.getenv("REVOCATION_URL") ?: "http://0.0.0.0:8086")
+ put("revocation.createStatusListCredentialAtHour", System.getenv("REVOCATION_CREATE_STATUS_LIST_CREDENTIAL_AT_HOUR") ?: "3")
}
// just a keepAliveConnection
DriverManager.getConnection(jdbcUrl)
@@ -97,6 +118,9 @@ object EnvironmentTestSetup {
SingletonTestData.signCredentialResponse = ""
SingletonTestData.isValidVerifiablePresentation = true
SingletonTestData.isValidVerifiableCredential = true
+ SingletonTestData.credentialIndex = 1
+ SingletonTestData.revocationListName = UUID.randomUUID().toString()
+ SingletonTestData.encodedList = NONE_REVOKED_ENCODED_LIST
}
fun setupEnvironmentWithMissingRoleMapping(environment: ApplicationEnvironment) {
@@ -104,9 +128,9 @@ object EnvironmentTestSetup {
(environment.config as MapApplicationConfig).apply {
put(
"auth.roleMappings", value = System.getenv("CX_AUTH_ROLE_MAPPINGS")
- ?: "no_create_wallets:create_wallets,no_view_wallets:view_wallets," +
- "no_update_wallets:update_wallets,no_delete_wallets:delete_wallets," +
- "view_wallet:view_wallet,update_wallet:update_wallet"
+ ?: ("no_create_wallets:create_wallets,no_view_wallets:view_wallets," +
+ "no_update_wallets:update_wallets,no_delete_wallets:delete_wallets," +
+ "view_wallet:view_wallet,update_wallet:update_wallet")
)
}
}
@@ -120,5 +144,13 @@ object EnvironmentTestSetup {
}
}
+ fun replaceWalletDid(bpn: String, desiredDid: String) {
+ transaction {
+ walletRepository.getWallet(bpn).apply {
+ did = desiredDid
+ }
+ }
+ }
+
}
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/PresentationsTest.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/PresentationsTest.kt
index 91649bf4e..ad025c299 100644
--- a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/PresentationsTest.kt
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/PresentationsTest.kt
@@ -25,11 +25,11 @@ import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.eclipse.tractusx.managedidentitywallets.models.WalletCreateDto
-import org.eclipse.tractusx.managedidentitywallets.models.WalletDto
import org.eclipse.tractusx.managedidentitywallets.models.ssi.*
import org.eclipse.tractusx.managedidentitywallets.models.ssi.acapy.VerifyResponse
import org.eclipse.tractusx.managedidentitywallets.plugins.*
import org.eclipse.tractusx.managedidentitywallets.routes.appRoutes
+import java.io.File
import kotlin.test.*
@kotlinx.serialization.ExperimentalSerializationApi
@@ -47,6 +47,11 @@ class PresentationsTest {
server.stop(1000, 10000)
}
+ @AfterTest
+ fun cleanSingletonTestData() {
+ SingletonTestData.cleanSingletonTestData()
+ }
+
@Test
fun testIssuePresentation() {
withTestApplication({
@@ -55,24 +60,31 @@ class PresentationsTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
configureStatusPages()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
// programmatically add a wallet
- val walletDto: WalletDto
runBlocking {
- walletDto = EnvironmentTestSetup
+ val walletDto = EnvironmentTestSetup
.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ SingletonTestData.baseWalletVerKey = walletDto.verKey!!
+ SingletonTestData.baseWalletDID = walletDto.did
}
val networkId = EnvironmentTestSetup.NETWORK_ID
- val invalidDID = walletDto.did.replace("did:indy:$networkId", "did:indy:$networkId WRONG")
+ val invalidDID = SingletonTestData.baseWalletDID
+ .replace(
+ "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}",
+ "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()} WRONG"
+ )
val verifiablePresentationRequestWithInvalidDIDs = VerifiablePresentationRequestDto(
- holderIdentifier = walletDto.did,
+ holderIdentifier = SingletonTestData.baseWalletDID,
verifiableCredentials = listOf(
VerifiableCredentialDto(
context = listOf(
@@ -84,7 +96,7 @@ class PresentationsTest {
issuer = invalidDID,
issuanceDate = "2019-06-16T18:56:59Z",
expirationDate = "2999-06-17T18:56:59Z",
- credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialSubject = mapOf("college" to "Test-University", "id" to SingletonTestData.baseWalletDID),
proof = LdProofDto(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
@@ -96,8 +108,6 @@ class PresentationsTest {
)
)
- SingletonTestData.baseWalletVerKey = walletDto.verKey!!
- SingletonTestData.baseWalletDID = walletDto.did
handleRequest(HttpMethod.Post, "/api/presentations") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
@@ -106,13 +116,13 @@ class PresentationsTest {
Json.encodeToString(
VerifiablePresentationRequestDto.serializer(),
verifiablePresentationRequestWithInvalidDIDs))
- }.apply {
+ }.apply {
assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
- }
+ }
// With invalid issuanceDate
val verifiablePresentationRequestWithInvalidDate = VerifiablePresentationRequestDto(
- holderIdentifier = walletDto.did,
+ holderIdentifier = SingletonTestData.baseWalletDID,
verifiableCredentials = listOf(
VerifiableCredentialDto(
context = listOf(
@@ -121,15 +131,15 @@ class PresentationsTest {
),
id = "http://example.edu/credentials/333",
type = listOf("University-Degree-Credential, VerifiableCredential"),
- issuer = walletDto.did,
+ issuer = SingletonTestData.baseWalletDID,
issuanceDate = "2999-06-16T18:56:59Z",
expirationDate = "2999-06-17T18:56:59Z",
- credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialSubject = mapOf("college" to "Test-University", "id" to SingletonTestData.baseWalletDID),
proof = LdProofDto(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "${walletDto.did}#key-1",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
@@ -149,11 +159,8 @@ class PresentationsTest {
assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
}
- SingletonTestData.baseWalletVerKey = ""
- SingletonTestData.baseWalletDID = ""
-
val verifiablePresentationRequest = VerifiablePresentationRequestDto(
- holderIdentifier = walletDto.did,
+ holderIdentifier = SingletonTestData.baseWalletDID,
verifiableCredentials = listOf(
VerifiableCredentialDto(
context = listOf(
@@ -162,29 +169,27 @@ class PresentationsTest {
),
id = "http://example.edu/credentials/333",
type = listOf("University-Degree-Credential, VerifiableCredential"),
- issuer = walletDto.did,
+ issuer = SingletonTestData.baseWalletDID,
issuanceDate = "2019-06-16T18:56:59Z",
expirationDate = "2999-06-17T18:56:59Z",
- credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialSubject = mapOf("college" to "Test-University", "id" to SingletonTestData.baseWalletDID),
proof = LdProofDto(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "${walletDto.did}#key-1",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
)
)
- SingletonTestData.baseWalletVerKey = walletDto.verKey!!
- SingletonTestData.baseWalletDID = walletDto.did
val signedCred = Json.encodeToString(
VerifiablePresentationDto.serializer(),
VerifiablePresentationDto(
context = listOf(JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1),
type = listOf("VerifiablePresentation"),
- holder = walletDto.did,
+ holder = SingletonTestData.baseWalletDID,
verifiableCredential = listOf(
VerifiableCredentialDto(
context = listOf(
@@ -193,15 +198,15 @@ class PresentationsTest {
),
id = "http://example.edu/credentials/3732",
type = listOf("University-Degree-Credential, VerifiableCredential"),
- issuer = walletDto.did,
+ issuer = SingletonTestData.baseWalletDID,
issuanceDate = "2019-06-16T18:56:59Z",
expirationDate = "2999-06-17T18:56:59Z",
- credentialSubject = mapOf("college" to "Test-University", "id" to walletDto.did),
+ credentialSubject = mapOf("college" to "Test-University", "id" to SingletonTestData.baseWalletDID),
proof = LdProofDto(
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "${walletDto.did}#key-1",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
@@ -210,7 +215,7 @@ class PresentationsTest {
type = "Ed25519Signature2018",
created = "2021-11-17T22:20:27Z",
proofPurpose = "assertionMethod",
- verificationMethod = "${walletDto.did}#key-1",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
)
)
@@ -306,11 +311,158 @@ class PresentationsTest {
assertEquals(HttpStatusCode.BadRequest, response.status())
}
- SingletonTestData.baseWalletVerKey = ""
- SingletonTestData.baseWalletDID = ""
- SingletonTestData.signCredentialResponse = ""
+ // clean up created wallet and singletonTestData
+ runBlocking {
+ EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN)
+ }
+ }
+ }
+
+ @Test
+ fun testIssuePresentationForRevokedCredential() {
+ withTestApplication({
+ EnvironmentTestSetup.setupEnvironment(environment)
+ configurePersistence()
+ configureOpenAPI()
+ configureSecurity()
+ configureRouting(EnvironmentTestSetup.walletService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
+ configureSerialization()
+ configureStatusPages()
+ Services.walletService = EnvironmentTestSetup.walletService
+ Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
+ Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
+ }) {
+ // programmatically add a wallet
+ runBlocking {
+ val walletDto = EnvironmentTestSetup
+ .walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name_default"))
+ SingletonTestData.baseWalletVerKey = walletDto.verKey!!
+ SingletonTestData.baseWalletDID = walletDto.did
+ SingletonTestData.revocationListName = walletDto.revocationListName!!
+ SingletonTestData.credentialIndex = 3
+ }
+
+ val verifiablePresentationRequest = VerifiablePresentationRequestDto(
+ holderIdentifier = SingletonTestData.baseWalletDID,
+ verifiableCredentials = listOf(
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/333",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2999-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to SingletonTestData.baseWalletDID),
+ credentialStatus = CredentialStatus(
+ statusId = "http://localhost:8080/api/credentials/status/${SingletonTestData.revocationListName}#${SingletonTestData.credentialIndex}",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "revocation",
+ index = "${SingletonTestData.credentialIndex}",
+ listUrl = "http://localhost:8080/api/credentials/status/${SingletonTestData.revocationListName}"
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+ )
+
+ val signedCred = Json.encodeToString(
+ VerifiablePresentationDto.serializer(),
+ VerifiablePresentationDto(
+ context = listOf(JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1),
+ type = listOf("VerifiablePresentation"),
+ holder = SingletonTestData.baseWalletDID,
+ verifiableCredential = listOf(
+ VerifiableCredentialDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/333",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuer = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2999-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to SingletonTestData.baseWalletDID),
+ credentialStatus = CredentialStatus(
+ statusId = "http://localhost:8080/api/credentials/status/${SingletonTestData.revocationListName}#${SingletonTestData.credentialIndex}",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "revocation",
+ index = "${SingletonTestData.credentialIndex}",
+ listUrl = "http://localhost:8080/api/credentials/status/${SingletonTestData.revocationListName}"
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ ),
+ proof = LdProofDto(
+ type = "Ed25519Signature2018",
+ created = "2021-11-17T22:20:27Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${SingletonTestData.baseWalletDID}#key-1",
+ jws = "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFZERTQSJ9..JNerzfrK46Mq4XxYZEnY9xOK80xsEaWCLAHuZsFie1-NTJD17wWWENn_DAlA_OwxGF5dhxUJ05P6Dm8lcmF5Cg"
+ )
+ )
+ )
+
+ // Good Case: Verifiable Credential is valid
+ SingletonTestData.signCredentialResponse = """{ "signed_doc": $signedCred }"""
SingletonTestData.isValidVerifiableCredential = true
+ handleRequest(HttpMethod.Post, "/api/presentations") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiablePresentationRequestDto.serializer(),
+ verifiablePresentationRequest))
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+ SingletonTestData.encodedList = EnvironmentTestSetup.ZERO_THIRD_REVOKED_ENCODED_LIST
+ handleRequest(HttpMethod.Post, "/api/presentations") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiablePresentationRequestDto.serializer(),
+ verifiablePresentationRequest))
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("The credential http://example.edu/credentials/333 has been revoked!"))
+ }
+
+ // ignore revocation
+ handleRequest(HttpMethod.Post, "/api/presentations?withRevocationValidation=false") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiablePresentationRequestDto.serializer(),
+ verifiablePresentationRequest))
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+ // clean up created wallet and singletonTestData
runBlocking {
EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN)
}
@@ -325,102 +477,21 @@ class PresentationsTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
configureStatusPages()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
+ Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
- // programmatically add base wallet and an additional one
+ // programmatically add base wallet
runBlocking {
EnvironmentTestSetup.walletService.createWallet(WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "base"))
}
-
- val validVP = """
- {
- "@context": [
- "https://www.w3.org/2018/credentials/v1"
- ],
- "id": "73e9e2f1-c0f9-4453-9619-d26244c83f15",
- "type": [
- "VerifiablePresentation"
- ],
- "holder": "did:indy:local:test:AA5EEDcn8yTfMobaTcabj9",
- "verifiableCredential": [
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D",
- "issuanceDate": "2021-06-16T18:56:59Z",
- "expirationDate": "2026-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master",
- "degreeType": "Undergraduate",
- "name": "Master of Test"
- },
- "college": "Test",
- "id": "did:indy:local:test:AA5EEDcn8yTfMobaTcabj9"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:13:16Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
- }
- },
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D",
- "issuanceDate": "2021-06-16T18:56:59Z",
- "expirationDate": "2027-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master1",
- "degreeType": "Undergraduate2",
- "name": "Master of Test1"
- },
- "college": "Test2",
- "id": "did:indy:local:test:AA5EEDcn8yTfMobaTcabj9"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:16:45Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..6oIPVm3ealRVzpgiFKItyIzVWlNUT150fbh9OcBElj9FvaICAd-wc1yzrwka3ns1SmrPFsWIIe0wC1rJQLISBA"
- }
- }
- ],
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:28:44Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:AA5EEDcn8yTfMobaTcabj9#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..FYkZonVoXojBcwC3yWvhiyBh4uR0hNZR1qyu5cZS5_PXiB8BEyKUolWzqBAX_u7bbKD5QGqbTECs9qLyD63wAg"
- }
- }
- """.trimIndent()
+ //TODO replace did:sov in all used json files when indy did method is supported by AcaPy
+ val validVP = File("./src/test/resources/presentations-test-data/validVP.json").readText(Charsets.UTF_8)
handleRequest(HttpMethod.Post, "/api/presentations/validation") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
@@ -432,91 +503,7 @@ class PresentationsTest {
assertTrue { output.valid }
}
- val vpWithInvalidDID = """
- {
- "@context": [
- "https://www.w3.org/2018/credentials/v1"
- ],
- "id": "73e9e2f1-c0f9-4453-9619-d26244c83f15",
- "type": [
- "VerifiablePresentation"
- ],
- "holder": "did:local:indy:test:AA5EEDcn8yTfMobaTcabj9",
- "verifiableCredential": [
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:local:indy:test:LCNSw1JxSTDw7EpR1UMG7D",
- "issuanceDate": "2021-06-16T18:56:59Z",
- "expirationDate": "2026-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master",
- "degreeType": "Undergraduate",
- "name": "Master of Test"
- },
- "college": "Test",
- "id": "did:local:indy:test:AA5EEDcn8yTfMobaTcabj9"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:13:16Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:local:indy:test:LCNSw1JxSTDw7EpR1UMG7D#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
- }
- },
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:local:indy:test:LCNSw1JxSTDw7EpR1UMG7D",
- "issuanceDate": "2021-06-16T18:56:59Z",
- "expirationDate": "2027-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master1",
- "degreeType": "Undergraduate2",
- "name": "Master of Test1"
- },
- "college": "Test2",
- "id": "did:local:indy:test:AA5EEDcn8yTfMobaTcabj9"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:16:45Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:local:indy:test:LCNSw1JxSTDw7EpR1UMG7D#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..6oIPVm3ealRVzpgiFKItyIzVWlNUT150fbh9OcBElj9FvaICAd-wc1yzrwka3ns1SmrPFsWIIe0wC1rJQLISBA"
- }
- }
- ],
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:28:44Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:local:indy:test:AA5EEDcn8yTfMobaTcabj9#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..FYkZonVoXojBcwC3yWvhiyBh4uR0hNZR1qyu5cZS5_PXiB8BEyKUolWzqBAX_u7bbKD5QGqbTECs9qLyD63wAg"
- }
- }
- """.trimIndent()
+ val vpWithInvalidDID = File("./src/test/resources/presentations-test-data/vpWithInvalidDID.json").readText(Charsets.UTF_8)
handleRequest(HttpMethod.Post, "/api/presentations/validation") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
@@ -526,54 +513,7 @@ class PresentationsTest {
assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
}
-
- val vpWithoutProof = """
- {
- "@context": [
- "https://www.w3.org/2018/credentials/v1"
- ],
- "id": "73e9e2f1-c0f9-4453-9619-d26244c83f15",
- "type": [
- "VerifiablePresentation"
- ],
- "holder": "did:indy:local:test:AA5EEDcn8yTfMobaTcabj9",
- "verifiableCredential": [
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D",
- "issuanceDate": "2021-06-16T18:56:59Z",
- "expirationDate": "2026-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master",
- "degreeType": "Undergraduate",
- "name": "Master of Test"
- },
- "college": "Test",
- "id": "did:indy:local:test:AA5EEDcn8yTfMobaTcabj9"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:13:16Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
- }
- }
- ]
- }
- """.trimIndent()
-
+ val vpWithoutProof = File("./src/test/resources/presentations-test-data/vpWithoutProof.json").readText(Charsets.UTF_8)
handleRequest(HttpMethod.Post, "/api/presentations/validation") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
@@ -584,60 +524,7 @@ class PresentationsTest {
assertTrue(response.content!!.contains("Cannot verify verifiable presentation due to missing proof"))
}
- val vpWithOutdatedVC = """
- {
- "@context": [
- "https://www.w3.org/2018/credentials/v1"
- ],
- "id": "0c96720a-734d-41ea-89ca-92b4f8ba2fa8",
- "type": [
- "VerifiablePresentation"
- ],
- "holder": "did:indy:local:test:YHXZLLSLnKxz5D2HQaKXcP",
- "verifiableCredential": [
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:indy:local:test:M6Mis1fZKuhEw71GNY3TAb",
- "issuanceDate": "2021-06-16T18:56:59Z",
- "expirationDate": "2021-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master1",
- "degreeType": "Undergraduate2",
- "name": "Master of Test11"
- },
- "college": "Test2",
- "id": "did:indy:local:test:YHXZLLSLnKxz5D2HQaKXcP"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-13T14:18:56Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:M6Mis1fZKuhEw71GNY3TAb#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..qFl7sQ9-PUQwz7KV0ONn89AEDpx3DkUO_1LDYBHvdbw2FlPi_XM51pvh_6tx4fLwyMlZEp3VdAbxyRR-AdZWDw"
- }
- }
- ],
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-13T14:19:32Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:YHXZLLSLnKxz5D2HQaKXcP#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..lAbqnkVHOzt5GGuTebAgqBdt0p5vZvn7Z4dIarKPW3_BCSv9ATDzegkjzqOM3B91WP7flp93fgqmq5T-bT9YBw"
- }
- }
- """.trimIndent()
-
+ val vpWithOutdatedVC = File("./src/test/resources/presentations-test-data/vpWithOutdatedVC.json").readText(Charsets.UTF_8)
val withDateValidation = WithDateValidation()
assertFalse { withDateValidation.withDateValidation!! }
@@ -652,60 +539,7 @@ class PresentationsTest {
"Verifiable credential http://example.edu/credentials/3735 expired 2021-06-17T18:56:59Z"))
}
- val vpWithFutureVC = """
- {
- "@context": [
- "https://www.w3.org/2018/credentials/v1"
- ],
- "id": "7aed00f7-8e04-4093-b467-9bd084b42086",
- "type": [
- "VerifiablePresentation"
- ],
- "holder": "did:indy:local:test:YHXZLLSLnKxz5D2HQaKXcP",
- "verifiableCredential": [
- {
- "id": "http://example.edu/credentials/3888",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:indy:local:test:M6Mis1fZKuhEw71GNY3TAb",
- "issuanceDate": "2999-06-16T18:56:59Z",
- "expirationDate": "2999-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master1",
- "degreeType": "Undergraduate2",
- "name": "Master of Test11"
- },
- "college": "Test2",
- "id": "did:indy:local:test:YHXZLLSLnKxz5D2HQaKXcP"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-21T13:17:21Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:M6Mis1fZKuhEw71GNY3TAb#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..CvGRIw0aqQrXsXy1n3ChGfN1xs0Y56eiwS3spTlf_Ph4l5OQSFKId7SKNxBpFfI4GaQMKi8ajDVXvaIdT-N0DA"
- }
- }
- ],
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-21T13:18:07Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:YHXZLLSLnKxz5D2HQaKXcP#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..pnipnhAJ34b9k8kBpRJfEAOdbiaSZK38TAJveSYyoBrKAMhF3DAJ_b0pChHvgghzy9QiAsal5ZFkl5fakIGwAg"
- }
- }
- """.trimIndent()
-
+ val vpWithFutureVC = File("./src/test/resources/presentations-test-data/vpWithFutureVC.json").readText(Charsets.UTF_8)
handleRequest(HttpMethod.Post, "/api/presentations/validation?withDateValidation=true") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
@@ -718,53 +552,7 @@ class PresentationsTest {
"in verifiable credential http://example.edu/credentials/3888"))
}
- val vpWithVcWithoutProof = """
- {
- "@context": [
- "https://www.w3.org/2018/credentials/v1"
- ],
- "id": "d312945e-826e-49cc-9baa-3c78d090745b",
- "type": [
- "VerifiablePresentation"
- ],
- "holder": "did:indy:local:test:YHXZLLSLnKxz5D2HQaKXcP",
- "verifiableCredential": [
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:indy:local:test:M6Mis1fZKuhEw71GNY3TAb",
- "issuanceDate": "2025-06-16T18:56:59Z",
- "expirationDate": "2026-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master1",
- "degreeType": "Undergraduate2",
- "name": "Master of Test11"
- },
- "college": "Test2",
- "id": "did:indy:local:test:YHXZLLSLnKxz5D2HQaKXcP"
- }
- }
- ],
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-13T14:47:36Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:YHXZLLSLnKxz5D2HQaKXcP#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..fGJqT596Y9696mw97DVFkNZsuXU5xO-VCZWkEysOaeljl6loRZkQAVGmyzfZK4ZImcLKMFwHfgLv1E-Xxze7Bw"
- }
- }
- """.trimIndent()
-
+ val vpWithVcWithoutProof = File("./src/test/resources/presentations-test-data/vpWithVcWithoutProof.json").readText(Charsets.UTF_8)
handleRequest(HttpMethod.Post, "/api/presentations/validation") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
@@ -777,59 +565,7 @@ class PresentationsTest {
" http://example.edu/credentials/3735 due to missing proof"))
}
- val vpWithoutHolder = """
- {
- "@context": [
- "https://www.w3.org/2018/credentials/v1"
- ],
- "id": "73e9e2f1-c0f9-4453-9619-d26244c83f15",
- "type": [
- "VerifiablePresentation"
- ],
- "verifiableCredential": [
- {
- "id": "http://example.edu/credentials/3735",
- "@context": [
- "https://www.w3.org/2018/credentials/v1",
- "https://www.w3.org/2018/credentials/examples/v1"
- ],
- "type": [
- "University-Degree-Credential",
- "VerifiableCredential"
- ],
- "issuer": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D",
- "issuanceDate": "2021-06-16T18:56:59Z",
- "expirationDate": "2026-06-17T18:56:59Z",
- "credentialSubject": {
- "givenName": "TestAfterQuestion",
- "familyName": "Student",
- "degree": {
- "type": "Master",
- "degreeType": "Undergraduate",
- "name": "Master of Test"
- },
- "college": "Test",
- "id": "did:indy:local:test:AA5EEDcn8yTfMobaTcabj9"
- },
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:13:16Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:LCNSw1JxSTDw7EpR1UMG7D#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
- }
- }
- ],
- "proof": {
- "type": "Ed25519Signature2018",
- "created": "2022-07-12T12:28:44Z",
- "proofPurpose": "assertionMethod",
- "verificationMethod": "did:indy:local:test:AA5EEDcn8yTfMobaTcabj9#key-1",
- "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..FYkZonVoXojBcwC3yWvhiyBh4uR0hNZR1qyu5cZS5_PXiB8BEyKUolWzqBAX_u7bbKD5QGqbTECs9qLyD63wAg"
- }
- }
- """.trimIndent()
-
+ val vpWithoutHolder: String = File("./src/test/resources/presentations-test-data/vpWithoutHolder.json").readText(Charsets.UTF_8)
handleRequest(HttpMethod.Post, "/api/presentations/validation") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
@@ -863,13 +599,167 @@ class PresentationsTest {
}.apply {
assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
}
- SingletonTestData.isValidVerifiableCredential = true
// clean up created wallets
runBlocking {
EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN) // base wallet
assertEquals(0, EnvironmentTestSetup.walletService.getAll().size)
}
+ }
+ }
+
+ @Test
+ fun testVerifyPresentationWithRevocationCheck() {
+ withTestApplication({
+ EnvironmentTestSetup.setupEnvironment(environment)
+ configurePersistence()
+ configureOpenAPI()
+ configureSecurity()
+ configureRouting(EnvironmentTestSetup.walletService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
+ configureSerialization()
+ configureStatusPages()
+ Services.walletService = EnvironmentTestSetup.walletService
+ Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
+ Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
+ }) {
+ // programmatically add base wallet
+ runBlocking {
+ val walletDto = EnvironmentTestSetup.walletService.createWallet(
+ WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "base")
+ )
+ SingletonTestData.baseWalletDID = walletDto.did
+ SingletonTestData.baseWalletVerKey = walletDto.verKey!!
+ SingletonTestData.credentialIndex = 3
+ SingletonTestData.revocationListName = walletDto.revocationListName!!
+ }
+
+ val vpWithPlaceholders = File("./src/test/resources/presentations-test-data/vpWithPlaceholders.json").readText(Charsets.UTF_8)
+ var validVp = vpWithPlaceholders.replace("", SingletonTestData.baseWalletDID)
+ .replace("", SingletonTestData.baseWalletDID)
+ .replace("",
+ "http://localhost:8080/api/credentials/status/${SingletonTestData.revocationListName}")
+ .replace("", SingletonTestData.credentialIndex.toString())
+ .replace("", CredentialStatus.CREDENTIAL_TYPE)
+ .replace("", CredentialStatus.STATUS_PURPOSE)
+ SingletonTestData.encodedList = EnvironmentTestSetup.NONE_REVOKED_ENCODED_LIST
+ handleRequest(HttpMethod.Post, "/api/presentations/validation") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(validVp)
+ }.apply {
+ val output = Json.decodeFromString(response.content!!)
+ assertEquals(HttpStatusCode.OK, response.status())
+ assertTrue { output.valid }
+ }
+
+ SingletonTestData.encodedList = EnvironmentTestSetup.ZERO_THIRD_REVOKED_ENCODED_LIST
+ handleRequest(HttpMethod.Post, "/api/presentations/validation") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(validVp)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("The credential http://example.edu/credentials/3735 has been revoked!"))
+ }
+
+ var vpWithWrongStatusType = vpWithPlaceholders.replace("", SingletonTestData.baseWalletDID)
+ .replace("", SingletonTestData.baseWalletDID)
+ .replace("",
+ "http://localhost:8080/api/credentials/status/${SingletonTestData.revocationListName}")
+ .replace("", SingletonTestData.credentialIndex.toString())
+ .replace("", "WRONG_TYPE")
+ .replace("", CredentialStatus.STATUS_PURPOSE)
+ handleRequest(HttpMethod.Post, "/api/presentations/validation") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(vpWithWrongStatusType)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("has invalid credential status 'Type'"))
+ }
+
+ var vpWithWrongStatusPurpose = vpWithPlaceholders.replace("", SingletonTestData.baseWalletDID)
+ .replace("", SingletonTestData.baseWalletDID)
+ .replace("",
+ "http://localhost:8080/api/credentials/status/${SingletonTestData.revocationListName}")
+ .replace("", SingletonTestData.credentialIndex.toString())
+ .replace("", CredentialStatus.CREDENTIAL_TYPE)
+ .replace("", "WRONG_PURPOSE")
+ handleRequest(HttpMethod.Post, "/api/presentations/validation") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(vpWithWrongStatusPurpose)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("has invalid 'statusPurpose'"))
+ }
+
+ var vpWithEmptyStatusListIndex =
+ vpWithPlaceholders.replace("", SingletonTestData.baseWalletDID)
+ .replace("", SingletonTestData.baseWalletDID)
+ .replace("",
+ "http://localhost:8080/api/credentials/status/${SingletonTestData.revocationListName}")
+ .replace("", " ")
+ .replace("", CredentialStatus.CREDENTIAL_TYPE)
+ .replace("", CredentialStatus.STATUS_PURPOSE)
+ handleRequest(HttpMethod.Post, "/api/presentations/validation") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(vpWithEmptyStatusListIndex)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("has invalid 'statusListIndex'"))
+ }
+
+ var vpWithEmptyStatusListUrl = vpWithPlaceholders.replace("", SingletonTestData.baseWalletDID)
+ .replace("", SingletonTestData.baseWalletDID)
+ .replace("", " ")
+ .replace("", SingletonTestData.credentialIndex.toString())
+ .replace("", CredentialStatus.CREDENTIAL_TYPE)
+ .replace("", CredentialStatus.STATUS_PURPOSE)
+ handleRequest(HttpMethod.Post, "/api/presentations/validation") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(vpWithEmptyStatusListUrl)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("has invalid 'statusListCredential'"))
+ }
+
+ var vpWithIssuerConflict =
+ vpWithPlaceholders.replace("",
+ "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}AA5EEDcn8yTfMobaTcabj9")
+ .replace("", SingletonTestData.baseWalletDID)
+ .replace("",
+ "http://localhost:8080/api/credentials/status/${SingletonTestData.revocationListName}")
+ .replace("", SingletonTestData.credentialIndex.toString())
+ .replace("", CredentialStatus.CREDENTIAL_TYPE)
+ .replace("", CredentialStatus.STATUS_PURPOSE)
+ handleRequest(HttpMethod.Post, "/api/presentations/validation") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(vpWithIssuerConflict)
+ }.apply {
+ assertEquals(HttpStatusCode.UnprocessableEntity, response.status())
+ assertTrue(response.content!!.contains("The issuer of the given credential " +
+ "http://example.edu/credentials/3735 is not the issuer of the StatusListCredential"))
+ }
+
+ // clean up created wallet
+ runBlocking {
+ EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN) // base wallet
+ assertEquals(0, EnvironmentTestSetup.walletService.getAll().size)
+ }
}
}
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/RevocationMockedService.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/RevocationMockedService.kt
new file mode 100644
index 000000000..c825fcece
--- /dev/null
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/RevocationMockedService.kt
@@ -0,0 +1,76 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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
+
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.*
+import org.eclipse.tractusx.managedidentitywallets.services.IRevocationService
+import java.util.*
+
+class RevocationMockedService(private val networkIdentifier: String): IRevocationService {
+
+ override suspend fun registerList(profileName: String, issueCredential: Boolean) = UUID.randomUUID().toString()
+
+ override suspend fun addStatusEntry(profileName: String): CredentialStatus {
+ return CredentialStatus(
+ statusId = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}#${SingletonTestData.credentialIndex}",
+ credentialType = "StatusList2021Entry",
+ statusPurpose = "revocation",
+ index = SingletonTestData.credentialIndex.toString(),
+ listUrl = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}"
+ )
+ }
+
+ override suspend fun getStatusListCredentialOfManagedWallet(listName: String): VerifiableCredentialDto {
+ return getStatusListCredentialOfUrl("Mocked-URL")
+ }
+
+ override suspend fun getStatusListCredentialOfUrl(statusListUrl: String): VerifiableCredentialDto {
+ val didOfIssuer = "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(SingletonTestData.baseWalletDID)}"
+ return VerifiableCredentialDto(
+ id = "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}",
+ context = listOf( "https://www.w3.org/2018/credentials/v1","https://w3id.org/vc/status-list/2021/v1"),
+ type = listOf("VerifiableCredential", "StatusList2021Credential"),
+ issuer = didOfIssuer,
+ issuanceDate = "2022-09-01T11:59:00Z",
+ credentialSubject = mapOf(
+ "id" to "https://example.com/api/credentials/status/${SingletonTestData.revocationListName}#list",
+ "type" to "StatusList2021",
+ "statusPurpose" to "revocation",
+ "encodedList" to SingletonTestData.encodedList
+ ),
+ proof = LdProofDto(
+ type= "Ed25519Signature2018",
+ created = "2022-09-01T11:59:01Z",
+ proofPurpose = "assertionMethod",
+ verificationMethod = "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}${getIdentifierOfDid(SingletonTestData.baseWalletDID)}#key-1",
+ jws ="eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..DwNECMRBYRbnyGrqL16O97rGdLuuZCsDf9Qc6_RLiValwMdRsD9WcrBnWuBAHDIK_EQ8copXgCEWSZLj-RR9DQ"
+ )
+ )
+ }
+
+ override suspend fun revoke(profileName: String, indexOfCredential: Long) { }
+
+ override suspend fun issueStatusListCredentials(profileName: String?, force: Boolean?) { }
+
+ private fun getIdentifierOfDid(did: String): String {
+ val elementsOfDid: List = did.split(":")
+ return elementsOfDid[elementsOfDid.size - 1]
+ }
+}
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/SelfManagedInteractionTest.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/SelfManagedInteractionTest.kt
new file mode 100644
index 000000000..f05003fed
--- /dev/null
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/SelfManagedInteractionTest.kt
@@ -0,0 +1,243 @@
+/********************************************************************************
+ * Copyright (c) 2021,2022 Contributors to the CatenaX (ng) GitHub Organisation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, 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
+
+import io.ktor.http.*
+import io.ktor.server.testing.*
+import kotlinx.coroutines.runBlocking
+import kotlinx.serialization.json.Json
+import org.eclipse.tractusx.managedidentitywallets.models.*
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.JsonLdContexts
+import org.eclipse.tractusx.managedidentitywallets.models.ssi.VerifiableCredentialIssuanceFlowRequestDto
+import org.eclipse.tractusx.managedidentitywallets.plugins.*
+import org.eclipse.tractusx.managedidentitywallets.routes.appRoutes
+import org.hyperledger.aries.api.connection.ConnectionState
+import org.jetbrains.exposed.sql.transactions.transaction
+import kotlin.test.*
+
+@kotlinx.serialization.ExperimentalSerializationApi
+class SelfManagedInteractionTest {
+
+ private val server = TestServer().initServer()
+
+ @BeforeTest
+ fun setup() {
+ server.start()
+ }
+
+ @AfterTest
+ fun cleanSingletonTestData() {
+ SingletonTestData.cleanSingletonTestData()
+ }
+
+ @AfterTest
+ fun tearDown() {
+ server.stop(1000, 10000)
+ }
+
+ @Test
+ fun testRegisterSelfManagedWalletAndTriggerIssuanceFlow() { // true
+ withTestApplication({
+ EnvironmentTestSetup.setupEnvironment(environment)
+ configurePersistence()
+ configureOpenAPI()
+ configureSecurity()
+ configureRouting(EnvironmentTestSetup.walletService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
+ configureSerialization()
+ configureStatusPages()
+ Services.walletService = EnvironmentTestSetup.walletService
+ Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
+ Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
+ }) {
+ // programmatically add a wallet
+ runBlocking {
+ val baseWallet = EnvironmentTestSetup.walletService.createWallet(
+ WalletCreateDto(EnvironmentTestSetup.DEFAULT_BPN, "name1")
+ )
+ SingletonTestData.baseWalletDID = baseWallet.did
+ SingletonTestData.connectionId = "123"
+ SingletonTestData.threadId = "456"
+ }
+
+ val selfManagedWalletCreateDto = SelfManagedWalletCreateDto(
+ bpn = "e-bpn",
+ did = "${SingletonTestData.getDidMethodPrefixWithNetworkIdentifier()}YHXZLLSLnKxz5D2HQaKXcP",
+ name = "e-name",
+ webhookUrl = "http://example.com/webhook"
+ )
+ handleRequest(HttpMethod.Post, "/api/wallets/self-managed-wallets") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(Json.encodeToString(SelfManagedWalletCreateDto.serializer(), selfManagedWalletCreateDto))
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+
+ runBlocking {
+ transaction {
+ val connections = EnvironmentTestSetup.connectionRepository
+ .getConnections(SingletonTestData.baseWalletDID, null)
+ assertEquals(1, connections.size)
+ assertEquals(ConnectionState.REQUEST.name, connections[0].state)
+
+ val webhook = EnvironmentTestSetup.webhookService.getWebhookByThreadId(SingletonTestData.threadId)
+ assertNotNull(webhook)
+ assertEquals(ConnectionState.REQUEST.name, webhook.state)
+
+ EnvironmentTestSetup.webhookRepository.deleteWebhook(SingletonTestData.threadId)
+ val webhookConnection = EnvironmentTestSetup.webhookService.getWebhookByThreadId(SingletonTestData.threadId)
+ assertEquals(null, webhookConnection)
+
+ val listOfBpn = EnvironmentTestSetup.walletService.getAllBpns()
+ assertEquals(2, listOfBpn.size)
+ }
+ }
+
+ // Issuance flow
+ val vc = VerifiableCredentialIssuanceFlowRequestDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuerIdentifier = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2299-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University"),
+ holderIdentifier = selfManagedWalletCreateDto.did,
+ isRevocable = true,
+ webhookUrl = "http://example.com/webhook"
+ )
+
+ SingletonTestData.threadId = "987"
+ handleRequest(HttpMethod.Post, "/api/credentials/issuance-flow") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiableCredentialIssuanceFlowRequestDto.serializer(),
+ vc,
+ )
+ )
+ }.apply {
+ assertEquals(HttpStatusCode.InternalServerError, response.status())
+ assertTrue(response.content!!.contains("Invalid connection between"))
+
+ }
+
+ // force update connection to `COMPLETED`
+ runBlocking {
+ transaction {
+ EnvironmentTestSetup.connectionRepository.updateConnectionState(
+ SingletonTestData.connectionId,
+ ConnectionState.COMPLETED
+ )
+ }
+ }
+
+ handleRequest(HttpMethod.Post, "/api/credentials/issuance-flow") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(VerifiableCredentialIssuanceFlowRequestDto.serializer(), vc)
+ )
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+
+ // remove Webhook element to clean and avoid conflict
+ runBlocking {
+ transaction {
+ EnvironmentTestSetup.webhookRepository.deleteWebhook(SingletonTestData.threadId)
+ val webhookCredential = EnvironmentTestSetup.webhookService.getWebhookByThreadId(SingletonTestData.threadId)
+ assertEquals(null, webhookCredential)
+ }
+ }
+
+ val vcWithCredentialSubjectId = VerifiableCredentialIssuanceFlowRequestDto(
+ context = listOf(
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_V1,
+ JsonLdContexts.JSONLD_CONTEXT_W3C_2018_CREDENTIALS_EXAMPLES_V1
+ ),
+ id = "http://example.edu/credentials/3732",
+ type = listOf("University-Degree-Credential, VerifiableCredential"),
+ issuerIdentifier = SingletonTestData.baseWalletDID,
+ issuanceDate = "2019-06-16T18:56:59Z",
+ expirationDate = "2299-06-17T18:56:59Z",
+ credentialSubject = mapOf("college" to "Test-University", "id" to selfManagedWalletCreateDto.did),
+ isRevocable = true,
+ webhookUrl = "http://example.com/webhook"
+ )
+
+ handleRequest(HttpMethod.Post, "/api/credentials/issuance-flow") {
+ addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.UPDATE_TOKEN}")
+ addHeader(HttpHeaders.Accept, ContentType.Application.Json.toString())
+ addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
+ setBody(
+ Json.encodeToString(
+ VerifiableCredentialIssuanceFlowRequestDto.serializer(),
+ vcWithCredentialSubjectId,
+ )
+ )
+ }.apply {
+ assertEquals(HttpStatusCode.Created, response.status())
+ }
+
+ runBlocking {
+ transaction {
+ val webhookCredential =
+ EnvironmentTestSetup.webhookService.getWebhookByThreadId(SingletonTestData.threadId)
+ assertNotNull(webhookCredential)
+ assertEquals(vc.webhookUrl, webhookCredential.webhookUrl)
+ }
+ }
+
+ // clean
+ runBlocking {
+ transaction {
+ EnvironmentTestSetup.webhookRepository.deleteWebhook(SingletonTestData.threadId)
+ val webhookCredential = EnvironmentTestSetup.webhookService.getWebhookByThreadId(SingletonTestData.threadId)
+ assertEquals(null, webhookCredential)
+ }
+
+ var connections = EnvironmentTestSetup.connectionRepository.getAll()
+ assertEquals(1, connections.size)
+
+ EnvironmentTestSetup.walletService.deleteWallet("e-bpn")
+ connections = EnvironmentTestSetup.connectionRepository.getAll()
+ assertEquals(0, connections.size)
+
+ EnvironmentTestSetup.walletService.deleteWallet(EnvironmentTestSetup.DEFAULT_BPN) // base wallet
+ val listOfBpn = EnvironmentTestSetup.walletService.getAllBpns()
+ assertEquals(0, listOfBpn.size)
+ }
+
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/SingletonTestData.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/SingletonTestData.kt
new file mode 100644
index 000000000..9224204c0
--- /dev/null
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/SingletonTestData.kt
@@ -0,0 +1,33 @@
+package org.eclipse.tractusx.managedidentitywallets
+
+object SingletonTestData {
+ lateinit var baseWalletDID: String
+ lateinit var baseWalletVerKey: String
+ lateinit var signCredentialResponse: String
+ var isValidVerifiableCredential: Boolean = true
+ var isValidVerifiablePresentation: Boolean = true
+ lateinit var revocationListName: String
+ lateinit var credentialIndex: Number
+ lateinit var encodedList: String
+ lateinit var connectionId: String
+ lateinit var threadId: String
+
+ fun cleanSingletonTestData() {
+ this.baseWalletDID = ""
+ this.baseWalletVerKey = ""
+ this.signCredentialResponse = ""
+ this.isValidVerifiableCredential = true
+ this.isValidVerifiablePresentation = true
+ this.revocationListName = ""
+ this.credentialIndex = 0
+ this.encodedList = EnvironmentTestSetup.NONE_REVOKED_ENCODED_LIST
+ this.connectionId = ""
+ this.threadId = ""
+ }
+
+ fun getDidMethodPrefixWithNetworkIdentifier(): String {
+ //TODO replace implementation when indy method is supported by AcaPy
+ //return "did:indy:${EnvironmentTestSetup.NETWORK_ID}:"
+ return "did:sov:"
+ }
+}
diff --git a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/WalletsTest.kt b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/WalletsTest.kt
index 48b2a426c..e3355e302 100644
--- a/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/WalletsTest.kt
+++ b/src/test/kotlin/org/eclipse/tractusx/managedidentitywallets/WalletsTest.kt
@@ -52,11 +52,13 @@ class WalletsTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
handleRequest(HttpMethod.Get, "/api/wallets") {
addHeader(HttpHeaders.Authorization, "Bearer ${EnvironmentTestSetup.VIEW_TOKEN}")
@@ -171,12 +173,14 @@ class WalletsTest {
configureSecurity()
configureOpenAPI()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
configureStatusPages()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
handleRequest(HttpMethod.Get, "/api/wallets/${EnvironmentTestSetup.DEFAULT_BPN}") {
@@ -263,7 +267,7 @@ class WalletsTest {
setBody("""{"bpn":"bpn4", "name": "name4"}""")
}.apply {
assertEquals(HttpStatusCode.Conflict, response.status())
- assertTrue(response.content!!.contains("Wallet with identifier bpn4 already exists!"))
+ assertTrue(response.content!!.contains("Wallet with given identifier already exists!"))
}
// clean up created wallets
@@ -283,11 +287,13 @@ class WalletsTest {
configureOpenAPI()
configureSecurity()
configureRouting(EnvironmentTestSetup.walletService)
- appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService)
+ appRoutes(EnvironmentTestSetup.walletService, EnvironmentTestSetup.bpdService, EnvironmentTestSetup.revocationMockedService, EnvironmentTestSetup.utilsService)
configureSerialization()
Services.walletService = EnvironmentTestSetup.walletService
Services.businessPartnerDataService = EnvironmentTestSetup.bpdService
Services.utilsService = EnvironmentTestSetup.utilsService
+ Services.revocationService = EnvironmentTestSetup.revocationMockedService
+ Services.webhookService = EnvironmentTestSetup.webhookService
}) {
var verKey: String
@@ -317,7 +323,7 @@ class WalletsTest {
assertEquals(HttpStatusCode.NotFound, response.status())
}
}
- assertTrue(exception.message!!.contains("non_existing_bpn not found"))
+ assertTrue(exception.message!!.contains("not found"))
exception = assertFailsWith {
handleRequest(HttpMethod.Post, "/api/wallets/non_base_bpn/public") {
diff --git a/src/test/resources/credentials-test-data/vcWithReplaceableSubjectId.json b/src/test/resources/credentials-test-data/vcWithReplaceableSubjectId.json
new file mode 100644
index 000000000..f260cb83e
--- /dev/null
+++ b/src/test/resources/credentials-test-data/vcWithReplaceableSubjectId.json
@@ -0,0 +1,32 @@
+{
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:sov:LCNSw1JxSTDw7EpR1UMG7D",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2026-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master",
+ "degreeType": "Undergraduate",
+ "name": "Master of Test"
+ },
+ "college": "Test",
+ "id": ""
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:13:16Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:LCNSw1JxSTDw7EpR1UMG7D#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/presentations-test-data/validVP.json b/src/test/resources/presentations-test-data/validVP.json
new file mode 100644
index 000000000..ad2ad0fef
--- /dev/null
+++ b/src/test/resources/presentations-test-data/validVP.json
@@ -0,0 +1,83 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "id": "73e9e2f1-c0f9-4453-9619-d26244c83f15",
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:sov:AA5EEDcn8yTfMobaTcabj9",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:sov:LCNSw1JxSTDw7EpR1UMG7D",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2026-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master",
+ "degreeType": "Undergraduate",
+ "name": "Master of Test"
+ },
+ "college": "Test",
+ "id": "did:sov:AA5EEDcn8yTfMobaTcabj9"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:13:16Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:LCNSw1JxSTDw7EpR1UMG7D#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
+ }
+ },
+ {
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:sov:LCNSw1JxSTDw7EpR1UMG7D",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2027-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master1",
+ "degreeType": "Undergraduate2",
+ "name": "Master of Test1"
+ },
+ "college": "Test2",
+ "id": "did:sov:AA5EEDcn8yTfMobaTcabj9"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:16:45Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:LCNSw1JxSTDw7EpR1UMG7D#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..6oIPVm3ealRVzpgiFKItyIzVWlNUT150fbh9OcBElj9FvaICAd-wc1yzrwka3ns1SmrPFsWIIe0wC1rJQLISBA"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:28:44Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:AA5EEDcn8yTfMobaTcabj9#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..FYkZonVoXojBcwC3yWvhiyBh4uR0hNZR1qyu5cZS5_PXiB8BEyKUolWzqBAX_u7bbKD5QGqbTECs9qLyD63wAg"
+ }
+}
diff --git a/src/test/resources/presentations-test-data/vpWithFutureVC.json b/src/test/resources/presentations-test-data/vpWithFutureVC.json
new file mode 100644
index 000000000..31747e0f4
--- /dev/null
+++ b/src/test/resources/presentations-test-data/vpWithFutureVC.json
@@ -0,0 +1,51 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "id": "7aed00f7-8e04-4093-b467-9bd084b42086",
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:sov:YHXZLLSLnKxz5D2HQaKXcP",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3888",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:sov:M6Mis1fZKuhEw71GNY3TAb",
+ "issuanceDate": "2999-06-16T18:56:59Z",
+ "expirationDate": "2999-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master1",
+ "degreeType": "Undergraduate2",
+ "name": "Master of Test11"
+ },
+ "college": "Test2",
+ "id": "did:sov:YHXZLLSLnKxz5D2HQaKXcP"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-21T13:17:21Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:M6Mis1fZKuhEw71GNY3TAb#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..CvGRIw0aqQrXsXy1n3ChGfN1xs0Y56eiwS3spTlf_Ph4l5OQSFKId7SKNxBpFfI4GaQMKi8ajDVXvaIdT-N0DA"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-21T13:18:07Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:YHXZLLSLnKxz5D2HQaKXcP#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..pnipnhAJ34b9k8kBpRJfEAOdbiaSZK38TAJveSYyoBrKAMhF3DAJ_b0pChHvgghzy9QiAsal5ZFkl5fakIGwAg"
+ }
+}
diff --git a/src/test/resources/presentations-test-data/vpWithInvalidDID.json b/src/test/resources/presentations-test-data/vpWithInvalidDID.json
new file mode 100644
index 000000000..0d2084f01
--- /dev/null
+++ b/src/test/resources/presentations-test-data/vpWithInvalidDID.json
@@ -0,0 +1,83 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "id": "73e9e2f1-c0f9-4453-9619-d26244c83f15",
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:local:AA5EEDcn8yTfMobaTcabj9",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:local:LCNSw1JxSTDw7EpR1UMG7D",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2026-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master",
+ "degreeType": "Undergraduate",
+ "name": "Master of Test"
+ },
+ "college": "Test",
+ "id": "did:sov:AA5EEDcn8yTfMobaTcabj9"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:13:16Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:LCNSw1JxSTDw7EpR1UMG7D#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
+ }
+ },
+ {
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:local:LCNSw1JxSTDw7EpR1UMG7D",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2027-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master1",
+ "degreeType": "Undergraduate2",
+ "name": "Master of Test1"
+ },
+ "college": "Test2",
+ "id": "did:local:AA5EEDcn8yTfMobaTcabj9"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:16:45Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:local:LCNSw1JxSTDw7EpR1UMG7D#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..6oIPVm3ealRVzpgiFKItyIzVWlNUT150fbh9OcBElj9FvaICAd-wc1yzrwka3ns1SmrPFsWIIe0wC1rJQLISBA"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:28:44Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:local:AA5EEDcn8yTfMobaTcabj9#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..FYkZonVoXojBcwC3yWvhiyBh4uR0hNZR1qyu5cZS5_PXiB8BEyKUolWzqBAX_u7bbKD5QGqbTECs9qLyD63wAg"
+ }
+}
diff --git a/src/test/resources/presentations-test-data/vpWithOutdatedVC.json b/src/test/resources/presentations-test-data/vpWithOutdatedVC.json
new file mode 100644
index 000000000..debc936ad
--- /dev/null
+++ b/src/test/resources/presentations-test-data/vpWithOutdatedVC.json
@@ -0,0 +1,51 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "id": "0c96720a-734d-41ea-89ca-92b4f8ba2fa8",
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:sov:YHXZLLSLnKxz5D2HQaKXcP",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:sov:M6Mis1fZKuhEw71GNY3TAb",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2021-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master1",
+ "degreeType": "Undergraduate2",
+ "name": "Master of Test11"
+ },
+ "college": "Test2",
+ "id": "did:sov:YHXZLLSLnKxz5D2HQaKXcP"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-13T14:18:56Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:M6Mis1fZKuhEw71GNY3TAb#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..qFl7sQ9-PUQwz7KV0ONn89AEDpx3DkUO_1LDYBHvdbw2FlPi_XM51pvh_6tx4fLwyMlZEp3VdAbxyRR-AdZWDw"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-13T14:19:32Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:YHXZLLSLnKxz5D2HQaKXcP#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..lAbqnkVHOzt5GGuTebAgqBdt0p5vZvn7Z4dIarKPW3_BCSv9ATDzegkjzqOM3B91WP7flp93fgqmq5T-bT9YBw"
+ }
+}
diff --git a/src/test/resources/presentations-test-data/vpWithPlaceholders.json b/src/test/resources/presentations-test-data/vpWithPlaceholders.json
new file mode 100644
index 000000000..0e4d921b8
--- /dev/null
+++ b/src/test/resources/presentations-test-data/vpWithPlaceholders.json
@@ -0,0 +1,90 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "id": "73e9e2f1-c0f9-4453-9619-d26244c83f15",
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:sov:AA5EEDcn8yTfMobaTcabj9",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2026-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master",
+ "degreeType": "Undergraduate",
+ "name": "Master of Test"
+ },
+ "college": "Test",
+ "id": "did:sov:AA5EEDcn8yTfMobaTcabj9"
+ },
+ "credentialStatus": {
+ "id": "#",
+ "type": "",
+ "statusPurpose": "",
+ "statusListIndex": "",
+ "statusListCredential": ""
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:13:16Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
+ }
+ },
+ {
+ "id": "http://example.edu/credentials/3333",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:sov:LCNSw1JxSTDw7EpR1UMG7D",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2027-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master1",
+ "degreeType": "Undergraduate2",
+ "name": "Master of Test1"
+ },
+ "college": "Test2",
+ "id": "did:sov:AA5EEDcn8yTfMobaTcabj9"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:16:45Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:LCNSw1JxSTDw7EpR1UMG7D#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..6oIPVm3ealRVzpgiFKItyIzVWlNUT150fbh9OcBElj9FvaICAd-wc1yzrwka3ns1SmrPFsWIIe0wC1rJQLISBA"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:28:44Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:AA5EEDcn8yTfMobaTcabj9#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..FYkZonVoXojBcwC3yWvhiyBh4uR0hNZR1qyu5cZS5_PXiB8BEyKUolWzqBAX_u7bbKD5QGqbTECs9qLyD63wAg"
+ }
+}
diff --git a/src/test/resources/presentations-test-data/vpWithVcWithoutProof.json b/src/test/resources/presentations-test-data/vpWithVcWithoutProof.json
new file mode 100644
index 000000000..35cf7ed4f
--- /dev/null
+++ b/src/test/resources/presentations-test-data/vpWithVcWithoutProof.json
@@ -0,0 +1,44 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "id": "d312945e-826e-49cc-9baa-3c78d090745b",
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:sov:YHXZLLSLnKxz5D2HQaKXcP",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:sov:M6Mis1fZKuhEw71GNY3TAb",
+ "issuanceDate": "2025-06-16T18:56:59Z",
+ "expirationDate": "2026-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master1",
+ "degreeType": "Undergraduate2",
+ "name": "Master of Test11"
+ },
+ "college": "Test2",
+ "id": "did:sov:YHXZLLSLnKxz5D2HQaKXcP"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-13T14:47:36Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:YHXZLLSLnKxz5D2HQaKXcP#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..fGJqT596Y9696mw97DVFkNZsuXU5xO-VCZWkEysOaeljl6loRZkQAVGmyzfZK4ZImcLKMFwHfgLv1E-Xxze7Bw"
+ }
+}
diff --git a/src/test/resources/presentations-test-data/vpWithoutHolder.json b/src/test/resources/presentations-test-data/vpWithoutHolder.json
new file mode 100644
index 000000000..ac9c31384
--- /dev/null
+++ b/src/test/resources/presentations-test-data/vpWithoutHolder.json
@@ -0,0 +1,50 @@
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "id": "73e9e2f1-c0f9-4453-9619-d26244c83f15",
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:sov:LCNSw1JxSTDw7EpR1UMG7D",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2026-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master",
+ "degreeType": "Undergraduate",
+ "name": "Master of Test"
+ },
+ "college": "Test",
+ "id": "did:sov:AA5EEDcn8yTfMobaTcabj9"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:13:16Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:LCNSw1JxSTDw7EpR1UMG7D#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
+ }
+ }
+ ],
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:28:44Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:AA5EEDcn8yTfMobaTcabj9#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..FYkZonVoXojBcwC3yWvhiyBh4uR0hNZR1qyu5cZS5_PXiB8BEyKUolWzqBAX_u7bbKD5QGqbTECs9qLyD63wAg"
+ }
+}
diff --git a/src/test/resources/presentations-test-data/vpWithoutProof.json b/src/test/resources/presentations-test-data/vpWithoutProof.json
new file mode 100644
index 000000000..88dd2b915
--- /dev/null
+++ b/src/test/resources/presentations-test-data/vpWithoutProof.json
@@ -0,0 +1,45 @@
+
+{
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1"
+ ],
+ "id": "73e9e2f1-c0f9-4453-9619-d26244c83f15",
+ "type": [
+ "VerifiablePresentation"
+ ],
+ "holder": "did:sov:AA5EEDcn8yTfMobaTcabj9",
+ "verifiableCredential": [
+ {
+ "id": "http://example.edu/credentials/3735",
+ "@context": [
+ "https://www.w3.org/2018/credentials/v1",
+ "https://www.w3.org/2018/credentials/examples/v1"
+ ],
+ "type": [
+ "University-Degree-Credential",
+ "VerifiableCredential"
+ ],
+ "issuer": "did:sov:LCNSw1JxSTDw7EpR1UMG7D",
+ "issuanceDate": "2021-06-16T18:56:59Z",
+ "expirationDate": "2026-06-17T18:56:59Z",
+ "credentialSubject": {
+ "givenName": "TestAfterQuestion",
+ "familyName": "Student",
+ "degree": {
+ "type": "Master",
+ "degreeType": "Undergraduate",
+ "name": "Master of Test"
+ },
+ "college": "Test",
+ "id": "did:sov:AA5EEDcn8yTfMobaTcabj9"
+ },
+ "proof": {
+ "type": "Ed25519Signature2018",
+ "created": "2022-07-12T12:13:16Z",
+ "proofPurpose": "assertionMethod",
+ "verificationMethod": "did:sov:LCNSw1JxSTDw7EpR1UMG7D#key-1",
+ "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..0_1pSjyxk4MCPkaatFlv78rTiE6JkI4iXM9QEOPwIGwLiyORkkKPe6TwaHoVvuarouC7ozpGZxWEGmVRqfiWDg"
+ }
+ }
+ ]
+}
diff --git a/ui-src/DEPENDENCIES b/ui-src/DEPENDENCIES
index a45d848e7..17eddc57c 100644
--- a/ui-src/DEPENDENCIES
+++ b/ui-src/DEPENDENCIES
@@ -157,7 +157,7 @@ npm/npmjs/-/eslint-utils/3.0.0, MIT, approved, #2431
npm/npmjs/-/eslint-visitor-keys/2.1.0, Apache-2.0, approved, #2433
npm/npmjs/-/eslint-visitor-keys/3.3.0, Apache-2.0, approved, #2696
npm/npmjs/-/eslint-webpack-plugin/3.1.1, MIT, approved, clearlydefined
-npm/npmjs/-/eslint/8.15.0, LGPL-2.0-or-later AND MIT AND ISC AND OFL-1.1 AND (CC-BY-3.0 AND CC-BY-SA-2.0) AND (JSON AND LicenseRef-scancode-proprietary-license AND MIT), restricted, #3095
+npm/npmjs/-/eslint/8.15.0, , approved, #3095
npm/npmjs/-/espree/9.3.1, BSD-2-Clause AND BSD-3-Clause AND MIT, approved, #2697
npm/npmjs/-/espree/9.3.2, BSD-2-Clause AND BSD-3-Clause AND MIT, approved, #2697
npm/npmjs/-/esquery/1.4.0, BSD-3-Clause, approved, #1100
diff --git a/ui-src/yarn.lock b/ui-src/yarn.lock
index 0735ce7f6..d382f1004 100644
--- a/ui-src/yarn.lock
+++ b/ui-src/yarn.lock
@@ -234,15 +234,37 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
+"@jridgewell/gen-mapping@^0.3.0":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
+ integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
"@jridgewell/resolve-uri@^3.0.3":
- version "3.0.5"
- resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c"
- integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+ integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/source-map@^0.3.2":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
+ integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@^1.4.10":
- version "1.4.11"
- resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec"
- integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==
+ version "1.4.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@^0.3.0":
version "0.3.4"
@@ -252,6 +274,14 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
+"@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
+ integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.0.3"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
"@leichtgewicht/ip-codec@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0"
@@ -1082,12 +1112,7 @@ acorn-walk@^8.0.0, acorn-walk@^8.0.2:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
-acorn@^8.0.4, acorn@^8.0.5, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0:
- version "8.7.0"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
- integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
-
-acorn@^8.7.1:
+acorn@^8.0.4, acorn@^8.0.5, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0, acorn@^8.7.1:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
@@ -1209,9 +1234,9 @@ array-union@^2.1.0:
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
async@^2.6.2:
- version "2.6.3"
- resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
- integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
+ version "2.6.4"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
+ integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
dependencies:
lodash "^4.17.14"
@@ -2452,9 +2477,9 @@ flatted@^3.1.0:
integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==
follow-redirects@^1.0.0:
- version "1.14.7"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
- integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
+ version "1.14.9"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
+ integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
fork-ts-checker-webpack-plugin@^6.4.0:
version "6.5.1"
@@ -3332,9 +3357,9 @@ minimatch@^3.1.2:
brace-expansion "^1.1.7"
minimist@^1.2.0, minimist@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
- integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+ version "1.2.6"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
+ integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minipass@^3.1.1:
version "3.1.6"
@@ -4478,11 +4503,6 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
-source-map@~0.7.2:
- version "0.7.3"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
- integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
-
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
@@ -4693,13 +4713,13 @@ terser-webpack-plugin@^5.1.1, terser-webpack-plugin@^5.1.3:
terser "^5.7.2"
terser@^5.10.0, terser@^5.7.2:
- version "5.12.1"
- resolved "https://registry.yarnpkg.com/terser/-/terser-5.12.1.tgz#4cf2ebed1f5bceef5c83b9f60104ac4a78b49e9c"
- integrity sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==
+ version "5.14.2"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
+ integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
dependencies:
+ "@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
commander "^2.20.0"
- source-map "~0.7.2"
source-map-support "~0.5.20"
text-table@^0.2.0: