From 120fa57d82f03bf85c94a36125086140687b0a8b Mon Sep 17 00:00:00 2001 From: Allain Magyar Date: Wed, 14 Aug 2024 15:09:03 -0300 Subject: [PATCH 1/9] ci: changes to new release process (#1280) Signed-off-by: Allain Magyar Signed-off-by: Yurii Shynbuiev Co-authored-by: Yurii Shynbuiev --- .github/workflows/build.yml | 83 +++++++++++++++++++ .github/workflows/deployment.yml | 45 ----------- .github/workflows/release-clients.yml | 6 ++ .github/workflows/release.yml | 51 ++++++------ .github/workflows/unit-tests.yml | 4 + package.json | 111 -------------------------- release.config.mjs | 56 +++++++++++++ 7 files changed, 175 insertions(+), 181 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/deployment.yml create mode 100644 release.config.mjs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..fa4019ae43 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,83 @@ +name: Build and Publish Revision + +concurrency: + group: release + +on: + workflow_dispatch: + push: + branches: + - "main" + +jobs: + build: + if: ${{ !contains(github.event.head_commit.message, 'chore(release)') }} + env: + GITHUB_ACTOR: "hyperledger-bot" + GITHUB_ACTOR_EMAIL: "hyperledger-bot@hyperledger.org" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + JAVA_TOOL_OPTIONS: -Djava.net.preferIPv4Stack=true + SBT_OPTS: -Xmx2G + runs-on: ubuntu-latest + + permissions: + contents: write + packages: write + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + + - name: Setup Java and Scala + uses: olafurpg/setup-scala@v14 + with: + java-version: openjdk@1.17 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ env.GITHUB_ACTOR }} + password: ${{ env.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Get short commit hash + run: echo "COMMIT_HASH=${GITHUB_SHA::7}" >> $GITHUB_ENV + + - name: Set build number + run: echo "BUILD_NUMBER=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV + + - name: Extract version from version.sbt + id: get_version + run: | + VERSION=$(grep -Eo 'version := "[^"]+"' version.sbt | sed 's/version := "//; s/"//; s/-SNAPSHOT//') + echo "VERSION=${VERSION}" >> $GITHUB_ENV + + - name: Set build version + run: echo "BUILD_VERSION=${{ env.VERSION }}-${{ env.COMMIT_HASH }}-${{ env.BUILD_NUMBER }}" >> $GITHUB_ENV + + - name: Build and push Docker image + run: | + sbt "set version := \"${{ env.BUILD_VERSION }}\"" "docker:stage" + docker buildx build --platform=linux/arm64,linux/amd64 --push -t ghcr.io/hyperledger/identus-cloud-agent:${{ env.BUILD_VERSION}} ./cloud-agent/service/server/target/docker/stage + + - name: Trigger helm chart update + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.ATALA_GITHUB_TOKEN }} + repository: input-output-hk/atala-prism-helm-charts + event-type: build-chart-package + client-payload: '{"version": "${{ env.BUILD_VERSION }}", "chart": "cloud-agent"}' diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml deleted file mode 100644 index 4f4fa1866d..0000000000 --- a/.github/workflows/deployment.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Deployment - -# Trigger deployment when a new tag is pushed -# to a service component -on: - workflow_call: - inputs: - component-tag: - required: true - type: string - env: - required: false - type: string - default: "dev" - workflow_dispatch: - inputs: - component-tag: - description: "Tag of a component to trigger the update" - required: true - env: - description: "Environment to trigger update on" - required: false - default: "dev" - -jobs: - trigger-deployment: - runs-on: self-hosted - steps: - - name: Parse input parameters - id: parse-params - run: | - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - echo "COMPONENT_TAG=${{ github.event.inputs.component-tag }}" >> "${GITHUB_OUTPUT}" - echo "ENV=${{ github.event.inputs.env }}" >> "${GITHUB_OUTPUT}" - else - echo "COMPONENT_TAG=${{ inputs.component-tag }}" >> "${GITHUB_OUTPUT}" - echo "ENV=${{ inputs.env }}" >> "${GITHUB_OUTPUT}" - fi - - name: Trigger deployment - uses: peter-evans/repository-dispatch@v3 - with: - token: ${{ secrets.ATALA_GITHUB_TOKEN }} - repository: input-output-hk/atala-prism-dev-deployments - event-type: trigger-deployment - client-payload: '{"component-tag": "${{ steps.parse-params.outputs.COMPONENT_TAG }}", "env": "${{ steps.parse-params.outputs.ENV }}"}' diff --git a/.github/workflows/release-clients.yml b/.github/workflows/release-clients.yml index ff687ffad5..1631f268e0 100644 --- a/.github/workflows/release-clients.yml +++ b/.github/workflows/release-clients.yml @@ -1,6 +1,12 @@ name: Publish Identus-cloud-agent clients on: + workflow_call: + inputs: + version: + description: "Version to release clients (e.g. v1.33.0)" + required: true + type: string workflow_dispatch: inputs: releaseTag: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6dcd027ce9..a54368a9c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,18 +4,7 @@ concurrency: group: release on: - workflow_call: - inputs: - release-branch: - required: false - type: string - default: "main" workflow_dispatch: - inputs: - release-branch: - description: "Branch to release from" - required: false - default: "main" jobs: release: @@ -33,46 +22,46 @@ jobs: contents: write packages: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} ref: ${{ github.event.inputs.release-branch }} fetch-depth: 0 persist-credentials: false + - name: Setup Java and Scala uses: olafurpg/setup-scala@v14 with: java-version: openjdk@1.17 + - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: "lts/*" - - name: Setup Helm - uses: azure/setup-helm@v3 - with: - version: "3.12.2" # default is latest (stable) - id: install - - name: Setup yq - portable yaml processor - uses: mikefarah/yq@v4.34.2 - - uses: crazy-max/ghaction-import-gpg@v3 + + - uses: crazy-max/ghaction-import-gpg@v6 id: import_gpg with: - gpg-private-key: ${{ secrets.HYP_BOT_GPG_PRIVATE }} + gpg_private_key: ${{ secrets.HYP_BOT_GPG_PRIVATE }} passphrase: ${{ secrets.HYP_BOT_GPG_PASSWORD }} - git-user-signingkey: true - git-commit-gpgsign: true + git_user_signingkey: true + git_commit_gpgsign: true git_config_global: true - git_tag_gpgsign: true + git_tag_gpgsign: false + - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ env.GITHUB_ACTOR }} password: ${{ env.GITHUB_TOKEN }} + - name: Set up QEMU uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 + - name: Release env: GIT_AUTHOR_EMAIL: ${{ steps.import_gpg.outputs.email }} @@ -83,3 +72,15 @@ jobs: run: | npm install npx semantic-release + + - name: Get release version + id: get_version + run: echo "RELEASE_VERSION=$(cat .release-version)" >> $GITHUB_ENV + + - name: Trigger helm chart update + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ secrets.ATALA_GITHUB_TOKEN }} + repository: input-output-hk/atala-prism-helm-charts + event-type: build-chart-package + client-payload: '{"version": "${{ env.RELEASE_VERSION }}", "chart": "cloud-agent"}' diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 478e5b34b6..27324c9f66 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -12,6 +12,10 @@ on: - "main" pull_request: +permissions: + checks: write + pull-requests: write + jobs: build-and-unit-tests: name: "Build and unit tests" diff --git a/package.json b/package.json index 3b9750b32f..8663eb9e73 100644 --- a/package.json +++ b/package.json @@ -17,116 +17,5 @@ "prettier": "^3.2.5", "semantic-release": "^23.0.8", "semantic-release-slack-bot": "^4.0.2" - }, - "release": { - "branches": [ - { - "name": "main" - }, - { - "name": "prerelease/*", - "prerelease": "snapshot.${name.replace(\"prerelease/\", \"\")}" - } - ], - "tagFormat": "cloud-agent-v${version}", - "plugins": [ - "@semantic-release/commit-analyzer", - "@semantic-release/release-notes-generator", - [ - "@semantic-release/exec", - { - "prepareCmd": "npm version ${nextRelease.version} --git-tag-version false" - } - ], - [ - "@semantic-release/exec", - { - "prepareCmd": "sed -i.bak \"s/AGENT_VERSION=.*/AGENT_VERSION=${nextRelease.version}/\" ./infrastructure/local/.env && rm -f ./infrastructure/local/.env.bak" - } - ], - [ - "@semantic-release/exec", - { - "prepareCmd": "sbt dumpLicenseReportAggregate && cp ./target/license-reports/root-licenses.md ./DEPENDENCIES.md" - } - ], - [ - "@semantic-release/exec", - { - "prepareCmd": "sbt \"cloudAgentServer/test:runMain org.hyperledger.identus.api.util.Tapir2StaticOAS ${process.env.PWD}/cloud-agent/service/api/http/cloud-agent-openapi-spec.yaml ${nextRelease.version}\"" - } - ], - [ - "@semantic-release/exec", - { - "prepareCmd": "sbt \"release release-version ${nextRelease.version} next-version ${nextRelease.version}-SNAPSHOT with-defaults\"" - } - ], - [ - "@semantic-release/exec", - { - "prepareCmd": "yq eval -i '.appVersion = \"${nextRelease.version}\" | .version = \"${nextRelease.version}\"' ./infrastructure/charts/agent/Chart.yaml" - } - ], - [ - "@semantic-release/exec", - { - "prepareCmd": "helm package -d infrastructure/charts -u infrastructure/charts/agent" - } - ], - [ - "@semantic-release/exec", - { - "prepareCmd": "helm repo index --url \"https://raw.githubusercontent.com/hyperledger/identus-cloud-agent/main/infrastructure/charts\" --merge index.yaml infrastructure/charts" - } - ], - [ - "@semantic-release/exec", - { - "prepareCmd": "yq -i -P infrastructure/charts/index.yaml" - } - ], - [ - "@semantic-release/exec", - { - "prepareCmd": "docker buildx build --platform=linux/arm64,linux/amd64 --push -t ghcr.io/hyperledger/identus-cloud-agent:${nextRelease.version} ./cloud-agent/service/server/target/docker/stage" - } - ], - [ - "@semantic-release/changelog", - { - "changelogFile": "CHANGELOG.md" - } - ], - [ - "@semantic-release/git", - { - "assets": [ - "version.sbt", - "CHANGELOG.md", - "DEPENDENCIES.md", - "package.json", - "package-lock.json", - "cloud-agent/service/api/http/cloud-agent-openapi-spec.yaml", - "infrastructure/charts/agent/Chart.yaml", - "infrastructure/charts/index.yaml", - "infrastructure/charts/*.tgz", - "infrastructure/local/.env" - ], - "message": "chore(release): cut Identus Cloud agent ${nextRelease.version} release\n\n${nextRelease.notes}\n\nSigned-off-by: Allain Magyar " - } - ], - [ - "semantic-release-slack-bot", - { - "notifyOnSuccess": true, - "notifyOnFail": true, - "markdownReleaseNotes": true, - "onSuccessTemplate": { - "text": "A new version of Identus Cloud Agent successfully released!\nVersion: `$npm_package_version`\nTag: $repo_url/releases/tag/cloud-agent-v$npm_package_version\n\nRelease notes:\n$release_notes" - } - } - ] - ] } } diff --git a/release.config.mjs b/release.config.mjs new file mode 100644 index 0000000000..58928bb6a1 --- /dev/null +++ b/release.config.mjs @@ -0,0 +1,56 @@ +export default { + branches: [ + 'main', + '+([0-9])?(.{+([0-9]),x}).x', + { name: 'beta/*', prerelease: 'rc' } + ], + plugins: [ + '@semantic-release/commit-analyzer', + ["@semantic-release/exec", { + "prepareCmd": "docker buildx build --platform=linux/arm64,linux/amd64 --push -t ghcr.io/hyperledger/identus-cloud-agent:${nextRelease.version} ./cloud-agent/service/server/target/docker/stage" + }], + ["@semantic-release/exec", { + "prepareCmd": "echo ${nextRelease.version} > .release-version" + }], + '@semantic-release/release-notes-generator', + ["@semantic-release/changelog", { + "changelogFile": "CHANGELOG.md" + }], + ["@semantic-release/exec", { + "prepareCmd": "sbt \"release release-version ${nextRelease.version} next-version ${nextRelease.version}-SNAPSHOT with-defaults\"" + }], + ["@semantic-release/exec", { + "prepareCmd": "npm version ${nextRelease.version} --git-tag-version false" + }], + ["@semantic-release/exec", { + "prepareCmd": "sbt dumpLicenseReportAggregate && cp ./target/license-reports/root-licenses.md ./DEPENDENCIES.md" + }], + ["@semantic-release/exec", { + "prepareCmd": "sed -i.bak \"s/AGENT_VERSION=.*/AGENT_VERSION=${nextRelease.version}/\" ./infrastructure/local/.env && rm -f ./infrastructure/local/.env.bak" + }], + ["@semantic-release/exec", { + "prepareCmd": "sbt \"cloudAgentServer/test:runMain org.hyperledger.identus.api.util.Tapir2StaticOAS ${process.env.PWD}/cloud-agent/service/api/http/cloud-agent-openapi-spec.yaml ${nextRelease.version}\"" + }], + ["@semantic-release/git", { + "assets": [ + "version.sbt", + "CHANGELOG.md", + "DEPENDENCIES.md", + "package.json", + "package-lock.json", + "cloud-agent/service/api/http/cloud-agent-openapi-spec.yaml", + "infrastructure/local/.env" + ], + "message": "chore(release): cut the Identus Cloud agent ${nextRelease.version} release\n\n${nextRelease.notes} [skip ci]\n\nSigned-off-by: Hyperledger Bot " + }], + ["semantic-release-slack-bot", { + "notifyOnSuccess": true, + "notifyOnFail": true, + "markdownReleaseNotes": true, + "onSuccessTemplate": { + "text": "A new version of Identus Cloud Agent successfully released!\nVersion: `$npm_package_version`\nTag: $repo_url/releases/tag/cloud-agent-v$npm_package_version\n\nRelease notes:\n$release_notes" + } + }], + ], + tagFormat: "cloud-agent-v${version}" +} From c5846d15cbe1cfd4b9776ad7c259962182057e0c Mon Sep 17 00:00:00 2001 From: Shailesh Patil <53746241+mineme0110@users.noreply.github.com> Date: Thu, 15 Aug 2024 10:18:04 +0100 Subject: [PATCH 2/9] feat: connection less presentation (#1285) Signed-off-by: mineme0110 --- build.sbt | 2 +- .../agent/server/ControllerHelper.scala | 3 +- .../jobs/DIDStateSyncBackgroundJobs.scala | 4 +- .../server/jobs/PresentBackgroundJobs.scala | 148 ++++++++++-- .../controller/PresentProofController.scala | 11 + .../PresentProofControllerImpl.scala | 226 +++++++++++++----- .../controller/PresentProofEndpoints.scala | 62 +++++ .../PresentProofServerEndpoints.scala | 37 ++- .../AcceptRequestPresentationInvitation.scala | 34 +++ .../http/OOBPresentationInvitation.scala | 79 ++++++ .../http/OOBRequestPresentationInput.scala | 160 +++++++++++++ .../controller/http/PresentationStatus.scala | 45 +++- .../http/PresentationStatusPage.scala | 23 ++ .../invitation/v2/OutOfBandSpec.scala | 1 - .../presentproof/PresentProofInvitation.scala | 27 +++ .../presentproof/RequestPresentation.scala | 16 +- .../RequestPresentationSpec.scala | 4 +- .../core/model/PresentationRecord.scala | 9 +- .../core/model/error/PresentationError.scala | 30 +++ .../core/service/PresentationService.scala | 19 +- .../service/PresentationServiceImpl.scala | 118 +++++++-- .../service/PresentationServiceNotifier.scala | 32 ++- .../PresentationRepositorySpecSuite.scala | 5 +- .../service/MockPresentationService.scala | 72 +++++- .../PresentationServiceNotifierSpec.scala | 5 +- .../service/PresentationServiceSpec.scala | 52 ++-- .../PresentationServiceSpecHelper.scala | 16 +- ..._invitation_column_presentation_record.sql | 4 + .../JdbcPresentationRepository.scala | 10 + 29 files changed, 1067 insertions(+), 187 deletions(-) create mode 100644 cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/AcceptRequestPresentationInvitation.scala create mode 100644 cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/OOBPresentationInvitation.scala create mode 100644 cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/OOBRequestPresentationInput.scala create mode 100644 mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala create mode 100644 pollux/sql-doobie/src/main/resources/sql/pollux/V24__add_invitation_column_presentation_record.sql diff --git a/build.sbt b/build.sbt index 8c3c7c797b..515717e031 100644 --- a/build.sbt +++ b/build.sbt @@ -602,7 +602,7 @@ lazy val protocolPresentProof = project .settings(libraryDependencies += D.zio) .settings(libraryDependencies ++= Seq(D.circeCore, D.circeGeneric, D.circeParser)) .settings(libraryDependencies += D.munitZio) - .dependsOn(models) + .dependsOn(models, protocolInvitation) lazy val vc = project .in(file("mercury/vc")) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala index 8a5aa712ec..f79863d9b4 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala @@ -14,8 +14,9 @@ import org.hyperledger.identus.connect.core.model.error.ConnectionServiceError.{ import org.hyperledger.identus.connect.core.model.ConnectionRecord import org.hyperledger.identus.connect.core.model.ConnectionRecord.{ProtocolState, Role} import org.hyperledger.identus.connect.core.service.ConnectionService -import org.hyperledger.identus.mercury.model.DidId +import org.hyperledger.identus.mercury.model.* import org.hyperledger.identus.shared.models.WalletAccessContext +import zio.* import zio.{IO, ZIO} import java.util.UUID diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/DIDStateSyncBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/DIDStateSyncBackgroundJobs.scala index 22e4e9d568..5d4ff494ea 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/DIDStateSyncBackgroundJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/DIDStateSyncBackgroundJobs.scala @@ -1,11 +1,13 @@ package org.hyperledger.identus.agent.server.jobs +import org.hyperledger.identus.agent.walletapi.model.error.GetManagedDIDError import org.hyperledger.identus.agent.walletapi.service.ManagedDIDService +import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* object DIDStateSyncBackgroundJobs { - val syncDIDPublicationStateFromDlt = + val syncDIDPublicationStateFromDlt: ZIO[WalletAccessContext with ManagedDIDService, GetManagedDIDError, Unit] = for { managedDidService <- ZIO.service[ManagedDIDService] _ <- managedDidService.syncManagedDIDState diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala index c42f4899b5..a07ac1f04a 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala @@ -96,13 +96,17 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { for { _ <- ZIO.logDebug(s"Running action with records => $record") _ <- record match { - case PresentationRecord(id, _, _, _, _, _, _, _, ProposalPending, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(_, _, _, _, _, _, _, _, InvitationGenerated, _, _, _, _, _, _, _, _, _, _, _, _, _) => + ZIO.unit + case PresentationRecord(_, _, _, _, _, _, _, _, InvitationExpired, _, _, _, _, _, _, _, _, _, _, _, _, _) => + ZIO.unit + case PresentationRecord(id, _, _, _, _, _, _, _, ProposalPending, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProposalSent, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProposalSent, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProposalReceived, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProposalReceived, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProposalRejected, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProposalRejected, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) case PresentationRecord( id, @@ -115,6 +119,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, RequestPending, _, + _, None, _, _, @@ -139,6 +144,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, RequestPending, _, + _, Some(requestPresentation), _, _, @@ -152,7 +158,30 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _ ) => // Verifier Verifier.handleRequestPending(id, requestPresentation) - case PresentationRecord(id, _, _, _, _, _, _, _, RequestSent, _, _, _, _, _, _, _, _, _, _, _, _) => // Verifier + case PresentationRecord( + id, + _, + _, + _, + _, + _, + _, + _, + RequestSent, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _ + ) => // Verifier ZIO.logDebug("PresentationRecord: RequestSent") *> ZIO.unit case PresentationRecord( id, @@ -175,6 +204,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, _ ) => // Prover ZIO.logDebug("PresentationRecord: RequestReceived") *> ZIO.unit @@ -199,14 +229,38 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, _ ) => // Prover ZIO.logDebug("PresentationRecord: RequestRejected") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportPending, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportPending, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportSent, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportSent, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.fail(NotImplemented) - case PresentationRecord(id, _, _, _, _, _, _, _, ProblemReportReceived, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord( + id, + _, + _, + _, + _, + _, + _, + _, + ProblemReportReceived, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _ + ) => ZIO.fail(NotImplemented) case PresentationRecord( id, @@ -219,6 +273,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, PresentationPending, _, + _, None, _, _, @@ -244,6 +299,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, PresentationPending, credentialFormat, + _, Some(requestPresentation), _, _, @@ -277,6 +333,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, None, _, _, @@ -302,6 +359,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, Some(presentation), _, _, @@ -315,7 +373,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ZIO.logDebug("PresentationRecord: PresentationGenerated") *> ZIO.unit Prover.handlePresentationGenerated(id, presentation) - case PresentationRecord(id, _, _, _, _, _, _, _, PresentationSent, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, PresentationSent, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.logDebug("PresentationRecord: PresentationSent") *> ZIO.unit case PresentationRecord( id, @@ -330,6 +388,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, None, _, _, @@ -341,7 +400,30 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _ ) => // Verifier ZIO.fail(InvalidState("PresentationRecord in 'PresentationReceived' with no Presentation")) - case PresentationRecord(_, _, _, _, _, _, _, _, PresentationReceived, _, None, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord( + _, + _, + _, + _, + _, + _, + _, + _, + PresentationReceived, + _, + _, + None, + _, + _, + _, + _, + _, + _, + _, + _, + _, + _ + ) => ZIO.fail(InvalidState("PresentationRecord in 'PresentationReceived' with no Presentation Request")) case PresentationRecord( id, @@ -354,6 +436,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, PresentationReceived, credentialFormat, + _, Some(requestPresentation), _, Some(presentation), @@ -390,15 +473,18 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, _ ) => ZIO.logDebug("PresentationRecord: PresentationVerificationFailed") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, PresentationAccepted, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, PresentationAccepted, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.logDebug("PresentationRecord: PresentationVerifiedAccepted") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, PresentationVerified, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, PresentationVerified, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.logDebug("PresentationRecord: PresentationVerified") *> ZIO.unit - case PresentationRecord(id, _, _, _, _, _, _, _, PresentationRejected, _, _, _, _, _, _, _, _, _, _, _, _) => + case PresentationRecord(id, _, _, _, _, _, _, _, PresentationRejected, _, _, _, _, _, _, _, _, _, _, _, _, _) => ZIO.logDebug("PresentationRecord: PresentationRejected") *> ZIO.unit + case _ => + ZIO.logWarning(s"Unhandled PresentationRecord state: ${record.protocolState}") } } yield () } @@ -453,7 +539,9 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { Unit ] = { val proverPresentationPendingToGeneratedFlow = for { - walletAccessContext <- buildWalletAccessContextLayer(requestPresentation.to) + walletAccessContext <- buildWalletAccessContextLayer( + requestPresentation.to.getOrElse(throw new RuntimeException("to is None is not possible")) + ) _ <- for { presentationService <- ZIO.service[PresentationService] prover <- createPrismDIDIssuerFromPresentationCredentials(id, credentialsToUse.getOrElse(Nil)) @@ -486,8 +574,8 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ) ), thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), - from = requestPresentation.to, - to = requestPresentation.from + from = requestPresentation.to.getOrElse(throw new RuntimeException("to is None is not possible")), + to = requestPresentation.from.getOrElse(throw new RuntimeException("from is None is not possible")) ) ) } yield presentation @@ -509,7 +597,9 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ERROR, Unit ] = for { - walletAccessContext <- buildWalletAccessContextLayer(requestPresentation.to) + walletAccessContext <- buildWalletAccessContextLayer( + requestPresentation.to.getOrElse(throw new RuntimeException("to is None is not possible")) + ) result <- for { presentationService <- ZIO.service[PresentationService] @@ -539,7 +629,9 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { maybeCredentialsToUseJson match { case Some(credentialsToUseJson) => val proverPresentationPendingToGeneratedFlow = for { - walletAccessContext <- buildWalletAccessContextLayer(requestPresentation.to) + walletAccessContext <- buildWalletAccessContextLayer( + requestPresentation.to.getOrElse(throw new RuntimeException("to is None is not possible")) + ) result <- for { presentationService <- ZIO.service[PresentationService] anoncredCredentialProofs <- @@ -741,10 +833,14 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { val verifierReqPendingToSentFlow = for { _ <- ZIO.log(s"PresentationRecord: RequestPending (Send Message)") - walletAccessContext <- buildWalletAccessContextLayer(record.from) + walletAccessContext <- buildWalletAccessContextLayer( + record.from.getOrElse(throw new RuntimeException("from is None is not possible")) + ) result <- for { didOps <- ZIO.service[DidOps] - didCommAgent <- buildDIDCommAgent(record.from).provideSomeLayer(ZLayer.succeed(walletAccessContext)) + didCommAgent <- buildDIDCommAgent( + record.from.getOrElse(throw new RuntimeException("from is None is not possible")) + ).provideSomeLayer(ZLayer.succeed(walletAccessContext)) resp <- MessagingService .send(record.makeMessage) @@ -1037,11 +1133,11 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { } } - val syncDIDPublicationStateFromDlt: ZIO[WalletAccessContext & ManagedDIDService, GetManagedDIDError, Unit] = - for { - managedDidService <- ZIO.service[ManagedDIDService] - _ <- managedDidService.syncManagedDIDState - _ <- managedDidService.syncUnconfirmedUpdateOperations - } yield () +// val syncDIDPublicationStateFromDlt: ZIO[WalletAccessContext & ManagedDIDService, GetManagedDIDError, Unit] = +// for { +// managedDidService <- ZIO.service[ManagedDIDService] +// _ <- managedDidService.syncManagedDIDState +// _ <- managedDidService.syncUnconfirmedUpdateOperations +// } yield () } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofController.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofController.scala index 027dc6ce7b..fe4f5edd44 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofController.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofController.scala @@ -31,6 +31,17 @@ trait PresentProofController { rc: RequestContext ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatus] + def createOOBRequestPresentationInvitation( + request: OOBRequestPresentationInput + )(implicit + rc: RequestContext + ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatus] + + def acceptRequestPresentationInvitation( + request: AcceptRequestPresentationInvitation + )(implicit + rc: RequestContext + ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatus] } object PresentProofController { diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala index c743c83a79..044ae2d40b 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala @@ -1,6 +1,8 @@ package org.hyperledger.identus.presentproof.controller +import org.hyperledger.identus.agent.server.config.AppConfig import org.hyperledger.identus.agent.server.ControllerHelper +import org.hyperledger.identus.agent.walletapi.service.ManagedDIDService import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext} import org.hyperledger.identus.api.http.model.PaginationInput import org.hyperledger.identus.connect.core.model.error.ConnectionServiceError @@ -10,6 +12,7 @@ import org.hyperledger.identus.mercury.protocol.presentproof.ProofType import org.hyperledger.identus.pollux.core.model.{CredentialFormat, DidCommID, PresentationRecord} import org.hyperledger.identus.pollux.core.model.error.PresentationError import org.hyperledger.identus.pollux.core.model.presentation.Options +import org.hyperledger.identus.pollux.core.service.serdes.AnoncredPresentationRequestV1 import org.hyperledger.identus.pollux.core.service.PresentationService import org.hyperledger.identus.presentproof.controller.http.* import org.hyperledger.identus.presentproof.controller.PresentProofController.toDidCommID @@ -23,7 +26,9 @@ import scala.language.implicitConversions class PresentProofControllerImpl( presentationService: PresentationService, - connectionService: ConnectionService + connectionService: ConnectionService, + managedDIDService: ManagedDIDService, + appConfig: AppConfig ) extends PresentProofController with ControllerHelper { override def requestPresentation(request: RequestPresentationInput)(implicit @@ -31,74 +36,145 @@ class PresentProofControllerImpl( ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatus] = { val result: ZIO[WalletAccessContext, ConnectionServiceError | PresentationError, PresentationStatus] = for { didIdPair <- getPairwiseDIDs(request.connectionId).provideSomeLayer(ZLayer.succeed(connectionService)) - credentialFormat = request.credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT) - record <- - credentialFormat match { - case CredentialFormat.JWT => - presentationService - .createJwtPresentationRecord( - pairwiseVerifierDID = didIdPair.myDID, - pairwiseProverDID = didIdPair.theirDid, - thid = DidCommID(), - connectionId = Some(request.connectionId.toString), - proofTypes = request.proofs.map { e => - ProofType( - schema = e.schemaId, - requiredFields = None, - trustIssuers = Some(e.trustIssuers.map(DidId(_))) - ) - }, - options = request.options.map(x => Options(x.challenge, x.domain)) - ) - case CredentialFormat.SDJWT => - request.claims match { - case Some(claims) => - for { - s <- presentationService.createSDJWTPresentationRecord( - pairwiseVerifierDID = didIdPair.myDID, - pairwiseProverDID = didIdPair.theirDid, - thid = DidCommID(), - connectionId = Some(request.connectionId.toString), - proofTypes = request.proofs.map { e => - ProofType( - schema = e.schemaId, - requiredFields = None, - trustIssuers = Some(e.trustIssuers.map(DidId(_))) - ) - }, - claimsToDisclose = claims, - options = request.options.map(o => Options(o.challenge, o.domain)) - ) - } yield s + record <- createRequestPresentation( + verifierDID = didIdPair.myDID, + proverDID = Some(didIdPair.theirDid), + connectionId = Some(request.connectionId.toString), + request = request + ) + } yield PresentationStatus.fromDomain(record) + result + } - case None => - ZIO.fail( - PresentationError.MissingAnoncredPresentationRequest( - "presentation request is missing claims to be disclosed" - ) - ) - } - case CredentialFormat.AnonCreds => - request.anoncredPresentationRequest match { - case Some(presentationRequest) => - presentationService - .createAnoncredPresentationRecord( - pairwiseVerifierDID = didIdPair.myDID, - pairwiseProverDID = didIdPair.theirDid, - thid = DidCommID(), - connectionId = Some(request.connectionId.toString), - presentationRequest = presentationRequest - ) - case None => - ZIO.fail( - PresentationError.MissingAnoncredPresentationRequest("Anoncred presentation request is missing") - ) - } - } + override def createOOBRequestPresentationInvitation(request: OOBRequestPresentationInput)(implicit + rc: RequestContext + ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatus] = { + val result: ZIO[WalletAccessContext, ConnectionServiceError | PresentationError, PresentationStatus] = for { + peerDid <- managedDIDService.createAndStorePeerDID(appConfig.agent.didCommEndpoint.publicEndpointUrl) + record <- createRequestPresentation( + verifierDID = peerDid.did, + proverDID = None, + connectionId = None, + request = request + ) } yield PresentationStatus.fromDomain(record) result } + private def createRequestPresentation( + verifierDID: DidId, + proverDID: Option[DidId], + connectionId: Option[String], + request: RequestPresentationInput | OOBRequestPresentationInput + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { + request match { + case req: RequestPresentationInput => + createPresentationRecord( + verifierDID, + proverDID, + connectionId, + req.credentialFormat, + req.proofs, + req.options.map(o => Options(o.challenge, o.domain)), + req.claims, + req.anoncredPresentationRequest, + None, + None + ) + case req: OOBRequestPresentationInput => + createPresentationRecord( + verifierDID, + proverDID, + connectionId, + req.credentialFormat, + req.proofs, + req.options.map(o => Options(o.challenge, o.domain)), + req.claims, + req.anoncredPresentationRequest, + req.goalCode, + req.goal + ) + } + } + + private def createPresentationRecord( + verifierDID: DidId, + proverDID: Option[DidId], + connectionId: Option[String], + credentialFormat: Option[String], + proofs: Seq[ProofRequestAux], + options: Option[Options], + claims: Option[zio.json.ast.Json.Obj], + anoncredPresentationRequest: Option[AnoncredPresentationRequestV1], + goalCode: Option[String], + goal: Option[String] + ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { + val format = credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT) + format match { + case CredentialFormat.JWT => + presentationService.createJwtPresentationRecord( + pairwiseVerifierDID = verifierDID, + pairwiseProverDID = proverDID, + thid = DidCommID(), + connectionId = connectionId, + proofTypes = proofs.map { e => + ProofType( + schema = e.schemaId, + requiredFields = None, + trustIssuers = Some(e.trustIssuers.map(DidId(_))) + ) + }, + options = options, + goalCode = goalCode, + goal = goal + ) + case CredentialFormat.SDJWT => + claims match { + case Some(claimsToDisclose) => + presentationService.createSDJWTPresentationRecord( + pairwiseVerifierDID = verifierDID, + pairwiseProverDID = proverDID, + thid = DidCommID(), + connectionId = connectionId, + proofTypes = proofs.map { e => + ProofType( + schema = e.schemaId, + requiredFields = None, + trustIssuers = Some(e.trustIssuers.map(DidId(_))) + ) + }, + claimsToDisclose = claimsToDisclose, + options = options, + goalCode = goalCode, + goal = goal + ) + case None => + ZIO.fail( + PresentationError.MissingSDJWTPresentationRequest( + "presentation request is missing claims to be disclosed" + ) + ) + } + case CredentialFormat.AnonCreds => + anoncredPresentationRequest match { + case Some(presentationRequest) => + presentationService.createAnoncredPresentationRecord( + pairwiseVerifierDID = verifierDID, + pairwiseProverDID = proverDID, + thid = DidCommID(), + connectionId = connectionId, + presentationRequest = presentationRequest, + goalCode = goalCode, + goal = goal + ) + case None => + ZIO.fail( + PresentationError.MissingAnoncredPresentationRequest("Anoncred presentation request is missing") + ) + } + } + } + override def getPresentations(paginationInput: PaginationInput, thid: Option[String])(implicit rc: RequestContext ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatusPage] = { @@ -168,9 +244,27 @@ class PresentProofControllerImpl( result } + + def acceptRequestPresentationInvitation( + request: AcceptRequestPresentationInvitation + )(implicit + rc: RequestContext + ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatus] = { + for { + pairwiseDid <- managedDIDService.createAndStorePeerDID(appConfig.agent.didCommEndpoint.publicEndpointUrl) + requestPresentation <- presentationService.getRequestPresentationFromInvitation( + pairwiseDid.did, + request.invitation + ) + record <- presentationService.receiveRequestPresentation( + None, // connectionless hence none + requestPresentation + ) // TODO should we store the invitation in prover db ??? + } yield PresentationStatus.fromDomain(record) + } } object PresentProofControllerImpl { - val layer: URLayer[PresentationService & ConnectionService, PresentProofController] = - ZLayer.fromFunction(PresentProofControllerImpl(_, _)) + val layer: URLayer[PresentationService & ConnectionService & ManagedDIDService & AppConfig, PresentProofController] = + ZLayer.fromFunction(PresentProofControllerImpl(_, _, _, _)) } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofEndpoints.scala index 2534746a3d..e24318ee5d 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofEndpoints.scala @@ -118,4 +118,66 @@ object PresentProofEndpoints { .out(jsonBody[PresentationStatus]) .errorOut(basicFailureAndNotFoundAndForbidden) + val createOOBRequestPresentationInvitation: Endpoint[ + (ApiKeyCredentials, JwtCredentials), + (RequestContext, OOBRequestPresentationInput), + ErrorResponse, + PresentationStatus, + Any + ] = + endpoint.post + .tag("Present Proof") + .name("createOOBRequestPresentationInvitation") + .summary( + "As a Verifier, create a new OOB Invitation as proof presentation request that can be delivered out-of-band to a invitee/prover." + ) + .description(""" + |Create a new presentation request invitation that can be delivered out-of-band to a peer Agent, regardless of whether it resides in Cloud Agent or edge environment. + |The generated invitation adheres to the DIDComm Messaging v2.0 - [Out of Band Messages](https://identity.foundation/didcomm-messaging/spec/v2.0/#out-of-band-messages) specification [section 9.5.4](https://identity.foundation/didcomm-messaging/spec/v2.0/#invitation). + |The from field of the out-of-band invitation message contains a freshly generated Peer DID that complies with the [did:peer:2](https://identity.foundation/peer-did-method-spec/#generating-a-didpeer2) specification. + |This Peer DID includes the 'uri' location of the DIDComm messaging service, essential for the prover's subsequent execution of the connection flow. + |In the Agent database, the created presentation record has an initial state set to `InvitationGenerated`. + |The invitation is in the form of a presentation request (as described https://github.com/decentralized-identity/waci-didcomm/blob/main/present_proof/present-proof-v3.md), which is included as an attachment in the OOB DIDComm message sent to the invitee/prover. + |""".stripMargin) + .securityIn(apiKeyHeader) + .securityIn(jwtAuthHeader) + .in("present-proof" / "presentations" / "invitation") + .in(extractFromRequest[RequestContext](RequestContext.apply)) + .in(jsonBody[OOBRequestPresentationInput].description("The present proof creation request.")) + .out( + statusCode(StatusCode.Created).description( + "The proof presentation request invitation was created successfully and that can be delivered as out-of-band to a peer Agent.." + ) + ) + .out(jsonBody[PresentationStatus]) + .errorOut(basicFailureAndNotFoundAndForbidden) + + val acceptRequestPresentationInvitation: Endpoint[ + (ApiKeyCredentials, JwtCredentials), + (RequestContext, AcceptRequestPresentationInvitation), + ErrorResponse, + PresentationStatus, + Any + ] = + endpoint.post + .tag("Present Proof") + .name("acceptRequestPresentationInvitation") + .summary( + "Decode the invitation extract Request Presentation and Create the proof presentation record with RequestReceived state." + ) + .description("Accept Invitation for request presentation") + .securityIn(apiKeyHeader) + .securityIn(jwtAuthHeader) + .in(extractFromRequest[RequestContext](RequestContext.apply)) + .in( + "present-proof" / "presentations" / "accept-invitation" + ) + .in( + jsonBody[AcceptRequestPresentationInvitation].description( + "The action to perform on the proof presentation request invitation." + ) + ) + .out(statusCode(StatusCode.Ok).description("The proof presentation record was successfully updated.")) + .out(jsonBody[PresentationStatus]) + .errorOut(basicFailureAndNotFoundAndForbidden) } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofServerEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofServerEndpoints.scala index 798902f51c..e7f30f9864 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofServerEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofServerEndpoints.scala @@ -4,8 +4,15 @@ import org.hyperledger.identus.agent.walletapi.model.BaseEntity import org.hyperledger.identus.api.http.model.PaginationInput import org.hyperledger.identus.api.http.RequestContext import org.hyperledger.identus.iam.authentication.{Authenticator, Authorizer, DefaultAuthenticator, SecurityLogic} -import org.hyperledger.identus.presentproof.controller.http.{RequestPresentationAction, RequestPresentationInput} +import org.hyperledger.identus.presentproof.controller.http.{ + AcceptRequestPresentationInvitation, + OOBRequestPresentationInput, + RequestPresentationAction, + RequestPresentationInput +} import org.hyperledger.identus.presentproof.controller.PresentProofEndpoints.{ + acceptRequestPresentationInvitation, + createOOBRequestPresentationInvitation, getAllPresentations, getPresentation, requestPresentation, @@ -71,11 +78,37 @@ class PresentProofServerEndpoints( } } + private val createOOBRequestPresentationInvitationEndpoint: ZServerEndpoint[Any, Any] = + createOOBRequestPresentationInvitation + .zServerSecurityLogic(SecurityLogic.authorizeWalletAccessWith(_)(authenticator, authorizer)) + .serverLogic { wac => + { case (ctx: RequestContext, action: OOBRequestPresentationInput) => + presentProofController + .createOOBRequestPresentationInvitation(action)(ctx) + .provideSomeLayer(ZLayer.succeed(wac)) + .logTrace(ctx) + } + } + + private val acceptRequestPresentationInvitationEndpoint: ZServerEndpoint[Any, Any] = + acceptRequestPresentationInvitation + .zServerSecurityLogic(SecurityLogic.authorizeWalletAccessWith(_)(authenticator, authorizer)) + .serverLogic { wac => + { case (ctx: RequestContext, action: AcceptRequestPresentationInvitation) => + presentProofController + .acceptRequestPresentationInvitation(action)(ctx) + .provideSomeLayer(ZLayer.succeed(wac)) + .logTrace(ctx) + } + } + val all: List[ZServerEndpoint[Any, Any]] = List( requestPresentationEndpoint, getAllPresentationsEndpoint, getPresentationEndpoint, - updatePresentationEndpoint + updatePresentationEndpoint, + createOOBRequestPresentationInvitationEndpoint, + acceptRequestPresentationInvitationEndpoint ) } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/AcceptRequestPresentationInvitation.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/AcceptRequestPresentationInvitation.scala new file mode 100644 index 0000000000..b7e4ecdf2f --- /dev/null +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/AcceptRequestPresentationInvitation.scala @@ -0,0 +1,34 @@ +package org.hyperledger.identus.presentproof.controller.http + +import org.hyperledger.identus.api.http.Annotation +import sttp.tapir.Schema +import sttp.tapir.Schema.annotations.{description, encodedExample} +import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} +import AcceptRequestPresentationInvitation.annotations + +case class AcceptRequestPresentationInvitation( + @description(annotations.invitation.description) + @encodedExample(annotations.invitation.example) + invitation: String +) + +object AcceptRequestPresentationInvitation { + + object annotations { + object invitation + extends Annotation[String]( + description = "The base64-encoded raw invitation.", + example = + "eyJAaWQiOiIzZmE4NWY2NC01NzE3LTQ1NjItYjNmYy0yYzk2M2Y2NmFmYTYiLCJAdHlwZSI6Imh0dHBzOi8vZGlkY29tbS5vcmcvbXktZmFtaWx5LzEuMC9teS1tZXNzYWdlLXR5cGUiLCJkaWQiOiJXZ1d4cXp0ck5vb0c5MlJYdnhTVFd2IiwiaW1hZ2VVcmwiOiJodHRwOi8vMTkyLjE2OC41Ni4xMDEvaW1nL2xvZ28uanBnIiwibGFiZWwiOiJCb2IiLCJyZWNpcGllbnRLZXlzIjpbIkgzQzJBVnZMTXY2Z21NTmFtM3VWQWpacGZrY0pDd0R3blpuNnozd1htcVBWIl0sInJvdXRpbmdLZXlzIjpbIkgzQzJBVnZMTXY2Z21NTmFtM3VWQWpacGZrY0pDd0R3blpuNnozd1htcVBWIl0sInNlcnZpY2VFbmRwb2ludCI6Imh0dHA6Ly8xOTIuMTY4LjU2LjEwMTo4MDIwIn0=" + ) + } + + given encoder: JsonEncoder[AcceptRequestPresentationInvitation] = + DeriveJsonEncoder.gen[AcceptRequestPresentationInvitation] + + given decoder: JsonDecoder[AcceptRequestPresentationInvitation] = + DeriveJsonDecoder.gen[AcceptRequestPresentationInvitation] + + given schema: Schema[AcceptRequestPresentationInvitation] = Schema.derived + +} diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/OOBPresentationInvitation.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/OOBPresentationInvitation.scala new file mode 100644 index 0000000000..2061a3a4db --- /dev/null +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/OOBPresentationInvitation.scala @@ -0,0 +1,79 @@ +package org.hyperledger.identus.presentproof.controller.http + +import org.hyperledger.identus.api.http.Annotation +import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation +import sttp.tapir.Schema +import sttp.tapir.Schema.annotations.{description, encodedExample} +import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} +import OOBPresentationInvitation.annotations + +import java.util.UUID + +case class OOBPresentationInvitation( + @description(annotations.id.description) + @encodedExample(annotations.id.example) + id: UUID, + @description(annotations.`type`.description) + @encodedExample(annotations.`type`.example) + `type`: String, + @description(annotations.from.description) + @encodedExample(annotations.from.example) + from: String, + @description(annotations.invitationUrl.description) + @encodedExample(annotations.invitationUrl.example) + invitationUrl: String +) + +object OOBPresentationInvitation { + + def fromDomain(invitation: Invitation) = OOBPresentationInvitation( + id = UUID.fromString(invitation.id), + `type` = invitation.`type`, + from = invitation.from.value, + invitationUrl = s"https://my.domain.com/path?_oob=${invitation.toBase64}" + ) + + object annotations { + object id + extends Annotation[UUID]( + description = + "The unique identifier of the invitation. It should be used as parent thread ID (pthid) for the Connection Request message that follows.", + example = UUID.fromString("0527aea1-d131-3948-a34d-03af39aba8b4") + ) + + object `type` + extends Annotation[String]( + description = "The DIDComm Message Type URI (MTURI) the invitation message complies with.", + example = "https://didcomm.org/out-of-band/2.0/invitation" + ) + + object from + extends Annotation[String]( + description = "The DID representing the sender to be used by recipients for future interactions.", + example = "did:peer:1234457" + ) + + object invitationUrl + extends Annotation[String]( + description = + "The invitation message encoded as a URL. This URL follows the Out of [Band 2.0 protocol](https://identity.foundation/didcomm-messaging/spec/v2.0/#out-of-band-messages) and can be used to generate a QR code for example.", + example = + "https://my.domain.com/path?_oob=eyJAaWQiOiIzZmE4NWY2NC01NzE3LTQ1NjItYjNmYy0yYzk2M2Y2NmFmYTYiLCJAdHlwZSI6Imh0dHBzOi8vZGlkY29tbS5vcmcvbXktZmFtaWx5LzEuMC9teS1tZXNzYWdlLXR5cGUiLCJkaWQiOiJXZ1d4cXp0ck5vb0c5MlJYdnhTVFd2IiwiaW1hZ2VVcmwiOiJodHRwOi8vMTkyLjE2OC41Ni4xMDEvaW1nL2xvZ28uanBnIiwibGFiZWwiOiJCb2IiLCJyZWNpcGllbnRLZXlzIjpbIkgzQzJBVnZMTXY2Z21NTmFtM3VWQWpacGZrY0pDd0R3blpuNnozd1htcVBWIl0sInJvdXRpbmdLZXlzIjpbIkgzQzJBVnZMTXY2Z21NTmFtM3VWQWpacGZrY0pDd0R3blpuNnozd1htcVBWIl0sInNlcnZpY2VFbmRwb2ludCI6Imh0dHA6Ly8xOTIuMTY4LjU2LjEwMTo4MDIwIn0=" + ) + } + + val Example = OOBPresentationInvitation( + id = annotations.id.example, + `type` = annotations.`type`.example, + from = annotations.from.example, + invitationUrl = annotations.invitationUrl.example + ) + + given encoder: JsonEncoder[OOBPresentationInvitation] = + DeriveJsonEncoder.gen[OOBPresentationInvitation] + + given decoder: JsonDecoder[OOBPresentationInvitation] = + DeriveJsonDecoder.gen[OOBPresentationInvitation] + + given schema: Schema[OOBPresentationInvitation] = Schema.derived +} diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/OOBRequestPresentationInput.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/OOBRequestPresentationInput.scala new file mode 100644 index 0000000000..eb2d57251b --- /dev/null +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/OOBRequestPresentationInput.scala @@ -0,0 +1,160 @@ +package org.hyperledger.identus.presentproof.controller.http + +import org.hyperledger.identus.api.http.Annotation +import org.hyperledger.identus.pollux.core.service.serdes.{ + AnoncredNonRevokedIntervalV1, + AnoncredPresentationRequestV1, + AnoncredRequestedAttributeV1, + AnoncredRequestedPredicateV1 +} +import sttp.tapir.{Schema, Validator} +import sttp.tapir.json.zio.* +import sttp.tapir.Schema.annotations.{description, encodedExample} +import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} +import OOBRequestPresentationInput.annotations + +//TODO Should I just use RequestPresentationInput and add the optional fields will that cause any confusion +final case class OOBRequestPresentationInput( + @description(annotations.goalcode.description) + @encodedExample(annotations.goalcode.example) + goalCode: Option[String] = None, + @description(annotations.goal.description) + @encodedExample(annotations.goal.example) + goal: Option[String] = None, + @description(annotations.options.description) + @encodedExample(annotations.options.example) + options: Option[Options] = None, + @description(annotations.proofs.description) + @encodedExample(annotations.proofs.example) + proofs: Seq[ProofRequestAux], + @description(annotations.anoncredPresentationRequest.description) + @encodedExample(annotations.anoncredPresentationRequest.example) + anoncredPresentationRequest: Option[AnoncredPresentationRequestV1], + @description(annotations.claims.description) + @encodedExample(annotations.claims.example) + claims: Option[zio.json.ast.Json.Obj], + @description(annotations.credentialFormat.description) + @encodedExample(annotations.credentialFormat.example) + credentialFormat: Option[String], +) + +object OOBRequestPresentationInput { + object annotations { + object goalcode + extends Annotation[String]( + description = + "A self-attested code the receiver may want to display to the user or use in automatically deciding what to do with the out-of-band message.", + example = "present-vp" + ) + + object goal + extends Annotation[String]( + description = + "A self-attested string that the receiver may want to display to the user about the context-specific goal of the out-of-band message.", + example = "Request proof of vaccine" + ) + + object options + extends Annotation[Option[Options]]( + description = "The options to use when creating the proof presentation request (e.g., domain, challenge).", + example = None + ) + object proofs + extends Annotation[Seq[ProofRequestAux]]( + description = + "The type of proofs requested in the context of this proof presentation request (e.g., VC schema, trusted issuers, etc.)", + example = Seq.empty + ) + + object anoncredPresentationRequest + extends Annotation[Option[AnoncredPresentationRequestV1]]( + description = "Anoncred Presentation Request", + example = Some( + AnoncredPresentationRequestV1( + requested_attributes = Map( + "attribute1" -> AnoncredRequestedAttributeV1( + "Attribute 1", + List( + Map( + "cred_def_id" -> "credential_definition_id_of_attribute1" + ) + ), + Some( + AnoncredNonRevokedIntervalV1( + Some(1635734400), + Some(1735734400) + ) + ) + ) + ), + requested_predicates = Map( + "predicate1" -> + AnoncredRequestedPredicateV1( + "Predicate 1", + ">=", + 18, + List( + Map( + "schema_id" -> "schema_id_of_predicate1" + ) + ), + Some( + AnoncredNonRevokedIntervalV1( + Some(1635734400), + None + ) + ) + ) + ), + name = "Example Presentation Request", + nonce = "1234567890", + version = "1.0", + non_revoked = None + ) + ) + ) + object claims + extends Annotation[Option[zio.json.ast.Json.Obj]]( + description = """ + |The set of claims to be disclosed from the issued credential. + |The JSON object should comply with the schema applicable for this offer (i.e. 'schemaId' or 'credentialDefinitionId'). + |""".stripMargin, + example = Some( + zio.json.ast.Json.Obj( + "firstname" -> zio.json.ast.Json.Str("Alice"), + "lastname" -> zio.json.ast.Json.Str("Wonderland"), + ) + ) + ) + object credentialFormat + extends Annotation[Option[String]]( + description = "The credential format (default to 'JWT')", + example = Some("JWT"), + validator = Validator.enumeration( + List( + Some("JWT"), + Some("SDJWT"), + Some("AnonCreds") + ) + ) + ) + } + + given encoder: JsonEncoder[OOBRequestPresentationInput] = + DeriveJsonEncoder.gen[OOBRequestPresentationInput] + + given decoder: JsonDecoder[OOBRequestPresentationInput] = + DeriveJsonDecoder.gen[OOBRequestPresentationInput] + + import AnoncredPresentationRequestV1.given + + given Schema[AnoncredPresentationRequestV1] = Schema.derived + + given Schema[AnoncredRequestedAttributeV1] = Schema.derived + + given Schema[AnoncredRequestedPredicateV1] = Schema.derived + + given Schema[AnoncredNonRevokedIntervalV1] = Schema.derived + + given schema: Schema[OOBRequestPresentationInput] = Schema.derived +} diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatus.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatus.scala index f9eeeac563..7d4ba13f47 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatus.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatus.scala @@ -33,6 +33,17 @@ final case class PresentationStatus( @description(annotations.connectionId.description) @encodedExample(annotations.connectionId.example) connectionId: Option[String] = None, + @description(annotations.goalcode.description) + @encodedExample(annotations.goalcode.example) + goalCode: Option[String] = None, + @description(annotations.goal.description) + @encodedExample(annotations.goal.example) + goal: Option[String] = None, + @description(annotations.myDid.description) + @encodedExample(annotations.myDid.example) + myDid: Option[String] = None, + @description(annotations.invitation.description) + invitation: Option[OOBPresentationInvitation] = None, @description(annotations.metaRetries.description) @encodedExample(annotations.metaRetries.example) metaRetries: Int, @@ -60,6 +71,10 @@ object PresentationStatus { proofs = Seq.empty, data = data, connectionId = domain.connectionId, + invitation = domain.invitation.map(invitation => OOBPresentationInvitation.fromDomain(invitation)), + goalCode = domain.invitation.flatMap(_.body.goal_code), + goal = domain.invitation.flatMap(_.body.goal), + myDid = domain.invitation.map(_.from.value), metaRetries = domain.metaRetries, metaLastFailure = domain.metaLastFailure.map(failure => ErrorResponse.failureToErrorResponseConversion(failure)), ) @@ -111,7 +126,9 @@ object PresentationStatus { "PresentationRejected", "ProblemReportPending", "ProblemReportSent", - "ProblemReportReceived" + "ProblemReportReceived", + "InvitationGenerated", + "InvitationReceived" ) ) ) @@ -144,6 +161,32 @@ object PresentationStatus { example = ErrorResponse.failureToErrorResponseConversion(FailureInfo("Error", StatusCode.NotFound, "Not Found")) ) + + object goalcode + extends Annotation[String]( + description = + "A self-attested code the receiver may want to display to the user or use in automatically deciding what to do with the out-of-band message.", + example = "present-vp" + ) + + object goal + extends Annotation[String]( + description = + "A self-attested string that the receiver may want to display to the user about the context-specific goal of the out-of-band message.", + example = "To verify a Peter College Graduate credential" + ) + + object myDid + extends Annotation[String]( + description = "The DID representing me as the inviter or invitee in this specific connection.", + example = "did:peer:12345" + ) + + object invitation + extends Annotation[OOBPresentationInvitation]( + description = "The invitation for this Request Presentation", + example = OOBPresentationInvitation.Example + ) } given encoder: JsonEncoder[PresentationStatus] = diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatusPage.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatusPage.scala index df72dc1107..e7c34c1689 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatusPage.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/PresentationStatusPage.scala @@ -6,6 +6,8 @@ import sttp.tapir.Schema import sttp.tapir.Schema.annotations.{description, encodedExample} import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} +import java.util.UUID + final case class PresentationStatusPage( @description(annotations.contents.description) @encodedExample( // This is a hammer - to be improved in the future @@ -70,6 +72,7 @@ object PresentationStatusPage { proofs = Seq.empty, data = Seq.empty, connectionId = Some("e0d81be9-47ca-4e0b-b8a7-325e8c3abc2f"), + invitation = None, metaRetries = 5 ), PresentationStatus( @@ -102,6 +105,26 @@ object PresentationStatusPage { connectionId = Some("e0d81be9-47ca-4e0b-b8a7-325e8c3abc2f"), metaRetries = 5 ), + PresentationStatus( + presentationId = "938bfc23-f78d-4734-9bf3-6dccf300856f", + thid = "04112f4d-e894-4bff-a706-85b3e7190a2c", + role = "Verifier", + status = "InvitationGenerated", + proofs = Seq.empty, + data = Seq.empty, + connectionId = None, + myDid = Some("did:peer:veriferPeerDID1234567890"), + invitation = Some( + OOBPresentationInvitation( + id = UUID.fromString("04112f4d-e894-4bff-a706-85b3e7190a2c"), + `type` = "didcomm/aip2;rfc0048/invitation", + from = "did:peer:veriferPeerDID1234567890", + invitationUrl = + "http://localhost:8000/present-proof/invitation?_oob=eyJpZCI6ImU2M2JkNzQ1LWZjYzYtNGQ0My05NjgzLTY4MjUyOTNlYTgxNiIsInR5cGUiOiJodHRwczovL2RpZGNvbW0ub3JnL291dC1vZi1iYW5kLzIuMC9pbnZpdGF0aW9uIiwiZnJvbSI6ImRpZDpwZWVyOjIuRXo2TFNoOWFSQmRFQlV6WkFRSzN5VnFBRnRYS0pVMVZ1cUZlMVd1U1ZRcnRvRGROZi5WejZNa3NCWmZkc3U4UmFxWjNmdjlBdkJ0elVGd1VyaW5td0xRODFNVjVoc29td2JZLlNleUowSWpvaVpHMGlMQ0p6SWpwN0luVnlhU0k2SW1oMGRIQTZMeTh4T1RJdU1UWTRMakV1TVRrNU9qZ3dOekF2Wkdsa1kyOXRiU0lzSW5JaU9sdGRMQ0poSWpwYkltUnBaR052YlcwdmRqSWlYWDE5IiwiYm9keSI6eyJnb2FsX2NvZGUiOiJwcmVzZW50LXZwIiwiZ29hbCI6IlJlcXVlc3QgcHJvb2Ygb2YgdmFjY2luYXRpb24gaW5mb3JtYXRpb24iLCJhY2NlcHQiOltdfSwiYXR0YWNobWVudHMiOlt7ImlkIjoiZTE5ZjNkNmMtY2U2Ni00Y2EwLWI1ZWUtZDBiY2ZhOGI1MTc3IiwibWVkaWFfdHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJkYXRhIjp7Impzb24iOnsiaWQiOiIxYjMwYzRjZi05MmVjLTQwOTMtYWFlOC1hZDk3NmIzODljY2MiLCJ0eXBlIjoiaHR0cHM6Ly9kaWRjb21tLmF0YWxhcHJpc20uaW8vcHJlc2VudC1wcm9vZi8zLjAvcmVxdWVzdC1wcmVzZW50YXRpb24iLCJib2R5Ijp7ImdvYWxfY29kZSI6IlJlcXVlc3QgUHJvb2YgUHJlc2VudGF0aW9uIiwid2lsbF9jb25maXJtIjpmYWxzZSwicHJvb2ZfdHlwZXMiOltdfSwiYXR0YWNobWVudHMiOlt7ImlkIjoiNDBiZjcyNzUtMDNkNS00MjI1LWFlYjAtMzhhZDYyODhhMThkIiwibWVkaWFfdHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJkYXRhIjp7Impzb24iOnsib3B0aW9ucyI6eyJjaGFsbGVuZ2UiOiIxMWM5MTQ5My0wMWIzLTRjNGQtYWMzNi1iMzM2YmFiNWJkZGYiLCJkb21haW4iOiJodHRwczovL3ByaXNtLXZlcmlmaWVyLmNvbSJ9LCJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjkyODkyMjJmLWY3ZmItNDk4Yi1iMmE0LTNlODdiNzdiMzk5ZiIsImlucHV0X2Rlc2NyaXB0b3JzIjpbXX19fSwiZm9ybWF0IjoicHJpc20vand0In1dLCJ0aGlkIjoiZTYzYmQ3NDUtZmNjNi00ZDQzLTk2ODMtNjgyNTI5M2VhODE2IiwiZnJvbSI6ImRpZDpwZWVyOjIuRXo2TFNoOWFSQmRFQlV6WkFRSzN5VnFBRnRYS0pVMVZ1cUZlMVd1U1ZRcnRvRGROZi5WejZNa3NCWmZkc3U4UmFxWjNmdjlBdkJ0elVGd1VyaW5td0xRODFNVjVoc29td2JZLlNleUowSWpvaVpHMGlMQ0p6SWpwN0luVnlhU0k2SW1oMGRIQTZMeTh4T1RJdU1UWTRMakV1TVRrNU9qZ3dOekF2Wkdsa1kyOXRiU0lzSW5JaU9sdGRMQ0poSWpwYkltUnBaR052YlcwdmRqSWlYWDE5In19fV19" + ) + ), + metaRetries = 5 + ), ) ) } diff --git a/mercury/protocol-invitation/src/test/scala/org/hyperledger/identus/mercury/protocol/invitation/v2/OutOfBandSpec.scala b/mercury/protocol-invitation/src/test/scala/org/hyperledger/identus/mercury/protocol/invitation/v2/OutOfBandSpec.scala index 182dbacb43..60d0cec95c 100644 --- a/mercury/protocol-invitation/src/test/scala/org/hyperledger/identus/mercury/protocol/invitation/v2/OutOfBandSpec.scala +++ b/mercury/protocol-invitation/src/test/scala/org/hyperledger/identus/mercury/protocol/invitation/v2/OutOfBandSpec.scala @@ -23,7 +23,6 @@ class OutOfBandSpec extends FunSuite { ), Body(Some("request-mediate"), Some("RequestMediate"), Seq("didcomm/v2", "didcomm/aip2;env=rfc587")), ) - assertEquals(ret, Right(expected)) } diff --git a/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala new file mode 100644 index 0000000000..1418eb3410 --- /dev/null +++ b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala @@ -0,0 +1,27 @@ +package org.hyperledger.identus.mercury.protocol.presentproof + +import org.hyperledger.identus.mercury.model.AttachmentDescriptor +import org.hyperledger.identus.mercury.model.DidId +import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation +object PresentProofInvitation { + def makeInvitation( + from: DidId, + goalCode: Option[String], + goal: Option[String], + invitationId: String, + requestPresentation: RequestPresentation + ): Invitation = { + val attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment(payload = requestPresentation) + Invitation( + id = invitationId, + from = from, + body = Invitation.Body( + goal_code = goalCode, + goal = goal, + Nil + ), + attachments = Some(Seq(attachmentDescriptor)) + ) + } + +} diff --git a/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/RequestPresentation.scala b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/RequestPresentation.scala index bc522377b5..1548d08800 100644 --- a/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/RequestPresentation.scala +++ b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/RequestPresentation.scala @@ -12,15 +12,15 @@ final case class RequestPresentation( attachments: Seq[AttachmentDescriptor], // extra thid: Option[String] = None, - from: DidId, - to: DidId, + from: Option[DidId], + to: Option[DidId], ) { def makeMessage: Message = Message( id = this.id, `type` = this.`type`, - from = Some(this.from), - to = Seq(this.to), + from = this.from, + to = this.to.toSeq, thid = this.thid, body = this.body.asJson.asObject.get, // TODO get attachments = Some(this.attachments), @@ -64,9 +64,9 @@ object RequestPresentation { thid = Some(msg.id), from = { assert(msg.to.length == 1, "The recipient is ambiguous. Need to have only 1 recipient") // TODO return error - msg.to.head + msg.to.headOption }, - to = msg.from.get, // TODO get + to = msg.from, ) } @@ -79,10 +79,10 @@ object RequestPresentation { body = body, attachments = message.attachments.getOrElse(Seq.empty), thid = message.thid, - from = message.from.get, // TODO get + from = message.from, to = { assert(message.to.length == 1, "The recipient is ambiguous. Need to have only 1 recipient") // TODO return error - message.to.head + message.to.headOption }, ) diff --git a/mercury/protocol-present-proof/src/test/scala/org/hyperledger/identus/mercury/protocol/presentproof/RequestPresentationSpec.scala b/mercury/protocol-present-proof/src/test/scala/org/hyperledger/identus/mercury/protocol/presentproof/RequestPresentationSpec.scala index 7f8458fe99..a647cd96c4 100644 --- a/mercury/protocol-present-proof/src/test/scala/org/hyperledger/identus/mercury/protocol/presentproof/RequestPresentationSpec.scala +++ b/mercury/protocol-present-proof/src/test/scala/org/hyperledger/identus/mercury/protocol/presentproof/RequestPresentationSpec.scala @@ -34,8 +34,8 @@ class RequestCredentialSpec extends ZSuite { id = "061bf917-2cbe-460b-8d12-b1a9609505c2", body = body, attachments = Seq(attachmentDescriptor), - to = DidId("did:prism:test123"), - from = DidId("did:prism:test123"), + to = Some(DidId("did:prism:test123")), + from = Some(DidId("did:prism:test123")), ) val result = requestPresentation.asJson.deepDropNullValues diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/PresentationRecord.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/PresentationRecord.scala index 6847451cfd..d6fbaa972e 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/PresentationRecord.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/PresentationRecord.scala @@ -1,6 +1,7 @@ package org.hyperledger.identus.pollux.core.model import org.hyperledger.identus.mercury.model.DidId +import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation import org.hyperledger.identus.mercury.protocol.presentproof.{Presentation, ProposePresentation, RequestPresentation} import org.hyperledger.identus.shared.models.Failure @@ -18,9 +19,10 @@ final case class PresentationRecord( schemaId: Option[String], connectionId: Option[String], role: PresentationRecord.Role, - subjectId: DidId, + subjectId: DidId, // TODO Remove protocolState: PresentationRecord.ProtocolState, credentialFormat: CredentialFormat, + invitation: Option[Invitation], requestPresentationData: Option[RequestPresentation], proposePresentationData: Option[ProposePresentation], presentationData: Option[Presentation], @@ -90,4 +92,9 @@ object PresentationRecord { // Verifier has rejected the presentation (proof) (Verifier DB) case PresentationRejected extends ProtocolState // TODO send problem report + // Verifier has created a OOB Presentation request (in Verifier DB) + case InvitationGenerated extends ProtocolState + // Verifier receives a presentation from an expired OOB Presentation request (update Verifier DB) //TODO send problem report + case InvitationExpired extends ProtocolState + } diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala index 7150fa8605..93acaf4ef4 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala @@ -121,6 +121,11 @@ object PresentationError { StatusCode.InternalServerError, error ) + final case class MissingSDJWTPresentationRequest(error: String) + extends PresentationError( + StatusCode.InternalServerError, + error + ) final case class NotMatchingPresentationCredentialFormat(cause: Throwable) extends PresentationError( @@ -199,4 +204,29 @@ object PresentationError { StatusCode.InternalServerError, msg ) + + final case class RequestPresentationDecodingError(msg: String) + extends PresentationError( + StatusCode.InternalServerError, + msg + ) + + final case class InvitationParsingError(cause: String) + extends PresentationError( + StatusCode.BadRequest, + cause + ) + + final case class InvitationAlreadyReceived(msg: String) + extends PresentationError( + StatusCode.BadRequest, + msg + ) + + final case class MissingInvitationAttachment(msg: String) + extends PresentationError( + StatusCode.BadRequest, + msg + ) + } diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala index 95f7a91aa0..0a781ae851 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala @@ -6,6 +6,7 @@ import org.hyperledger.identus.pollux.anoncreds.AnoncredPresentation import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.model.error.PresentationError import org.hyperledger.identus.pollux.core.model.presentation.* +import org.hyperledger.identus.pollux.core.model.presentation.Options import org.hyperledger.identus.pollux.core.service.serdes.{AnoncredCredentialProofsV1, AnoncredPresentationRequestV1} import org.hyperledger.identus.pollux.sdjwt.{HolderPrivateKey, PresentationCompact} import org.hyperledger.identus.pollux.vc.jwt.* @@ -22,29 +23,35 @@ trait PresentationService { def createJwtPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + goalCode: Option[String], + goal: Option[String], ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] def createSDJWTPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], claimsToDisclose: ast.Json.Obj, options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + goalCode: Option[String], + goal: Option[String], ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] def createAnoncredPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], - presentationRequest: AnoncredPresentationRequestV1 + presentationRequest: AnoncredPresentationRequestV1, + goalCode: Option[String], + goal: Option[String], ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] def getPresentationRecords( @@ -171,4 +178,8 @@ trait PresentationService { failReason: Option[Failure] ): UIO[Unit] + def getRequestPresentationFromInvitation( + pairwiseProverDID: DidId, + invitation: String + ): ZIO[WalletAccessContext, PresentationError, RequestPresentation] } diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala index bf23847f03..9178b2f688 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala @@ -6,6 +6,7 @@ import io.circe.* import io.circe.parser.* import io.circe.syntax.* import org.hyperledger.identus.mercury.model.* +import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation import org.hyperledger.identus.mercury.protocol.issuecredential.IssueCredentialIssuedFormat import org.hyperledger.identus.mercury.protocol.presentproof.* import org.hyperledger.identus.pollux.anoncreds.* @@ -20,6 +21,7 @@ import org.hyperledger.identus.pollux.sdjwt.{CredentialCompact, HolderPrivateKey import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.shared.models.* import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect +import org.hyperledger.identus.shared.utils.Base64Utils import zio.* import zio.json.* @@ -195,8 +197,8 @@ private class PresentationServiceImpl( ) ), thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), - from = requestPresentation.to, - to = requestPresentation.from + from = requestPresentation.to.getOrElse(throw RuntimeException(s"RequestPresentation to field is missing")), + to = requestPresentation.from.getOrElse(throw RuntimeException(s"RequestPresentation from field is missing")) ) ) } yield presentation @@ -266,8 +268,8 @@ private class PresentationServiceImpl( ) ), thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), - from = requestPresentation.to, - to = requestPresentation.from + from = requestPresentation.to.getOrElse(throw RuntimeException(s"RequestPresentation to field is missing")), + to = requestPresentation.from.getOrElse(throw RuntimeException(s"RequestPresentation from field is missing")) ) ) } yield presentation @@ -303,11 +305,13 @@ private class PresentationServiceImpl( override def createJwtPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], - options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options] + options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + goalCode: Option[String] = None, + goal: Option[String] = None, ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { createPresentationRecord( pairwiseVerifierDID, @@ -316,18 +320,22 @@ private class PresentationServiceImpl( connectionId, CredentialFormat.JWT, proofTypes, - options.map(o => Seq(toJWTAttachment(o))).getOrElse(Seq.empty) + options.map(o => Seq(toJWTAttachment(o))).getOrElse(Seq.empty), + goalCode, + goal ) } override def createSDJWTPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], claimsToDisclose: ast.Json.Obj, options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + goalCode: Option[String] = None, + goal: Option[String] = None, ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { createPresentationRecord( pairwiseVerifierDID, @@ -336,16 +344,20 @@ private class PresentationServiceImpl( connectionId, CredentialFormat.SDJWT, proofTypes, - attachments = Seq(toSDJWTAttachment(options, claimsToDisclose)) + attachments = Seq(toSDJWTAttachment(options, claimsToDisclose)), + goalCode, + goal ) } override def createAnoncredPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], - presentationRequest: AnoncredPresentationRequestV1 + presentationRequest: AnoncredPresentationRequestV1, + goalCode: Option[String] = None, + goal: Option[String] = None, ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { createPresentationRecord( pairwiseVerifierDID, @@ -354,29 +366,45 @@ private class PresentationServiceImpl( connectionId, CredentialFormat.AnonCreds, Seq.empty, - Seq(toAnoncredAttachment(presentationRequest)) + Seq(toAnoncredAttachment(presentationRequest)), + goalCode, + goal ) } private def createPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], format: CredentialFormat, proofTypes: Seq[ProofType], - attachments: Seq[AttachmentDescriptor] + attachments: Seq[AttachmentDescriptor], + goalCode: Option[String] = None, + goal: Option[String] = None, ) = { for { request <- ZIO.succeed( createDidCommRequestPresentation( proofTypes, thid, - pairwiseVerifierDID, + Some(pairwiseVerifierDID), pairwiseProverDID, attachments ) ) + invitation = connectionId.fold( + Some( + PresentProofInvitation.makeInvitation( + pairwiseVerifierDID, + goalCode, + goal, + thid.value, + request + ) + ) + )(_ => None) + record <- ZIO.succeed( PresentationRecord( id = DidCommID(), @@ -386,9 +414,12 @@ private class PresentationServiceImpl( connectionId = connectionId, schemaId = None, // TODO REMOVE from DB role = PresentationRecord.Role.Verifier, - subjectId = pairwiseProverDID, - protocolState = PresentationRecord.ProtocolState.RequestPending, + subjectId = pairwiseProverDID.getOrElse(DidId("TODO REMOVE subject did")), + protocolState = invitation.fold(PresentationRecord.ProtocolState.RequestPending)(_ => + PresentationRecord.ProtocolState.InvitationGenerated + ), credentialFormat = format, + invitation = invitation, requestPresentationData = Some(request), proposePresentationData = None, presentationData = None, @@ -463,9 +494,11 @@ private class PresentationServiceImpl( connectionId = connectionId, schemaId = None, role = Role.Prover, - subjectId = request.to, + subjectId = + request.to.getOrElse(throw RuntimeException(s"RequestPresentation from field is missing")), // TODO REMOVE protocolState = PresentationRecord.ProtocolState.RequestReceived, credentialFormat = format, + invitation = None, requestPresentationData = Some(request), proposePresentationData = None, presentationData = None, @@ -1147,8 +1180,8 @@ private class PresentationServiceImpl( private def createDidCommRequestPresentation( proofTypes: Seq[ProofType], thid: DidCommID, - pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseVerifierDID: Option[DidId], + pairwiseProverDID: Option[DidId], attachments: Seq[AttachmentDescriptor] ): RequestPresentation = { RequestPresentation( @@ -1172,8 +1205,8 @@ private class PresentationServiceImpl( RequestPresentation( body = body, attachments = proposePresentation.attachments, - from = proposePresentation.to, - to = proposePresentation.from, + from = Some(proposePresentation.to), + to = Some(proposePresentation.from), thid = proposePresentation.thid ) } @@ -1189,6 +1222,47 @@ private class PresentationServiceImpl( record <- getRecord(id) } yield record + override def getRequestPresentationFromInvitation( + pairwiseProverDID: DidId, + invitation: String + ): ZIO[WalletAccessContext, PresentationError, RequestPresentation] = { + for { + invitation <- ZIO + .fromEither(io.circe.parser.decode[Invitation](Base64Utils.decodeUrlToString(invitation))) + .mapError(err => InvitationParsingError(err.getMessage)) + _ <- presentationRepository + .findPresentationRecordByThreadId(DidCommID(invitation.id)) + .flatMap { + case None => ZIO.unit + case Some(_) => ZIO.fail(InvitationAlreadyReceived(invitation.id)) + } + requestPresentation <- ZIO.fromEither { + invitation.attachments + .flatMap( + _.headOption.map(attachment => + decode[org.hyperledger.identus.mercury.model.JsonData]( + attachment.data.asJson.noSpaces + ) // TODO Move mercury to use ZIO JSON + .flatMap { data => + RequestPresentation.given_Decoder_RequestPresentation + .decodeJson(data.json.asJson) + .map(r => r.copy(to = Some(pairwiseProverDID))) + .leftMap(err => + PresentationDecodingError( + s"RequestPresentation As Attachment decoding error: ${err.getMessage}" + ) + ) + } + .leftMap(err => PresentationDecodingError(s"Invitation Attachment JsonData decoding error: $err")) + ) + ) + .getOrElse( + Left(MissingInvitationAttachment("Missing Invitation Attachment for RequestPresentation")) + ) + } + } yield requestPresentation + + } } object PresentationServiceImpl { diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifier.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifier.scala index cc622522e9..955149f1df 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifier.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifier.scala @@ -31,11 +31,13 @@ class PresentationServiceNotifier( override def createJwtPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], options: Option[Options], + goalCode: Option[String], + goal: Option[String], ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = notifyOnSuccess( svc.createJwtPresentationRecord( @@ -44,18 +46,22 @@ class PresentationServiceNotifier( thid, connectionId, proofTypes, - options + options, + goalCode, + goal ) ) override def createSDJWTPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], claimsToDisclose: ast.Json.Obj, - options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options] + options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + goalCode: Option[String], + goal: Option[String], ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = notifyOnSuccess( svc.createSDJWTPresentationRecord( @@ -66,15 +72,19 @@ class PresentationServiceNotifier( proofTypes, claimsToDisclose, options, + goalCode, + goal ) ) def createAnoncredPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], - presentationRequest: AnoncredPresentationRequestV1 + presentationRequest: AnoncredPresentationRequestV1, + goalCode: Option[String], + goal: Option[String] ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = notifyOnSuccess( svc.createAnoncredPresentationRecord( @@ -82,7 +92,9 @@ class PresentationServiceNotifier( pairwiseProverDID, thid, connectionId, - presentationRequest + presentationRequest, + goalCode, + goal ) ) @@ -282,6 +294,12 @@ class PresentationServiceNotifier( recordId: DidCommID, failReason: Option[Failure] ): UIO[Unit] = svc.reportProcessingFailure(recordId, failReason) + + override def getRequestPresentationFromInvitation( + pairwiseProverDID: DidId, + invitation: String + ): ZIO[WalletAccessContext, PresentationError, RequestPresentation] = + svc.getRequestPresentationFromInvitation(pairwiseProverDID, invitation) } object PresentationServiceNotifier { diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositorySpecSuite.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositorySpecSuite.scala index 13c270c149..0d1aab356c 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositorySpecSuite.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositorySpecSuite.scala @@ -27,6 +27,7 @@ object PresentationRepositorySpecSuite { subjectId = DidId("did:prism:aaa"), protocolState = PresentationRecord.ProtocolState.RequestPending, credentialFormat = CredentialFormat.JWT, + invitation = None, requestPresentationData = None, proposePresentationData = None, presentationData = None, @@ -41,8 +42,8 @@ object PresentationRepositorySpecSuite { ).withTruncatedTimestamp() private def requestPresentation = RequestPresentation( - from = DidId("did:prism:aaa"), - to = DidId("did:prism:bbb"), + from = Some(DidId("did:prism:aaa")), + to = Some(DidId("did:prism:bbb")), thid = Some(UUID.randomUUID.toString), body = RequestPresentation.Body(goal_code = Some("request Presentation")), attachments = Nil diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockPresentationService.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockPresentationService.scala index fd47d0d48e..bcfb0d95d7 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockPresentationService.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockPresentationService.scala @@ -26,20 +26,47 @@ object MockPresentationService extends Mock[PresentationService] { object CreateJwtPresentationRecord extends Effect[ - (DidId, DidId, DidCommID, Option[String], Seq[ProofType], Option[Options]), + ( + DidId, + Option[DidId], + DidCommID, + Option[String], + Seq[ProofType], + Option[Options], + Option[String], + Option[String] + ), PresentationError, PresentationRecord ] object CreateSDJWTPresentationRecord extends Effect[ - (DidId, DidId, DidCommID, Option[String], Seq[ProofType], ast.Json.Obj, Option[Options]), + ( + DidId, + Option[DidId], + DidCommID, + Option[String], + Seq[ProofType], + ast.Json.Obj, + Option[Options], + Option[String], + Option[String] + ), PresentationError, PresentationRecord ] object CreateAnoncredPresentationRecord extends Effect[ - (DidId, DidId, DidCommID, Option[String], AnoncredPresentationRequestV1), + ( + DidId, + Option[DidId], + DidCommID, + Option[String], + AnoncredPresentationRequestV1, + Option[String], + Option[String] + ), PresentationError, PresentationRecord ] @@ -60,6 +87,8 @@ object MockPresentationService extends Mock[PresentationService] { object AcceptRequestPresentation extends Effect[(DidCommID, Seq[String]), PresentationError, PresentationRecord] + object AcceptRequestPresentationInvitation extends Effect[(DidId, String), PresentationError, RequestPresentation] + object AcceptSDJWTRequestPresentation extends Effect[(DidCommID, Seq[String], Option[ast.Json.Obj]), PresentationError, PresentationRecord] @@ -90,41 +119,57 @@ object MockPresentationService extends Mock[PresentationService] { override def createJwtPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], - options: Option[Options] + options: Option[Options], + goalCode: Option[String], + goal: Option[String] ): IO[PresentationError, PresentationRecord] = proxy( CreateJwtPresentationRecord, - (pairwiseVerifierDID, pairwiseProverDID, thid, connectionId, proofTypes, options) + (pairwiseVerifierDID, pairwiseProverDID, thid, connectionId, proofTypes, options, goalCode, goal) ) override def createSDJWTPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], proofTypes: Seq[ProofType], claimsToDisclose: ast.Json.Obj, options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + goalCode: Option[String], + goal: Option[String] ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = proxy( CreateSDJWTPresentationRecord, - (pairwiseVerifierDID, pairwiseProverDID, thid, connectionId, proofTypes, claimsToDisclose, options) + ( + pairwiseVerifierDID, + pairwiseProverDID, + thid, + connectionId, + proofTypes, + claimsToDisclose, + options, + goalCode, + goal + ) ) override def createAnoncredPresentationRecord( pairwiseVerifierDID: DidId, - pairwiseProverDID: DidId, + pairwiseProverDID: Option[DidId], thid: DidCommID, connectionId: Option[String], - presentationRequest: AnoncredPresentationRequestV1 + presentationRequest: AnoncredPresentationRequestV1, + goalCode: Option[String], + goal: Option[String] ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] = { proxy( CreateAnoncredPresentationRecord, - (pairwiseVerifierDID, pairwiseProverDID, thid, connectionId, presentationRequest) + (pairwiseVerifierDID, pairwiseProverDID, thid, connectionId, presentationRequest, goalCode, goal) ) } @@ -264,6 +309,11 @@ object MockPresentationService extends Mock[PresentationService] { failReason: Option[Failure] ): UIO[Unit] = ??? + override def getRequestPresentationFromInvitation( + pairwiseProverDID: DidId, + invitation: String + ): IO[PresentationError, RequestPresentation] = + proxy(AcceptRequestPresentationInvitation, (pairwiseProverDID, invitation)) } } diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifierSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifierSpec.scala index 608e92305b..9eef4b8372 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifierSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifierSpec.scala @@ -37,6 +37,7 @@ object PresentationServiceNotifierSpec extends ZIOSpecDefault with PresentationS None, None, None, + None, 5, None, None @@ -109,10 +110,12 @@ object PresentationServiceNotifierSpec extends ZIOSpecDefault with PresentationS record <- svc.createJwtPresentationRecord( DidId(""), - DidId(""), + Some(DidId("")), DidCommID(""), None, Seq.empty, + None, + None, None ) _ <- svc.markRequestPresentationSent(record.id) diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpec.scala index 7acb44110e..b135946e1d 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpec.scala @@ -60,7 +60,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp check( Gen.uuid.map(e => DidCommID(e.toString)), - Gen.option(Gen.string), + Gen.string, Gen.listOfBounded(1, 5)(proofTypeGen), Gen.option(optionsGen) ) { (thid, connectionId, proofTypes, options) => @@ -70,20 +70,22 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp pairwiseProverDid = DidId("did:peer:Prover") record <- svc.createJwtPresentationRecord( pairwiseVerifierDid, - pairwiseProverDid, + Some(pairwiseProverDid), thid, - connectionId, + Some(connectionId), proofTypes, - options + options, + None, + None ) } yield { assertTrue(record.thid == thid) && assertTrue(record.updatedAt.isEmpty) && - assertTrue(record.connectionId == connectionId) && + assertTrue(record.connectionId.contains(connectionId)) && assertTrue(record.role == PresentationRecord.Role.Verifier) && assertTrue(record.protocolState == PresentationRecord.ProtocolState.RequestPending) && assertTrue(record.requestPresentationData.isDefined) && - assertTrue(record.requestPresentationData.get.to == pairwiseProverDid) && + assertTrue(record.requestPresentationData.get.to.contains(pairwiseProverDid)) && assertTrue(record.requestPresentationData.get.thid.contains(thid.toString)) && assertTrue(record.requestPresentationData.get.body.goal_code.contains("Request Proof Presentation")) && assertTrue(record.requestPresentationData.get.body.proof_types == proofTypes) && @@ -116,7 +118,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp test("createPresentationRecord creates a valid Anoncred PresentationRecord") { check( Gen.uuid.map(e => DidCommID(e.toString)), - Gen.option(Gen.string), + Gen.string, Gen.string, Gen.string, Gen.string @@ -136,19 +138,21 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp record <- svc.createAnoncredPresentationRecord( pairwiseVerifierDid, - pairwiseProverDid, + Some(pairwiseProverDid), thid, - connectionId, - anoncredPresentationRequestV1 + Some(connectionId), + anoncredPresentationRequestV1, + None, + None ) } yield { assertTrue(record.thid == thid) && assertTrue(record.updatedAt.isEmpty) && - assertTrue(record.connectionId == connectionId) && + assertTrue(record.connectionId.contains(connectionId)) && assertTrue(record.role == PresentationRecord.Role.Verifier) && assertTrue(record.protocolState == PresentationRecord.ProtocolState.RequestPending) && assertTrue(record.requestPresentationData.isDefined) && - assertTrue(record.requestPresentationData.get.to == pairwiseProverDid) && + assertTrue(record.requestPresentationData.get.to.contains(pairwiseProverDid)) && assertTrue(record.requestPresentationData.get.thid.contains(thid.toString)) && assertTrue(record.requestPresentationData.get.body.goal_code.contains("Request Proof Presentation")) && assertTrue( @@ -322,8 +326,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp "domain": "us.gov/DriverLicense", "credential_manifest": {} }""" - prover = DidId("did:peer:Prover") - verifier = DidId("did:peer:Verifier") + prover = Some(DidId("did:peer:Prover")) + verifier = Some(DidId("did:peer:Verifier")) attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment( payload = presentationAttachmentAsJson, @@ -350,8 +354,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp "domain": "us.gov/DriverLicense", "credential_manifest": {} }""" - prover = DidId("did:peer:Prover") - verifier = DidId("did:peer:Verifier") + prover = Some(DidId("did:peer:Prover")) + verifier = Some(DidId("did:peer:Verifier")) attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment( payload = presentationAttachmentAsJson, @@ -379,8 +383,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp "domain": "us.gov/DriverLicense", "credential_manifest": {} }""" - prover = DidId("did:peer:Prover") - verifier = DidId("did:peer:Verifier") + prover = Some(DidId("did:peer:Prover")) + verifier = Some(DidId("did:peer:Verifier")) attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment( payload = presentationAttachmentAsJson, @@ -536,8 +540,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp requestPresentation = RequestPresentation( body = RequestPresentation.Body(goal_code = Some("Presentation Request")), attachments = Seq(attachmentDescriptor), - to = DidId("did:peer:Prover"), - from = DidId("did:peer:Verifier"), + to = Some(DidId("did:peer:Prover")), + from = Some(DidId("did:peer:Verifier")), ) aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentation) credentialsToUse = @@ -598,8 +602,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp requestPresentation = RequestPresentation( body = RequestPresentation.Body(goal_code = Some("Presentation Request")), attachments = Seq(attachmentDescriptor), - to = DidId("did:peer:Prover"), - from = DidId("did:peer:Verifier"), + to = Some(DidId("did:peer:Prover")), + from = Some(DidId("did:peer:Verifier")), ) aRecord <- svc.receiveRequestPresentation(connectionId, requestPresentation) credentialsToUse = Seq(aIssueCredentialRecord.id.value) @@ -772,8 +776,8 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp svc <- ZIO.service[PresentationService] connectionId = Some("connectionId") body = RequestPresentation.Body(goal_code = Some("Presentation Request")) - prover = DidId("did:peer:Prover") - verifier = DidId("did:peer:Verifier") + prover = Some(DidId("did:peer:Prover")) + verifier = Some(DidId("did:peer:Verifier")) attachmentDescriptor = attachment requestPresentation = RequestPresentation( body = body, diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala index 0c356aeba6..701852d856 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala @@ -72,8 +72,8 @@ trait PresentationServiceSpecHelper { "domain": "us.gov/DriverLicense", "credential_manifest": {} }""" - val prover = DidId("did:peer:Prover") - val verifier = DidId("did:peer:Verifier") + val prover = Some(DidId("did:peer:Prover")) + val verifier = Some(DidId("did:peer:Verifier")) val attachmentDescriptor = AttachmentDescriptor.buildJsonAttachment( payload = presentationAttachmentAsJson, @@ -165,10 +165,12 @@ trait PresentationServiceSpecHelper { svc.createJwtPresentationRecord( thid = thid, pairwiseVerifierDID = pairwiseVerifierDID, - pairwiseProverDID = pairwiseProverDID, + pairwiseProverDID = Some(pairwiseProverDID), connectionId = Some("connectionId"), proofTypes = Seq(proofType), - options = options + options = options, + goalCode = None, + goal = None ) } @@ -208,9 +210,11 @@ trait PresentationServiceSpecHelper { svc.createAnoncredPresentationRecord( thid = thid, pairwiseVerifierDID = pairwiseVerifierDID, - pairwiseProverDID = pairwiseProverDID, + pairwiseProverDID = Some(pairwiseProverDID), connectionId = Some("connectionId"), - anoncredPresentationRequestV1 + anoncredPresentationRequestV1, + goalCode = None, + goal = None ) } } diff --git a/pollux/sql-doobie/src/main/resources/sql/pollux/V24__add_invitation_column_presentation_record.sql b/pollux/sql-doobie/src/main/resources/sql/pollux/V24__add_invitation_column_presentation_record.sql new file mode 100644 index 0000000000..f5b7600651 --- /dev/null +++ b/pollux/sql-doobie/src/main/resources/sql/pollux/V24__add_invitation_column_presentation_record.sql @@ -0,0 +1,4 @@ +-- presentation_records +-- Introduce new field invitation for connection-less presentation +ALTER TABLE public.presentation_records + ADD COLUMN "invitation" TEXT; \ No newline at end of file diff --git a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcPresentationRepository.scala b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcPresentationRepository.scala index 57a22d2ee8..6df0a78f6d 100644 --- a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcPresentationRepository.scala +++ b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcPresentationRepository.scala @@ -11,6 +11,7 @@ import io.circe import io.circe.* import io.circe.parser.* import io.circe.syntax.* +import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation import org.hyperledger.identus.mercury.protocol.presentproof.* import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.repository.PresentationRepository @@ -164,6 +165,9 @@ class JdbcPresentationRepository( given failureGet: Get[Failure] = Get[String].temap(_.fromJson[FailureInfo]) given failurePut: Put[Failure] = Put[String].contramap(_.asFailureInfo.toJson) + given invitationGet: Get[Invitation] = Get[String].map(decode[Invitation](_).getOrElse(???)) + given invitationPut: Put[Invitation] = Put[String].contramap(_.asJson.toString) + override def createPresentationRecord(record: PresentationRecord): URIO[WalletAccessContext, Unit] = { val cxnIO = sql""" | INSERT INTO public.presentation_records( @@ -177,6 +181,7 @@ class JdbcPresentationRepository( | subject_id, | protocol_state, | credential_format, + | invitation, | request_presentation_data, | credentials_to_use, | anoncred_credentials_to_use_json_schema_id, @@ -198,6 +203,7 @@ class JdbcPresentationRepository( | ${record.subjectId}, | ${record.protocolState}, | ${record.credentialFormat}, + | ${record.invitation}, | ${record.requestPresentationData}, | ${record.credentialsToUse.map(_.toList)}, | ${record.anoncredCredentialsToUseJsonSchemaId}, @@ -235,6 +241,7 @@ class JdbcPresentationRepository( | subject_id, | protocol_state, | credential_format, + | invitation, | request_presentation_data, | propose_presentation_data, | presentation_data, @@ -286,6 +293,7 @@ class JdbcPresentationRepository( | subject_id, | protocol_state, | credential_format, + | invitation, | request_presentation_data, | propose_presentation_data, | presentation_data, @@ -334,6 +342,7 @@ class JdbcPresentationRepository( | subject_id, | protocol_state, | credential_format, + | invitation, | request_presentation_data, | propose_presentation_data, | presentation_data, @@ -371,6 +380,7 @@ class JdbcPresentationRepository( | subject_id, | protocol_state, | credential_format, + | invitation, | request_presentation_data, | propose_presentation_data, | presentation_data, From 88efa9ca1bc323af4cac35fb3096ac44b74e74bc Mon Sep 17 00:00:00 2001 From: womfoo Date: Fri, 16 Aug 2024 18:04:11 +1000 Subject: [PATCH 3/9] fix: misc spelling (#1288) Signed-off-by: Kranium Gikos Mendoza --- CONTRIBUTING.md | 2 +- README.md | 2 +- cloud-agent/service/README.md | 4 ++-- .../multitenancy/tenant-onboarding-ext-iam.md | 4 ++-- docs/guides/deploying-node.md | 22 +++++++++---------- infrastructure/dev/README.md | 2 +- infrastructure/local/README.md | 2 +- infrastructure/multi/README.md | 2 +- .../Coordinate-Mediation-Protocol.md | 2 +- .../DidExchange-Protocol.md | 2 +- .../Invitation-Protocol.md | 2 +- .../Report-Problem-Protocol.md | 2 +- .../Revocation-notification-protocol.md | 2 +- mercury/protocol-routing/Routing-Protocol.md | 2 +- 14 files changed, 26 insertions(+), 26 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4f3e52535..f030e6aee9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ If you would like to *implement* a new feature, please consider the size of the Before you submit an issue, please search the issue tracker. An issue for your problem might already exist and the discussion might inform you of workarounds readily available. -You can file new issues by selecting a `Bug Report` template on our [Issues submition page](https://github.com/hyperledger/identus-cloud-agent/issues/new/choose). +You can file new issues by selecting a `Bug Report` template on our [Issues submission page](https://github.com/hyperledger/identus-cloud-agent/issues/new/choose). ### Submitting a Pull Request (PR) diff --git a/README.md b/README.md index dc0e47eab8..8c3f541f78 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Before starting to use the Cloud Agent, it is important to understand the basic The next diagrams offer a concise architectural overview, depicting a Cloud Agent instance, a controller, the interconnections linking the controller and agent, as well as the external routes to other agents and public ledgers across the Internet. -#### Identus Ecosystem Achitecture +#### Identus Ecosystem Architecture ![Identus Architecture](docs/images/identus-architecture-dark.png#gh-dark-mode-only) ![Identus Architecture](docs/images/identus-architecture-light.png#gh-light-mode-only) diff --git a/cloud-agent/service/README.md b/cloud-agent/service/README.md index e26bd473ee..4ec4cc7f02 100644 --- a/cloud-agent/service/README.md +++ b/cloud-agent/service/README.md @@ -13,12 +13,12 @@ see `./infrastucture/local/README.md` for instruction `cloud-agent` is a cloud agent that represents the digital identity (is a DID controller) of the Issuing / Verification organization. As a DID controller, it needs to perform the operation with private and public keys through the Wallet API abstraction level. -The interface for key-mangement is heavily inspired by +The interface for key-management is heavily inspired by [indy-sdk-java-wrapper](https://github.com/hyperledger/indy-sdk/tree/main/wrappers/java). There is an `wallet-api` subproject which is responsible for managing and storing DID key-pairs. The main goal is to wrap Castor and Pollux libraries which does not handle private-keys -and ease the usage by providing key-mangement capabilities. +and ease the usage by providing key-management capabilities. Similar to [Indy Wallet SDK - secret API](https://github.com/hyperledger/indy-sdk/tree/main/docs/design/003-wallet-storage#secrets-api), *it does not expose a private-key* for external use, instead it provide functions to perform cryptographic actions using internally stored private-keys. diff --git a/docs/docusaurus/multitenancy/tenant-onboarding-ext-iam.md b/docs/docusaurus/multitenancy/tenant-onboarding-ext-iam.md index 61aee50ac5..c6fc3b6749 100644 --- a/docs/docusaurus/multitenancy/tenant-onboarding-ext-iam.md +++ b/docs/docusaurus/multitenancy/tenant-onboarding-ext-iam.md @@ -184,10 +184,10 @@ Example response log < content-length: 0 ``` -The response should return status `201 Created` indicating the new user is registerd with username `alice` with a password `1234`. +The response should return status `201 Created` indicating the new user is registered with username `alice` with a password `1234`. The user ID can be observed from `Location` header of the response. This ID will be used for creating permission later in this tutorial. -For in-depth user management, please consult the official Keycloaak administration documentation on [managing users section](https://www.keycloak.org/docs/latest/server_admin/index.html#assembly-managing-users_server_administration_guide). +For in-depth user management, please consult the official Keycloak administration documentation on [managing users section](https://www.keycloak.org/docs/latest/server_admin/index.html#assembly-managing-users_server_administration_guide). ### 4. Grant the user permission to the wallet diff --git a/docs/guides/deploying-node.md b/docs/guides/deploying-node.md index 982bc0ebab..e57d739f16 100644 --- a/docs/guides/deploying-node.md +++ b/docs/guides/deploying-node.md @@ -13,7 +13,7 @@ The Open Enterprise Agent communicates with the Node, with all operations conduc The Node consists of four separate executables: 1. Node gRPC server -2. Node PosgresQL database +2. Node PostgreSQL database 3. Cardano wallet backend 4. DB sync @@ -22,7 +22,7 @@ The Node consists of four separate executables: The Node gRPC is a service responsible for submitting transactions to the Cardano network with a configurable frequency, retrieving blocks, and processing the data for storage in a database. -#### Node PosgresQL database +#### Node PostgreSQL database The database used by the Node to store processed data, namely DID documents and their respective states. @@ -52,15 +52,15 @@ graph TB ``` -### Node enviroment variables +### Node environment variables | Environment Variable | Description | Default Value | Data Type | |-----------------------------------------|---------------------------------------------------------------------------------------|-----------------|----------------------------------| -| NODE_PSQL_HOST | Host and port of Node PosgresQL database | localhost:5432 | String | -| NODE_PSQL_DATABASE | Name of the database to connect to | node_db | String | +| NODE_PSQL_HOST | Host and port of Node PostgreSQL database | localhost:5432 | String | +| NODE_PSQL_DATABASE | Name of the database to connect to | node_db | String | | NODE_PSQL_USERNAME | Username for database authentication | postgres | String | -| NODE_PSQL_PASSWORD | Password for database authentication | postgres | String | +| NODE_PSQL_PASSWORD | Password for database authentication | postgres | String | | NODE_PSQL_AWAIT_CONNECTION_THREADS | Maximum amount of database connections | 8 | Int | | NODE_LEDGER | Ledger which will be used for txs and blocks | in-memory | Enum(in-memory, cardano) | | NODE_REFRESH_AND_SUBMIT_PERIOD | Time period between refreshing transaction statuses and submitting pending operations | 20s | String | @@ -74,7 +74,7 @@ graph TB | NODE_ID_CHAR_LIMIT | Maximum number of characters id field of pk and service can have | 50 | Int | | NODE_CARDANO_NETWORK | Cardano network node should operate on | testnet | Enum(testnet, mainnet) | | NODE_CARDANO_WALLET_ID | ID (hex encoded) of the wallet to use for payments | | String | -| NODE_CARDANO_WALLET_PASSPHRASE | Spending passphrase of NODE_CARDANO_WALLET_ID | | String | +| NODE_CARDANO_WALLET_PASSPHRASE | Spending passphrase of NODE_CARDANO_WALLET_ID | | String | | NODE_CARDANO_PAYMENT_ADDRESS | Address (hex encoded) to make payments to, can be NODE_CARDANO_WALLET_ID itself | | String | | NODE_CARDANO_WALLET_API_HOST | Cardano wallet backend API host | localhost | String | | NODE_CARDANO_WALLET_API_PORT | Cardano wallet backend API port | 8090 | Int | @@ -82,7 +82,7 @@ graph TB | NODE_CARDANO_PRISM_GENESIS_BLOCK | Index of the first block from which node should start syncing from | 1868381 | Int | | NODE_CARDANO_CONFIRMATION_BLOCKS | Number of blocks to wait before transaction is considered to be confirmed | 112 | Int | | NODE_CARDANO_DB_SYNC_HOST | Db sync database host and port | localhost:5433 | String | -| NODE_CARDANO_DB_SYNC_DATABASE | databse name in DB sync PosgresQL database | cexplorer | String | +| NODE_CARDANO_DB_SYNC_DATABASE | database name in DB sync PostgreSQL database | cexplorer | String | | NODE_CARDANO_DB_SYNC_USERNAME | Username for db sync database authentication | postgres | String | | NODE_CARDANO_DB_SYNC_PASSWORD | Password for db sync database authentication | password | String | @@ -110,8 +110,8 @@ Once you have these services up and running, specify their respective URLs in th - `NODE_CARDANO_WALLET_ROUTING_HEADER_NAME` for wallet routing header name * DB-sync - `NODE_CARDANO_DB_SYNC_HOST` for DB-sync host and port in a format `host:port` - - `NODE_CARDANO_DB_SYNC_DATABASE` the databse name in DB-sync postgres database - - `NODE_CARDANO_DB_SYNC_USERNAME` DB-sync Database username + - `NODE_CARDANO_DB_SYNC_DATABASE` the database name in DB-sync postgres database + - `NODE_CARDANO_DB_SYNC_USERNAME` DB-sync Database username - `NODE_CARDANO_DB_SYNC_PASSWORD` DB-sync Database password When running the Node with Cardano ledger, you must specify which network to use, either `mainnet` or `testnet`, using the `NODE_CARDANO_NETWORK` environment variable. While this environment variable is essential for the correct operation of the Node, it does not define the usable network. As mentioned earlier, the interface communicates with the Cardano node; subsequently, the network is DB-sync and Cardano wallet backend. Therefore, when configuring those services, you must specify the network used in their respective configurations. It is possible to run DB-sync on testnet, and Cardano wallet backend on mainnet, and select either one via `NODE_CARDANO_NETWORK`. The Node won't report any errors, but this configuration would be incorrect and won't work correctly. You are responsible for syncing these three components. If you intend to use the testnet, set `NODE_CARDANO_NETWORK` to the testnet, but also run Cardano wallet backend connected to the testnet and start DB-sync to sync from the Node that is also running on the testnet as well. The same goes with mainnet. @@ -139,4 +139,4 @@ Node DB is a simple PostgreSQL database. Cardano wallet is an application that communicates with the Cardano network; it functions as a server that you can start and connect to either the mainnet or testnet. You must provide the Node runnable's host and port as environment variables. -DB-sync is an application responsible for syncing the Cardano blockchain with a PostgreSQL database. It would help if you used it to sync with either the mainnet or testnet and must provide the database host with port, database name, and credentials as environment variables to the Node runnable. \ No newline at end of file +DB-sync is an application responsible for syncing the Cardano blockchain with a PostgreSQL database. It would help if you used it to sync with either the mainnet or testnet and must provide the database host with port, database name, and credentials as environment variables to the Node runnable. diff --git a/infrastructure/dev/README.md b/infrastructure/dev/README.md index 937e80b411..f2e304cc30 100644 --- a/infrastructure/dev/README.md +++ b/infrastructure/dev/README.md @@ -31,7 +31,7 @@ echo $GITHUB_TOKEN | docker login ghcr.io -u YOUR_USERNAME_HERE} --password-stdi | Name | Purpose | Notes | | -------- | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | build.sh | Compile and Publish libraries and docker images locally | Requires sbt and Docker installed | -| run.sh | Retrieves versions from local components and brings up a docker-compose stack using built components | Does not build images or libraries. Can be used to run multiple instances with command line paramaters [see below] | +| run.sh | Retrieves versions from local components and brings up a docker-compose stack using built components | Does not build images or libraries. Can be used to run multiple instances with command line parameters [see below] | | stop.sh | Stops a running instance | Used to stop a running instance if you've executed `run.sh` with the `-b/--background` option. Please note - you must supply the same `-n/--name` parameter to this script if you have used a non-default value in the `run.sh` script | | clean.sh | Clean local build cache | Runs sbt clean;cleanFiles to clear local cache / state from build directories | | full.sh | Runs `build.sh` followed by `run.sh` | Use this to build and run the stack. Sometimes quite slow if not rebuilding all components | diff --git a/infrastructure/local/README.md b/infrastructure/local/README.md index a31fc48464..3998e45156 100644 --- a/infrastructure/local/README.md +++ b/infrastructure/local/README.md @@ -36,7 +36,7 @@ To run the Atala `building-block` stack - execute the `run.sh` script. This can | Name | Purpose | Notes | | ------ | ------------------------------------ | ------------------------------------------------------------------------ | -| run.sh | Run the Atala `building-block` stack | Runs using docker-compose and versions are controlled by the `.env` file. Can be used to run multiple instances with command line paramaters [see below] | +| run.sh | Run the Atala `building-block` stack | Runs using docker-compose and versions are controlled by the `.env` file. Can be used to run multiple instances with command line parameters [see below] | | stop.sh | Stops a running instance | Used to stop a running instance if you've executed `run.sh` with the `-b/--background` option. Please note - you must supply the same `-n/--name` parameter to this script if you have used a non-default value in the `run.sh` script | ## run.sh diff --git a/infrastructure/multi/README.md b/infrastructure/multi/README.md index 6ee77466c2..c1987b346a 100644 --- a/infrastructure/multi/README.md +++ b/infrastructure/multi/README.md @@ -1,6 +1,6 @@ # Running multiple locally -This folder contains scripts to run mulitple copies of the Atala `building-block` stack for end-user usage - without building any local components. +This folder contains scripts to run multiple copies of the Atala `building-block` stack for end-user usage - without building any local components. This allows you to run an issuer, holder and verifier with a single run command - but - these are locked to fixed ports and cannot be customised at run time. diff --git a/mercury/protocol-coordinate-mediation/Coordinate-Mediation-Protocol.md b/mercury/protocol-coordinate-mediation/Coordinate-Mediation-Protocol.md index 4c65fadaee..4ee4228f39 100644 --- a/mercury/protocol-coordinate-mediation/Coordinate-Mediation-Protocol.md +++ b/mercury/protocol-coordinate-mediation/Coordinate-Mediation-Protocol.md @@ -1,6 +1,6 @@ # Coordinate Mediation Protocol -This Protocol is parte of the **DIDComm.org** +This Protocol is part of the **DIDComm.org** Is a protocol to coordinate mediation configuration between a mediating agent and the recipient. diff --git a/mercury/protocol-did-exchange/DidExchange-Protocol.md b/mercury/protocol-did-exchange/DidExchange-Protocol.md index 7875d8153b..f3d04ef338 100644 --- a/mercury/protocol-did-exchange/DidExchange-Protocol.md +++ b/mercury/protocol-did-exchange/DidExchange-Protocol.md @@ -1,6 +1,6 @@ # DidExchange Protocol -This Protocol is parte of the DIDComm Messaging Specification. +This Protocol is part of the DIDComm Messaging Specification. Protocol to exchange DIDs between agents when establishing a DID-based relationship diff --git a/mercury/protocol-invitation/Invitation-Protocol.md b/mercury/protocol-invitation/Invitation-Protocol.md index d46e28aba0..fb683c4b2d 100644 --- a/mercury/protocol-invitation/Invitation-Protocol.md +++ b/mercury/protocol-invitation/Invitation-Protocol.md @@ -1,6 +1,6 @@ # Invitation Protocol -This Protocol is parte of the **DIDComm Messaging Specification** but also **Aries RFC 0434: Out-of-Band Protocol 1.1** +This Protocol is part of the **DIDComm Messaging Specification** but also **Aries RFC 0434: Out-of-Band Protocol 1.1** Its a out-of-band style protocol. diff --git a/mercury/protocol-report-problem/Report-Problem-Protocol.md b/mercury/protocol-report-problem/Report-Problem-Protocol.md index 2381e41c17..2f343af8b0 100644 --- a/mercury/protocol-report-problem/Report-Problem-Protocol.md +++ b/mercury/protocol-report-problem/Report-Problem-Protocol.md @@ -1,6 +1,6 @@ # Report Problem Protocol 1.0 & 2.0 -This Protocol is parte of Aries (RFC 0035). +This Protocol is part of Aries (RFC 0035). Describes how to report errors and warnings in a powerful, interoperable way. - Version 1.0 - see [https://github.com/hyperledger/aries-rfcs/tree/main/features/0035-report-problem] diff --git a/mercury/protocol-revocation-notification/Revocation-notification-protocol.md b/mercury/protocol-revocation-notification/Revocation-notification-protocol.md index 9aaa147647..61ec9eea3d 100644 --- a/mercury/protocol-revocation-notification/Revocation-notification-protocol.md +++ b/mercury/protocol-revocation-notification/Revocation-notification-protocol.md @@ -1,6 +1,6 @@ # Revocation notification protocol -This Protocol for an Isuser to notify the revocation of a credential to the holder. +This Protocol for an Issuer to notify the revocation of a credential to the holder. diff --git a/mercury/protocol-routing/Routing-Protocol.md b/mercury/protocol-routing/Routing-Protocol.md index 91f94944f2..30669b0f4a 100644 --- a/mercury/protocol-routing/Routing-Protocol.md +++ b/mercury/protocol-routing/Routing-Protocol.md @@ -1,6 +1,6 @@ # Routing Protocol 2.0 -This Protocol is parte of the DIDComm Messaging Specification. +This Protocol is part of the DIDComm Messaging Specification. See [https://identity.foundation/didcomm-messaging/spec/#routing-protocol-20] From 5416423f2cf4f7cc85609db46c0a2a53aafe1b25 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev - IOHK Date: Fri, 16 Aug 2024 18:48:04 +0700 Subject: [PATCH 4/9] ci: build and publish the cloud-agent revision together with a helm chart and http clients (#1289) Signed-off-by: Yurii Shynbuiev --- .github/workflows/build.yml | 79 ++++++++++++++++++++++++--- .github/workflows/release-clients.yml | 65 ++++++++++++++++++++-- 2 files changed, 130 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fa4019ae43..4d8fee4e4c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,16 +1,35 @@ name: Build and Publish Revision +run-name: Build and Publish Cloud-Agent Revision from ${{ github.head_ref || github.ref_name }} run ${{ github.run_number }} + concurrency: - group: release + group: build-${{ github.sha }} on: workflow_dispatch: push: branches: - "main" + - "beta" + paths: + - "cloud-agent/**" + - "castor/**" + - "connect/**" + - "mercury/**" + - "pollux/**" + - "shared/**" + - "shared-test/**" + - "*.sbt" + - "project/**" + - "prism-node/**" + +permissions: + contents: write + packages: write jobs: build: + name: "Build and Publish Cloud-Agent Revision" if: ${{ !contains(github.event.head_commit.message, 'chore(release)') }} env: GITHUB_ACTOR: "hyperledger-bot" @@ -19,11 +38,15 @@ jobs: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} JAVA_TOOL_OPTIONS: -Djava.net.preferIPv4Stack=true SBT_OPTS: -Xmx2G + runs-on: ubuntu-latest - permissions: - contents: write - packages: write + outputs: + COMMIT_HASH: ${{ env.COMMIT_HASH }} + BUILD_VERSION: ${{ env.BUILD_VERSION }} + REVISION_VERSION: ${{ env.REVISION_VERSION }} + OAS_CHECKSUM: ${{ env.OAS_CHECKSUM }} + OAS_PUBLISHED: ${{ steps.upload-oas.conclusion == 'success' }} steps: - uses: actions/checkout@v4 @@ -36,6 +59,9 @@ jobs: with: java-version: openjdk@1.17 + - uses: coursier/cache-action@v6 + id: coursier-cache + - name: Setup Node.js uses: actions/setup-node@v4 with: @@ -55,7 +81,8 @@ jobs: uses: docker/setup-buildx-action@v2 - name: Get short commit hash - run: echo "COMMIT_HASH=${GITHUB_SHA::7}" >> $GITHUB_ENV + run: | + echo "COMMIT_HASH=${GITHUB_SHA::7}" >> $GITHUB_ENV - name: Set build number run: echo "BUILD_NUMBER=${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV @@ -64,10 +91,11 @@ jobs: id: get_version run: | VERSION=$(grep -Eo 'version := "[^"]+"' version.sbt | sed 's/version := "//; s/"//; s/-SNAPSHOT//') + REVISION_VERSION=${VERSION}-${{ env.COMMIT_HASH }} + BUILD_VERSION=${REVISION_VERSION}-${{ env.BUILD_NUMBER }} echo "VERSION=${VERSION}" >> $GITHUB_ENV - - - name: Set build version - run: echo "BUILD_VERSION=${{ env.VERSION }}-${{ env.COMMIT_HASH }}-${{ env.BUILD_NUMBER }}" >> $GITHUB_ENV + echo "REVISION_VERSION=${REVISION_VERSION}" >> $GITHUB_ENV + echo "BUILD_VERSION=${BUILD_VERSION}" >> $GITHUB_ENV - name: Build and push Docker image run: | @@ -81,3 +109,38 @@ jobs: repository: input-output-hk/atala-prism-helm-charts event-type: build-chart-package client-payload: '{"version": "${{ env.BUILD_VERSION }}", "chart": "cloud-agent"}' + + - name: Build Cloud-Agent Open API Specification + id: build-oas + run: | + sbt "cloudAgentServer/test:runMain org.hyperledger.identus.api.util.Tapir2StaticOAS ../../../cloud-agent-openapi-spec-${{ env.REVISION_VERSION}}.yaml revision" + checksum=$(sha256sum ./cloud-agent-openapi-spec-${{ env.REVISION_VERSION }}.yaml | awk '{ print $1 }') + OAS_CHECKSUM=${checksum:0:8} + echo "OAS_CHECKSUM=$OAS_CHECKSUM" >> $GITHUB_ENV + + - name: Publish Cloud-Agent Open API Specification + id: upload-oas + uses: actions/upload-artifact@v3 + with: + name: cloud-agent-openapi-spec-${{ env.OAS_CHECKSUM}} + path: ./cloud-agent-openapi-spec-${{ env.REVISION_VERSION}}.yaml + overwrite: true + compression-level: 0 + + - name: Set outputs + id: set_outputs + run: | + echo "COMMIT_HASH=${{ env.COMMIT_HASH }}" >> $GITHUB_ENV + echo "BUILD_VERSION=${{ env.BUILD_VERSION }}" >> $GITHUB_ENV + echo "REVISION_VERSION=${{ env.REVISION_VERSION }}" >> $GITHUB_ENV + echo "OAS_CHECKSUM=${{ env.OAS_CHECKSUM }}" >> $GITHUB_ENV + echo "OAS_PUBLISHED=${{ env.OAS_PUBLISHED }}" >> $GITHUB_ENV + + build-and-publish-clients: + needs: build + if: needs.build.outputs.OAS_PUBLISHED + uses: ./.github/workflows/release-clients.yml + secrets: inherit + with: + revision: ${{ needs.build.outputs.REVISION_VERSION }} + check_sum: ${{ needs.build.outputs.OAS_CHECKSUM }} diff --git a/.github/workflows/release-clients.yml b/.github/workflows/release-clients.yml index 1631f268e0..c77bb225e3 100644 --- a/.github/workflows/release-clients.yml +++ b/.github/workflows/release-clients.yml @@ -1,24 +1,38 @@ name: Publish Identus-cloud-agent clients +run-name: Build and publish Identus Cloud Agent clients from from ${{ github.head_ref || github.ref_name }} run ${{ github.run_number }} + on: workflow_call: inputs: - version: - description: "Version to release clients (e.g. v1.33.0)" + revision: + description: "Revision to build and publish clients (e.g. 1.33.0-a3j4456-1)" + required: true + type: string + check_sum: + description: "Checksum of the OAS specification" required: true type: string workflow_dispatch: inputs: releaseTag: description: "Tag to release clients (e.g. cloud-agent-v1.33.0)" - required: true + required: false + type: string + revision: + description: "Revision to build and publish clients (e.g. 1.33.0-a3j4456-1)" + required: false + type: string + check_sum: + description: "Checksum of the OAS specification" + required: false type: string push: tags: - "cloud-agent-v*" permissions: - contents: read + contents: write packages: write jobs: @@ -26,7 +40,6 @@ jobs: name: "Build and publish Identus Cloud Agent clients" runs-on: ubuntu-latest env: - VERSION_TAG: ${{inputs.releaseTag || github.ref_name}} GITHUB_ACTOR: "hyperledger-bot" GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -60,6 +73,29 @@ jobs: - name: Setup yq - portable yaml processor uses: mikefarah/yq@v4.34.2 + - name: Delete existing Open API specification + if: ${{ !inputs.releaseTag }} + run: | + echo "Current directory: $(pwd)" + rm -f ./cloud-agent/service/api/http/cloud-agent-openapi-spec.yaml + + - name: Download OpenAPI specification + if: ${{ !inputs.releaseTag }} + uses: actions/download-artifact@v3 + with: + name: cloud-agent-openapi-spec-${{ inputs.check_sum }} + path: ./cloud-agent/service/api/http + + - name: Rename OpenAPI specification + working-directory: cloud-agent/service/api/http + run: | + mv cloud-agent-openapi-spec-${{ inputs.revision }}.yaml cloud-agent-openapi-spec.yaml + + - name: Set revision version + if: ${{ !inputs.releaseTag }} + working-directory: cloud-agent/client/generator + run: yarn version --new-version ${{ inputs.revision }} --no-git-tag-version + - name: Install generator dependencies working-directory: cloud-agent/client/generator run: yarn install @@ -68,6 +104,23 @@ jobs: working-directory: cloud-agent/client/generator run: yarn generate:all + - name: Set version for clients + run: | + if [ -z "${{ github.event.inputs.releaseTag }}" ]; then + echo "VERSION_TAG=cloud-agent-v${{ inputs.revision }}" >> $GITHUB_ENV + else + echo "VERSION_TAG=${{ github.event.inputs.releaseTag }}" >> $GITHUB_ENV + fi + - name: Publish clients working-directory: cloud-agent/client/generator - run: yarn publish:clients + env: + VERSION_TAG: ${{ env.VERSION_TAG }} + run: | + if [ -z "${{ github.event.inputs.releaseTag }}" ]; then + echo "Using revision version for publishing: ${VERSION_TAG}" + yarn publish:clients + else + echo "Using release tag for publishing: ${VERSION_TAG}" + yarn publish:clients + fi From 2f09550997d1523485e5105ea19e46d94245ba63 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev - IOHK Date: Mon, 19 Aug 2024 14:56:00 +0700 Subject: [PATCH 5/9] ci: make-revision-semver-compliant (#1291) Signed-off-by: Yurii Shynbuiev --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4d8fee4e4c..5cb4c7151d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,7 +92,7 @@ jobs: run: | VERSION=$(grep -Eo 'version := "[^"]+"' version.sbt | sed 's/version := "//; s/"//; s/-SNAPSHOT//') REVISION_VERSION=${VERSION}-${{ env.COMMIT_HASH }} - BUILD_VERSION=${REVISION_VERSION}-${{ env.BUILD_NUMBER }} + BUILD_VERSION=${{ env.BUILD_NUMBER }}-${REVISION_VERSION} echo "VERSION=${VERSION}" >> $GITHUB_ENV echo "REVISION_VERSION=${REVISION_VERSION}" >> $GITHUB_ENV echo "BUILD_VERSION=${BUILD_VERSION}" >> $GITHUB_ENV From 525b3bcb7006599d873e8a089e8f03da361e74eb Mon Sep 17 00:00:00 2001 From: patlo-iog Date: Mon, 19 Aug 2024 15:13:43 +0700 Subject: [PATCH 6/9] fix: migrate wallet nonsecret storage to quill (#1290) Signed-off-by: Pat Losoponkul --- .../agent/server/ControllerHelper.scala | 1 - .../sql/JdbcWalletNonSecretStorage.scala | 180 ++++-------------- .../agent/walletapi/sql/model/Wallet.scala | 118 ++++++++++++ .../agent/walletapi/sql/model/package.scala | 15 ++ .../identus/agent/walletapi/sql/package.scala | 67 +------ .../presentproof/PresentProofInvitation.scala | 3 +- .../core/service/PresentationService.scala | 2 - 7 files changed, 169 insertions(+), 217 deletions(-) create mode 100644 cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/Wallet.scala create mode 100644 cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/package.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala index f79863d9b4..a8d2f0ddad 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala @@ -17,7 +17,6 @@ import org.hyperledger.identus.connect.core.service.ConnectionService import org.hyperledger.identus.mercury.model.* import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* -import zio.{IO, ZIO} import java.util.UUID import scala.util.Try diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletNonSecretStorage.scala index d817f0ba58..6a45bd2b09 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletNonSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletNonSecretStorage.scala @@ -1,144 +1,71 @@ package org.hyperledger.identus.agent.walletapi.sql -import cats.data.NonEmptyList import doobie.* -import doobie.implicits.* -import doobie.postgres.implicits.* import doobie.util.transactor.Transactor import org.hyperledger.identus.agent.walletapi.model.Wallet +import org.hyperledger.identus.agent.walletapi.sql.model.{WalletNotificationSql, WalletSql} +import org.hyperledger.identus.agent.walletapi.sql.model as db import org.hyperledger.identus.agent.walletapi.storage.WalletNonSecretStorage import org.hyperledger.identus.event.notification.EventNotificationConfig import org.hyperledger.identus.shared.db.ContextAwareTask -import org.hyperledger.identus.shared.db.Implicits.{*, given} +import org.hyperledger.identus.shared.db.Implicits.* import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import zio.* -import java.net.URL -import java.time.Instant import java.util.UUID class JdbcWalletNonSecretStorage(xa: Transactor[ContextAwareTask]) extends WalletNonSecretStorage { override def createWallet(wallet: Wallet, seedDigest: Array[Byte]): UIO[Wallet] = { - val cxnIO = (row: WalletRow) => sql""" - | INSERT INTO public.wallet( - | wallet_id, - | name, - | created_at, - | updated_at, - | seed_digest - | ) - | VALUES ( - | ${row.id}, - | ${row.name}, - | ${row.createdAt}, - | ${row.updatedAt}, - | ${seedDigest} - | ) - """.stripMargin.update - - val row = WalletRow.from(wallet) - cxnIO(row).run + WalletSql + .insert(db.Wallet.from(wallet, seedDigest)) .transactWithoutContext(xa) - .as(wallet) .orDie + .map(_.toModel) } override def findWalletById(walletId: WalletId): UIO[Option[Wallet]] = { - val cxnIO = - sql""" - | SELECT - | wallet_id, - | name, - | created_at, - | updated_at - | FROM public.wallet - | WHERE wallet_id = $walletId - """.stripMargin - .query[WalletRow] - .option - - cxnIO + WalletSql + .findByIds(Seq(walletId)) .transactWithoutContext(xa) - .map(_.map(_.toDomain)) .orDie + .map(_.headOption.map(_.toModel)) } override def findWalletBySeed(seedDigest: Array[Byte]): UIO[Option[Wallet]] = { - val cxnIO = - sql""" - | SELECT - | wallet_id, - | name, - | created_at, - | updated_at - | FROM public.wallet - | WHERE seed_digest = $seedDigest - """.stripMargin - .query[WalletRow] - .option - - cxnIO + WalletSql + .findBySeed(seedDigest) .transactWithoutContext(xa) - .map(_.map(_.toDomain)) .orDie + .map(_.headOption.map(_.toModel)) } override def getWallets(walletIds: Seq[WalletId]): UIO[Seq[Wallet]] = { walletIds match case Nil => ZIO.succeed(Nil) - case head +: tail => - val nel = NonEmptyList.of(head, tail*) - val conditionFragment = Fragments.in(fr"wallet_id", nel) - val cxnIO = - sql""" - | SELECT - | wallet_id, - | name, - | created_at, - | updated_at - | FROM public.wallet - | WHERE $conditionFragment - """.stripMargin - .query[WalletRow] - .to[List] - - cxnIO + case ids => + WalletSql + .findByIds(ids) .transactWithoutContext(xa) - .map(_.map(_.toDomain)) .orDie + .map(_.map(_.toModel)) } override def listWallet( offset: Option[Int], limit: Option[Int] ): UIO[(Seq[Wallet], RuntimeFlags)] = { - val countCxnIO = - sql""" - | SELECT COUNT(*) - | FROM public.wallet - """.stripMargin - .query[Int] - .unique - - val baseFr = - sql""" - | SELECT - | wallet_id, - | name, - | created_at, - | updated_at - | FROM public.wallet - | ORDER BY created_at - """.stripMargin - val withOffsetFr = offset.fold(baseFr)(offsetValue => baseFr ++ fr"OFFSET $offsetValue") - val withOffsetAndLimitFr = limit.fold(withOffsetFr)(limitValue => withOffsetFr ++ fr"LIMIT $limitValue") - val walletsCxnIO = withOffsetAndLimitFr.query[WalletRow].to[List] + val countCxnIO = WalletSql.lookupCount() + + val walletsCxnIO = WalletSql.lookup( + offset = offset.getOrElse(0), + limit = limit.getOrElse(1000) + ) val effect = for { totalCount <- countCxnIO - rows <- walletsCxnIO.map(_.map(_.toDomain)) - } yield (rows, totalCount) + rows <- walletsCxnIO.map(_.map(_.toModel)) + } yield (rows, totalCount.toInt) effect .transactWithoutContext(xa) @@ -146,72 +73,33 @@ class JdbcWalletNonSecretStorage(xa: Transactor[ContextAwareTask]) extends Walle } override def countWalletNotification: URIO[WalletAccessContext, Int] = { - val countIO = sql""" - | SELECT COUNT(*) - | FROM public.wallet_notification - """.stripMargin - .query[Int] - .unique - - countIO + WalletNotificationSql + .lookupCount() .transactWallet(xa) + .map(_.toInt) .orDie } override def createWalletNotification( config: EventNotificationConfig ): URIO[WalletAccessContext, Unit] = { - val insertIO = (row: WalletNofiticationRow) => sql""" - | INSERT INTO public.wallet_notification ( - | id, - | wallet_id, - | url, - | custom_headers, - | created_at - | ) VALUES ( - | ${row.id}, - | ${row.walletId}, - | ${row.url}, - | ${row.customHeaders}, - | ${row.createdAt} - | ) - """.stripMargin.update - - val row = WalletNofiticationRow.from(config) - - insertIO(row).run + WalletNotificationSql + .insert(db.WalletNotification.from(config)) .transactWallet(xa) .ensureOneAffectedRowOrDie } override def walletNotification: URIO[WalletAccessContext, Seq[EventNotificationConfig]] = { - val cxn = - sql""" - | SELECT - | id, - | wallet_id, - | url, - | custom_headers, - | created_at - | FROM public.wallet_notification - """.stripMargin - .query[WalletNofiticationRow] - .to[List] - - cxn + WalletNotificationSql + .lookup() .transactWallet(xa) - .flatMap(rows => ZIO.foreach(rows) { row => ZIO.fromTry(row.toDomain) }) + .map(_.map(_.toModel)) .orDie } override def deleteWalletNotification(id: UUID): URIO[WalletAccessContext, Unit] = { - val cxn = - sql""" - | DELETE FROM public.wallet_notification - | WHERE id = $id - """.stripMargin.update - - cxn.run + WalletNotificationSql + .delete(id) .transactWallet(xa) .ensureOneAffectedRowOrDie } diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/Wallet.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/Wallet.scala new file mode 100644 index 0000000000..b552d27599 --- /dev/null +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/Wallet.scala @@ -0,0 +1,118 @@ +package org.hyperledger.identus.agent.walletapi.sql.model + +import io.getquill.* +import io.getquill.context.json.PostgresJsonExtensions +import io.getquill.doobie.DoobieContext +import org.hyperledger.identus.agent.walletapi.model +import org.hyperledger.identus.event.notification.EventNotificationConfig +import org.hyperledger.identus.shared.models.WalletId + +import java.net.URL +import java.time.Instant +import java.util.UUID + +final case class Wallet( + walletId: WalletId, + name: String, + createdAt: Instant, + updatedAt: Instant, + seedDigest: Array[Byte] +) + +object Wallet { + def from(wallet: model.Wallet, seedDigest: Array[Byte]): Wallet = { + Wallet( + walletId = wallet.id, + name = wallet.name, + createdAt = wallet.createdAt, + updatedAt = wallet.updatedAt, + seedDigest = seedDigest + ) + } + + extension (wallet: Wallet) { + def toModel: model.Wallet = + model.Wallet( + id = wallet.walletId, + name = wallet.name, + createdAt = wallet.createdAt, + updatedAt = wallet.updatedAt + ) + } +} + +object WalletSql extends DoobieContext.Postgres(SnakeCase) { + + def insert(wallet: Wallet) = run { + quote( + query[Wallet] + .insertValue(lift(wallet)) + ).returning(w => w) + } + + def findByIds(walletIds: Seq[WalletId]) = run { + quote(query[Wallet].filter(p => liftQuery(walletIds.map(_.toUUID)).contains(p.walletId))) + } + + def findBySeed(seedDigest: Array[Byte]) = run { + quote(query[Wallet].filter(_.seedDigest == lift(seedDigest)).take(1)) + } + + def lookupCount() = run { quote(query[Wallet].size) } + + def lookup(offset: Int, limit: Int) = run { + quote(query[Wallet].drop(lift(offset)).take(lift(limit))) + } +} + +final case class WalletNotification( + id: UUID, + walletId: WalletId, + url: URL, + customHeaders: JsonValue[Map[String, String]], + createdAt: Instant, +) + +object WalletNotification { + def from(notification: EventNotificationConfig): WalletNotification = { + WalletNotification( + id = notification.id, + walletId = notification.walletId, + url = notification.url, + customHeaders = JsonValue(notification.customHeaders), + createdAt = notification.createdAt, + ) + } + + extension (notification: WalletNotification) { + def toModel: EventNotificationConfig = + EventNotificationConfig( + id = notification.id, + walletId = notification.walletId, + url = notification.url, + customHeaders = notification.customHeaders.value, + createdAt = notification.createdAt, + ) + } +} + +object WalletNotificationSql extends DoobieContext.Postgres(SnakeCase), PostgresJsonExtensions { + def insert(notification: WalletNotification) = run { + quote( + query[WalletNotification] + .insertValue(lift(notification)) + ) + } + + def lookupCount() = run { + quote(query[WalletNotification].size) + } + + def lookup() = run { + quote(query[WalletNotification]) + } + + def delete(id: UUID) = run { + quote(query[WalletNotification].filter(_.id == lift(id)).delete) + } +} diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/package.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/package.scala new file mode 100644 index 0000000000..464d924a26 --- /dev/null +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/model/package.scala @@ -0,0 +1,15 @@ +package org.hyperledger.identus.agent.walletapi.sql + +import io.getquill.MappedEncoding +import org.hyperledger.identus.shared.models.WalletId + +import java.net.{URI, URL} +import java.util.UUID + +package object model { + given MappedEncoding[WalletId, UUID] = MappedEncoding[WalletId, UUID](_.toUUID) + given MappedEncoding[UUID, WalletId] = MappedEncoding[UUID, WalletId](WalletId.fromUUID) + + given MappedEncoding[URL, String] = MappedEncoding[URL, String](_.toString) + given MappedEncoding[String, URL] = MappedEncoding[String, URL](URI(_).toURL) +} diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/package.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/package.scala index 300b262a83..c955c640cf 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/package.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/package.scala @@ -4,11 +4,8 @@ import com.nimbusds.jose.jwk.OctetKeyPair import doobie.* import doobie.postgres.implicits.* import doobie.util.invariant.InvalidEnum -import io.circe.* -import io.circe.parser.* -import io.circe.syntax.* import io.iohk.atala.prism.protos.node_models -import org.hyperledger.identus.agent.walletapi.model.{KeyManagementMode, ManagedDIDState, PublicationState, Wallet} +import org.hyperledger.identus.agent.walletapi.model.{KeyManagementMode, ManagedDIDState, PublicationState} import org.hyperledger.identus.castor.core.model.did.{ EllipticCurve, InternalKeyPurpose, @@ -18,7 +15,6 @@ import org.hyperledger.identus.castor.core.model.did.{ VerificationRelationship } import org.hyperledger.identus.castor.core.model.ProtoModelHelper.* -import org.hyperledger.identus.event.notification.EventNotificationConfig import org.hyperledger.identus.shared.crypto.jwk.JWK import org.hyperledger.identus.shared.models.WalletId import zio.json.* @@ -27,7 +23,6 @@ import zio.json.ast.Json.* import java.net.{URI, URL} import java.time.Instant -import java.util.UUID import scala.collection.immutable.ArraySeq import scala.util.Try @@ -213,64 +208,4 @@ package object sql { } } - final case class WalletRow( - id: WalletId, - name: String, - createdAt: Instant, - updatedAt: Instant - ) { - def toDomain: Wallet = { - Wallet( - id: WalletId, - name: String, - createdAt: Instant, - updatedAt: Instant - ) - } - } - - object WalletRow { - def from(wallet: Wallet): WalletRow = { - WalletRow( - id = wallet.id, - name = wallet.name, - createdAt = wallet.createdAt, - updatedAt = wallet.updatedAt - ) - } - } - - final case class WalletNofiticationRow( - id: UUID, - walletId: WalletId, - url: URL, - customHeaders: String, - createdAt: Instant, - ) { - def toDomain: Try[EventNotificationConfig] = { - decode[Map[String, String]](customHeaders).toTry - .map { headers => - EventNotificationConfig( - id = id, - walletId = walletId, - url = url, - customHeaders = headers, - createdAt = createdAt, - ) - } - } - } - - object WalletNofiticationRow { - def from(config: EventNotificationConfig): WalletNofiticationRow = { - WalletNofiticationRow( - id = config.id, - walletId = config.walletId, - url = config.url, - customHeaders = config.customHeaders.asJson.noSpacesSortKeys, - createdAt = config.createdAt, - ) - } - } - } diff --git a/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala index 1418eb3410..905438b6d9 100644 --- a/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala +++ b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentProofInvitation.scala @@ -1,7 +1,6 @@ package org.hyperledger.identus.mercury.protocol.presentproof -import org.hyperledger.identus.mercury.model.AttachmentDescriptor -import org.hyperledger.identus.mercury.model.DidId +import org.hyperledger.identus.mercury.model.{AttachmentDescriptor, DidId} import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation object PresentProofInvitation { def makeInvitation( diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala index 0a781ae851..5981338168 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationService.scala @@ -6,7 +6,6 @@ import org.hyperledger.identus.pollux.anoncreds.AnoncredPresentation import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.model.error.PresentationError import org.hyperledger.identus.pollux.core.model.presentation.* -import org.hyperledger.identus.pollux.core.model.presentation.Options import org.hyperledger.identus.pollux.core.service.serdes.{AnoncredCredentialProofsV1, AnoncredPresentationRequestV1} import org.hyperledger.identus.pollux.sdjwt.{HolderPrivateKey, PresentationCompact} import org.hyperledger.identus.pollux.vc.jwt.* @@ -16,7 +15,6 @@ import zio.json.ast import java.time.Instant import java.util.UUID -import java.util as ju trait PresentationService { def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID] From 48c591e19f2955e9c6751e1b372cd63ce5df91b7 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev - IOHK Date: Mon, 19 Aug 2024 17:20:22 +0700 Subject: [PATCH 7/9] ci: make revision semver compliant (fix the order) (#1292) Signed-off-by: Yurii Shynbuiev Signed-off-by: Yurii Shynbuiev - IOHK --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5cb4c7151d..74797a30e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,8 +91,8 @@ jobs: id: get_version run: | VERSION=$(grep -Eo 'version := "[^"]+"' version.sbt | sed 's/version := "//; s/"//; s/-SNAPSHOT//') - REVISION_VERSION=${VERSION}-${{ env.COMMIT_HASH }} - BUILD_VERSION=${{ env.BUILD_NUMBER }}-${REVISION_VERSION} + REVISION_VERSION=${VERSION}-${{ env.COMMIT_HASH }} # Revision version is used for the OAS file name and client libraries + BUILD_VERSION=${VERSION}-${{ env.BUILD_NUMBER }}-${{ env.COMMIT_HASH }} # Build version is used for the Docker image tag echo "VERSION=${VERSION}" >> $GITHUB_ENV echo "REVISION_VERSION=${REVISION_VERSION}" >> $GITHUB_ENV echo "BUILD_VERSION=${BUILD_VERSION}" >> $GITHUB_ENV From 31b35db913a4133b1084fbd307a361e1e377e8c1 Mon Sep 17 00:00:00 2001 From: Allain Magyar Date: Mon, 19 Aug 2024 11:45:13 -0300 Subject: [PATCH 8/9] ci: fix release pipeline checkout (#1287) Signed-off-by: Allain Magyar Co-authored-by: Yurii Shynbuiev - IOHK --- .github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a54368a9c4..f8a0cd5f5b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,6 @@ jobs: - uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ github.event.inputs.release-branch }} fetch-depth: 0 persist-credentials: false From d7394c82c25f652b9f5cf473d803b87ff30c85af Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev - IOHK Date: Tue, 20 Aug 2024 16:27:47 +0700 Subject: [PATCH 9/9] =?UTF-8?q?chore:=20bump=20the=20project=20version=20t?= =?UTF-8?q?o=20proceed=20with=20the=20semver-sioning=20in=E2=80=A6=20(#129?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Yurii Shynbuiev --- version.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.sbt b/version.sbt index 163d9c0802..7fbae4f46f 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -ThisBuild / version := "1.38.0-SNAPSHOT" +ThisBuild / version := "1.39.0-SNAPSHOT"