diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b4fb5ea65d..dd72d47379 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,27 +1 @@
-# Castor:
-/castor/ @patlo-iog @yshyn-iohk
-
-# Connect:
-/connect/ @bvoiturier @FabioPinheiro @mineme0110
-
-# Pollux:
-/pollux/ @patlo-iog @CryptoKnightIOG @mineme0110
-
-# Cloud Agent:
-/cloud-agent/ @bvoiturier @yshyn-iohk @patlo-iog
-
-# CI pipelines:
-/.github/ @mineme0110 @patlo-iog
-
-# PRISM Node:
-/prism-node/ @shotexa
-
-# Shared:
-/shared/ @patlo-iog @FabioPinheiro @mineme0110 @yshyn-iohk @bvoiturier @shotexa @CryptoKnightIOG
-
-# E2E tests:
-/tests/ @todorkoleviohk @amagyar-iohk @yshyn-iohk @patlo-iog @mineme0110
-
-# Docs:
-/docs/ @bvoiturier @yshyn-iohk
-*.md @petevielhaber
+* @hyperledger/identus-maintainers
diff --git a/.sbtopts b/.sbtopts
new file mode 100644
index 0000000000..2872000dd8
--- /dev/null
+++ b/.sbtopts
@@ -0,0 +1 @@
+-Dquill.macro.log=false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0907a99844..9a3be86fbf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+# [1.38.0](https://github.com/hyperledger/identus-cloud-agent/compare/cloud-agent-v1.37.0...cloud-agent-v1.38.0) (2024-07-15)
+
+
+### Bug Fixes
+
+* Move InMemory classes to the test moduels ([#1240](https://github.com/hyperledger/identus-cloud-agent/issues/1240)) ([823057a](https://github.com/hyperledger/identus-cloud-agent/commit/823057adaa6127eca80dba4df123f07098d34f65))
+* move mocks into the test modules ([#1236](https://github.com/hyperledger/identus-cloud-agent/issues/1236)) ([df83026](https://github.com/hyperledger/identus-cloud-agent/commit/df83026704980e071f7aa158634da20fbc2527c3))
+* use Put and Get for DID in doobie statement ([#1250](https://github.com/hyperledger/identus-cloud-agent/issues/1250)) ([fc1cf51](https://github.com/hyperledger/identus-cloud-agent/commit/fc1cf5157f5503143c23da54c8ea6fe78a776640))
+* Wallet Management Error Handling ([#1248](https://github.com/hyperledger/identus-cloud-agent/issues/1248)) ([cfd5101](https://github.com/hyperledger/identus-cloud-agent/commit/cfd5101f18276b9f59830c47c0d7fa64b30662db))
+
+
+### Features
+
+* upgrade docusaurus and semantic-release packages ([de53f1d](https://github.com/hyperledger/identus-cloud-agent/commit/de53f1db15a25e4d66cba1b191fc6e591b42284b))
+
# [1.37.0](https://github.com/hyperledger/identus-cloud-agent/compare/cloud-agent-v1.36.1...cloud-agent-v1.37.0) (2024-07-01)
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index f25d5d9208..be74257280 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -422,34 +422,34 @@ MIT | [The MIT License (MIT)](https://opensource.org/licenses/MIT) | [com.dimafe
Public Domain | [Public Domain, per Creative Commons CC0](http://creativecommons.org/publicdomain/zero/1.0/) | [org.hdrhistogram # HdrHistogram # 2.1.12](http://hdrhistogram.github.io/HdrHistogram/) |
Public Domain | [Public Domain, per Creative Commons CC0](http://creativecommons.org/publicdomain/zero/1.0/) | [org.latencyutils # LatencyUtils # 2.0.3](http://latencyutils.github.io/LatencyUtils/) |
none specified | []() | [net.jcip # jcip-annotations # 1.0](http://jcip.net/) |
-none specified | []() | [org.hyperledger # castor-core_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # cloud-agent-wallet-api_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # connect-core_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # connect-sql-doobie_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # event-notification_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-agent-core_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-agent-didcommx_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-data-models_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-connection_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-coordinate-mediation_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-invitation_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-issue-credential_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-mailbox_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-outofband-login_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-present-proof_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-report-problem_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-revocation-notification_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-routing-2-0_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-protocol-trust-ping_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-resolver_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # mercury-verifiable-credentials_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # pollux-anoncreds_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # pollux-core_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # pollux-sd-jwt_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # pollux-sql-doobie_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # pollux-vc-jwt_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # prism-node-client_3 # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # shared # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # shared-crypto # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
-none specified | []() | [org.hyperledger # shared-test # 1.36.1-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # castor-core_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # cloud-agent-wallet-api_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # connect-core_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # connect-sql-doobie_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # event-notification_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-agent-core_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-agent-didcommx_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-data-models_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-connection_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-coordinate-mediation_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-invitation_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-issue-credential_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-mailbox_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-outofband-login_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-present-proof_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-report-problem_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-revocation-notification_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-routing-2-0_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-protocol-trust-ping_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-resolver_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # mercury-verifiable-credentials_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # pollux-anoncreds_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # pollux-core_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # pollux-sd-jwt_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # pollux-sql-doobie_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # pollux-vc-jwt_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # prism-node-client_3 # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # shared # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # shared-crypto # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
+none specified | []() | [org.hyperledger # shared-test # 1.37.0-SNAPSHOT](https://github.com/hyperledger/identus-cloud-agent) |
diff --git a/build.sbt b/build.sbt
index ddf3a5750c..48824dfe14 100644
--- a/build.sbt
+++ b/build.sbt
@@ -32,7 +32,6 @@ inThisBuild(
"-feature",
"-deprecation",
"-unchecked",
- "-Dquill.macro.log=false", // disable quill macro logs
"-Wunused:all",
"-Wconf:any:warning", // TODO: change unused imports to errors, Wconf configuration string is different from scala 2, figure out how!
// TODO "-feature",
diff --git a/cloud-agent/service/api/http/cloud-agent-openapi-spec.yaml b/cloud-agent/service/api/http/cloud-agent-openapi-spec.yaml
index 588b1873af..fd0c0735af 100644
--- a/cloud-agent/service/api/http/cloud-agent-openapi-spec.yaml
+++ b/cloud-agent/service/api/http/cloud-agent-openapi-spec.yaml
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Identus Cloud Agent API Reference
- version: 1.37.0
+ version: 1.38.0
description: |2
The Identus Cloud Agent API facilitates the integration and management of self-sovereign identity capabilities within applications.
@@ -5441,7 +5441,7 @@ components:
type: string
description: The date and time when the issue credential record was created.
format: date-time
- example: '2024-07-01T08:50:32.358710422Z'
+ example: '2024-07-15T12:05:35.865441004Z'
updatedAt:
type: string
description: The date and time when the issue credential record was last
@@ -6213,7 +6213,7 @@ components:
type: string
description: Issuance timestamp of status list credential
format: date-time
- example: '2024-07-01T08:50:32.396776613Z'
+ example: '2024-07-15T12:05:35.908034143Z'
credentialSubject:
$ref: '#/components/schemas/CredentialSubject'
proof:
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/Modules.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/Modules.scala
index 6f0dc8996f..492fb55b7c 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/Modules.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/Modules.scala
@@ -38,7 +38,7 @@ import org.hyperledger.identus.iam.authentication.oidc.{
KeycloakConfig,
KeycloakEntity
}
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement
+import org.hyperledger.identus.iam.authorization.core.PermissionManagementService
import org.hyperledger.identus.iam.authorization.keycloak.admin.KeycloakPermissionManagementService
import org.hyperledger.identus.pollux.vc.jwt.{DidResolver as JwtDidResolver, PrismDidResolver}
import org.hyperledger.identus.shared.crypto.Apollo
@@ -103,7 +103,7 @@ object AppModule {
)
val keycloakAuthenticatorLayer: RLayer[
- AppConfig & WalletManagementService & Client & PermissionManagement.Service[KeycloakEntity],
+ AppConfig & WalletManagementService & Client & PermissionManagementService[KeycloakEntity],
KeycloakAuthenticator
] =
ZLayer.fromZIO {
@@ -113,7 +113,7 @@ object AppModule {
if (!isEnabled) KeycloakAuthenticatorImpl.disabled
else
ZLayer.makeSome[
- AppConfig & WalletManagementService & Client & PermissionManagement.Service[KeycloakEntity],
+ AppConfig & WalletManagementService & Client & PermissionManagementService[KeycloakEntity],
KeycloakAuthenticator
](
KeycloakConfig.layer,
@@ -125,14 +125,14 @@ object AppModule {
}.flatten
val keycloakPermissionManagementLayer
- : RLayer[AppConfig & WalletManagementService & Client, PermissionManagement.Service[KeycloakEntity]] = {
+ : RLayer[AppConfig & WalletManagementService & Client, PermissionManagementService[KeycloakEntity]] = {
ZLayer.fromZIO {
ZIO
.serviceWith[AppConfig](_.agent.authentication.keycloak.enabled)
.map { isEnabled =>
if (!isEnabled) KeycloakPermissionManagementService.disabled
else
- ZLayer.makeSome[AppConfig & WalletManagementService & Client, PermissionManagement.Service[KeycloakEntity]](
+ ZLayer.makeSome[AppConfig & WalletManagementService & Client, PermissionManagementService[KeycloakEntity]](
KeycloakClientImpl.authzClientLayer,
KeycloakClientImpl.layer,
KeycloakConfig.layer,
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobError.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobError.scala
index 3ac9e438b9..e1e5539c3a 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobError.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobError.scala
@@ -1,7 +1,7 @@
package org.hyperledger.identus.agent.server.jobs
import org.hyperledger.identus.mercury.HttpResponse
-import org.hyperledger.identus.shared.models._
+import org.hyperledger.identus.shared.models.*
sealed trait BackgroundJobError(
override val statusCode: org.hyperledger.identus.shared.models.StatusCode,
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/ConnectBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/ConnectBackgroundJobs.scala
index 5ed92b4441..c16c4143c6 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/ConnectBackgroundJobs.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/ConnectBackgroundJobs.scala
@@ -6,8 +6,10 @@ import org.hyperledger.identus.agent.walletapi.model.error.DIDSecretStorageError
import org.hyperledger.identus.agent.walletapi.model.error.DIDSecretStorageError.{KeyNotFoundError, WalletNotFoundError}
import org.hyperledger.identus.agent.walletapi.service.ManagedDIDService
import org.hyperledger.identus.agent.walletapi.storage.DIDNonSecretStorage
-import org.hyperledger.identus.connect.core.model.error.ConnectionServiceError.InvalidStateForOperation
-import org.hyperledger.identus.connect.core.model.error.ConnectionServiceError.RecordIdNotFound
+import org.hyperledger.identus.connect.core.model.error.ConnectionServiceError.{
+ InvalidStateForOperation,
+ RecordIdNotFound
+}
import org.hyperledger.identus.connect.core.model.ConnectionRecord
import org.hyperledger.identus.connect.core.model.ConnectionRecord.*
import org.hyperledger.identus.connect.core.service.ConnectionService
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 15fc1b22d7..c2d321ecd3 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
@@ -55,7 +55,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper {
private type MESSAGING_RESOURCES = DidOps & DIDResolver & HttpClient
val presentProofExchanges: ZIO[RESOURCES, Throwable, Unit] = {
- val presentProofDidComExchange = for {
+ for {
presentationService <- ZIO.service[PresentationService]
config <- ZIO.service[AppConfig]
records <- presentationService
@@ -72,7 +72,6 @@ object PresentBackgroundJobs extends BackgroundJobsHelper {
.foreachPar(records)(performPresentProofExchange)
.withParallelism(config.pollux.presentationBgJobProcessingParallelism)
} yield ()
- presentProofDidComExchange
}
private def counterMetric(key: String) = Metric
@@ -81,16 +80,19 @@ object PresentBackgroundJobs extends BackgroundJobsHelper {
private def performPresentProofExchange(record: PresentationRecord): URIO[RESOURCES, Unit] =
aux(record)
- .tapError({
- (error: PresentationError | DIDSecretStorageError | BackgroundJobError | CredentialServiceError |
- CastorDIDResolutionError | GetManagedDIDError | Failure) =>
- ZIO.logErrorCause(
- s"Present Proof - Error processing record: ${record.id}",
- Cause.fail(error)
- )
- })
- .catchAll(e => ZIO.logErrorCause(s"Present Proof - Error processing record: ${record.id} ", Cause.fail(e)))
- .catchAllDefect(d => ZIO.logErrorCause(s"Present Proof - Defect processing record: ${record.id}", Cause.fail(d)))
+ .catchAll {
+ case ex: Failure =>
+ ZIO
+ .service[PresentationService]
+ .flatMap(_.reportProcessingFailure(record.id, Some(ex)))
+ .catchAll(ex =>
+ ZIO.logErrorCause(s"PresentBackgroundJobs - Fail to recover from ${record.id}", Cause.fail(ex))
+ )
+ case ex => ZIO.logErrorCause(s"PresentBackgroundJobs - Error processing record: ${record.id}", Cause.fail(ex))
+ }
+ .catchAllDefect(d =>
+ ZIO.logErrorCause(s"PresentBackgroundJobs - Defect processing record: ${record.id}", Cause.fail(d))
+ )
private def aux(record: PresentationRecord): ZIO[RESOURCES, ERROR, Unit] = {
import org.hyperledger.identus.pollux.core.model.PresentationRecord.ProtocolState.*
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/KeycloakAuthenticatorImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/KeycloakAuthenticatorImpl.scala
index 5c416a4d19..6f3426ecb4 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/KeycloakAuthenticatorImpl.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authentication/oidc/KeycloakAuthenticatorImpl.scala
@@ -3,8 +3,8 @@ package org.hyperledger.identus.iam.authentication.oidc
import org.hyperledger.identus.agent.walletapi.model.EntityRole
import org.hyperledger.identus.iam.authentication.AuthenticationError
import org.hyperledger.identus.iam.authentication.AuthenticationError.AuthenticationMethodNotEnabled
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error.PermissionNotAvailable
+import org.hyperledger.identus.iam.authorization.core.PermissionManagementService
+import org.hyperledger.identus.iam.authorization.core.PermissionManagementServiceError.PermissionNotAvailable
import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext}
import zio.*
@@ -13,7 +13,7 @@ import java.util.UUID
class KeycloakAuthenticatorImpl(
client: KeycloakClient,
keycloakConfig: KeycloakConfig,
- keycloakPermissionService: PermissionManagement.Service[KeycloakEntity],
+ keycloakPermissionService: PermissionManagementService[KeycloakEntity],
) extends KeycloakAuthenticator {
override def isEnabled: Boolean = keycloakConfig.enabled
@@ -48,7 +48,7 @@ class KeycloakAuthenticatorImpl(
.listWalletPermissions(entity)
.mapError {
case PermissionNotAvailable(_, msg) => AuthenticationError.InvalidCredentials(msg)
- case e => AuthenticationError.UnexpectedError(e.message)
+ case e => AuthenticationError.UnexpectedError(e.userFacingMessage)
}
.flatMap {
case head +: Nil => ZIO.succeed(head)
@@ -68,7 +68,7 @@ class KeycloakAuthenticatorImpl(
.listWalletPermissions(entity)
.provide(ZLayer.succeed(WalletAdministrationContext.Admin()))
.mapBoth(
- e => AuthenticationError.UnexpectedError(e.message),
+ e => AuthenticationError.UnexpectedError(e.userFacingMessage),
wallets => WalletAdministrationContext.SelfService(wallets)
)
@@ -90,7 +90,7 @@ class KeycloakAuthenticatorImpl(
object KeycloakAuthenticatorImpl {
val layer: RLayer[
- KeycloakClient & KeycloakConfig & PermissionManagement.Service[KeycloakEntity],
+ KeycloakClient & KeycloakConfig & PermissionManagementService[KeycloakEntity],
KeycloakAuthenticator
] =
ZLayer.fromFunction(KeycloakAuthenticatorImpl(_, _, _))
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/DefaultPermissionManagementService.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/DefaultPermissionManagementService.scala
index 53a33371f7..18121922b1 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/DefaultPermissionManagementService.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/DefaultPermissionManagementService.scala
@@ -2,31 +2,38 @@ package org.hyperledger.identus.iam.authorization
import org.hyperledger.identus.agent.walletapi.model.{BaseEntity, Entity}
import org.hyperledger.identus.iam.authentication.oidc.KeycloakEntity
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error
+import org.hyperledger.identus.iam.authorization.core.{PermissionManagementService, PermissionManagementServiceError}
import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
import zio.*
class DefaultPermissionManagementService(
- entityPermission: PermissionManagement.Service[Entity],
- keycloakPermission: PermissionManagement.Service[KeycloakEntity]
-) extends PermissionManagement.Service[BaseEntity] {
+ entityPermission: PermissionManagementService[Entity],
+ keycloakPermission: PermissionManagementService[KeycloakEntity]
+) extends PermissionManagementService[BaseEntity] {
- def grantWalletToUser(walletId: WalletId, entity: BaseEntity): ZIO[WalletAdministrationContext, Error, Unit] = {
+ def grantWalletToUser(
+ walletId: WalletId,
+ entity: BaseEntity
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] = {
entity match {
case entity: Entity => entityPermission.grantWalletToUser(walletId, entity)
case kcEntity: KeycloakEntity => keycloakPermission.grantWalletToUser(walletId, kcEntity)
}
}
- def revokeWalletFromUser(walletId: WalletId, entity: BaseEntity): ZIO[WalletAdministrationContext, Error, Unit] = {
+ def revokeWalletFromUser(
+ walletId: WalletId,
+ entity: BaseEntity
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] = {
entity match {
case entity: Entity => entityPermission.revokeWalletFromUser(walletId, entity)
case kcEntity: KeycloakEntity => keycloakPermission.revokeWalletFromUser(walletId, kcEntity)
}
}
- def listWalletPermissions(entity: BaseEntity): ZIO[WalletAdministrationContext, Error, Seq[WalletId]] = {
+ def listWalletPermissions(
+ entity: BaseEntity
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Seq[WalletId]] = {
entity match {
case entity: Entity => entityPermission.listWalletPermissions(entity)
case kcEntity: KeycloakEntity => keycloakPermission.listWalletPermissions(kcEntity)
@@ -37,8 +44,8 @@ class DefaultPermissionManagementService(
object DefaultPermissionManagementService {
def layer: URLayer[
- PermissionManagement.Service[KeycloakEntity] & PermissionManagement.Service[Entity],
- PermissionManagement.Service[BaseEntity]
+ PermissionManagementService[KeycloakEntity] & PermissionManagementService[Entity],
+ PermissionManagementService[BaseEntity]
] =
ZLayer.fromFunction(DefaultPermissionManagementService(_, _))
}
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementService.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementService.scala
index 6b6f5ebba8..a27b8fd84c 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementService.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementService.scala
@@ -2,38 +2,45 @@ package org.hyperledger.identus.iam.authorization.core
import org.hyperledger.identus.agent.walletapi.model.Entity
import org.hyperledger.identus.agent.walletapi.service.EntityService
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error.{ServiceError, WalletNotFoundById}
+import org.hyperledger.identus.iam.authorization.core.PermissionManagementServiceError.*
import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
import zio.*
import scala.language.implicitConversions
-class EntityPermissionManagementService(entityService: EntityService) extends PermissionManagement.Service[Entity] {
+class EntityPermissionManagementService(entityService: EntityService) extends PermissionManagementService[Entity] {
- override def grantWalletToUser(walletId: WalletId, entity: Entity): ZIO[WalletAdministrationContext, Error, Unit] = {
+ override def grantWalletToUser(
+ walletId: WalletId,
+ entity: Entity
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] = {
for {
_ <- ZIO
.serviceWith[WalletAdministrationContext](_.isAuthorized(walletId))
- .filterOrFail(identity)(Error.WalletNotFoundById(walletId))
+ .filterOrFail(identity)(WalletNotFoundById(walletId))
_ <- entityService.assignWallet(entity.id, walletId.toUUID).orDieAsUnmanagedFailure
} yield ()
}
- override def revokeWalletFromUser(walletId: WalletId, entity: Entity): ZIO[WalletAdministrationContext, Error, Unit] =
- ZIO.fail(Error.ServiceError(s"Revoking wallet permission for an Entity is not yet supported."))
+ override def revokeWalletFromUser(
+ walletId: WalletId,
+ entity: Entity
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] =
+ ZIO.fail(ServiceError(s"Revoking wallet permission for an Entity is not yet supported."))
- override def listWalletPermissions(entity: Entity): ZIO[WalletAdministrationContext, Error, Seq[WalletId]] = {
+ override def listWalletPermissions(
+ entity: Entity
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Seq[WalletId]] = {
val walletId = WalletId.fromUUID(entity.walletId)
ZIO
.serviceWith[WalletAdministrationContext](_.isAuthorized(walletId))
- .filterOrFail(identity)(Error.WalletNotFoundById(walletId))
+ .filterOrFail(identity)(WalletNotFoundById(walletId))
.as(Seq(walletId))
}
}
object EntityPermissionManagementService {
- val layer: URLayer[EntityService, PermissionManagement.Service[Entity]] =
+ val layer: URLayer[EntityService, PermissionManagementService[Entity]] =
ZLayer.fromFunction(EntityPermissionManagementService(_))
}
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagement.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagement.scala
index ef10b8bbca..8b13789179 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagement.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagement.scala
@@ -1,39 +1 @@
-package org.hyperledger.identus.iam.authorization.core
-import org.hyperledger.identus.agent.walletapi.model.BaseEntity
-import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
-import zio.*
-
-import java.util.UUID
-
-object PermissionManagement {
- trait Service[E <: BaseEntity] {
- def grantWalletToUser(walletId: WalletId, entity: E): ZIO[WalletAdministrationContext, Error, Unit]
- def revokeWalletFromUser(walletId: WalletId, entity: E): ZIO[WalletAdministrationContext, Error, Unit]
- def listWalletPermissions(entity: E): ZIO[WalletAdministrationContext, Error, Seq[WalletId]]
- }
-
- sealed trait Error(val message: String)
-
- object Error {
- case class UserNotFoundById(userId: UUID, cause: Option[Throwable] = None)
- extends Error(s"User $userId is not found" + cause.map(t => s" Cause: ${t.getMessage}"))
- case class WalletNotFoundByUserId(userId: UUID) extends Error(s"Wallet for user $userId is not found")
-
- case class WalletNotFoundById(walletId: WalletId) extends Error(s"Wallet not found by ${walletId.toUUID}")
-
- case class WalletResourceNotFoundById(walletId: WalletId)
- extends Error(s"Wallet resource not found by ${walletId.toUUID}")
-
- case class PermissionNotFoundById(userId: UUID, walletId: WalletId, walletResourceId: String)
- extends Error(
- s"Permission not found by userId: $userId, walletId: ${walletId.toUUID}, walletResourceId: $walletResourceId"
- )
-
- case class PermissionNotAvailable(userId: UUID, cause: String) extends Error(cause)
-
- case class UnexpectedError(cause: Throwable) extends Error(cause.getMessage)
-
- case class ServiceError(cause: String) extends Error(cause)
- }
-}
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagementService.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagementService.scala
new file mode 100644
index 0000000000..ce309c1561
--- /dev/null
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagementService.scala
@@ -0,0 +1,19 @@
+package org.hyperledger.identus.iam.authorization.core
+
+import org.hyperledger.identus.agent.walletapi.model.BaseEntity
+import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
+import zio.*
+
+trait PermissionManagementService[E <: BaseEntity] {
+ def grantWalletToUser(
+ walletId: WalletId,
+ entity: E
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit]
+ def revokeWalletFromUser(
+ walletId: WalletId,
+ entity: E
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit]
+ def listWalletPermissions(
+ entity: E
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Seq[WalletId]]
+}
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagementServiceError.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagementServiceError.scala
new file mode 100644
index 0000000000..7b8ca64b4a
--- /dev/null
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/core/PermissionManagementServiceError.scala
@@ -0,0 +1,50 @@
+package org.hyperledger.identus.iam.authorization.core
+
+import org.hyperledger.identus.shared.models.{Failure, StatusCode, WalletId}
+
+import java.util.UUID
+
+sealed trait PermissionManagementServiceError(
+ val statusCode: StatusCode,
+ val userFacingMessage: String
+) extends Failure {
+ override val namespace: String = "PermissionManagementServiceError"
+}
+
+object PermissionManagementServiceError {
+
+ case class UserNotFoundById(userId: UUID, cause: Option[Throwable] = None)
+ extends PermissionManagementServiceError(
+ StatusCode.BadRequest,
+ s"User $userId is not found" + cause.map(t => s" Cause: ${t.getMessage}")
+ )
+
+ case class WalletNotFoundByUserId(userId: UUID)
+ extends PermissionManagementServiceError(
+ StatusCode.BadRequest,
+ s"Wallet for user $userId is not found"
+ )
+
+ case class WalletNotFoundById(walletId: WalletId)
+ extends PermissionManagementServiceError(
+ StatusCode.BadRequest,
+ s"Wallet not found by ${walletId.toUUID}"
+ )
+
+ case class WalletResourceNotFoundById(walletId: WalletId)
+ extends PermissionManagementServiceError(
+ StatusCode.BadRequest,
+ s"Wallet resource not found by ${walletId.toUUID}"
+ )
+
+ case class PermissionNotFoundById(userId: UUID, walletId: WalletId, walletResourceId: String)
+ extends PermissionManagementServiceError(
+ StatusCode.BadRequest,
+ s"Permission not found by userId: $userId, walletId: ${walletId.toUUID}, walletResourceId: $walletResourceId"
+ )
+
+ case class PermissionNotAvailable(userId: UUID, cause: String)
+ extends PermissionManagementServiceError(StatusCode.BadRequest, cause)
+
+ case class ServiceError(cause: String) extends PermissionManagementServiceError(StatusCode.InternalServerError, cause)
+}
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementService.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementService.scala
index b06586d798..d4eb6963cc 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementService.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementService.scala
@@ -3,9 +3,8 @@ package org.hyperledger.identus.iam.authorization.keycloak.admin
import org.hyperledger.identus.agent.walletapi.model.Wallet
import org.hyperledger.identus.agent.walletapi.service.WalletManagementService
import org.hyperledger.identus.iam.authentication.oidc.{KeycloakClient, KeycloakEntity}
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error.*
+import org.hyperledger.identus.iam.authorization.core.{PermissionManagementService, PermissionManagementServiceError}
+import org.hyperledger.identus.iam.authorization.core.PermissionManagementServiceError.*
import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
import org.keycloak.authorization.client.AuthzClient
import org.keycloak.representations.idm.authorization.{ResourceRepresentation, UmaPermissionRepresentation}
@@ -19,7 +18,7 @@ case class KeycloakPermissionManagementService(
authzClient: AuthzClient,
keycloakClient: KeycloakClient,
walletManagementService: WalletManagementService
-) extends PermissionManagement.Service[KeycloakEntity] {
+) extends PermissionManagementService[KeycloakEntity] {
private def walletResourceName(walletId: WalletId) = s"wallet-${walletId.toUUID.toString}"
@@ -28,60 +27,58 @@ case class KeycloakPermissionManagementService(
override def grantWalletToUser(
walletId: WalletId,
entity: KeycloakEntity
- ): ZIO[WalletAdministrationContext, PermissionManagement.Error, Unit] = {
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] = {
for {
_ <- walletManagementService
.findWallet(walletId)
.someOrFail(WalletNotFoundById(walletId))
walletResourceOpt <- findWalletResource(walletId)
- .logError("Error while finding wallet resource")
- .mapError(UnexpectedError.apply)
walletResource <- ZIO
.fromOption(walletResourceOpt)
.orElse(createWalletResource(walletId))
- .logError("Error while creating wallet resource")
- .mapError(UnexpectedError.apply)
_ <- ZIO.log(s"Wallet resource created ${walletResource.toString}")
permission <- createResourcePermission(walletResource.getId, entity.id.toString)
- .mapError(UnexpectedError.apply)
_ <- ZIO.log(s"Permission created with id ${permission.getId} and name ${permission.getName}")
} yield ()
}
- private def createResourcePermission(resourceId: String, userId: String): Task[UmaPermissionRepresentation] = {
+ private def createResourcePermission(resourceId: String, userId: String): UIO[UmaPermissionRepresentation] = {
val policy = UmaPermissionRepresentation()
policy.setName(policyName(userId, resourceId))
policy.setUsers(Set(userId).asJava)
for {
- umaPermissionRepresentation <- ZIO.attemptBlocking(
- authzClient
- .protection()
- .policy(resourceId)
- .create(policy)
- )
+ umaPermissionRepresentation <- ZIO
+ .attemptBlocking(
+ authzClient
+ .protection()
+ .policy(resourceId)
+ .create(policy)
+ )
+ .orDie
} yield umaPermissionRepresentation
}
- private def findWalletResource(walletId: WalletId): Task[Option[ResourceRepresentation]] = {
+ private def findWalletResource(walletId: WalletId): UIO[Option[ResourceRepresentation]] = {
for {
walletResource <- ZIO
.attemptBlocking(
- authzClient.protection().resource().findById(walletId.toUUID.toString())
+ authzClient.protection().resource().findById(walletId.toUUID.toString)
)
.asSome
.catchSome { case e: RuntimeException =>
- if (e.getMessage().contains("Could not find resource")) ZIO.none
+ if (e.getMessage.contains("Could not find resource")) ZIO.none
else ZIO.fail(e)
}
+ .orDie
} yield walletResource
}
- private def createWalletResource(walletId: WalletId): Task[ResourceRepresentation] = {
+ private def createWalletResource(walletId: WalletId): UIO[ResourceRepresentation] = {
val walletResource = ResourceRepresentation()
walletResource.setId(walletId.toUUID.toString)
walletResource.setUris(Set(s"/wallets/${walletResourceName(walletId)}").asJava)
@@ -90,18 +87,22 @@ case class KeycloakPermissionManagementService(
for {
_ <- ZIO.log(s"Creating resource for the wallet ${walletId.toUUID.toString}")
- response <- ZIO.attemptBlocking(
- authzClient
- .protection()
- .resource()
- .create(walletResource)
- )
- resource <- ZIO.attemptBlocking(
- authzClient
- .protection()
- .resource()
- .findById(walletResource.getId)
- )
+ response <- ZIO
+ .attemptBlocking(
+ authzClient
+ .protection()
+ .resource()
+ .create(walletResource)
+ )
+ .orDie
+ resource <- ZIO
+ .attemptBlocking(
+ authzClient
+ .protection()
+ .resource()
+ .findById(walletResource.getId)
+ )
+ .orDie
_ <- ZIO.log(s"Resource for the wallet created id: ${resource.getId}, name ${resource.getName}")
} yield resource
}
@@ -109,17 +110,14 @@ case class KeycloakPermissionManagementService(
override def revokeWalletFromUser(
walletId: WalletId,
entity: KeycloakEntity
- ): ZIO[WalletAdministrationContext, PermissionManagement.Error, Unit] = {
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Unit] = {
val userId = entity.id
for {
_ <- walletManagementService
.findWallet(walletId)
.someOrFail(WalletNotFoundById(walletId))
- walletResource <- findWalletResource(walletId)
- .logError("Error while finding wallet resource")
- .mapError(UnexpectedError.apply)
- .someOrFail(WalletResourceNotFoundById(walletId))
+ walletResource <- findWalletResource(walletId).someOrFail(WalletNotFoundById(walletId))
permissionOpt <- ZIO
.attemptBlocking(
@@ -133,9 +131,8 @@ case class KeycloakPermissionManagementService(
1
)
)
+ .orDie
.map(_.asScala.headOption)
- .logError(s"Error while finding permission by name ${policyName(userId.toString, walletResource.getId)}")
- .mapError(UnexpectedError.apply)
permission <- ZIO
.fromOption(permissionOpt)
@@ -148,8 +145,7 @@ case class KeycloakPermissionManagementService(
.policy(walletResource.getId)
.delete(permission.getId)
)
- .logError(s"Error while deleting permission ${permission.getId}")
- .mapError(UnexpectedError.apply)
+ .orDie
_ <- ZIO.log(
s"Permission ${permission.getId} deleted for user ${userId.toString} and wallet ${walletResource.getId}"
@@ -157,29 +153,33 @@ case class KeycloakPermissionManagementService(
} yield ()
}
- override def listWalletPermissions(entity: KeycloakEntity): ZIO[WalletAdministrationContext, Error, Seq[WalletId]] = {
+ override def listWalletPermissions(
+ entity: KeycloakEntity
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Seq[WalletId]] = {
for {
token <- ZIO
.fromOption(entity.accessToken)
- .mapError(_ => Error.ServiceError("AccessToken is missing for listing permissions."))
- tokenIsRpt <- ZIO.fromEither(token.isRpt).mapError(Error.ServiceError(_))
+ .mapError(_ => ServiceError("AccessToken is missing for listing permissions."))
+ tokenIsRpt <- ZIO.fromEither(token.isRpt).mapError(ServiceError.apply)
rpt <-
if (tokenIsRpt) ZIO.succeed(token)
else if (keycloakClient.keycloakConfig.autoUpgradeToRPT) {
keycloakClient
.getRpt(token)
.logError("Fail to obtail RPT for wallet permissions")
- .mapError(e => Error.ServiceError(e.message))
- } else ZIO.fail(Error.PermissionNotAvailable(entity.id, s"AccessToken is not RPT."))
+ .mapError(e => ServiceError(e.message))
+ } else ZIO.fail(PermissionNotAvailable(entity.id, s"AccessToken is not RPT."))
permittedResources <- keycloakClient
.checkPermissions(rpt)
.logError("Fail to list resource permissions on keycloak")
- .mapError(e => Error.ServiceError(e.message))
+ .mapError(e => ServiceError(e.message))
permittedWallet <- getPermittedWallet(permittedResources)
} yield permittedWallet.map(_.id)
}
- private def getPermittedWallet(resourceIds: Seq[String]): ZIO[WalletAdministrationContext, Error, Seq[Wallet]] = {
+ private def getPermittedWallet(
+ resourceIds: Seq[String]
+ ): ZIO[WalletAdministrationContext, PermissionManagementServiceError, Seq[Wallet]] = {
val walletIds = resourceIds.flatMap(id => Try(UUID.fromString(id)).toOption).map(WalletId.fromUUID)
walletManagementService
.getWallets(walletIds)
@@ -189,17 +189,27 @@ case class KeycloakPermissionManagementService(
object KeycloakPermissionManagementService {
val layer: URLayer[
AuthzClient & KeycloakClient & WalletManagementService,
- PermissionManagement.Service[KeycloakEntity]
+ PermissionManagementService[KeycloakEntity]
] =
ZLayer.fromFunction(KeycloakPermissionManagementService(_, _, _))
- val disabled: ULayer[PermissionManagement.Service[KeycloakEntity]] =
+ val disabled: ULayer[PermissionManagementService[KeycloakEntity]] =
ZLayer.succeed {
- val notEnabledError = ZIO.fail(PermissionManagement.Error.ServiceError("Keycloak is not enabled"))
- new PermissionManagement.Service[KeycloakEntity] {
- override def grantWalletToUser(walletId: WalletId, entity: KeycloakEntity): IO[Error, Unit] = notEnabledError
- override def revokeWalletFromUser(walletId: WalletId, entity: KeycloakEntity): IO[Error, Unit] = notEnabledError
- override def listWalletPermissions(entity: KeycloakEntity): IO[Error, Seq[WalletId]] = notEnabledError
+ val notEnabledError = ZIO.fail(ServiceError("Keycloak is not enabled"))
+ new PermissionManagementService[KeycloakEntity] {
+ override def grantWalletToUser(
+ walletId: WalletId,
+ entity: KeycloakEntity
+ ): IO[PermissionManagementServiceError, Unit] = notEnabledError
+
+ override def revokeWalletFromUser(
+ walletId: WalletId,
+ entity: KeycloakEntity
+ ): IO[PermissionManagementServiceError, Unit] = notEnabledError
+
+ override def listWalletPermissions(
+ entity: KeycloakEntity
+ ): IO[PermissionManagementServiceError, Seq[WalletId]] = notEnabledError
}
}
}
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/wallet/http/controller/WalletManagementController.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/wallet/http/controller/WalletManagementController.scala
index 2f070e39cc..ed271ffc85 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/wallet/http/controller/WalletManagementController.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/iam/wallet/http/controller/WalletManagementController.scala
@@ -6,7 +6,7 @@ import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext}
import org.hyperledger.identus.api.http.model.{CollectionStats, PaginationInput}
import org.hyperledger.identus.api.util.PaginationUtils
import org.hyperledger.identus.iam.authentication.oidc.KeycloakEntity
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement
+import org.hyperledger.identus.iam.authorization.core.PermissionManagementService
import org.hyperledger.identus.iam.wallet.http.model.{
CreateWalletRequest,
CreateWalletUmaPermissionRequest,
@@ -41,26 +41,11 @@ trait WalletManagementController {
)(implicit rc: RequestContext): ZIO[WalletAdministrationContext, ErrorResponse, Unit]
}
-object WalletManagementController {
- given permissionManagementErrorConversion: Conversion[PermissionManagement.Error, ErrorResponse] = {
- case e: PermissionManagement.Error.PermissionNotFoundById => ErrorResponse.badRequest(detail = Some(e.message))
- case e: PermissionManagement.Error.ServiceError => ErrorResponse.internalServerError(detail = Some(e.message))
- case e: PermissionManagement.Error.UnexpectedError => ErrorResponse.internalServerError(detail = Some(e.message))
- case e: PermissionManagement.Error.UserNotFoundById => ErrorResponse.badRequest(detail = Some(e.message))
- case e: PermissionManagement.Error.WalletNotFoundById => ErrorResponse.badRequest(detail = Some(e.message))
- case e: PermissionManagement.Error.WalletNotFoundByUserId => ErrorResponse.badRequest(detail = Some(e.message))
- case e: PermissionManagement.Error.WalletResourceNotFoundById => ErrorResponse.badRequest(detail = Some(e.message))
- case e: PermissionManagement.Error.PermissionNotAvailable => ErrorResponse.badRequest(detail = Some(e.message))
- }
-}
-
class WalletManagementControllerImpl(
walletService: WalletManagementService,
- permissionService: PermissionManagement.Service[BaseEntity],
+ permissionService: PermissionManagementService[BaseEntity],
) extends WalletManagementController {
- import WalletManagementController.given
-
override def listWallet(
paginationInput: PaginationInput
)(implicit rc: RequestContext): ZIO[WalletAdministrationContext, ErrorResponse, WalletDetailPage] = {
@@ -152,6 +137,6 @@ class WalletManagementControllerImpl(
}
object WalletManagementControllerImpl {
- val layer: URLayer[WalletManagementService & PermissionManagement.Service[BaseEntity], WalletManagementController] =
+ val layer: URLayer[WalletManagementService & PermissionManagementService[BaseEntity], WalletManagementController] =
ZLayer.fromFunction(WalletManagementControllerImpl(_, _))
}
diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerServerEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerServerEndpoints.scala
index c1fb766d0d..9a356efb14 100644
--- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerServerEndpoints.scala
+++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerServerEndpoints.scala
@@ -2,13 +2,7 @@ package org.hyperledger.identus.oid4vci
import org.hyperledger.identus.agent.walletapi.model.BaseEntity
import org.hyperledger.identus.api.http.ErrorResponse
-import org.hyperledger.identus.iam.authentication.{
- Authenticator,
- Authorizer,
- DefaultAuthenticator,
- Oid4vciAuthenticatorFactory,
- SecurityLogic
-}
+import org.hyperledger.identus.iam.authentication.*
import org.hyperledger.identus.oid4vci.controller.CredentialIssuerController
import org.hyperledger.identus.oid4vci.http.{CredentialErrorResponse, ExtendedErrorResponse, NonceResponse}
import org.hyperledger.identus.LogUtils.*
diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/agent/server/AgentInitializationSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/agent/server/AgentInitializationSpec.scala
index 9fbd912937..33db03632e 100644
--- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/agent/server/AgentInitializationSpec.scala
+++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/agent/server/AgentInitializationSpec.scala
@@ -103,7 +103,7 @@ object AgentInitializationSpec extends ZIOSpecDefault, PostgresTestContainerSupp
_ <- AgentInitialization.run.overrideConfig(seed = Some("0" * 128))
actualSeed <- ZIO
.serviceWithZIO[WalletSecretStorage](
- _.getWalletSeed
+ _.findWalletSeed
.provide(ZLayer.succeed(WalletAccessContext(WalletId.default)))
)
} yield assert(actualSeed.get.toByteArray)(equalTo(Array.fill[Byte](64)(0)))
diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementSpec.scala
index d1e1b9e1ac..6bb296c9a6 100644
--- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementSpec.scala
+++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/core/EntityPermissionManagementSpec.scala
@@ -12,7 +12,10 @@ import org.hyperledger.identus.agent.walletapi.sql.{
JdbcWalletNonSecretStorage,
JdbcWalletSecretStorage
}
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error.{ServiceError, WalletNotFoundById}
+import org.hyperledger.identus.iam.authorization.core.PermissionManagementServiceError.{
+ ServiceError,
+ WalletNotFoundById
+}
import org.hyperledger.identus.shared.crypto.ApolloSpecHelper
import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport
@@ -48,7 +51,7 @@ object EntityPermissionManagementSpec extends ZIOSpecDefault, PostgresTestContai
test("grant wallet access to the user") {
for {
entityService <- ZIO.service[EntityService]
- permissionService <- ZIO.service[PermissionManagement.Service[Entity]]
+ permissionService <- ZIO.service[PermissionManagementService[Entity]]
walletService <- ZIO.service[WalletManagementService]
wallet1 <- walletService
.createWallet(Wallet("test"))
@@ -73,7 +76,7 @@ object EntityPermissionManagementSpec extends ZIOSpecDefault, PostgresTestContai
test("revoke wallet is not support") {
for {
entityService <- ZIO.service[EntityService]
- permissionService <- ZIO.service[PermissionManagement.Service[Entity]]
+ permissionService <- ZIO.service[PermissionManagementService[Entity]]
walletService <- ZIO.service[WalletManagementService]
wallet1 <- walletService
.createWallet(Wallet("test"))
@@ -94,7 +97,7 @@ object EntityPermissionManagementSpec extends ZIOSpecDefault, PostgresTestContai
val walletId2 = WalletId.random
for {
entityService <- ZIO.service[EntityService]
- permissionService <- ZIO.service[PermissionManagement.Service[Entity]]
+ permissionService <- ZIO.service[PermissionManagementService[Entity]]
walletService <- ZIO.service[WalletManagementService]
wallet1 <- walletService
.createWallet(Wallet("test", walletId1))
@@ -117,7 +120,7 @@ object EntityPermissionManagementSpec extends ZIOSpecDefault, PostgresTestContai
val walletId2 = WalletId.random
for {
entityService <- ZIO.service[EntityService]
- permissionService <- ZIO.service[PermissionManagement.Service[Entity]]
+ permissionService <- ZIO.service[PermissionManagementService[Entity]]
walletService <- ZIO.service[WalletManagementService]
wallet1 <- walletService
.createWallet(Wallet("test", walletId1))
diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala
index c215c0494e..c4e98d6d17 100644
--- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala
+++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/iam/authorization/keycloak/admin/KeycloakPermissionManagementServiceSpec.scala
@@ -5,8 +5,8 @@ import org.hyperledger.identus.agent.walletapi.service.{WalletManagementService,
import org.hyperledger.identus.agent.walletapi.sql.{JdbcWalletNonSecretStorage, JdbcWalletSecretStorage}
import org.hyperledger.identus.iam.authentication.oidc.*
import org.hyperledger.identus.iam.authentication.AuthenticationError.ResourceNotPermitted
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement
-import org.hyperledger.identus.iam.authorization.core.PermissionManagement.Error.{UnexpectedError, WalletNotFoundById}
+import org.hyperledger.identus.iam.authorization.core.PermissionManagementService
+import org.hyperledger.identus.iam.authorization.core.PermissionManagementServiceError.WalletNotFoundById
import org.hyperledger.identus.shared.crypto.ApolloSpecHelper
import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext, WalletId}
import org.hyperledger.identus.sharedtest.containers.{
@@ -72,7 +72,7 @@ object KeycloakPermissionManagementServiceSpec
user <- createUser(username = username, password = password)
entity = KeycloakEntity(id = UUID.fromString(user.getId))
- permissionService <- ZIO.service[PermissionManagement.Service[KeycloakEntity]]
+ permissionService <- ZIO.service[PermissionManagementService[KeycloakEntity]]
_ <- permissionService.grantWalletToUser(wallet.id, entity)
token <- client.getAccessToken(username, password).map(_.access_token)
@@ -95,7 +95,7 @@ object KeycloakPermissionManagementServiceSpec
user <- createUser(username = username, password = password)
entity = KeycloakEntity(id = UUID.fromString(user.getId))
- permissionService <- ZIO.service[PermissionManagement.Service[KeycloakEntity]]
+ permissionService <- ZIO.service[PermissionManagementService[KeycloakEntity]]
_ <- permissionService.grantWalletToUser(wallet.id, entity)
token <- client.getAccessToken(username, password).map(_.access_token)
@@ -116,7 +116,7 @@ object KeycloakPermissionManagementServiceSpec
private val failureCasesSuite = suite("Failure Cases Suite")(
test("grant wallet access to the user with invalid wallet id") {
for {
- permissionService <- ZIO.service[PermissionManagement.Service[KeycloakEntity]]
+ permissionService <- ZIO.service[PermissionManagementService[KeycloakEntity]]
entity = KeycloakEntity(id = UUID.randomUUID())
exit <- permissionService.grantWalletToUser(WalletId.random, entity).exit
} yield assert(exit)(fails(isSubtype[WalletNotFoundById](anything)))
@@ -128,9 +128,9 @@ object KeycloakPermissionManagementServiceSpec
walletService <- ZIO.service[WalletManagementService]
wallet <- walletService.createWallet(Wallet("test_1"))
entity = KeycloakEntity(id = UUID.randomUUID())
- permissionService <- ZIO.service[PermissionManagement.Service[KeycloakEntity]]
+ permissionService <- ZIO.service[PermissionManagementService[KeycloakEntity]]
exit <- permissionService.grantWalletToUser(wallet.id, entity).exit
- } yield assert(exit)(fails(isSubtype[UnexpectedError](anything)))
+ } yield assert(exit)(dies(hasMessage(equalTo(s"Error creating policy for resource [${wallet.id}]"))))
}
).provideSomeLayer(ZLayer.succeed(WalletAdministrationContext.Admin()))
@@ -152,7 +152,7 @@ object KeycloakPermissionManagementServiceSpec
user <- createUser(username = username, password = password)
entity = KeycloakEntity(id = UUID.fromString(user.getId))
- permissionService <- ZIO.service[PermissionManagement.Service[KeycloakEntity]]
+ permissionService <- ZIO.service[PermissionManagementService[KeycloakEntity]]
_ <- permissionService
.grantWalletToUser(wallet.id, entity)
.provideSomeLayer(ZLayer.succeed(WalletAdministrationContext.SelfService(Seq(walletId))))
@@ -179,7 +179,7 @@ object KeycloakPermissionManagementServiceSpec
user <- createUser(username = username, password = password)
entity = KeycloakEntity(id = UUID.fromString(user.getId))
- permissionService <- ZIO.service[PermissionManagement.Service[KeycloakEntity]]
+ permissionService <- ZIO.service[PermissionManagementService[KeycloakEntity]]
_ <- permissionService
.grantWalletToUser(wallet.id, entity)
.provideSomeLayer(ZLayer.succeed(WalletAdministrationContext.Admin()))
@@ -216,7 +216,7 @@ object KeycloakPermissionManagementServiceSpec
user <- createUser(username = username, password = password)
entity = KeycloakEntity(id = UUID.fromString(user.getId))
- permissionService <- ZIO.service[PermissionManagement.Service[KeycloakEntity]]
+ permissionService <- ZIO.service[PermissionManagementService[KeycloakEntity]]
exit <- permissionService
.grantWalletToUser(WalletId.random, entity)
.provideSomeLayer(ZLayer.succeed(WalletAdministrationContext.SelfService(Seq(walletId))))
@@ -239,7 +239,7 @@ object KeycloakPermissionManagementServiceSpec
user <- createUser(username = username, password = password)
entity = KeycloakEntity(id = UUID.fromString(user.getId))
- permissionService <- ZIO.service[PermissionManagement.Service[KeycloakEntity]]
+ permissionService <- ZIO.service[PermissionManagementService[KeycloakEntity]]
_ <- permissionService
.grantWalletToUser(wallet.id, entity)
.provideSomeLayer(ZLayer.succeed(WalletAdministrationContext.Admin()))
diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/oid4vci/domain/OIDCCredentialIssuerServiceSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/oid4vci/domain/OIDCCredentialIssuerServiceSpec.scala
index eba646e4b8..2de767e43e 100644
--- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/oid4vci/domain/OIDCCredentialIssuerServiceSpec.scala
+++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/oid4vci/domain/OIDCCredentialIssuerServiceSpec.scala
@@ -1,7 +1,6 @@
package org.hyperledger.identus.oid4vci.domain
import com.nimbusds.jose.*
-import org.bouncycastle.util.encoders.Hex
import org.hyperledger.identus.agent.walletapi.memory.GenericSecretStorageInMemory
import org.hyperledger.identus.agent.walletapi.service.{ManagedDIDService, MockManagedDIDService}
import org.hyperledger.identus.agent.walletapi.storage.{DIDNonSecretStorage, MockDIDNonSecretStorage}
@@ -95,7 +94,6 @@ object OIDCCredentialIssuerServiceSpec
val longFormDid = PrismDID.buildLongFormFromOperation(holderOp)
val keyIndex = holderDidData.publicKeys.find(_.purpose == VerificationRelationship.AssertionMethod).get.id
val kid = longFormDid.toString + "#" + keyIndex
- val encodedKey = Hex.toHexString(holderKp.privateKey.getEncoded)
makeJwtProof(kid, nonce, aud, iat, holderKp.privateKey)
}
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/DIDSecretStorageError.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/DIDSecretStorageError.scala
index 85a305541e..9112f841c0 100644
--- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/DIDSecretStorageError.scala
+++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/error/DIDSecretStorageError.scala
@@ -1,7 +1,7 @@
package org.hyperledger.identus.agent.walletapi.model.error
import org.hyperledger.identus.mercury.model.DidId
-import org.hyperledger.identus.shared.models._
+import org.hyperledger.identus.shared.models.*
sealed trait DIDSecretStorageError(
override val statusCode: StatusCode,
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDCreateHandler.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDCreateHandler.scala
index 4138581bc0..1c2f239b55 100644
--- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDCreateHandler.scala
+++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDCreateHandler.scala
@@ -29,7 +29,7 @@ private[walletapi] class DIDCreateHandler(
val operationFactory = OperationFactory(apollo)
for {
walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId)
- seed <- walletSecretStorage.getWalletSeed
+ seed <- walletSecretStorage.findWalletSeed
.someOrElseZIO(ZIO.dieMessage(s"Wallet seed for wallet $walletId does not exist"))
didIndex <- nonSecretStorage
.getMaxDIDIndex()
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDUpdateHandler.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDUpdateHandler.scala
index 73d5d2eb57..de517075dd 100644
--- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDUpdateHandler.scala
+++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/DIDUpdateHandler.scala
@@ -40,7 +40,7 @@ private[walletapi] class DIDUpdateHandler(
val did = state.createOperation.did
for {
walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId)
- seed <- walletSecretStorage.getWalletSeed
+ seed <- walletSecretStorage.findWalletSeed
.someOrElseZIO(ZIO.dieMessage(s"Wallet seed for wallet $walletId does not exist"))
keyCounter <- nonSecretStorage
.getHdKeyCounter(did)
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDNonSecretStorage.scala
index c2e3f44611..4bc6154035 100644
--- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDNonSecretStorage.scala
+++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDNonSecretStorage.scala
@@ -429,7 +429,7 @@ class JdbcDIDNonSecretStorage(xa: Transactor[ContextAwareTask], xb: Transactor[T
| wallet_id
| FROM public.prism_did_wallet_state
| WHERE
- | did = ${prismDid.toString}
+ | did = ${prismDid}
""".stripMargin
.query[WalletId]
.option
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletSecretStorage.scala
index 3325880268..824b64b73d 100644
--- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletSecretStorage.scala
+++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcWalletSecretStorage.scala
@@ -36,7 +36,7 @@ class JdbcWalletSecretStorage(xa: Transactor[ContextAwareTask]) extends WalletSe
} yield ()
}
- override def getWalletSeed: URIO[WalletAccessContext, Option[WalletSeed]] = {
+ override def findWalletSeed: URIO[WalletAccessContext, Option[WalletSeed]] = {
val cxnIO =
sql"""
| SELECT seed
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/WalletSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/WalletSecretStorage.scala
index f3da8a4684..b8c6f179d9 100644
--- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/WalletSecretStorage.scala
+++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/WalletSecretStorage.scala
@@ -6,5 +6,5 @@ import zio.*
trait WalletSecretStorage {
def setWalletSeed(seed: WalletSeed): URIO[WalletAccessContext, Unit]
- def getWalletSeed: URIO[WalletAccessContext, Option[WalletSeed]]
+ def findWalletSeed: URIO[WalletAccessContext, Option[WalletSeed]]
}
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/KeyResolver.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/KeyResolver.scala
index 038752f898..81a5fa8035 100644
--- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/KeyResolver.scala
+++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/KeyResolver.scala
@@ -29,7 +29,7 @@ class KeyResolver(
}
private def deriveHdKey(path: ManagedDIDHdKeyPath): RIO[WalletAccessContext, Option[Secp256k1KeyPair]] =
- walletSecretStorage.getWalletSeed.flatMap {
+ walletSecretStorage.findWalletSeed.flatMap {
case None => ZIO.none
case Some(seed) => apollo.secp256k1.deriveKeyPair(seed.toByteArray)(path.derivationPath*).asSome
}
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultWalletSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultWalletSecretStorage.scala
index 6c688e3145..f9ef9cd2b2 100644
--- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultWalletSecretStorage.scala
+++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultWalletSecretStorage.scala
@@ -20,7 +20,7 @@ class VaultWalletSecretStorage(vaultKV: VaultKVClient) extends WalletSecretStora
} yield ()
}
- override def getWalletSeed: URIO[WalletAccessContext, Option[WalletSeed]] = {
+ override def findWalletSeed: URIO[WalletAccessContext, Option[WalletSeed]] = {
for {
walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId)
path = walletSeedPath(walletId)
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/memory/GenericSecretStorageInMemory.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/memory/GenericSecretStorageInMemory.scala
similarity index 100%
rename from cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/memory/GenericSecretStorageInMemory.scala
rename to cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/memory/GenericSecretStorageInMemory.scala
diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/memory/WalletSecretStorageInMemory.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/memory/WalletSecretStorageInMemory.scala
similarity index 92%
rename from cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/memory/WalletSecretStorageInMemory.scala
rename to cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/memory/WalletSecretStorageInMemory.scala
index 27ea027dee..91fed64a93 100644
--- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/memory/WalletSecretStorageInMemory.scala
+++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/memory/WalletSecretStorageInMemory.scala
@@ -14,7 +14,7 @@ class WalletSecretStorageInMemory(storeRef: Ref[Map[WalletId, WalletSeed]]) exte
} yield ()
}
- override def getWalletSeed: URIO[WalletAccessContext, Option[WalletSeed]] = {
+ override def findWalletSeed: URIO[WalletAccessContext, Option[WalletSeed]] = {
for {
walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId)
seed <- storeRef.get.map(_.get(walletId))
diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/WalletManagementServiceSpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/WalletManagementServiceSpec.scala
index 6be6eef494..be4d5e174c 100644
--- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/WalletManagementServiceSpec.scala
+++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/WalletManagementServiceSpec.scala
@@ -88,7 +88,7 @@ object WalletManagementServiceSpec
secretStorage <- ZIO.service[WalletSecretStorage]
createdWallet <- svc.createWallet(Wallet("wallet-1"))
listedWallets <- svc.listWallets().map(_._1)
- seed <- secretStorage.getWalletSeed.provide(ZLayer.succeed(WalletAccessContext(createdWallet.id)))
+ seed <- secretStorage.findWalletSeed.provide(ZLayer.succeed(WalletAccessContext(createdWallet.id)))
} yield assert(listedWallets)(hasSameElements(Seq(createdWallet))) &&
assert(seed)(isSome)
},
@@ -99,7 +99,7 @@ object WalletManagementServiceSpec
createdWallets <- ZIO.foreach(1 to 10)(i => svc.createWallet(Wallet(s"wallet-$i")))
listedWallets <- svc.listWallets().map(_._1)
seeds <- ZIO.foreach(listedWallets) { wallet =>
- secretStorage.getWalletSeed.provide(ZLayer.succeed(WalletAccessContext(wallet.id)))
+ secretStorage.findWalletSeed.provide(ZLayer.succeed(WalletAccessContext(wallet.id)))
}
} yield assert(createdWallets)(hasSameElements(listedWallets)) &&
assert(seeds)(forall(isSome))
@@ -111,7 +111,7 @@ object WalletManagementServiceSpec
seed1 = WalletSeed.fromByteArray(Array.fill[Byte](64)(0)).toOption.get
createdWallet <- svc.createWallet(Wallet("wallet-1"), Some(seed1))
listedWallets <- svc.listWallets().map(_._1)
- seed2 <- secretStorage.getWalletSeed.provide(ZLayer.succeed(WalletAccessContext(createdWallet.id)))
+ seed2 <- secretStorage.findWalletSeed.provide(ZLayer.succeed(WalletAccessContext(createdWallet.id)))
} yield assert(listedWallets)(hasSameElements(Seq(createdWallet))) &&
assert(seed2)(isSome(equalTo(seed1)))
},
@@ -123,7 +123,7 @@ object WalletManagementServiceSpec
createdWallets <- ZIO.foreach(seeds1) { seed => svc.createWallet(Wallet("test-wallet"), Some(seed)) }
listedWallets <- svc.listWallets().map(_._1)
seeds2 <- ZIO.foreach(listedWallets) { wallet =>
- secretStorage.getWalletSeed.provide(ZLayer.succeed(WalletAccessContext(wallet.id)))
+ secretStorage.findWalletSeed.provide(ZLayer.succeed(WalletAccessContext(wallet.id)))
}
} yield assert(createdWallets)(hasSameElements(listedWallets)) &&
assert(seeds2.flatten)(hasSameElements(seeds1))
diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/WalletSecretStorageSpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/WalletSecretStorageSpec.scala
index d9b87b2f34..c201d7036b 100644
--- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/WalletSecretStorageSpec.scala
+++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/WalletSecretStorageSpec.scala
@@ -46,9 +46,9 @@ object WalletSecretStorageSpec extends ZIOSpecDefault, PostgresTestContainerSupp
.map(_.id)
walletAccessCtx = ZLayer.succeed(WalletAccessContext(walletId))
seed = WalletSeed.fromByteArray(Array.fill[Byte](64)(0)).toOption.get
- seedBefore <- storage.getWalletSeed.provide(walletAccessCtx)
+ seedBefore <- storage.findWalletSeed.provide(walletAccessCtx)
_ <- storage.setWalletSeed(seed).provide(walletAccessCtx)
- seedAfter <- storage.getWalletSeed.provide(walletAccessCtx)
+ seedAfter <- storage.findWalletSeed.provide(walletAccessCtx)
} yield assert(seedBefore)(isNone) &&
assert(seedAfter)(isSome(equalTo(seed)))
},
@@ -69,7 +69,7 @@ object WalletSecretStorageSpec extends ZIOSpecDefault, PostgresTestContainerSupp
seeds <- ZIO
.foreach(wallets) { wallet =>
val walletAccessCtx = ZLayer.succeed(WalletAccessContext(wallet.id))
- storage.getWalletSeed.provideSomeLayer(walletAccessCtx)
+ storage.findWalletSeed.provideSomeLayer(walletAccessCtx)
}
.map(_.flatten)
} yield assert(seeds.size)(equalTo(10)) && assert(seeds)(isDistinct)
diff --git a/connect/core/src/main/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepository.scala b/connect/core/src/main/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepository.scala
index 9ad70b3160..9d25fc133a 100644
--- a/connect/core/src/main/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepository.scala
+++ b/connect/core/src/main/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepository.scala
@@ -3,8 +3,7 @@ package org.hyperledger.identus.connect.core.repository
import org.hyperledger.identus.connect.core.model.ConnectionRecord
import org.hyperledger.identus.connect.core.model.ConnectionRecord.ProtocolState
import org.hyperledger.identus.mercury.protocol.connection.*
-import org.hyperledger.identus.shared.models.Failure
-import org.hyperledger.identus.shared.models.WalletAccessContext
+import org.hyperledger.identus.shared.models.{Failure, WalletAccessContext}
import zio.{UIO, URIO}
import java.util.UUID
diff --git a/connect/core/src/main/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepositoryInMemory.scala b/connect/core/src/test/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepositoryInMemory.scala
similarity index 100%
rename from connect/core/src/main/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepositoryInMemory.scala
rename to connect/core/src/test/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepositoryInMemory.scala
diff --git a/connect/core/src/test/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepositorySpecSuite.scala b/connect/core/src/test/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepositorySpecSuite.scala
index ac7b1a6f6c..cb795b00b2 100644
--- a/connect/core/src/test/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepositorySpecSuite.scala
+++ b/connect/core/src/test/scala/org/hyperledger/identus/connect/core/repository/ConnectionRepositorySpecSuite.scala
@@ -6,7 +6,6 @@ import org.hyperledger.identus.mercury.model.DidId
import org.hyperledger.identus.mercury.protocol.connection.{ConnectionRequest, ConnectionResponse}
import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation
import org.hyperledger.identus.shared.models.*
-import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId}
import zio.{Cause, Exit, ZIO, ZLayer}
import zio.test.*
import zio.Exit.Failure
diff --git a/docs/docusaurus/credentials/issue.md b/docs/docusaurus/credentials/issue.md
index 086178a18e..b392d310c2 100644
--- a/docs/docusaurus/credentials/issue.md
+++ b/docs/docusaurus/credentials/issue.md
@@ -1,7 +1,7 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
-# Issue credentials
+# Issue credentials (DIDComm)
In the Identus Platform, the [Issue Credentials Protocol](/docs/concepts/glossary#issue-credentials-protocol) allows you to create, retrieve, and manage issued [verifiable credentials (VCs)](/docs/concepts/glossary#verifiable-credentials) between a VC issuer and a VC holder.
@@ -183,7 +183,7 @@ The `issuingDID` and `connectionId` properties come from completing the pre-requ
:::
- 📌 **Note:** Claims can also include the `exp` Expiration Time attribute, which is part of JWT claims. `exp` attribute is disclosable if specified and can have a value in epoch time (in seconds), indicating when the SDJWT credential expires for more details
-
+[RFC5719](https://datatracker.ietf.org/doc/html/rfc7519#page-9)
Once the request initiates, a new credential record for the issuer gets created with a unique ID. The state of this record is now `OfferPending`.
@@ -352,7 +352,7 @@ curl -X POST "http://localhost:8090/cloud-agent/issue-credentials/records/$holde
}
```
2. `keyId`: This is optional field but must be specified to choose which key bounds to the verifiable credential.
- For more information on key-binding, .
+ For more information on key-binding, [ietf-oauth-selective-disclosure-jwt](https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt).
Currently, we only support the EdDSA algorithm and curve Ed25519.
The specified keyId should be of type Ed25519.
The purpose of the keyId should be authentication.
diff --git a/docs/docusaurus/credentials/oid4vci.md b/docs/docusaurus/credentials/oid4vci.md
new file mode 100644
index 0000000000..a84b7c7dfe
--- /dev/null
+++ b/docs/docusaurus/credentials/oid4vci.md
@@ -0,0 +1,74 @@
+# Issue credentials (OID4VCI)
+
+[OID4VCI](/docs/concepts/glossary#oid4vci) (OpenID for Verifiable Credential Issuance) is a protocol that extends OAuth2 to issue credentials.
+It involves a Credential Issuer server and an Authorization server working together,
+using the authorization and token endpoints on the Authorization Server to grant holders access to credentials on the Credential Issuer server.
+These servers may or may not be the same, depending on the implementation.
+
+The Identus Cloud Agent can act as a Credential Issuer server and integrate with any Authorization Server that follows the integration contract. The contract for the Authorization Server in the OID4VCI flow can be found [here](https://github.com/hyperledger/identus-cloud-agent/blob/main/docs/general/authserver-oid4vci-contract.md).
+
+## Example: OID4VCI Authorization Code Issuance
+
+Example is available [here](https://github.com/hyperledger/identus-cloud-agent/tree/main/examples/st-oid4vci).
+
+Following the instructions, the example demonstrates a single-tenant agent setup using an external Keycloak as the Issuer Authorization Server. The demo application walks through the authorization code issuance flow step-by-step.
+
+#### 1. Launching Local Example Stack
+
+```bash
+docker-compose up
+```
+
+After running the `docker-compose up` command, all the containers should be running and initialized with the necessary configurations. The following logs should appear indicating that the stack is ready to execute the flow
+
+```
+ _ _ _ _ _
+ | |_| |_| |_ _ __| | | ___
+ | ' \ _| _| '_ \_ _(_-<
+ |_||_\__|\__| .__/ |_|/__/
+ |_|
+ 2024-07-16_11:51:01.301 INFO o.h.b.s.BlazeServerBuilder@L424:[ZScheduler-Worker-5] {} - http4s v0.23.23 on blaze v0.23.15 started at http://0.0.0.0:8085/
+
+```
+
+#### 2. Building the demo application
+
+```bash
+docker build -t identus-oid4vci-demo:latest ./demo
+```
+
+#### 3. Running the demo application
+
+```bash
+docker run --network -it identus-oid4vci-demo:latest
+```
+The parameter `NETWORK_NAME` should be the same as the network name in docker-compose.
+This name can be discovered by running the `docker network ls` command.
+
+The demo application acts as both issuer and Holder in the same script.
+See the source code for detailed steps on how to implement this flow.
+The demo application will interactively prompt the next step in the issuance flow.
+Keep continuing until this log appears asking the user to log in using the browser.
+
+```
+##############################
+
+Open this link in the browser to login
+
+http://localhost:9980/realms/students/protocol/openid-connect/auth?redirect_uri=.....
+
+##############################
+
+wating for authorization redirect ...
+```
+
+Open this URL in the browser. Enter `alice` for the username and `1234` for the password.
+
+After a successful login, this log should appear indicating the demo application has received the credentials.
+
+```
+::::: Credential Received :::::
+{
+ "credential": "eyJ0eXAiOiJKV1QiLC...SK1vJK-fx6zjXw"
+}
+```
diff --git a/docs/docusaurus/schemas/credential-schema.md b/docs/docusaurus/schemas/credential-schema.md
index 77c826038b..56aff05830 100644
--- a/docs/docusaurus/schemas/credential-schema.md
+++ b/docs/docusaurus/schemas/credential-schema.md
@@ -168,7 +168,7 @@ DID of the identity which authored the credential schema.
### schema (JSON Schema)
A valid [JSON-SCHEMA](https://json-schema.org/) where the credential schema semantic gets defined.
-JSON Schema must be composed according to schema.
+JSON Schema must be composed according to the [Metaschema](https://json-schema.org/draft/2020-12/schema) schema.
**Example:**
```json
diff --git a/docs/docusaurus/secrets/seed-generation.md b/docs/docusaurus/secrets/seed-generation.md
index 5d9e805819..38bb865224 100644
--- a/docs/docusaurus/secrets/seed-generation.md
+++ b/docs/docusaurus/secrets/seed-generation.md
@@ -52,5 +52,5 @@ users can write down their mnemonic phrase, making it more convenient to keep tr
By using BIP39, users have options to choose a mnemonic phrase length as well as a passphrase.
There are many tools for generating a BIP39 seed including but not limited to:
-- (use the BIP39 seed field which provides a 128-chars hex string)
+- [BIP39](https://iancoleman.io/bip39/) (use the BIP39 seed field which provides a 128-chars hex string)
- [BIP39 - implementations section](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#other-implementations) also provides a list of implementations
diff --git a/docs/docusaurus/sidebars.js b/docs/docusaurus/sidebars.js
index 7e1712fa68..b746ea93f5 100644
--- a/docs/docusaurus/sidebars.js
+++ b/docs/docusaurus/sidebars.js
@@ -20,8 +20,9 @@ const sidebars = {
},
items: [
'credentials/issue',
+ 'credentials/oid4vci',
'credentials/present-proof',
- 'credentials/revocation',
+ 'credentials/revocation'
]
},
{
@@ -96,7 +97,7 @@ const sidebars = {
'multitenancy/tenant-onboarding-ext-iam',
'multitenancy/tenant-onboarding-self-service',
'multitenancy/tenant-migration',
- 'multitenancy/admin-authz-ext-iam',
+ 'multitenancy/admin-authz-ext-iam'
]
}
]
diff --git a/docs/general/authserver-oid4vci-contract.md b/docs/general/authserver-oid4vci-contract.md
new file mode 100644
index 0000000000..3260389333
--- /dev/null
+++ b/docs/general/authserver-oid4vci-contract.md
@@ -0,0 +1,72 @@
+# OID4VCI Authorization Server contract
+
+Identus Cloud Agent supports OID4VCI while allowing users to plug-in their authorization server.
+The agent provides a credential endpoint, while the authorization server handles the authorization and token endpoints.
+This flexibility enables integration with any existing authorization server potentially containing the holder user base.
+The issuance flow implementation requires the authorization server to adhere to the contract, ensuring the agent can manage the issuance session and coordinate the process.
+
+The Identus platform also provides the [Keycloak plugin reference implementation](https://github.com/hyperledger/identus-keycloak-plugins) showcasing the agent capability.
+However, the authorization server is not limited to only Keycloak.
+
+## Contract for Authorization Code issuance flow
+
+The sequence diagram is largely based on [OID4VCI spec](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-code-flow)
+with slight modification to the __TokenEndpoint__.
+
+```mermaid
+sequenceDiagram
+ participant Holder
+ participant Issuer
+ participant AuthServer
+ participant CloudAgent
+
+ Issuer ->>+ CloudAgent: Create CredentialOffer
+ CloudAgent ->>- Issuer: CredentialOffer
(issuer_state)
+ Issuer -->> Holder: Present offer
(issuer_state)
+ Holder ->>+ CloudAgent: Discover Metadata
+ CloudAgent ->>- Holder: IssuerMetadata
+ Holder ->>+ AuthServer: Discover Metadata
+ AuthServer ->>- Holder: AuthServerMetadata
+ Holder ->>+ AuthServer: AuthorizationRequest
(issuer_state)
+ AuthServer ->>- Holder: AuthorizationResponse
(code)
+ Holder ->>+ AuthServer: TokenRequest
(code)
+ AuthServer ->>+ CloudAgent: NonceRequest
(issuer_state)
+ CloudAgent ->>- AuthServer: NonceResponse
(c_nonce)
+ AuthServer ->>- Holder: TokenResponse
(c_nonce)
+ Holder ->>+ CloudAgent: CredentialRequest
(proof)
+ CloudAgent ->>- Holder: CredentialResponse
+```
+
+
+### Authorization Endpoint
+
+1. Authorization `scope` MUST be configured in the Authorization Server to the same value as in Credential Issuer Metadata
+2. The endpoint MUST accept the parameter `issuer_state` in the [__AuthorizationRequest__](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-5.1.3-2.3) and recall this value in the subsequent call on the __TokenEndpoint__
+
+### Token Endpoint
+
+1. When the holder makes a __TokenRequest__ to the __TokenEndpoint__, the __AuthorizationServer__ MUST recall the `issuer_state` parameter and make an HTTP call to the `/oid4vci/nonces` endpoint in the Cloud Agent using the following format.
+
+__NonceRequest__
+
+```
+POST /oid4vci/nonces
+Authorization: Bearer
+
+{
+ "issuerState": ""
+}
+```
+Where `JWT_TOKEN` is a valid token issued by the __AuthorizationServer__.
+
+__NonceResponse__
+
+```
+{
+ "nonce": "",
+ "nonceExpiresIn":
+}
+```
+
+2. The [__TokenResponse__](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-successful-token-response) must include `c_nonce` and `c_nonce_expires_in` parameter in the __TokenResponse__
+
diff --git a/examples/.nickel/caddy.ncl b/examples/.nickel/caddy.ncl
index 1f3ccf94b7..153ce9c879 100644
--- a/examples/.nickel/caddy.ncl
+++ b/examples/.nickel/caddy.ncl
@@ -25,7 +25,7 @@ in
handle_path /didcomm* {
reverse_proxy %{args.agent.host}:%{std.to_string args.agent.didcommPort}
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy %{args.agent.host}:%{std.to_string args.agent.restPort}
}
handle_path /keycloak* {
diff --git a/examples/.nickel/root.ncl b/examples/.nickel/root.ncl
index 1276ba261b..5fa174acc2 100644
--- a/examples/.nickel/root.ncl
+++ b/examples/.nickel/root.ncl
@@ -21,7 +21,7 @@ in
name = "issuer",
port = 9980,
realm = "students",
- extraEnvs = { IDENTUS_URL = "http://caddy-issuer:8080/prism-agent" }
+ extraEnvs = { IDENTUS_URL = "http://caddy-issuer:8080/cloud-agent" }
}
),
diff --git a/examples/.nickel/stack.ncl b/examples/.nickel/stack.ncl
index a7e713a090..323ec5d6ce 100644
--- a/examples/.nickel/stack.ncl
+++ b/examples/.nickel/stack.ncl
@@ -159,7 +159,7 @@ in
agentDb = makeSharedDbConfig "agent",
node = { host = "node" },
didcommServiceUrl = "http://%{hosts.caddy}:%{std.to_string args.port}/didcomm",
- restServiceUrl = "http://%{hosts.caddy}:%{std.to_string args.port}/prism-agent",
+ restServiceUrl = "http://%{hosts.caddy}:%{std.to_string args.port}/cloud-agent",
apikeyEnabled = args.apikeyEnabled,
}
& (
@@ -200,12 +200,12 @@ in
version = V.hurl,
hurlDir = "../.shared/hurl/simple_realm",
variables = {
- HURL_KEYCLOAK_BASE_URL = "http://%{hosts.keycloak}:8080",
- HURL_KEYCLOAK_ADMIN_USER = "admin",
- HURL_KEYCLOAK_ADMIN_PASSWORD = "admin",
- HURL_KEYCLOAK_REALM = "identus",
- HURL_KEYCLOAK_CLIENT_ID = "agent",
- HURL_KEYCLOAK_CLIENT_SECRET = "agent-secret",
+ HURL_keycloak_base_url = "http://%{hosts.keycloak}:8080",
+ HURL_keycloak_admin_user = "admin",
+ HURL_keycloak_admin_password = "admin",
+ HURL_keycloak_realm = "identus",
+ HURL_keycloak_client_id = "agent",
+ HURL_keycloak_client_secret = "agent-secret",
}
},
}
diff --git a/examples/.nickel/versions.ncl b/examples/.nickel/versions.ncl
index 5f6ee3f616..7ff18ae327 100644
--- a/examples/.nickel/versions.ncl
+++ b/examples/.nickel/versions.ncl
@@ -1,8 +1,8 @@
{
# identus
- agent = "1.36.1-SNAPSHOT",
+ agent = "1.38.0",
node = "2.4.0",
- identusKeycloak = "0.1.0",
+ identusKeycloak = "0.2.0",
# 3rd party
caddy = "2.7.6-alpine",
mockServer = "5.15.0",
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000000..670b327f54
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,74 @@
+# How to run examples
+
+## Prerequisites
+
+- docker-compose version >= `2.23.1`
+
+## Running examples
+
+Most of the examples should follow the same pattern.
+Simply go to each example directory and spin up the docker-compose of each example.
+
+```bash
+cd
+docker-compose up
+```
+
+If some example requires a different command, it should be provided in its own local README.
+
+Once finished, `docker-compose down --volumes` can be used to clean docker volumes to avoid unexpected behavior for the next run.
+
+## Examples
+
+| example | description |
+|---------------------|---------------------------------------------------------------------------|
+| `st` | single-tenant configuration without external services (except database) |
+| `st-multi` | 3 instances of single-tenant configuration |
+| `st-vault` | single-tenant with Vault for secret storage |
+| `st-oid4vci` | single-tenant agent with Keycloak as external Issuer Authorization Server |
+| `mt` | multi-tenant configuration using built-in IAM |
+| `mt-keycloak` | multi-tenant configuration using Keycloak for IAM |
+| `mt-keycloak-vault` | multi-tenant configuration using Keycloak and Vault |
+
+## Testing examples
+
+Some example directories may contain a sub-directory called `hurl`.
+Hurl is a CLI tool for testing HTTP requests and can be installed according to [this documentation](https://hurl.dev/docs/installation.html).
+If the example contains a sub-directory named `hurl`, the example can be tested against HTTP calls with the following commands.
+
+```bash
+cd ./hurl
+hurl --variables-file ./local *.hurl --test
+```
+
+# Contributing
+
+All of the docker-compose files in examples are generated using [Nickel](https://nickel-lang.org/).
+They are defined in a shared `.nickel` directory and generated using the `build.sh` script.
+
+## Prerequisites
+
+- [Nickel](https://nickel-lang.org/) version >= `1.5` installed
+
+## Generate example compose files
+
+To generate the docker-compose config for all examples, run
+
+```bash
+cd .nickel
+./build.sh
+```
+
+## Updating example compose files
+
+To update the configuration, simply edit the `*.ncl` config in the `.nickel` directory and regenerate the docker-compose files.
+
+## Adding new examples
+
+To add a new example with docker-compose file, simply create a new configuration key in the `root.ncl` and add a new entry in the `build.sh` script.
+You may need to create the target example directory if it does not already exist.
+
+## Example with bootstrapping script
+
+If any example requires initialize steps, it should be made part of the docker-compose `depends_on` construct.
+Ideally, infrastructure bootstrapping should be automatic (database, IAM), but not necessarily application bootstrapping (tenant onboarding).
diff --git a/examples/mt-keycloak-vault/README.md b/examples/mt-keycloak-vault/README.md
new file mode 100644
index 0000000000..207927dfcd
--- /dev/null
+++ b/examples/mt-keycloak-vault/README.md
@@ -0,0 +1,16 @@
+## Configuration
+
+| Exposed Service | Description |
+|---------------------------------|--------------------------|
+| `localhost:8080/cloudagent` | Multi-tenant Cloud Agent |
+| `localhost:8080/keycloak/admin` | Keycloak |
+| `localhost:8200` | Vault |
+
+__Keycloak__
+
+- Admin user `admin`
+- Admin password `admin`
+
+__Vault__
+
+- Root token `admin`
diff --git a/examples/mt-keycloak-vault/compose.yaml b/examples/mt-keycloak-vault/compose.yaml
index daf6939b36..6a313e1e19 100644
--- a/examples/mt-keycloak-vault/compose.yaml
+++ b/examples/mt-keycloak-vault/compose.yaml
@@ -5,7 +5,7 @@ configs:
handle_path /didcomm* {
reverse_proxy agent-default:8090
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy agent-default:8085
}
handle_path /keycloak* {
@@ -46,14 +46,14 @@ services:
POLLUX_DB_PASSWORD: postgres
POLLUX_DB_PORT: '5432'
POLLUX_DB_USER: postgres
- POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-default:8080/prism-agent
+ POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-default:8080/cloud-agent
PRISM_NODE_HOST: node
PRISM_NODE_PORT: '50053'
- REST_SERVICE_URL: http://caddy-default:8080/prism-agent
+ REST_SERVICE_URL: http://caddy-default:8080/cloud-agent
SECRET_STORAGE_BACKEND: vault
VAULT_ADDR: http://vault-default:8200
VAULT_TOKEN: admin
- image: ghcr.io/hyperledger/identus-cloud-agent:1.36.1-SNAPSHOT
+ image: ghcr.io/hyperledger/identus-cloud-agent:1.38.0
restart: always
caddy-default:
configs:
@@ -103,12 +103,12 @@ services:
- /hurl/*.hurl
- --test
environment:
- HURL_KEYCLOAK_ADMIN_PASSWORD: admin
- HURL_KEYCLOAK_ADMIN_USER: admin
- HURL_KEYCLOAK_BASE_URL: http://keycloak-default:8080
- HURL_KEYCLOAK_CLIENT_ID: agent
- HURL_KEYCLOAK_CLIENT_SECRET: agent-secret
- HURL_KEYCLOAK_REALM: identus
+ HURL_keycloak_admin_password: admin
+ HURL_keycloak_admin_user: admin
+ HURL_keycloak_base_url: http://keycloak-default:8080
+ HURL_keycloak_client_id: agent
+ HURL_keycloak_client_secret: agent-secret
+ HURL_keycloak_realm: identus
image: ghcr.io/orange-opensource/hurl:4.2.0
volumes:
- ../.shared/hurl/simple_realm:/hurl
diff --git a/examples/mt-keycloak-vault/tests/01_create_users.hurl b/examples/mt-keycloak-vault/hurl/01_create_users.hurl
similarity index 95%
rename from examples/mt-keycloak-vault/tests/01_create_users.hurl
rename to examples/mt-keycloak-vault/hurl/01_create_users.hurl
index 9a412b9668..0e5d13854a 100644
--- a/examples/mt-keycloak-vault/tests/01_create_users.hurl
+++ b/examples/mt-keycloak-vault/hurl/01_create_users.hurl
@@ -66,7 +66,7 @@ HTTP 200
issuer_access_token: jsonpath "$.access_token"
# Create Issuer wallet
-POST {{ agent_url }}/prism-agent/wallets
+POST {{ agent_url }}/cloud-agent/wallets
Authorization: Bearer {{ issuer_access_token }}
{
"name": "issuer-wallet"
@@ -85,7 +85,7 @@ HTTP 200
holder_access_token: jsonpath "$.access_token"
# Create Holder wallet
-POST {{ agent_url }}/prism-agent/wallets
+POST {{ agent_url }}/cloud-agent/wallets
Authorization: Bearer {{ holder_access_token }}
{
"name": "holder-wallet"
@@ -104,7 +104,7 @@ HTTP 200
verifier_access_token: jsonpath "$.access_token"
# Create Verifier wallet
-POST {{ agent_url }}/prism-agent/wallets
+POST {{ agent_url }}/cloud-agent/wallets
Authorization: Bearer {{ verifier_access_token }}
{
"name": "verifier-wallet"
diff --git a/examples/mt-keycloak-vault/tests/02_jwt_flow.hurl b/examples/mt-keycloak-vault/hurl/02_jwt_flow.hurl
similarity index 83%
rename from examples/mt-keycloak-vault/tests/02_jwt_flow.hurl
rename to examples/mt-keycloak-vault/hurl/02_jwt_flow.hurl
index c1ceb033b4..6e0c8edcab 100644
--- a/examples/mt-keycloak-vault/tests/02_jwt_flow.hurl
+++ b/examples/mt-keycloak-vault/hurl/02_jwt_flow.hurl
@@ -39,7 +39,7 @@ verifier_access_token: jsonpath "$.access_token"
# Prerequisites
##############################
# Issuer create DID
-POST {{ agent_url }}/prism-agent/did-registrar/dids
+POST {{ agent_url }}/cloud-agent/did-registrar/dids
Authorization: Bearer {{ issuer_access_token }}
{
"documentTemplate": {
@@ -54,10 +54,10 @@ Authorization: Bearer {{ issuer_access_token }}
}
HTTP 201
[Captures]
-issuer_did: jsonpath "$.longFormDid" regex "(did:prism:[a-z0-9]+):.+$"
+issuer_did: jsonpath "$.longFormDid" # regex "(did:prism:[a-z0-9]+):.+$"
# Holder create DID
-POST {{ agent_url }}/prism-agent/did-registrar/dids
+POST {{ agent_url }}/cloud-agent/did-registrar/dids
Authorization: Bearer {{ holder_access_token }}
{
"documentTemplate": {
@@ -78,7 +78,7 @@ holder_did: jsonpath "$.longFormDid" regex "(did:prism:[a-z0-9]+):.+$"
# Issuance Connection
##############################
# Inviter create connection
-POST {{ agent_url }}/prism-agent/connections
+POST {{ agent_url }}/cloud-agent/connections
Authorization: Bearer {{ issuer_access_token }}
{
"label": "My Connection"
@@ -89,7 +89,7 @@ raw_invitation: jsonpath "$.invitation.invitationUrl" regex ".*_oob=(.*)$"
issuer_connection_id: jsonpath "$.connectionId"
# Invitee accept connection
-POST {{ agent_url }}/prism-agent/connection-invitations
+POST {{ agent_url }}/cloud-agent/connection-invitations
Authorization: Bearer {{ holder_access_token }}
{
"invitation": "{{ raw_invitation }}"
@@ -99,7 +99,7 @@ HTTP 200
holder_connection_id: jsonpath "$.connectionId"
# Wait for inviter connection status
-GET {{ agent_url }}/prism-agent/connections/{{ issuer_connection_id }}
+GET {{ agent_url }}/cloud-agent/connections/{{ issuer_connection_id }}
Authorization: Bearer {{ issuer_access_token }}
[Options]
retry: -1
@@ -108,7 +108,7 @@ HTTP 200
jsonpath "$.state" == "ConnectionResponseSent"
# Wait for invitee connection status
-GET {{ agent_url }}/prism-agent/connections/{{ holder_connection_id }}
+GET {{ agent_url }}/cloud-agent/connections/{{ holder_connection_id }}
Authorization: Bearer {{ holder_access_token }}
[Options]
retry: -1
@@ -120,7 +120,7 @@ jsonpath "$.state" == "ConnectionResponseReceived"
# Issuance
##############################
# Issuer create credential offer
-POST {{ agent_url }}/prism-agent/issue-credentials/credential-offers
+POST {{ agent_url }}/cloud-agent/issue-credentials/credential-offers
Authorization: Bearer {{ issuer_access_token }}
{
"claims": {
@@ -138,7 +138,7 @@ issuer_cred_record_id: jsonpath "$.recordId"
didcomm_issuing_thid: jsonpath "$.thid"
# Holder wait for OfferReceived state
-GET {{ agent_url }}/prism-agent/issue-credentials/records
+GET {{ agent_url }}/cloud-agent/issue-credentials/records
Authorization: Bearer {{ holder_access_token }}
[QueryStringParams]
thid: {{ didcomm_issuing_thid }}
@@ -151,7 +151,7 @@ jsonpath "$.contents[0].protocolState" == "OfferReceived"
holder_cred_record_id: jsonpath "$.contents[0].recordId"
# Holder accept a credential-offer
-POST {{ agent_url }}/prism-agent/issue-credentials/records/{{ holder_cred_record_id }}/accept-offer
+POST {{ agent_url }}/cloud-agent/issue-credentials/records/{{ holder_cred_record_id }}/accept-offer
Authorization: Bearer {{ holder_access_token }}
{
"subjectId": "{{ holder_did }}"
@@ -159,7 +159,7 @@ Authorization: Bearer {{ holder_access_token }}
HTTP 200
# Holder wait for CredentialReceived state
-GET {{ agent_url }}/prism-agent/issue-credentials/records/{{ holder_cred_record_id }}
+GET {{ agent_url }}/cloud-agent/issue-credentials/records/{{ holder_cred_record_id }}
Authorization: Bearer {{ holder_access_token }}
[Options]
retry: -1
@@ -168,7 +168,7 @@ HTTP 200
jsonpath "$.protocolState" == "CredentialReceived"
# Issuer wait for CredentialSent state
-GET {{ agent_url }}/prism-agent/issue-credentials/records/{{ issuer_cred_record_id }}
+GET {{ agent_url }}/cloud-agent/issue-credentials/records/{{ issuer_cred_record_id }}
Authorization: Bearer {{ issuer_access_token }}
[Options]
retry: -1
@@ -180,7 +180,7 @@ jsonpath "$.protocolState" == "CredentialSent"
# Presentation Connection
##############################
# Inviter create connection
-POST {{ agent_url }}/prism-agent/connections
+POST {{ agent_url }}/cloud-agent/connections
Authorization: Bearer {{ verifier_access_token }}
{
"label": "My Connection"
@@ -191,7 +191,7 @@ raw_invitation: jsonpath "$.invitation.invitationUrl" regex ".*_oob=(.*)$"
verifier_connection_id: jsonpath "$.connectionId"
# Invitee accept connection
-POST {{ agent_url }}/prism-agent/connection-invitations
+POST {{ agent_url }}/cloud-agent/connection-invitations
Authorization: Bearer {{ holder_access_token }}
{
"invitation": "{{ raw_invitation }}"
@@ -201,7 +201,7 @@ HTTP 200
holder_connection_id: jsonpath "$.connectionId"
# Wait for inviter connection status
-GET {{ agent_url }}/prism-agent/connections/{{ verifier_connection_id }}
+GET {{ agent_url }}/cloud-agent/connections/{{ verifier_connection_id }}
Authorization: Bearer {{ verifier_access_token }}
[Options]
retry: -1
@@ -210,7 +210,7 @@ HTTP 200
jsonpath "$.state" == "ConnectionResponseSent"
# Wait for invitee connection status
-GET {{ agent_url }}/prism-agent/connections/{{ holder_connection_id }}
+GET {{ agent_url }}/cloud-agent/connections/{{ holder_connection_id }}
Authorization: Bearer {{ holder_access_token }}
[Options]
retry: -1
@@ -222,7 +222,7 @@ jsonpath "$.state" == "ConnectionResponseReceived"
# Presentation
##############################
# Verifier create presentation request
-POST {{ agent_url }}/prism-agent/present-proof/presentations
+POST {{ agent_url }}/cloud-agent/present-proof/presentations
Authorization: Bearer {{ verifier_access_token }}
{
"connectionId": "{{ verifier_connection_id }}",
@@ -238,7 +238,7 @@ verifier_presentation_id: jsonpath "$.presentationId"
didcomm_presentation_thid: jsonpath "$.thid"
# Holder wait for RequestReceived state
-GET {{ agent_url }}/prism-agent/present-proof/presentations
+GET {{ agent_url }}/cloud-agent/present-proof/presentations
Authorization: Bearer {{ holder_access_token }}
[QueryStringParams]
thid: {{ didcomm_presentation_thid }}
@@ -251,7 +251,7 @@ jsonpath "$.contents[0].status" == "RequestReceived"
holder_presentation_id: jsonpath "$.contents[0].presentationId"
# Holder accept presentation request
-PATCH {{ agent_url }}/prism-agent/present-proof/presentations/{{ holder_presentation_id }}
+PATCH {{ agent_url }}/cloud-agent/present-proof/presentations/{{ holder_presentation_id }}
Authorization: Bearer {{ holder_access_token }}
{
"action": "request-accept",
@@ -260,7 +260,7 @@ Authorization: Bearer {{ holder_access_token }}
HTTP 200
# Holder wait for PresentationSent state
-GET {{ agent_url }}/prism-agent/present-proof/presentations
+GET {{ agent_url }}/cloud-agent/present-proof/presentations
Authorization: Bearer {{ holder_access_token }}
[QueryStringParams]
thid: {{ didcomm_presentation_thid }}
@@ -271,7 +271,7 @@ HTTP 200
jsonpath "$.contents[0].status" == "PresentationSent"
# Verfiier wait for PresentationVerified state
-GET {{ agent_url }}/prism-agent/present-proof/presentations
+GET {{ agent_url }}/cloud-agent/present-proof/presentations
Authorization: Bearer {{ verifier_access_token }}
[QueryStringParams]
thid: {{ didcomm_presentation_thid }}
diff --git a/examples/mt-keycloak-vault/tests/local b/examples/mt-keycloak-vault/hurl/local
similarity index 100%
rename from examples/mt-keycloak-vault/tests/local
rename to examples/mt-keycloak-vault/hurl/local
diff --git a/examples/mt-keycloak/README.md b/examples/mt-keycloak/README.md
new file mode 100644
index 0000000000..373b8a4bfa
--- /dev/null
+++ b/examples/mt-keycloak/README.md
@@ -0,0 +1,11 @@
+## Configuration
+
+| Exposed Service | Description |
+|---------------------------------|--------------------------|
+| `localhost:8080/cloud-agent` | Multi-tenant Cloud Agent |
+| `localhost:8080/keycloak/admin` | Keycloak |
+
+__Keycloak__
+
+- Admin user `admin`
+- Admin password `admin`
diff --git a/examples/mt-keycloak/compose.yaml b/examples/mt-keycloak/compose.yaml
index 3ebc37d1ad..ea2b55a150 100644
--- a/examples/mt-keycloak/compose.yaml
+++ b/examples/mt-keycloak/compose.yaml
@@ -5,7 +5,7 @@ configs:
handle_path /didcomm* {
reverse_proxy agent-default:8090
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy agent-default:8085
}
handle_path /keycloak* {
@@ -46,12 +46,12 @@ services:
POLLUX_DB_PASSWORD: postgres
POLLUX_DB_PORT: '5432'
POLLUX_DB_USER: postgres
- POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-default:8080/prism-agent
+ POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-default:8080/cloud-agent
PRISM_NODE_HOST: node
PRISM_NODE_PORT: '50053'
- REST_SERVICE_URL: http://caddy-default:8080/prism-agent
+ REST_SERVICE_URL: http://caddy-default:8080/cloud-agent
SECRET_STORAGE_BACKEND: postgres
- image: ghcr.io/hyperledger/identus-cloud-agent:1.36.1-SNAPSHOT
+ image: ghcr.io/hyperledger/identus-cloud-agent:1.38.0
restart: always
caddy-default:
configs:
@@ -101,12 +101,12 @@ services:
- /hurl/*.hurl
- --test
environment:
- HURL_KEYCLOAK_ADMIN_PASSWORD: admin
- HURL_KEYCLOAK_ADMIN_USER: admin
- HURL_KEYCLOAK_BASE_URL: http://keycloak-default:8080
- HURL_KEYCLOAK_CLIENT_ID: agent
- HURL_KEYCLOAK_CLIENT_SECRET: agent-secret
- HURL_KEYCLOAK_REALM: identus
+ HURL_keycloak_admin_password: admin
+ HURL_keycloak_admin_user: admin
+ HURL_keycloak_base_url: http://keycloak-default:8080
+ HURL_keycloak_client_id: agent
+ HURL_keycloak_client_secret: agent-secret
+ HURL_keycloak_realm: identus
image: ghcr.io/orange-opensource/hurl:4.2.0
volumes:
- ../.shared/hurl/simple_realm:/hurl
diff --git a/examples/mt-keycloak/tests/01_create_users.hurl b/examples/mt-keycloak/hurl/01_create_users.hurl
similarity index 95%
rename from examples/mt-keycloak/tests/01_create_users.hurl
rename to examples/mt-keycloak/hurl/01_create_users.hurl
index 9a412b9668..0e5d13854a 100644
--- a/examples/mt-keycloak/tests/01_create_users.hurl
+++ b/examples/mt-keycloak/hurl/01_create_users.hurl
@@ -66,7 +66,7 @@ HTTP 200
issuer_access_token: jsonpath "$.access_token"
# Create Issuer wallet
-POST {{ agent_url }}/prism-agent/wallets
+POST {{ agent_url }}/cloud-agent/wallets
Authorization: Bearer {{ issuer_access_token }}
{
"name": "issuer-wallet"
@@ -85,7 +85,7 @@ HTTP 200
holder_access_token: jsonpath "$.access_token"
# Create Holder wallet
-POST {{ agent_url }}/prism-agent/wallets
+POST {{ agent_url }}/cloud-agent/wallets
Authorization: Bearer {{ holder_access_token }}
{
"name": "holder-wallet"
@@ -104,7 +104,7 @@ HTTP 200
verifier_access_token: jsonpath "$.access_token"
# Create Verifier wallet
-POST {{ agent_url }}/prism-agent/wallets
+POST {{ agent_url }}/cloud-agent/wallets
Authorization: Bearer {{ verifier_access_token }}
{
"name": "verifier-wallet"
diff --git a/examples/mt-keycloak/tests/02_jwt_flow.hurl b/examples/mt-keycloak/hurl/02_jwt_flow.hurl
similarity index 83%
rename from examples/mt-keycloak/tests/02_jwt_flow.hurl
rename to examples/mt-keycloak/hurl/02_jwt_flow.hurl
index 2169d2f777..41999eeb87 100644
--- a/examples/mt-keycloak/tests/02_jwt_flow.hurl
+++ b/examples/mt-keycloak/hurl/02_jwt_flow.hurl
@@ -39,7 +39,7 @@ verifier_access_token: jsonpath "$.access_token"
# Prerequisites
##############################
# Issuer create DID
-POST {{ agent_url }}/prism-agent/did-registrar/dids
+POST {{ agent_url }}/cloud-agent/did-registrar/dids
Authorization: Bearer {{ issuer_access_token }}
{
"documentTemplate": {
@@ -57,12 +57,12 @@ HTTP 201
issuer_did: jsonpath "$.longFormDid" regex "(did:prism:[a-z0-9]+):.+$"
# Issuer publish DID
-POST {{ agent_url }}/prism-agent/did-registrar/dids/{{ issuer_did }}/publications
+POST {{ agent_url }}/cloud-agent/did-registrar/dids/{{ issuer_did }}/publications
Authorization: Bearer {{ issuer_access_token }}
HTTP 202
# Issuer wait for DID to be published
-GET {{ agent_url }}/prism-agent/did-registrar/dids/{{ issuer_did }}
+GET {{ agent_url }}/cloud-agent/did-registrar/dids/{{ issuer_did }}
Authorization: Bearer {{ issuer_access_token }}
[Options]
retry: -1
@@ -71,7 +71,7 @@ HTTP 200
jsonpath "$.status" == "PUBLISHED"
# Holder create DID
-POST {{ agent_url }}/prism-agent/did-registrar/dids
+POST {{ agent_url }}/cloud-agent/did-registrar/dids
Authorization: Bearer {{ holder_access_token }}
{
"documentTemplate": {
@@ -92,7 +92,7 @@ holder_did: jsonpath "$.longFormDid"
# Issuance Connection
##############################
# Inviter create connection
-POST {{ agent_url }}/prism-agent/connections
+POST {{ agent_url }}/cloud-agent/connections
Authorization: Bearer {{ issuer_access_token }}
{
"label": "My Connection"
@@ -103,7 +103,7 @@ raw_invitation: jsonpath "$.invitation.invitationUrl" regex ".*_oob=(.*)$"
issuer_connection_id: jsonpath "$.connectionId"
# Invitee accept connection
-POST {{ agent_url }}/prism-agent/connection-invitations
+POST {{ agent_url }}/cloud-agent/connection-invitations
Authorization: Bearer {{ holder_access_token }}
{
"invitation": "{{ raw_invitation }}"
@@ -113,7 +113,7 @@ HTTP 200
holder_connection_id: jsonpath "$.connectionId"
# Wait for inviter connection status
-GET {{ agent_url }}/prism-agent/connections/{{ issuer_connection_id }}
+GET {{ agent_url }}/cloud-agent/connections/{{ issuer_connection_id }}
Authorization: Bearer {{ issuer_access_token }}
[Options]
retry: -1
@@ -122,7 +122,7 @@ HTTP 200
jsonpath "$.state" == "ConnectionResponseSent"
# Wait for invitee connection status
-GET {{ agent_url }}/prism-agent/connections/{{ holder_connection_id }}
+GET {{ agent_url }}/cloud-agent/connections/{{ holder_connection_id }}
Authorization: Bearer {{ holder_access_token }}
[Options]
retry: -1
@@ -134,7 +134,7 @@ jsonpath "$.state" == "ConnectionResponseReceived"
# Issuance
##############################
# Issuer create credential offer
-POST {{ agent_url }}/prism-agent/issue-credentials/credential-offers
+POST {{ agent_url }}/cloud-agent/issue-credentials/credential-offers
Authorization: Bearer {{ issuer_access_token }}
{
"claims": {
@@ -152,7 +152,7 @@ issuer_cred_record_id: jsonpath "$.recordId"
didcomm_issuing_thid: jsonpath "$.thid"
# Holder wait for OfferReceived state
-GET {{ agent_url }}/prism-agent/issue-credentials/records
+GET {{ agent_url }}/cloud-agent/issue-credentials/records
Authorization: Bearer {{ holder_access_token }}
[QueryStringParams]
thid: {{ didcomm_issuing_thid }}
@@ -165,7 +165,7 @@ jsonpath "$.contents[0].protocolState" == "OfferReceived"
holder_cred_record_id: jsonpath "$.contents[0].recordId"
# Holder accept a credential-offer
-POST {{ agent_url }}/prism-agent/issue-credentials/records/{{ holder_cred_record_id }}/accept-offer
+POST {{ agent_url }}/cloud-agent/issue-credentials/records/{{ holder_cred_record_id }}/accept-offer
Authorization: Bearer {{ holder_access_token }}
{
"subjectId": "{{ holder_did }}"
@@ -173,7 +173,7 @@ Authorization: Bearer {{ holder_access_token }}
HTTP 200
# Holder wait for CredentialReceived state
-GET {{ agent_url }}/prism-agent/issue-credentials/records/{{ holder_cred_record_id }}
+GET {{ agent_url }}/cloud-agent/issue-credentials/records/{{ holder_cred_record_id }}
Authorization: Bearer {{ holder_access_token }}
[Options]
retry: -1
@@ -182,7 +182,7 @@ HTTP 200
jsonpath "$.protocolState" == "CredentialReceived"
# Issuer wait for CredentialSent state
-GET {{ agent_url }}/prism-agent/issue-credentials/records/{{ issuer_cred_record_id }}
+GET {{ agent_url }}/cloud-agent/issue-credentials/records/{{ issuer_cred_record_id }}
Authorization: Bearer {{ issuer_access_token }}
[Options]
retry: -1
@@ -194,7 +194,7 @@ jsonpath "$.protocolState" == "CredentialSent"
# Presentation Connection
##############################
# Inviter create connection
-POST {{ agent_url }}/prism-agent/connections
+POST {{ agent_url }}/cloud-agent/connections
Authorization: Bearer {{ verifier_access_token }}
{
"label": "My Connection"
@@ -205,7 +205,7 @@ raw_invitation: jsonpath "$.invitation.invitationUrl" regex ".*_oob=(.*)$"
verifier_connection_id: jsonpath "$.connectionId"
# Invitee accept connection
-POST {{ agent_url }}/prism-agent/connection-invitations
+POST {{ agent_url }}/cloud-agent/connection-invitations
Authorization: Bearer {{ holder_access_token }}
{
"invitation": "{{ raw_invitation }}"
@@ -215,7 +215,7 @@ HTTP 200
holder_connection_id: jsonpath "$.connectionId"
# Wait for inviter connection status
-GET {{ agent_url }}/prism-agent/connections/{{ verifier_connection_id }}
+GET {{ agent_url }}/cloud-agent/connections/{{ verifier_connection_id }}
Authorization: Bearer {{ verifier_access_token }}
[Options]
retry: -1
@@ -224,7 +224,7 @@ HTTP 200
jsonpath "$.state" == "ConnectionResponseSent"
# Wait for invitee connection status
-GET {{ agent_url }}/prism-agent/connections/{{ holder_connection_id }}
+GET {{ agent_url }}/cloud-agent/connections/{{ holder_connection_id }}
Authorization: Bearer {{ holder_access_token }}
[Options]
retry: -1
@@ -236,7 +236,7 @@ jsonpath "$.state" == "ConnectionResponseReceived"
# Presentation
##############################
# Verifier create presentation request
-POST {{ agent_url }}/prism-agent/present-proof/presentations
+POST {{ agent_url }}/cloud-agent/present-proof/presentations
Authorization: Bearer {{ verifier_access_token }}
{
"connectionId": "{{ verifier_connection_id }}",
@@ -252,7 +252,7 @@ verifier_presentation_id: jsonpath "$.presentationId"
didcomm_presentation_thid: jsonpath "$.thid"
# Holder wait for RequestReceived state
-GET {{ agent_url }}/prism-agent/present-proof/presentations
+GET {{ agent_url }}/cloud-agent/present-proof/presentations
Authorization: Bearer {{ holder_access_token }}
[QueryStringParams]
thid: {{ didcomm_presentation_thid }}
@@ -265,7 +265,7 @@ jsonpath "$.contents[0].status" == "RequestReceived"
holder_presentation_id: jsonpath "$.contents[0].presentationId"
# Holder accept presentation request
-PATCH {{ agent_url }}/prism-agent/present-proof/presentations/{{ holder_presentation_id }}
+PATCH {{ agent_url }}/cloud-agent/present-proof/presentations/{{ holder_presentation_id }}
Authorization: Bearer {{ holder_access_token }}
{
"action": "request-accept",
@@ -274,7 +274,7 @@ Authorization: Bearer {{ holder_access_token }}
HTTP 200
# Holder wait for PresentationSent state
-GET {{ agent_url }}/prism-agent/present-proof/presentations
+GET {{ agent_url }}/cloud-agent/present-proof/presentations
Authorization: Bearer {{ holder_access_token }}
[QueryStringParams]
thid: {{ didcomm_presentation_thid }}
@@ -285,7 +285,7 @@ HTTP 200
jsonpath "$.contents[0].status" == "PresentationSent"
# Verfiier wait for PresentationVerified state
-GET {{ agent_url }}/prism-agent/present-proof/presentations
+GET {{ agent_url }}/cloud-agent/present-proof/presentations
Authorization: Bearer {{ verifier_access_token }}
[QueryStringParams]
thid: {{ didcomm_presentation_thid }}
diff --git a/examples/mt-keycloak/tests/local b/examples/mt-keycloak/hurl/local
similarity index 100%
rename from examples/mt-keycloak/tests/local
rename to examples/mt-keycloak/hurl/local
diff --git a/examples/mt/README.md b/examples/mt/README.md
new file mode 100644
index 0000000000..030af3b4bd
--- /dev/null
+++ b/examples/mt/README.md
@@ -0,0 +1,5 @@
+## Configuration
+
+| Exposed Service | Description |
+|------------------------------|--------------------------|
+| `localhost:8080/cloud-agent` | Multi-tenant Cloud Agent |
diff --git a/examples/mt/compose.yaml b/examples/mt/compose.yaml
index 84e4f7a3d1..0b355b8049 100644
--- a/examples/mt/compose.yaml
+++ b/examples/mt/compose.yaml
@@ -5,7 +5,7 @@ configs:
handle_path /didcomm* {
reverse_proxy agent-default:8090
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy agent-default:8085
}
handle_path /keycloak* {
@@ -39,12 +39,12 @@ services:
POLLUX_DB_PASSWORD: postgres
POLLUX_DB_PORT: '5432'
POLLUX_DB_USER: postgres
- POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-default:8080/prism-agent
+ POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-default:8080/cloud-agent
PRISM_NODE_HOST: node
PRISM_NODE_PORT: '50053'
- REST_SERVICE_URL: http://caddy-default:8080/prism-agent
+ REST_SERVICE_URL: http://caddy-default:8080/cloud-agent
SECRET_STORAGE_BACKEND: postgres
- image: ghcr.io/hyperledger/identus-cloud-agent:1.36.1-SNAPSHOT
+ image: ghcr.io/hyperledger/identus-cloud-agent:1.38.0
restart: always
caddy-default:
configs:
diff --git a/examples/st-multi/README.me b/examples/st-multi/README.me
new file mode 100644
index 0000000000..da8c69f181
--- /dev/null
+++ b/examples/st-multi/README.me
@@ -0,0 +1,7 @@
+## Configuration
+
+|Exposed Service |Description |
+|----------------------------|--------------------------------------|
+|`localhost:8080/cloud-agent`|Single-tenant Cloud Agent#1 (issuer) |
+|`localhost:8081/cloud-agent`|Single-tenant Cloud Agent#2 (holder) |
+|`localhost:8082/cloud-agent`|Single-tenant Cloud Agent#3 (verifier)|
diff --git a/examples/st-multi/compose.yaml b/examples/st-multi/compose.yaml
index 34650ad413..88771d576a 100644
--- a/examples/st-multi/compose.yaml
+++ b/examples/st-multi/compose.yaml
@@ -5,7 +5,7 @@ configs:
handle_path /didcomm* {
reverse_proxy agent-holder:8090
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy agent-holder:8085
}
handle_path /keycloak* {
@@ -21,7 +21,7 @@ configs:
handle_path /didcomm* {
reverse_proxy agent-issuer:8090
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy agent-issuer:8085
}
handle_path /keycloak* {
@@ -37,7 +37,7 @@ configs:
handle_path /didcomm* {
reverse_proxy agent-verifier:8090
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy agent-verifier:8085
}
handle_path /keycloak* {
@@ -71,12 +71,12 @@ services:
POLLUX_DB_PASSWORD: postgres
POLLUX_DB_PORT: '5432'
POLLUX_DB_USER: postgres
- POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-holder:8081/prism-agent
+ POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-holder:8081/cloud-agent
PRISM_NODE_HOST: node
PRISM_NODE_PORT: '50053'
- REST_SERVICE_URL: http://caddy-holder:8081/prism-agent
+ REST_SERVICE_URL: http://caddy-holder:8081/cloud-agent
SECRET_STORAGE_BACKEND: postgres
- image: ghcr.io/hyperledger/identus-cloud-agent:1.36.1-SNAPSHOT
+ image: ghcr.io/hyperledger/identus-cloud-agent:1.38.0
restart: always
agent-issuer:
depends_on:
@@ -101,12 +101,12 @@ services:
POLLUX_DB_PASSWORD: postgres
POLLUX_DB_PORT: '5432'
POLLUX_DB_USER: postgres
- POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-issuer:8080/prism-agent
+ POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-issuer:8080/cloud-agent
PRISM_NODE_HOST: node
PRISM_NODE_PORT: '50053'
- REST_SERVICE_URL: http://caddy-issuer:8080/prism-agent
+ REST_SERVICE_URL: http://caddy-issuer:8080/cloud-agent
SECRET_STORAGE_BACKEND: postgres
- image: ghcr.io/hyperledger/identus-cloud-agent:1.36.1-SNAPSHOT
+ image: ghcr.io/hyperledger/identus-cloud-agent:1.38.0
restart: always
agent-verifier:
depends_on:
@@ -131,12 +131,12 @@ services:
POLLUX_DB_PASSWORD: postgres
POLLUX_DB_PORT: '5432'
POLLUX_DB_USER: postgres
- POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-verifier:8082/prism-agent
+ POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-verifier:8082/cloud-agent
PRISM_NODE_HOST: node
PRISM_NODE_PORT: '50053'
- REST_SERVICE_URL: http://caddy-verifier:8082/prism-agent
+ REST_SERVICE_URL: http://caddy-verifier:8082/cloud-agent
SECRET_STORAGE_BACKEND: postgres
- image: ghcr.io/hyperledger/identus-cloud-agent:1.36.1-SNAPSHOT
+ image: ghcr.io/hyperledger/identus-cloud-agent:1.38.0
restart: always
caddy-holder:
configs:
diff --git a/examples/st-multi/tests/01_jwt_flow.hurl b/examples/st-multi/hurl/01_jwt_flow.hurl
similarity index 77%
rename from examples/st-multi/tests/01_jwt_flow.hurl
rename to examples/st-multi/hurl/01_jwt_flow.hurl
index 94bc540275..4752a1adb1 100644
--- a/examples/st-multi/tests/01_jwt_flow.hurl
+++ b/examples/st-multi/hurl/01_jwt_flow.hurl
@@ -2,7 +2,7 @@
# Prerequisites
##############################
# Issuer create DID
-POST {{ issuer_url }}/prism-agent/did-registrar/dids
+POST {{ issuer_url }}/cloud-agent/did-registrar/dids
{
"documentTemplate": {
"publicKeys": [
@@ -19,11 +19,11 @@ HTTP 201
issuer_did: jsonpath "$.longFormDid" regex "(did:prism:[a-z0-9]+):.+$"
# Issuer publish DID
-POST {{ issuer_url }}/prism-agent/did-registrar/dids/{{ issuer_did }}/publications
+POST {{ issuer_url }}/cloud-agent/did-registrar/dids/{{ issuer_did }}/publications
HTTP 202
# Issuer wait for DID to be published
-GET {{ issuer_url }}/prism-agent/did-registrar/dids/{{ issuer_did }}
+GET {{ issuer_url }}/cloud-agent/did-registrar/dids/{{ issuer_did }}
[Options]
retry: -1
HTTP 200
@@ -31,7 +31,7 @@ HTTP 200
jsonpath "$.status" == "PUBLISHED"
# Holder create DID
-POST {{ holder_url }}/prism-agent/did-registrar/dids
+POST {{ holder_url }}/cloud-agent/did-registrar/dids
{
"documentTemplate": {
"publicKeys": [
@@ -51,7 +51,7 @@ holder_did: jsonpath "$.longFormDid"
# Issuance Connection
##############################
# Inviter create connection
-POST {{ issuer_url }}/prism-agent/connections
+POST {{ issuer_url }}/cloud-agent/connections
{
"label": "My Connection"
}
@@ -61,7 +61,7 @@ raw_invitation: jsonpath "$.invitation.invitationUrl" regex ".*_oob=(.*)$"
issuer_connection_id: jsonpath "$.connectionId"
# Invitee accept connection
-POST {{ holder_url }}/prism-agent/connection-invitations
+POST {{ holder_url }}/cloud-agent/connection-invitations
{
"invitation": "{{ raw_invitation }}"
}
@@ -70,7 +70,7 @@ HTTP 200
holder_connection_id: jsonpath "$.connectionId"
# Wait for inviter connection status
-GET {{ issuer_url }}/prism-agent/connections/{{ issuer_connection_id }}
+GET {{ issuer_url }}/cloud-agent/connections/{{ issuer_connection_id }}
[Options]
retry: -1
HTTP 200
@@ -78,7 +78,7 @@ HTTP 200
jsonpath "$.state" == "ConnectionResponseSent"
# Wait for invitee connection status
-GET {{ holder_url }}/prism-agent/connections/{{ holder_connection_id }}
+GET {{ holder_url }}/cloud-agent/connections/{{ holder_connection_id }}
[Options]
retry: -1
HTTP 200
@@ -89,7 +89,7 @@ jsonpath "$.state" == "ConnectionResponseReceived"
# Issuance
##############################
# Issuer create credential offer
-POST {{ issuer_url }}/prism-agent/issue-credentials/credential-offers
+POST {{ issuer_url }}/cloud-agent/issue-credentials/credential-offers
{
"claims": {
"emailAddress": "alice@wonderland.com",
@@ -106,7 +106,7 @@ issuer_cred_record_id: jsonpath "$.recordId"
didcomm_issuing_thid: jsonpath "$.thid"
# Holder wait for OfferReceived state
-GET {{ holder_url }}/prism-agent/issue-credentials/records
+GET {{ holder_url }}/cloud-agent/issue-credentials/records
[QueryStringParams]
thid: {{ didcomm_issuing_thid }}
[Options]
@@ -118,14 +118,14 @@ jsonpath "$.contents[0].protocolState" == "OfferReceived"
holder_cred_record_id: jsonpath "$.contents[0].recordId"
# Holder accept a credential-offer
-POST {{ holder_url }}/prism-agent/issue-credentials/records/{{ holder_cred_record_id }}/accept-offer
+POST {{ holder_url }}/cloud-agent/issue-credentials/records/{{ holder_cred_record_id }}/accept-offer
{
"subjectId": "{{ holder_did }}"
}
HTTP 200
# Holder wait for CredentialReceived state
-GET {{ holder_url }}/prism-agent/issue-credentials/records/{{ holder_cred_record_id }}
+GET {{ holder_url }}/cloud-agent/issue-credentials/records/{{ holder_cred_record_id }}
[Options]
retry: -1
HTTP 200
@@ -133,7 +133,7 @@ HTTP 200
jsonpath "$.protocolState" == "CredentialReceived"
# Issuer wait for CredentialSent state
-GET {{ issuer_url }}/prism-agent/issue-credentials/records/{{ issuer_cred_record_id }}
+GET {{ issuer_url }}/cloud-agent/issue-credentials/records/{{ issuer_cred_record_id }}
[Options]
retry: -1
HTTP 200
@@ -144,7 +144,7 @@ jsonpath "$.protocolState" == "CredentialSent"
# Presentation Connection
##############################
# Inviter create connection
-POST {{ verifier_url }}/prism-agent/connections
+POST {{ verifier_url }}/cloud-agent/connections
{
"label": "My Connection"
}
@@ -154,7 +154,7 @@ raw_invitation: jsonpath "$.invitation.invitationUrl" regex ".*_oob=(.*)$"
verifier_connection_id: jsonpath "$.connectionId"
# Invitee accept connection
-POST {{ holder_url }}/prism-agent/connection-invitations
+POST {{ holder_url }}/cloud-agent/connection-invitations
{
"invitation": "{{ raw_invitation }}"
}
@@ -163,7 +163,7 @@ HTTP 200
holder_connection_id: jsonpath "$.connectionId"
# Wait for inviter connection status
-GET {{ verifier_url }}/prism-agent/connections/{{ verifier_connection_id }}
+GET {{ verifier_url }}/cloud-agent/connections/{{ verifier_connection_id }}
[Options]
retry: -1
HTTP 200
@@ -171,7 +171,7 @@ HTTP 200
jsonpath "$.state" == "ConnectionResponseSent"
# Wait for invitee connection status
-GET {{ holder_url }}/prism-agent/connections/{{ holder_connection_id }}
+GET {{ holder_url }}/cloud-agent/connections/{{ holder_connection_id }}
[Options]
retry: -1
HTTP 200
@@ -182,7 +182,7 @@ jsonpath "$.state" == "ConnectionResponseReceived"
# Presentation
##############################
# Verifier create presentation request
-POST {{ verifier_url }}/prism-agent/present-proof/presentations
+POST {{ verifier_url }}/cloud-agent/present-proof/presentations
{
"connectionId": "{{ verifier_connection_id }}",
"proofs":[],
@@ -197,7 +197,7 @@ verifier_presentation_id: jsonpath "$.presentationId"
didcomm_presentation_thid: jsonpath "$.thid"
# Holder wait for RequestReceived state
-GET {{ holder_url }}/prism-agent/present-proof/presentations
+GET {{ holder_url }}/cloud-agent/present-proof/presentations
[QueryStringParams]
thid: {{ didcomm_presentation_thid }}
[Options]
@@ -209,7 +209,7 @@ jsonpath "$.contents[0].status" == "RequestReceived"
holder_presentation_id: jsonpath "$.contents[0].presentationId"
# Holder accept presentation request
-PATCH {{ holder_url }}/prism-agent/present-proof/presentations/{{ holder_presentation_id }}
+PATCH {{ holder_url }}/cloud-agent/present-proof/presentations/{{ holder_presentation_id }}
{
"action": "request-accept",
"proofId": ["{{ holder_cred_record_id }}"]
@@ -217,7 +217,7 @@ PATCH {{ holder_url }}/prism-agent/present-proof/presentations/{{ holder_present
HTTP 200
# Holder wait for PresentationSent state
-GET {{ holder_url }}/prism-agent/present-proof/presentations
+GET {{ holder_url }}/cloud-agent/present-proof/presentations
[QueryStringParams]
thid: {{ didcomm_presentation_thid }}
[Options]
@@ -227,7 +227,7 @@ HTTP 200
jsonpath "$.contents[0].status" == "PresentationSent"
# Verfiier wait for PresentationVerified state
-GET {{ verifier_url }}/prism-agent/present-proof/presentations
+GET {{ verifier_url }}/cloud-agent/present-proof/presentations
[QueryStringParams]
thid: {{ didcomm_presentation_thid }}
[Options]
diff --git a/examples/st-multi/tests/local b/examples/st-multi/hurl/local
similarity index 100%
rename from examples/st-multi/tests/local
rename to examples/st-multi/hurl/local
diff --git a/examples/st-oid4vci/README.md b/examples/st-oid4vci/README.md
index 85b357d0a3..956da6a0db 100644
--- a/examples/st-oid4vci/README.md
+++ b/examples/st-oid4vci/README.md
@@ -3,24 +3,6 @@
## Prerequisites
- Docker installed v2.24.0 or later
-- Python 3 with the following packages installed
- - [requests](https://pypi.org/project/requests/)
- - [pyjwt](https://pyjwt.readthedocs.io/en/stable/)
- - [cryptography](https://cryptography.io/en/latest/)
-- Virtual environment (optional)
-
-Example of the script to install the required packages in a virtual environment:
-```shell
-python -m venv {path-to-the-project-dir}/identus-cloud-agent/examples/st-oid4vci/python-env
-source {path-to-the-project-dir}/identus-cloud-agent/examples/st-oid4vci/python-env/bin/activate
-pip install requests pyjwt cryptography
-```
-
-- the latest Cloud Agent image is built and available in the local Docker registry
-
-```shell
-sbt docker:publishLocal
-```
### 1. Spin up the agent stack with pre-configured Keycloak
@@ -32,10 +14,16 @@ The Keycloak UI is available at `http://localhost:9980` and the admin username i
### 2. Run the issuance demo script
+Build the demo application and run it using
+
```bash
-python demo.py
+docker build -t identus-oid4vci-demo:latest ./demo
+docker run --network -it identus-oid4vci-demo:latest
```
+where `NETWORK_NAME` is the docker-compose network name from agent stack.
+By default, this value should be `st-oid4vci_default`.
+
- 2.1 Follow the instructions in the terminal. The holder will then be asked to log in via a browser
- 2.2 Enter the username `alice` and password `1234` to log in
- 2.3 Grant access for the scopes displayed on the consent UI
diff --git a/examples/st-oid4vci/compose.yaml b/examples/st-oid4vci/compose.yaml
index dde0a828ff..adc542a455 100644
--- a/examples/st-oid4vci/compose.yaml
+++ b/examples/st-oid4vci/compose.yaml
@@ -5,7 +5,7 @@ configs:
handle_path /didcomm* {
reverse_proxy agent-issuer:8090
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy agent-issuer:8085
}
handle_path /keycloak* {
@@ -39,12 +39,12 @@ services:
POLLUX_DB_PASSWORD: postgres
POLLUX_DB_PORT: '5432'
POLLUX_DB_USER: postgres
- POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-issuer:8080/prism-agent
+ POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-issuer:8080/cloud-agent
PRISM_NODE_HOST: node
PRISM_NODE_PORT: '50053'
- REST_SERVICE_URL: http://caddy-issuer:8080/prism-agent
+ REST_SERVICE_URL: http://caddy-issuer:8080/cloud-agent
SECRET_STORAGE_BACKEND: postgres
- image: ghcr.io/hyperledger/identus-cloud-agent:1.36.1-SNAPSHOT
+ image: ghcr.io/hyperledger/identus-cloud-agent:1.38.0
restart: always
caddy-issuer:
configs:
@@ -102,10 +102,10 @@ services:
- --hostname-url=http://localhost:9980
- --hostname-admin-url=http://localhost:9980
environment:
- IDENTUS_URL: http://caddy-issuer:8080/prism-agent
+ IDENTUS_URL: http://caddy-issuer:8080/cloud-agent
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
- image: ghcr.io/hyperledger/identus-keycloak-plugins:0.1.0
+ image: ghcr.io/hyperledger/identus-keycloak-plugins:0.2.0
ports:
- 9980:8080
restart: always
diff --git a/examples/st-oid4vci/demo/Dockerfile b/examples/st-oid4vci/demo/Dockerfile
new file mode 100644
index 0000000000..b68166517b
--- /dev/null
+++ b/examples/st-oid4vci/demo/Dockerfile
@@ -0,0 +1,6 @@
+FROM python:3.12
+WORKDIR /workspace
+COPY requirements.txt /workspace/requirements.txt
+RUN pip install -r requirements.txt
+COPY . /workspace
+CMD ["python", "main.py"]
diff --git a/examples/st-oid4vci/demo.py b/examples/st-oid4vci/demo/main.py
similarity index 90%
rename from examples/st-oid4vci/demo.py
rename to examples/st-oid4vci/demo/main.py
index 0dd12acf4d..b48239a161 100755
--- a/examples/st-oid4vci/demo.py
+++ b/examples/st-oid4vci/demo/main.py
@@ -6,14 +6,14 @@
import requests
from cryptography.hazmat.primitives.asymmetric import ec
-MOCKSERVER_URL = "http://localhost:7777"
+MOCKSERVER_URL = "http://mockserver:1080"
LOGIN_REDIRECT_URL = "http://localhost:7777/cb"
-AGENT_URL = "http://localhost:8080/prism-agent"
+AGENT_URL = "http://caddy-issuer:8080/cloud-agent"
CREDENTIAL_ISSUER = None
CREDENTIAL_ISSUER_DID = None
CREDENTIAL_CONFIGURATION_ID = "UniversityDegreeCredential"
-AUTHORIZATION_SERVER = "http://localhost:9980/realms/students"
+AUTHORIZATION_SERVER = "http://external-keycloak-issuer:8080/realms/students"
ALICE_CLIENT_ID = "alice-wallet"
@@ -24,10 +24,11 @@
def prepare_mock_server():
- # reset mock server
- requests.put(f"{MOCKSERVER_URL}/mockserver/reset")
+ """
+ Prepare mock server for authorization redirect from front-end channel
+ """
- # mock wallet authorization callback endpoint
+ requests.put(f"{MOCKSERVER_URL}/mockserver/reset")
requests.put(
f"{MOCKSERVER_URL}/mockserver/expectation",
json={
@@ -41,7 +42,16 @@ def prepare_mock_server():
def prepare_issuer():
- # prepare issuging DID
+ """
+ Prepare an oid4vci issuer
+ 1. Create issuing DID
+ 2. Publish issuing DID
+ 3. Create credential schema
+ 4. Create oid4vci issuer
+ 5. Create oid4vci credential configuration from schema
+ """
+
+ # 1. Create issuing DID
dids = requests.get(f"{AGENT_URL}/did-registrar/dids").json()["contents"]
if len(dids) == 0:
requests.post(
@@ -55,6 +65,7 @@ def prepare_issuer():
)
dids = requests.get(f"{AGENT_URL}/did-registrar/dids").json()["contents"]
+ # 2. Publish issuing DID
issuer_did = dids[0]
while issuer_did["status"] != "PUBLISHED":
time.sleep(2)
@@ -72,7 +83,7 @@ def prepare_issuer():
global CREDENTIAL_ISSUER_DID
CREDENTIAL_ISSUER_DID = canonical_did
- # prepare schema
+ # 3. Create credential schema
schema = requests.post(
f"{AGENT_URL}/schema-registry/schemas",
json={
@@ -97,7 +108,7 @@ def prepare_issuer():
).json()
schema_guid = schema["guid"]
- # prepare issuer
+ # 4. Create oid4vci issuer
credential_issuer = requests.post(
f"{AGENT_URL}/oid4vci/issuers",
json={
@@ -112,14 +123,13 @@ def prepare_issuer():
global CREDENTIAL_ISSUER
CREDENTIAL_ISSUER = f"{AGENT_URL}/oid4vci/issuers/{issuer_id}"
- # prepare credential configuration
+ # 5. Create oid4vci credential configuration from schema
cred_config = requests.post(
f"{CREDENTIAL_ISSUER}/credential-configurations",
json={
"configurationId": CREDENTIAL_CONFIGURATION_ID,
"format": "jwt_vc_json",
- # TODO: align docker host URL
- "schemaId": f"http://localhost:8085/schema-registry/schemas/{schema_guid}/schema",
+ "schemaId": f"{AGENT_URL}/schema-registry/schemas/{schema_guid}/schema",
},
).json()
@@ -137,19 +147,8 @@ def issuer_create_credential_offer(claims):
def holder_get_issuer_metadata(credential_issuer: str):
- # FIXME: override this just to make url reachable from host
- def override_host(url):
- return url.replace("http://caddy-issuer:8080/prism-agent", AGENT_URL)
-
- metadata_url = override_host(
- f"{credential_issuer}/.well-known/openid-credential-issuer"
- )
+ metadata_url = f"{credential_issuer}/.well-known/openid-credential-issuer"
response = requests.get(metadata_url).json()
- response["credential_endpoint"] = override_host(response["credential_endpoint"])
- response["credential_issuer"] = override_host(response["credential_issuer"])
- response["authorization_servers"] = [
- AUTHORIZATION_SERVER for s in response["authorization_servers"]
- ]
return response
@@ -157,7 +156,6 @@ def holder_get_issuer_as_metadata(authorization_server: str):
metadata_url = f"{authorization_server}/.well-known/openid-configuration"
response = requests.get(metadata_url)
metadata = response.json()
- print(f"Metadata: {metadata}")
return metadata
@@ -293,6 +291,7 @@ def holder_get_credential(credential_endpoint: str, token_response):
issuer_as_token_endpoint = issuer_as_metadata["token_endpoint"]
issuer_as_authorization_endpoint = issuer_as_metadata["authorization_endpoint"]
print("\n::::: Issuer Authorization Server Metadata :::::")
+ print(json.dumps(issuer_as_metadata, indent=2))
print(f"issuer_as_auth_endpoint: {issuer_as_authorization_endpoint}")
print(f"issuer_as_token_endpoint: {issuer_as_token_endpoint}")
input("\nEnter to continue ...")
diff --git a/examples/st-oid4vci/demo/requirements.txt b/examples/st-oid4vci/demo/requirements.txt
new file mode 100644
index 0000000000..791e544886
--- /dev/null
+++ b/examples/st-oid4vci/demo/requirements.txt
@@ -0,0 +1,11 @@
+certifi==2024.7.4
+cffi==1.16.0
+charset-normalizer==3.3.2
+cryptography==42.0.8
+idna==3.7
+pycparser==2.22
+PyJWT==2.8.0
+requests==2.32.3
+setuptools==70.3.0
+urllib3==2.2.2
+wheel==0.43.0
diff --git a/examples/st-vault/README.md b/examples/st-vault/README.md
new file mode 100644
index 0000000000..782bea7136
--- /dev/null
+++ b/examples/st-vault/README.md
@@ -0,0 +1,10 @@
+## Configuration
+
+| Exposed Service | Description |
+|------------------------------|---------------------------|
+| `localhost:8080/cloud-agent` | Single-tenant Cloud Agent |
+| `localhost:8200` | Vault |
+
+__Vault__
+
+- Root token `admin`
diff --git a/examples/st-vault/compose.yaml b/examples/st-vault/compose.yaml
index 911a4a1f42..fdbb1909e7 100644
--- a/examples/st-vault/compose.yaml
+++ b/examples/st-vault/compose.yaml
@@ -5,7 +5,7 @@ configs:
handle_path /didcomm* {
reverse_proxy agent-issuer:8090
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy agent-issuer:8085
}
handle_path /keycloak* {
@@ -39,14 +39,14 @@ services:
POLLUX_DB_PASSWORD: postgres
POLLUX_DB_PORT: '5432'
POLLUX_DB_USER: postgres
- POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-issuer:8080/prism-agent
+ POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-issuer:8080/cloud-agent
PRISM_NODE_HOST: node
PRISM_NODE_PORT: '50053'
- REST_SERVICE_URL: http://caddy-issuer:8080/prism-agent
+ REST_SERVICE_URL: http://caddy-issuer:8080/cloud-agent
SECRET_STORAGE_BACKEND: vault
VAULT_ADDR: http://vault-issuer:8200
VAULT_TOKEN: admin
- image: ghcr.io/hyperledger/identus-cloud-agent:1.36.1-SNAPSHOT
+ image: ghcr.io/hyperledger/identus-cloud-agent:1.38.0
restart: always
caddy-issuer:
configs:
diff --git a/examples/st/README.md b/examples/st/README.md
new file mode 100644
index 0000000000..1575a89961
--- /dev/null
+++ b/examples/st/README.md
@@ -0,0 +1,5 @@
+## Configuration
+
+| Exposed Service | Description |
+|------------------------------|---------------------------|
+| `localhost:8080/cloud-agent` | Single-tenant Cloud Agent |
diff --git a/examples/st/compose.yaml b/examples/st/compose.yaml
index 167a2a199e..0b2b1dc541 100644
--- a/examples/st/compose.yaml
+++ b/examples/st/compose.yaml
@@ -5,7 +5,7 @@ configs:
handle_path /didcomm* {
reverse_proxy agent-issuer:8090
}
- handle_path /prism-agent* {
+ handle_path /cloud-agent* {
reverse_proxy agent-issuer:8085
}
handle_path /keycloak* {
@@ -39,12 +39,12 @@ services:
POLLUX_DB_PASSWORD: postgres
POLLUX_DB_PORT: '5432'
POLLUX_DB_USER: postgres
- POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-issuer:8080/prism-agent
+ POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL: http://caddy-issuer:8080/cloud-agent
PRISM_NODE_HOST: node
PRISM_NODE_PORT: '50053'
- REST_SERVICE_URL: http://caddy-issuer:8080/prism-agent
+ REST_SERVICE_URL: http://caddy-issuer:8080/cloud-agent
SECRET_STORAGE_BACKEND: postgres
- image: ghcr.io/hyperledger/identus-cloud-agent:1.36.1-SNAPSHOT
+ image: ghcr.io/hyperledger/identus-cloud-agent:1.38.0
restart: always
caddy-issuer:
configs:
diff --git a/infrastructure/charts/agent/Chart.yaml b/infrastructure/charts/agent/Chart.yaml
index 15c98c9c8b..0445277f57 100644
--- a/infrastructure/charts/agent/Chart.yaml
+++ b/infrastructure/charts/agent/Chart.yaml
@@ -13,12 +13,12 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 1.37.0
+version: 1.38.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
-appVersion: 1.37.0
+appVersion: 1.38.0
dependencies:
- name: vault
version: 0.24.1
diff --git a/infrastructure/charts/cloud-agent-1.38.0.tgz b/infrastructure/charts/cloud-agent-1.38.0.tgz
new file mode 100644
index 0000000000..0135b9a6e5
Binary files /dev/null and b/infrastructure/charts/cloud-agent-1.38.0.tgz differ
diff --git a/infrastructure/charts/index.yaml b/infrastructure/charts/index.yaml
index 71980545cb..97e5d96623 100644
--- a/infrastructure/charts/index.yaml
+++ b/infrastructure/charts/index.yaml
@@ -1,9 +1,27 @@
apiVersion: v1
entries:
cloud-agent:
+ - apiVersion: v2
+ appVersion: 1.38.0
+ created: "2024-07-15T12:29:39.496521751Z"
+ dependencies:
+ - name: vault
+ repository: https://helm.releases.hashicorp.com
+ version: 0.24.1
+ - condition: keycloak.enabled
+ name: keycloak
+ repository: https://charts.bitnami.com/bitnami
+ version: 17.2.0
+ description: A Helm chart for deploying cloud-agent
+ digest: 3b1bd1dd295c3361dba8e0c4ec58994ed76a12bea5484938aa77805192fd452b
+ name: cloud-agent
+ type: application
+ urls:
+ - https://raw.githubusercontent.com/hyperledger/identus-cloud-agent/main/infrastructure/charts/cloud-agent-1.38.0.tgz
+ version: 1.38.0
- apiVersion: v2
appVersion: 1.37.0
- created: "2024-07-01T09:13:50.234308304Z"
+ created: "2024-07-15T12:29:39.487203135Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -21,7 +39,7 @@ entries:
version: 1.37.0
- apiVersion: v2
appVersion: 1.36.1
- created: "2024-07-01T09:13:50.223902191Z"
+ created: "2024-07-15T12:29:39.476683729Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -39,7 +57,7 @@ entries:
version: 1.36.1
- apiVersion: v2
appVersion: 1.36.0
- created: "2024-07-01T09:13:50.214857923Z"
+ created: "2024-07-15T12:29:39.4664606Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -57,7 +75,7 @@ entries:
version: 1.36.0
- apiVersion: v2
appVersion: 1.35.1
- created: "2024-07-01T09:13:50.204930147Z"
+ created: "2024-07-15T12:29:39.456221677Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -75,7 +93,7 @@ entries:
version: 1.35.1
- apiVersion: v2
appVersion: 1.35.0
- created: "2024-07-01T09:13:50.195078852Z"
+ created: "2024-07-15T12:29:39.445760067Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -93,7 +111,7 @@ entries:
version: 1.35.0
- apiVersion: v2
appVersion: 1.34.0
- created: "2024-07-01T09:13:50.185271959Z"
+ created: "2024-07-15T12:29:39.435700056Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -112,7 +130,7 @@ entries:
prism-agent:
- apiVersion: v2
appVersion: 1.33.1
- created: "2024-07-01T09:13:50.461034123Z"
+ created: "2024-07-15T12:29:39.731773224Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -130,7 +148,7 @@ entries:
version: 1.33.1
- apiVersion: v2
appVersion: 1.33.0
- created: "2024-07-01T09:13:50.450096134Z"
+ created: "2024-07-15T12:29:39.721534328Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -148,7 +166,7 @@ entries:
version: 1.33.0
- apiVersion: v2
appVersion: 1.32.1
- created: "2024-07-01T09:13:50.440440387Z"
+ created: "2024-07-15T12:29:39.710669925Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -166,7 +184,7 @@ entries:
version: 1.32.1
- apiVersion: v2
appVersion: 1.32.0
- created: "2024-07-01T09:13:50.429229489Z"
+ created: "2024-07-15T12:29:39.700959357Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -184,7 +202,7 @@ entries:
version: 1.32.0
- apiVersion: v2
appVersion: 1.31.0
- created: "2024-07-01T09:13:50.420150878Z"
+ created: "2024-07-15T12:29:39.689846117Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -202,7 +220,7 @@ entries:
version: 1.31.0
- apiVersion: v2
appVersion: 1.30.1
- created: "2024-07-01T09:13:50.40938063Z"
+ created: "2024-07-15T12:29:39.679147948Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -220,7 +238,7 @@ entries:
version: 1.30.1
- apiVersion: v2
appVersion: 1.30.0
- created: "2024-07-01T09:13:50.398758055Z"
+ created: "2024-07-15T12:29:39.669134164Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -238,7 +256,7 @@ entries:
version: 1.30.0
- apiVersion: v2
appVersion: 1.29.0
- created: "2024-07-01T09:13:50.388282635Z"
+ created: "2024-07-15T12:29:39.657444145Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -256,7 +274,7 @@ entries:
version: 1.29.0
- apiVersion: v2
appVersion: 1.28.0
- created: "2024-07-01T09:13:50.378802886Z"
+ created: "2024-07-15T12:29:39.64729637Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -274,7 +292,7 @@ entries:
version: 1.28.0
- apiVersion: v2
appVersion: 1.27.0
- created: "2024-07-01T09:13:50.368443349Z"
+ created: "2024-07-15T12:29:39.636142885Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -292,7 +310,7 @@ entries:
version: 1.27.0
- apiVersion: v2
appVersion: 1.26.0
- created: "2024-07-01T09:13:50.358343839Z"
+ created: "2024-07-15T12:29:39.625580446Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -310,7 +328,7 @@ entries:
version: 1.26.0
- apiVersion: v2
appVersion: 1.25.0
- created: "2024-07-01T09:13:50.348875871Z"
+ created: "2024-07-15T12:29:39.615551643Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -328,7 +346,7 @@ entries:
version: 1.25.0
- apiVersion: v2
appVersion: 1.24.0
- created: "2024-07-01T09:13:50.339737128Z"
+ created: "2024-07-15T12:29:39.604404582Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -346,7 +364,7 @@ entries:
version: 1.24.0
- apiVersion: v2
appVersion: 1.23.0
- created: "2024-07-01T09:13:50.328935996Z"
+ created: "2024-07-15T12:29:39.593553755Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -364,7 +382,7 @@ entries:
version: 1.23.0
- apiVersion: v2
appVersion: 1.22.0
- created: "2024-07-01T09:13:50.318312167Z"
+ created: "2024-07-15T12:29:39.583832686Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -382,7 +400,7 @@ entries:
version: 1.22.0
- apiVersion: v2
appVersion: 1.21.1
- created: "2024-07-01T09:13:50.307947402Z"
+ created: "2024-07-15T12:29:39.573324718Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -400,7 +418,7 @@ entries:
version: 1.21.1
- apiVersion: v2
appVersion: 1.21.0
- created: "2024-07-01T09:13:50.298351541Z"
+ created: "2024-07-15T12:29:39.563170515Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -418,7 +436,7 @@ entries:
version: 1.21.0
- apiVersion: v2
appVersion: 1.20.1
- created: "2024-07-01T09:13:50.289340676Z"
+ created: "2024-07-15T12:29:39.553469417Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -436,7 +454,7 @@ entries:
version: 1.20.1
- apiVersion: v2
appVersion: 1.20.0
- created: "2024-07-01T09:13:50.279663615Z"
+ created: "2024-07-15T12:29:39.543477488Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -454,7 +472,7 @@ entries:
version: 1.20.0
- apiVersion: v2
appVersion: 1.19.1
- created: "2024-07-01T09:13:50.269885862Z"
+ created: "2024-07-15T12:29:39.53342354Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -472,7 +490,7 @@ entries:
version: 1.19.1
- apiVersion: v2
appVersion: 1.19.0
- created: "2024-07-01T09:13:50.259598085Z"
+ created: "2024-07-15T12:29:39.522721417Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -490,7 +508,7 @@ entries:
version: 1.19.0
- apiVersion: v2
appVersion: 1.18.0
- created: "2024-07-01T09:13:50.249658531Z"
+ created: "2024-07-15T12:29:39.512436124Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -504,7 +522,7 @@ entries:
version: 1.18.0
- apiVersion: v2
appVersion: 1.17.0
- created: "2024-07-01T09:13:50.246850237Z"
+ created: "2024-07-15T12:29:39.509538689Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -518,7 +536,7 @@ entries:
version: 1.17.0
- apiVersion: v2
appVersion: 1.16.4
- created: "2024-07-01T09:13:50.242960911Z"
+ created: "2024-07-15T12:29:39.505838077Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -532,7 +550,7 @@ entries:
version: 1.16.4
- apiVersion: v2
appVersion: 1.16.3
- created: "2024-07-01T09:13:50.239921005Z"
+ created: "2024-07-15T12:29:39.502999021Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -546,7 +564,7 @@ entries:
version: 1.16.3
- apiVersion: v2
appVersion: 1.16.2
- created: "2024-07-01T09:13:50.237164297Z"
+ created: "2024-07-15T12:29:39.50018937Z"
dependencies:
- name: vault
repository: https://helm.releases.hashicorp.com
@@ -558,4 +576,4 @@ entries:
urls:
- https://raw.githubusercontent.com/hyperledger/identus-cloud-agent/main/infrastructure/charts/prism-agent-1.16.2.tgz
version: 1.16.2
-generated: "2024-07-01T09:13:50.175364732Z"
+generated: "2024-07-15T12:29:39.425487877Z"
diff --git a/infrastructure/local/.env b/infrastructure/local/.env
index d151c7359e..c2fe0a6b2a 100644
--- a/infrastructure/local/.env
+++ b/infrastructure/local/.env
@@ -1,3 +1,3 @@
-AGENT_VERSION=1.37.0
+AGENT_VERSION=1.38.0
PRISM_NODE_VERSION=2.3.0
VAULT_DEV_ROOT_TOKEN_ID=root
diff --git a/mercury/models/src/main/scala/org/hyperledger/identus/mercury/model/error/package.scala b/mercury/models/src/main/scala/org/hyperledger/identus/mercury/model/error/package.scala
index 4ea83a1357..9886e80901 100644
--- a/mercury/models/src/main/scala/org/hyperledger/identus/mercury/model/error/package.scala
+++ b/mercury/models/src/main/scala/org/hyperledger/identus/mercury/model/error/package.scala
@@ -1,6 +1,6 @@
package org.hyperledger.identus.mercury.model
-import org.hyperledger.identus.shared.models._
+import org.hyperledger.identus.shared.models.*
package object error {
diff --git a/package-lock.json b/package-lock.json
index b2b54b9e17..03c7c280fc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "identus-cloud-agent",
- "version": "1.37.0",
+ "version": "1.38.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "identus-cloud-agent",
- "version": "1.37.0",
+ "version": "1.38.0",
"devDependencies": {
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
diff --git a/package.json b/package.json
index 84f56ed851..3b9750b32f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "identus-cloud-agent",
- "version": "1.37.0",
+ "version": "1.38.0",
"engines": {
"node": ">=16.13.0"
},
diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepository.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepository.scala
index 789a25473d..fffd057a59 100644
--- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepository.scala
+++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepository.scala
@@ -74,5 +74,5 @@ trait PresentationRepository {
def updateAfterFail(
recordId: DidCommID,
failReason: Option[Failure]
- ): URIO[WalletAccessContext, Unit]
+ ): UIO[Unit]
}
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 6038278a1c..7bdc865887 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
@@ -169,6 +169,6 @@ trait PresentationService {
def reportProcessingFailure(
recordId: DidCommID,
failReason: Option[Failure]
- ): ZIO[WalletAccessContext, PresentationError, Unit]
+ ): IO[PresentationError, Unit]
}
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 ff245f5aea..d7da9a6594 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
@@ -1096,14 +1096,11 @@ private class PresentationServiceImpl(
} yield result
}
- def reportProcessingFailure(
+ override def reportProcessingFailure(
recordId: DidCommID,
failReason: Option[Failure]
- ): ZIO[WalletAccessContext, PresentationError, Unit] =
- for {
- _ <- getRecord(recordId)
- result <- presentationRepository.updateAfterFail(recordId, failReason)
- } yield result
+ ): IO[PresentationError, Unit] =
+ presentationRepository.updateAfterFail(recordId, failReason)
private def getRecordFromThreadId(
thid: DidCommID
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 c8558054c0..696b4dd5c3 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
@@ -281,7 +281,7 @@ class PresentationServiceNotifier(
override def reportProcessingFailure(
recordId: DidCommID,
failReason: Option[Failure]
- ): ZIO[WalletAccessContext, PresentationError, Unit] = svc.reportProcessingFailure(recordId, failReason)
+ ): IO[PresentationError, Unit] = svc.reportProcessingFailure(recordId, failReason)
}
object PresentationServiceNotifier {
diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialDefinitionRepositoryInMemory.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/CredentialDefinitionRepositoryInMemory.scala
similarity index 100%
rename from pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialDefinitionRepositoryInMemory.scala
rename to pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/CredentialDefinitionRepositoryInMemory.scala
diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialRepositoryInMemory.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/CredentialRepositoryInMemory.scala
similarity index 100%
rename from pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialRepositoryInMemory.scala
rename to pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/CredentialRepositoryInMemory.scala
diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepositoryInMemory.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepositoryInMemory.scala
similarity index 100%
rename from pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepositoryInMemory.scala
rename to pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepositoryInMemory.scala
diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositoryInMemory.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositoryInMemory.scala
similarity index 87%
rename from pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositoryInMemory.scala
rename to pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositoryInMemory.scala
index 4f646f3bee..01d00ed4ea 100644
--- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositoryInMemory.scala
+++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/repository/PresentationRepositoryInMemory.scala
@@ -30,6 +30,17 @@ class PresentationRepositoryInMemory(
}(ZIO.succeed)
} yield walletRef
+ private def anyWalletStoreRefBy(
+ recordId: DidCommID
+ ): ZIO[Any, Nothing, Option[Ref[Map[DidCommID, PresentationRecord]]]] = {
+ for {
+ refs <- walletRefs.get
+ // walletsNoRef <- ZIO.foreach(refs)({ case (wID, ref) => ref.get.map(r => (wID, r)) })
+ tmp <- ZIO.foreach(refs)({ case (wID, ref) => ref.get.map(r => (ref, r)) })
+ walletRef = tmp.find(e => e._2.keySet.contains(recordId)).map(_._1)
+ } yield walletRef
+ }
+
override def createPresentationRecord(record: PresentationRecord): URIO[WalletAccessContext, Unit] = {
val result =
for {
@@ -324,35 +335,40 @@ class PresentationRepositoryInMemory(
result.ensureOneAffectedRowOrDie
}
+ // def updateAfterFailX(
+ // recordId: DidCommID,
+ // failReason: Option[Failure]
+ // ): URIO[WalletAccessContext, Unit] = {
override def updateAfterFail(
recordId: DidCommID,
failReason: Option[Failure]
- ): URIO[WalletAccessContext, Unit] = {
- val result =
- for {
- storeRef <- walletStoreRef
- maybeRecord <- findPresentationRecord(recordId)
- count <- maybeRecord
- .map(record =>
- for {
- _ <- storeRef.update(r =>
- r.updated(
- recordId,
- record.copy(
- metaRetries = math.max(0, record.metaRetries - 1),
- metaNextRetry =
- if (record.metaRetries - 1 <= 0) None
- else Some(Instant.now().plusSeconds(60)), // TODO exponention time
- metaLastFailure = failReason
+ ): UIO[Unit] =
+ anyWalletStoreRefBy(recordId).flatMap { mStoreRef =>
+ mStoreRef match
+ case None => ZIO.succeed(0)
+ case Some(storeRef) =>
+ for {
+ maybeRecord <- storeRef.get.map(store => store.get(recordId))
+ count <- maybeRecord
+ .map(record =>
+ for {
+ _ <- storeRef.update(r =>
+ r.updated(
+ recordId,
+ record.copy(
+ metaRetries = math.max(0, record.metaRetries - 1),
+ metaNextRetry =
+ if (record.metaRetries - 1 <= 0) None
+ else Some(Instant.now().plusSeconds(60)), // TODO exponention time
+ metaLastFailure = failReason
+ )
+ )
)
- )
+ } yield 1
)
- } yield 1
- )
- .getOrElse(ZIO.succeed(0))
- } yield count
- result.ensureOneAffectedRowOrDie
- }
+ .getOrElse(ZIO.succeed(0))
+ } yield count
+ }.ensureOneAffectedRowOrDie
}
object PresentationRepositoryInMemory {
diff --git a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcCredentialRepository.scala b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcCredentialRepository.scala
index 2cab203276..554d6c4852 100644
--- a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcCredentialRepository.scala
+++ b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcCredentialRepository.scala
@@ -548,7 +548,7 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[
.ensureOneAffectedRowOrDie
}
- def updateAfterFail(
+ override def updateAfterFail(
recordId: DidCommID,
failReason: Option[Failure]
): URIO[WalletAccessContext, Unit] = {
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 5d98bd1f23..57a22d2ee8 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
@@ -489,10 +489,10 @@ class JdbcPresentationRepository(
.ensureOneAffectedRowOrDie
}
- def updateAfterFail(
+ override def updateAfterFail(
recordId: DidCommID,
failReason: Option[Failure]
- ): URIO[WalletAccessContext, Unit] = {
+ ): UIO[Unit] = {
val cxnIO = sql"""
| UPDATE public.presentation_records
| SET
@@ -503,7 +503,7 @@ class JdbcPresentationRepository(
| id = $recordId
""".stripMargin.update
cxnIO.run
- .transactWallet(xa)
+ .transact(xb)
.orDie
.ensureOneAffectedRowOrDie
}
diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Proof.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Proof.scala
index 0ee6ffebbf..8bf6ab2086 100644
--- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Proof.scala
+++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/Proof.scala
@@ -2,7 +2,6 @@ package org.hyperledger.identus.pollux.vc.jwt
import cats.implicits.*
import com.nimbusds.jose.{JWSAlgorithm, JWSHeader, JWSObject, Payload}
-import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton
import com.nimbusds.jose.crypto.ECDSASigner
import com.nimbusds.jwt.SignedJWT
import io.circe.*
@@ -104,7 +103,6 @@ object EcdsaSecp256k1Signature2019ProofGenerator {
}
object EddsaJcs2022ProofGenerator {
- private val provider = BouncyCastleProviderSingleton.getInstance
private val ed25519MultiBaseHeader: Array[Byte] = Array(-19, 1) // 0xed01
private def pkToMultiKey(pk: Ed25519PublicKey): MultiKey = {
diff --git a/project/build.properties b/project/build.properties
index 081fdbbc76..ee4c672cd0 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version=1.10.0
+sbt.version=1.10.1
diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala
index 7a7c800ac9..c2ea5dc53f 100644
--- a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala
+++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala
@@ -1,7 +1,7 @@
package org.hyperledger.identus.shared.models
import zio.{URIO, ZIO}
-import zio.json._
+import zio.json.*
trait Failure {
def namespace: String
diff --git a/tests/integration-tests/build.gradle.kts b/tests/integration-tests/build.gradle.kts
index 00ba4ab3cd..1f56e07876 100644
--- a/tests/integration-tests/build.gradle.kts
+++ b/tests/integration-tests/build.gradle.kts
@@ -45,6 +45,10 @@ dependencies {
testImplementation("com.nimbusds:nimbus-jose-jwt:9.40")
testImplementation("org.bouncycastle:bcprov-jdk18on:1.78.1")
testImplementation("com.google.crypto.tink:tink:1.13.0")
+ testImplementation("io.iohk.atala.prism.apollo:apollo-jvm:1.3.4")
+ // OID4VCI
+ testImplementation("org.htmlunit:htmlunit:4.3.0")
+ testImplementation("eu.europa.ec.eudi:eudi-lib-jvm-openid4vci-kt:0.3.2")
}
serenity {
@@ -61,6 +65,12 @@ tasks.test {
finalizedBy("aggregate", "reports")
testLogging.showStandardStreams = true
systemProperty("cucumber.filter.tags", System.getProperty("cucumber.filter.tags"))
+ // Since the test runs on host and system-unter-test runs in containers,
+ // We need to make the test on host resolves host.docker.internal same as the containerized services,
+ // because some spec (e.g. OID4VCI) requires domain to be the same.
+ //
+ // The OID4VCI library does not allow mixing host.docker.internal and localhost
+ systemProperty("jdk.net.hosts.file", "hosts_test")
}
kotlin {
@@ -91,6 +101,7 @@ afterEvaluate {
systemProperty("PRISM_NODE_VERSION", System.getenv("PRISM_NODE_VERSION") ?: "")
systemProperty("AGENT_VERSION", System.getenv("AGENT_VERSION") ?: "")
systemProperty("cucumber.filter.tags", System.getProperty("cucumber.filter.tags"))
+ systemProperty("jdk.net.hosts.file", "hosts_test")
finalizedBy("aggregate", "reports")
outputs.upToDateWhen { false }
}
diff --git a/tests/integration-tests/hosts_test b/tests/integration-tests/hosts_test
new file mode 100644
index 0000000000..26590d4c39
--- /dev/null
+++ b/tests/integration-tests/hosts_test
@@ -0,0 +1,2 @@
+127.0.0.1 localhost
+127.0.0.1 host.docker.internal
\ No newline at end of file
diff --git a/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt b/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt
index 43c92230ed..4ea1481a90 100644
--- a/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt
+++ b/tests/integration-tests/src/test/kotlin/abilities/ListenToEvents.kt
@@ -14,6 +14,9 @@ import models.*
import net.serenitybdd.screenplay.Ability
import net.serenitybdd.screenplay.Actor
import net.serenitybdd.screenplay.HasTeardown
+import net.serenitybdd.screenplay.Question
+import org.hyperledger.identus.client.models.Connection
+import org.hyperledger.identus.client.models.IssueCredentialRecord
import java.net.URL
import java.time.OffsetDateTime
@@ -29,6 +32,7 @@ open class ListenToEvents(
var credentialEvents: MutableList = mutableListOf()
var presentationEvents: MutableList = mutableListOf()
var didEvents: MutableList = mutableListOf()
+ var authCodeCallbackEvents: MutableList> = mutableListOf()
private fun route(application: Application) {
application.routing {
@@ -46,6 +50,12 @@ open class ListenToEvents(
}
call.respond(HttpStatusCode.OK)
}
+ get("/auth-cb") {
+ val authCode = call.parameters["code"]!!
+ val state = call.parameters["state"]!!
+ authCodeCallbackEvents.add(Pair(authCode, state))
+ call.respond(HttpStatusCode.OK, "Login Successfully")
+ }
}
}
@@ -57,6 +67,42 @@ open class ListenToEvents(
fun with(actor: Actor): ListenToEvents {
return actor.abilityTo(ListenToEvents::class.java)
}
+
+ fun presentationProofStatus(actor: Actor): Question {
+ return Question.about("presentation status").answeredBy {
+ val proofEvent = with(actor).presentationEvents.lastOrNull {
+ it.data.thid == actor.recall("thid")
+ }
+ proofEvent?.data?.status
+ }
+ }
+
+ fun connectionState(actor: Actor): Question {
+ return Question.about("connection state").answeredBy {
+ val lastEvent = with(actor).connectionEvents.lastOrNull {
+ it.data.thid == actor.recall("connection").thid
+ }
+ lastEvent?.data?.state
+ }
+ }
+
+ fun credentialState(actor: Actor): Question {
+ return Question.about("credential state").answeredBy {
+ val credentialEvent = ListenToEvents.with(actor).credentialEvents.lastOrNull {
+ it.data.thid == actor.recall("thid")
+ }
+ credentialEvent?.data?.protocolState
+ }
+ }
+
+ fun didStatus(actor: Actor): Question {
+ return Question.about("did status").answeredBy {
+ val didEvent = ListenToEvents.with(actor).didEvents.lastOrNull {
+ it.data.did == actor.recall("shortFormDid")
+ }
+ didEvent?.data?.status
+ }
+ }
}
init {
diff --git a/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt b/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt
index dee1243f98..7dcc6ec6e3 100644
--- a/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt
+++ b/tests/integration-tests/src/test/kotlin/common/DidPurpose.kt
@@ -3,23 +3,30 @@ package common
import org.hyperledger.identus.client.models.*
enum class DidPurpose {
- EMPTY {
- override val publicKeys = emptyList()
- override val services = emptyList()
+ CUSTOM {
+ override val publicKeys = mutableListOf()
+ override val services = mutableListOf()
+ },
+ SD_JWT {
+ override val publicKeys = mutableListOf(
+ ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.ED25519),
+ ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.ED25519),
+ )
+ override val services = mutableListOf()
},
JWT {
- override val publicKeys = listOf(
+ override val publicKeys = mutableListOf(
ManagedDIDKeyTemplate("auth-1", Purpose.AUTHENTICATION, Curve.SECP256K1),
ManagedDIDKeyTemplate("auth-2", Purpose.AUTHENTICATION, Curve.ED25519),
ManagedDIDKeyTemplate("assertion-1", Purpose.ASSERTION_METHOD, Curve.SECP256K1),
)
- override val services = emptyList()
+ override val services = mutableListOf()
},
ANONCRED {
- override val publicKeys = emptyList()
- override val services = emptyList()
+ override val publicKeys = mutableListOf()
+ override val services = mutableListOf()
}, ;
- abstract val publicKeys: List
- abstract val services: List
+ abstract val publicKeys: MutableList
+ abstract val services: MutableList
}
diff --git a/tests/integration-tests/src/test/kotlin/config/services/Keycloak.kt b/tests/integration-tests/src/test/kotlin/config/services/Keycloak.kt
index fbe8c94602..d73a8814b5 100644
--- a/tests/integration-tests/src/test/kotlin/config/services/Keycloak.kt
+++ b/tests/integration-tests/src/test/kotlin/config/services/Keycloak.kt
@@ -15,17 +15,20 @@ import java.io.File
data class Keycloak(
@ConfigAlias("http_port") val httpPort: Int,
val realm: String = "atala-demo",
- @ConfigAlias("client_id") val clientId: String = "prism-agent",
- @ConfigAlias("client_secret") val clientSecret: String = "prism-agent-demo-secret",
+ @ConfigAlias("client_id") val clientId: String = "cloud-agent",
+ @ConfigAlias("client_secret") val clientSecret: String = "cloud-agent-secret",
@ConfigAlias("keep_running") override val keepRunning: Boolean = false,
@ConfigAlias("compose_file") val keycloakComposeFile: String = "src/test/resources/containers/keycloak.yml",
+ @ConfigAlias("logger_name") val loggerName: String = "keycloak",
@ConfigAlias("extra_envs") val extraEnvs: Map = emptyMap(),
+ @ConfigAlias("extra_clients") val extraClients: Map = emptyMap(),
+ @ConfigAlias("extra_scopes") val extraScopes: List = emptyList(),
) : ServiceBase() {
private val logger = Logger.get()
private val keycloakEnvConfig: Map = extraEnvs + mapOf(
"KEYCLOAK_HTTP_PORT" to httpPort.toString(),
)
- override val logServices: List = listOf("keycloak")
+ override val logServices: List = listOf(loggerName)
private val keycloakClientRoles: List = AgentRole.entries.map { it.roleName }
override val container: ComposeContainer =
ComposeContainer(File(keycloakComposeFile)).withEnv(keycloakEnvConfig)
@@ -43,8 +46,10 @@ data class Keycloak(
logger.info("Setting up Keycloak")
initRequestBuilder()
createRealm()
- createClient()
+ createAgentClient()
+ createPublicClients()
createClientRoles()
+ createScopes()
createUsers(users)
}
@@ -93,7 +98,7 @@ data class Keycloak(
.then().statusCode(HttpStatus.SC_CREATED)
}
- private fun createClient() {
+ private fun createAgentClient() {
RestAssured.given().spec(requestBuilder)
.body(
mapOf(
@@ -108,6 +113,55 @@ data class Keycloak(
.then().statusCode(HttpStatus.SC_CREATED)
}
+ private fun createPublicClients() {
+ extraClients.forEach { client ->
+ RestAssured.given().spec(requestBuilder)
+ .body(
+ mapOf(
+ "id" to client.key,
+ "publicClient" to true,
+ "consentRequired" to true,
+ "redirectUris" to client.value.redirectUris,
+ ),
+ )
+ .post("/admin/realms/$realm/clients")
+ .then().statusCode(HttpStatus.SC_CREATED)
+ }
+ }
+
+ private fun createScopes() {
+ extraScopes.forEach { scope ->
+ val response = RestAssured.given().spec(requestBuilder)
+ .body(
+ mapOf(
+ "name" to scope,
+ "description" to scope,
+ "protocol" to "openid-connect",
+ "attributes" to mapOf(
+ "consent.screen.text" to scope,
+ "display.on.consent.screen" to true,
+ "include.in.token.scope" to true,
+ "gui.order" to "",
+ ),
+ ),
+ )
+ .post("/admin/realms/$realm/client-scopes")
+ .thenReturn()
+ response.then().statusCode(HttpStatus.SC_CREATED)
+ val clientScopeId = response.getHeader("Location").split("/").last()
+ mapClientsScopeToClient(clientScopeId)
+ }
+ }
+
+ private fun mapClientsScopeToClient(clientScopeId: String) {
+ extraClients.keys.forEach { client ->
+ RestAssured.given().spec(requestBuilder)
+ .put("/admin/realms/$realm/clients/$client/optional-client-scopes/$clientScopeId")
+ .then()
+ .statusCode(HttpStatus.SC_NO_CONTENT)
+ }
+ }
+
private fun createClientRoles() {
keycloakClientRoles.forEach { roleName ->
RestAssured.given().spec(requestBuilder)
@@ -171,3 +225,5 @@ data class Keycloak(
.then().statusCode(HttpStatus.SC_NO_CONTENT)
}
}
+
+data class KeycloakPublicClientConfig(val redirectUris: List = listOf())
diff --git a/tests/integration-tests/src/test/kotlin/eu/europa/ec/eudi/openid4vci/Types.kt b/tests/integration-tests/src/test/kotlin/eu/europa/ec/eudi/openid4vci/Types.kt
new file mode 100644
index 0000000000..bae29d46a2
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/eu/europa/ec/eudi/openid4vci/Types.kt
@@ -0,0 +1,27 @@
+package eu.europa.ec.eudi.openid4vci
+
+import java.net.URI
+import java.net.URL
+
+/**
+ * https://github.com/eu-digital-identity-wallet/eudi-lib-jvm-openid4vci-kt/blob/e81802f3b90639b97e32a6fd1c06c20e5ff53f27/src/main/kotlin/eu/europa/ec/eudi/openid4vci/Types.kt#L45
+ *
+ * This overrides the implementation in EUDI to relax the HTTPS requirement making it easier for testing purpose
+ */
+@JvmInline
+value class HttpsUrl private constructor(val value: URL) {
+
+ override fun toString(): String = value.toString()
+
+ companion object {
+
+ /**
+ * Parses the provided [value] as a [URI] and tries creates a new [HttpsUrl].
+ */
+ operator fun invoke(value: String): Result = runCatching {
+ val uri = URI.create(value)
+ // require(uri.scheme.contentEquals("https", true)) { "URL must use https protocol" }
+ HttpsUrl(uri.toURL())
+ }
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/models/JwtCredential.kt b/tests/integration-tests/src/test/kotlin/models/JwtCredential.kt
index e4395e27e0..f1d3e71695 100644
--- a/tests/integration-tests/src/test/kotlin/models/JwtCredential.kt
+++ b/tests/integration-tests/src/test/kotlin/models/JwtCredential.kt
@@ -16,7 +16,6 @@ import java.io.Serializable
import java.security.Provider
import java.security.SecureRandom
import java.time.OffsetDateTime
-import java.util.Base64
import java.util.Date
import kotlin.reflect.KClass
@@ -146,7 +145,7 @@ class JwtCredential {
}
fun parseBase64(base64: String): JwtCredential {
- val jwt = String(Base64.getDecoder().decode(base64))
+ val jwt = Base64URL.from(base64).decodeToString()
return parseJwt(jwt)
}
diff --git a/tests/integration-tests/src/test/kotlin/models/SdJwtClaim.kt b/tests/integration-tests/src/test/kotlin/models/SdJwtClaim.kt
new file mode 100644
index 0000000000..0ce288c800
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/models/SdJwtClaim.kt
@@ -0,0 +1,7 @@
+package models
+
+data class SdJwtClaim(
+ val salt: String,
+ val key: String,
+ val value: String,
+)
diff --git a/tests/integration-tests/src/test/kotlin/steps/Setup.kt b/tests/integration-tests/src/test/kotlin/steps/Setup.kt
index 37701683fd..07038f919d 100644
--- a/tests/integration-tests/src/test/kotlin/steps/Setup.kt
+++ b/tests/integration-tests/src/test/kotlin/steps/Setup.kt
@@ -1,12 +1,14 @@
package steps
import abilities.ListenToEvents
+import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton
import com.sksamuel.hoplite.ConfigException
import com.sksamuel.hoplite.ConfigLoader
import common.TestConstants
import config.*
import io.cucumber.java.AfterAll
import io.cucumber.java.BeforeAll
+import io.ktor.server.util.url
import io.restassured.RestAssured
import io.restassured.builder.RequestSpecBuilder
import net.serenitybdd.screenplay.Actor
@@ -16,6 +18,7 @@ import net.serenitybdd.screenplay.rest.abilities.CallAnApi
import org.apache.http.HttpStatus
import org.hyperledger.identus.client.models.CreateWalletRequest
import org.hyperledger.identus.client.models.CreateWebhookNotification
+import java.security.Security
import java.util.UUID
object Setup {
@@ -123,6 +126,7 @@ object Setup {
}
if (role.webhook != null) {
actor.whoCan(ListenToEvents.at(role.webhook.url, role.webhook.localPort))
+ actor.remember("webhookUrl", role.webhook.url)
if (role.webhook.initRequired) {
registerWebhook(actor, role.webhook.url.toExternalForm())
}
@@ -130,12 +134,18 @@ object Setup {
actor.remember("baseUrl", role.url.toExternalForm())
}
if (config.services?.keycloakOid4vci != null) {
- val role = config.roles.find { it.name == "Issuer" } ?: throw ConfigException("Issuer role does not exist")
- val url = role.oid4vciAuthServer ?: throw ConfigException("Issuer's oid4vci_auth_server must be provided")
- val actor = cast.actorNamed(role.name)
- actor.remember("OID4VCI_AUTH_SERVER_URL", url.toExternalForm())
- actor.remember("OID4VCI_AUTH_SERVER_CLIENT_ID", config.services.keycloakOid4vci.clientId)
- actor.remember("OID4VCI_AUTH_SERVER_CLIENT_SECRET", config.services.keycloakOid4vci.clientSecret)
+ val issuerRole = config.roles.find { it.name == "Issuer" } ?: throw ConfigException("Issuer role does not exist")
+ val issuerActor = cast.actorNamed(issuerRole.name)
+ with(issuerActor) {
+ val url = issuerRole.oid4vciAuthServer ?: throw ConfigException("Issuer's oid4vci_auth_server must be provided")
+ remember("OID4VCI_AUTH_SERVER_URL", url.toExternalForm())
+ remember("OID4VCI_AUTH_SERVER_CLIENT_ID", config.services.keycloakOid4vci.clientId)
+ remember("OID4VCI_AUTH_SERVER_CLIENT_SECRET", config.services.keycloakOid4vci.clientSecret)
+ }
+
+ val holderRole = config.roles.find { it.name == "Holder" } ?: throw ConfigException("Holder role does not exist")
+ val holderActor = cast.actorNamed(holderRole.name)
+ holderActor.remember("OID4VCI_AUTH_SERVER_CLIENT_ID", "holder")
}
OnStage.setTheStage(cast)
}
@@ -189,6 +199,7 @@ object Setup {
@BeforeAll
fun init() {
+ Security.insertProviderAt(BouncyCastleProviderSingleton.getInstance(), 2)
Setup.initServices()
Setup.initActors()
}
diff --git a/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt b/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt
index bc885b15ee..d5c8428b13 100644
--- a/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt
+++ b/tests/integration-tests/src/test/kotlin/steps/common/CommonSteps.kt
@@ -12,29 +12,31 @@ import org.apache.http.HttpStatus
import org.hyperledger.identus.client.models.Connection
import org.hyperledger.identus.client.models.ConnectionsPage
import steps.connection.ConnectionSteps
-import steps.credentials.IssueCredentialsSteps
+import steps.credentials.*
import steps.did.PublishDidSteps
import steps.schemas.CredentialSchemasSteps
class CommonSteps {
@Given("{actor} has a jwt issued credential from {actor}")
- fun holderHasIssuedCredentialFromIssuer(holder: Actor, issuer: Actor) {
+ fun holderHasIssuedJwtCredentialFromIssuer(holder: Actor, issuer: Actor) {
actorsHaveExistingConnection(issuer, holder)
val publishDidSteps = PublishDidSteps()
publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.JWT)
publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.JWT)
- val issueSteps = IssueCredentialsSteps()
- issueSteps.issuerOffersACredential(issuer, holder, "short")
- issueSteps.holderReceivesCredentialOffer(holder)
- issueSteps.holderAcceptsCredentialOfferForJwt(holder)
- issueSteps.acmeIssuesTheCredential(issuer)
- issueSteps.bobHasTheCredentialIssued(holder)
+ val jwtCredentialSteps = JwtCredentialSteps()
+ val credentialSteps = CredentialSteps()
+
+ jwtCredentialSteps.issuerOffersAJwtCredential(issuer, holder, "short")
+ credentialSteps.holderReceivesCredentialOffer(holder)
+ jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder)
+ credentialSteps.issuerIssuesTheCredential(issuer)
+ credentialSteps.holderReceivesTheIssuedCredential(holder)
}
@Given("{actor} has a jwt issued credential with {} schema from {actor}")
- fun holderHasIssuedCredentialFromIssuerWithSchema(
+ fun holderHasIssuedJwtCredentialFromIssuerWithSchema(
holder: Actor,
schema: CredentialSchema,
issuer: Actor,
@@ -48,12 +50,47 @@ class CommonSteps {
val schemaSteps = CredentialSchemasSteps()
schemaSteps.agentHasAPublishedSchema(issuer, schema)
- val issueSteps = IssueCredentialsSteps()
- issueSteps.issuerOffersCredentialToHolderUsingSchema(issuer, holder, "short", schema)
- issueSteps.holderReceivesCredentialOffer(holder)
- issueSteps.holderAcceptsCredentialOfferForJwt(holder)
- issueSteps.acmeIssuesTheCredential(issuer)
- issueSteps.bobHasTheCredentialIssued(holder)
+ val jwtCredentialSteps = JwtCredentialSteps()
+ val credentialSteps = CredentialSteps()
+ jwtCredentialSteps.issuerOffersJwtCredentialToHolderUsingSchema(issuer, holder, "short", schema)
+ credentialSteps.holderReceivesCredentialOffer(holder)
+ jwtCredentialSteps.holderAcceptsJwtCredentialOfferForJwt(holder)
+ credentialSteps.issuerIssuesTheCredential(issuer)
+ credentialSteps.holderReceivesTheIssuedCredential(holder)
+ }
+
+ @Given("{actor} has a sd-jwt issued credential from {actor}")
+ fun holderHasIssuedSdJwtCredentialFromIssuer(holder: Actor, issuer: Actor) {
+ actorsHaveExistingConnection(issuer, holder)
+
+ val publishDidSteps = PublishDidSteps()
+ publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.SD_JWT)
+ publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.SD_JWT)
+
+ val sdJwtCredentialSteps = SdJwtCredentialSteps()
+ val credentialSteps = CredentialSteps()
+ sdJwtCredentialSteps.issuerOffersSdJwtCredentialToHolder(issuer, holder)
+ credentialSteps.holderReceivesCredentialOffer(holder)
+ sdJwtCredentialSteps.holderAcceptsSdJwtCredentialOffer(holder)
+ credentialSteps.issuerIssuesTheCredential(issuer)
+ credentialSteps.holderReceivesTheIssuedCredential(holder)
+ }
+
+ @Given("{actor} has a bound sd-jwt issued credential from {actor}")
+ fun holderHasIssuedSdJwtCredentialFromIssuerWithKeyBind(holder: Actor, issuer: Actor) {
+ actorsHaveExistingConnection(issuer, holder)
+
+ val publishDidSteps = PublishDidSteps()
+ publishDidSteps.agentHasAnUnpublishedDID(holder, DidPurpose.SD_JWT)
+ publishDidSteps.agentHasAPublishedDID(issuer, DidPurpose.SD_JWT)
+
+ val sdJwtCredentialSteps = SdJwtCredentialSteps()
+ val credentialSteps = CredentialSteps()
+ sdJwtCredentialSteps.issuerOffersSdJwtCredentialToHolder(issuer, holder)
+ credentialSteps.holderReceivesCredentialOffer(holder)
+ sdJwtCredentialSteps.holderAcceptsSdJwtCredentialOfferWithKeyBinding(holder, "auth-1")
+ credentialSteps.issuerIssuesTheCredential(issuer)
+ credentialSteps.holderReceivesTheIssuedCredential(holder)
}
@Given("{actor} and {actor} have an existing connection")
diff --git a/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt b/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt
index 886bb64b86..17df7e4bf3 100644
--- a/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt
+++ b/tests/integration-tests/src/test/kotlin/steps/connection/ConnectionSteps.kt
@@ -8,13 +8,16 @@ import io.cucumber.java.en.Then
import io.cucumber.java.en.When
import io.iohk.atala.automation.extensions.get
import io.iohk.atala.automation.serenity.ensure.Ensure
-import io.iohk.atala.automation.utils.Wait
+import io.iohk.atala.automation.serenity.interactions.PollingWait
import net.serenitybdd.rest.SerenityRest
import net.serenitybdd.screenplay.Actor
import org.apache.http.HttpStatus.SC_CREATED
import org.apache.http.HttpStatus.SC_OK
import org.assertj.core.api.Assertions.assertThat
+import org.hamcrest.CoreMatchers
import org.hyperledger.identus.client.models.*
+import org.hyperledger.identus.client.models.Connection.State.CONNECTION_RESPONSE_RECEIVED
+import org.hyperledger.identus.client.models.Connection.State.CONNECTION_RESPONSE_SENT
class ConnectionSteps {
@@ -73,27 +76,22 @@ class ConnectionSteps {
@When("{actor} receives the connection request and sends back the response")
fun inviterReceivesTheConnectionRequest(inviter: Actor) {
- Wait.until(
- errorMessage = "Inviter connection didn't reach ${Connection.State.CONNECTION_RESPONSE_SENT} state",
- ) {
- val lastEvent = ListenToEvents.with(inviter).connectionEvents.lastOrNull {
- it.data.thid == inviter.recall("connection").thid
- }
- lastEvent != null && lastEvent.data.state == Connection.State.CONNECTION_RESPONSE_SENT
- }
+ inviter.attemptsTo(
+ PollingWait.until(
+ ListenToEvents.connectionState(inviter),
+ CoreMatchers.equalTo(CONNECTION_RESPONSE_SENT),
+ ),
+ )
}
@When("{actor} receives the connection response")
fun inviteeReceivesTheConnectionResponse(invitee: Actor) {
- Wait.until(
- errorMessage = "Invitee connection didn't reach ${Connection.State.CONNECTION_RESPONSE_RECEIVED} state.",
- ) {
- val lastEvent = ListenToEvents.with(invitee).connectionEvents.lastOrNull {
- it.data.thid == invitee.recall("connection").thid
- }
- lastEvent != null &&
- lastEvent.data.state == Connection.State.CONNECTION_RESPONSE_RECEIVED
- }
+ invitee.attemptsTo(
+ PollingWait.until(
+ ListenToEvents.connectionState(invitee),
+ CoreMatchers.equalTo(CONNECTION_RESPONSE_RECEIVED),
+ ),
+ )
}
@Then("{actor} and {actor} have a connection")
@@ -120,8 +118,8 @@ class ConnectionSteps {
assertThat(inviter.recall("connection-with-${invitee.name}").theirDid)
.isEqualTo(invitee.recall("connection-with-${inviter.name}").myDid)
assertThat(inviter.recall("connection-with-${invitee.name}").state)
- .isEqualTo(Connection.State.CONNECTION_RESPONSE_SENT)
+ .isEqualTo(CONNECTION_RESPONSE_SENT)
assertThat(invitee.recall("connection-with-${inviter.name}").state)
- .isEqualTo(Connection.State.CONNECTION_RESPONSE_RECEIVED)
+ .isEqualTo(CONNECTION_RESPONSE_RECEIVED)
}
}
diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/AnoncredSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/AnoncredSteps.kt
new file mode 100644
index 0000000000..2cddddb2b6
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/steps/credentials/AnoncredSteps.kt
@@ -0,0 +1,50 @@
+package steps.credentials
+
+import interactions.Post
+import interactions.body
+import io.cucumber.java.en.When
+import io.iohk.atala.automation.extensions.get
+import io.iohk.atala.automation.serenity.ensure.Ensure
+import net.serenitybdd.rest.SerenityRest
+import net.serenitybdd.screenplay.Actor
+import org.apache.http.HttpStatus.SC_CREATED
+import org.apache.http.HttpStatus.SC_OK
+import org.hyperledger.identus.client.models.*
+
+class AnoncredSteps {
+
+ @When("{actor} accepts anoncred credential offer")
+ fun holderAcceptsCredentialOfferForAnoncred(holder: Actor) {
+ val recordId = holder.recall("recordId")
+ holder.attemptsTo(
+ Post.to("/issue-credentials/records/$recordId/accept-offer").body("{}"),
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
+ )
+ }
+
+ @When("{actor} offers anoncred to {actor}")
+ fun acmeOffersAnoncredToBob(issuer: Actor, holder: Actor) {
+ val credentialOfferRequest = CreateIssueCredentialRecordRequest(
+ credentialDefinitionId = issuer.recall("anoncredsCredentialDefinition").guid,
+ claims = linkedMapOf(
+ "name" to "Bob",
+ "age" to "21",
+ "sex" to "M",
+ ),
+ issuingDID = issuer.recall("shortFormDid"),
+ connectionId = issuer.recall("connection-with-${holder.name}").connectionId,
+ validityPeriod = 3600.0,
+ credentialFormat = "AnonCreds",
+ automaticIssuance = false,
+ )
+
+ issuer.attemptsTo(
+ Post.to("/issue-credentials/credential-offers").body(credentialOfferRequest),
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
+ )
+
+ val credentialRecord = SerenityRest.lastResponse().get()
+ issuer.remember("thid", credentialRecord.thid)
+ holder.remember("thid", credentialRecord.thid)
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt
new file mode 100644
index 0000000000..7c2251ec83
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/steps/credentials/CredentialSteps.kt
@@ -0,0 +1,79 @@
+package steps.credentials
+
+import abilities.ListenToEvents
+import interactions.Post
+import io.cucumber.java.en.Then
+import io.cucumber.java.en.When
+import io.iohk.atala.automation.serenity.ensure.Ensure
+import io.iohk.atala.automation.serenity.interactions.PollingWait
+import net.serenitybdd.screenplay.Actor
+import org.apache.http.HttpStatus.*
+import org.hamcrest.CoreMatchers.equalTo
+import org.hyperledger.identus.client.models.*
+import org.hyperledger.identus.client.models.IssueCredentialRecord.ProtocolState.*
+
+class CredentialSteps {
+
+ @When("{actor} receives the credential offer")
+ fun holderReceivesCredentialOffer(holder: Actor) {
+ holder.attemptsTo(
+ PollingWait.until(
+ ListenToEvents.credentialState(holder),
+ equalTo(OFFER_RECEIVED),
+ ),
+ )
+ val recordId = ListenToEvents.with(holder).credentialEvents.last().data.recordId
+ holder.remember("recordId", recordId)
+ }
+
+ @When("{actor} tries to issue the credential")
+ fun issuerTriesToIssueTheCredential(issuer: Actor) {
+ issuer.attemptsTo(
+ PollingWait.until(
+ ListenToEvents.credentialState(issuer),
+ equalTo(REQUEST_RECEIVED),
+ ),
+ )
+
+ val recordId = ListenToEvents.with(issuer).credentialEvents.last().data.recordId
+
+ issuer.attemptsTo(
+ Post.to("/issue-credentials/records/$recordId/issue-credential"),
+ )
+ }
+
+ @When("{actor} issues the credential")
+ fun issuerIssuesTheCredential(issuer: Actor) {
+ issuerTriesToIssueTheCredential(issuer)
+
+ issuer.attemptsTo(
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
+ )
+
+ issuer.attemptsTo(
+ PollingWait.until(
+ ListenToEvents.credentialState(issuer),
+ equalTo(CREDENTIAL_SENT),
+ ),
+ )
+ issuer.remember("issuedCredential", ListenToEvents.with(issuer).credentialEvents.last().data)
+ }
+
+ @Then("{actor} receives the issued credential")
+ fun holderReceivesTheIssuedCredential(holder: Actor) {
+ holder.attemptsTo(
+ PollingWait.until(
+ ListenToEvents.credentialState(holder),
+ equalTo(CREDENTIAL_RECEIVED),
+ ),
+ )
+ holder.remember("issuedCredential", ListenToEvents.with(holder).credentialEvents.last().data)
+ }
+
+ @Then("{actor} should see that credential issuance has failed")
+ fun issuerShouldSeeThatCredentialIssuanceHasFailed(issuer: Actor) {
+ issuer.attemptsTo(
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_UNPROCESSABLE_ENTITY),
+ )
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/IssueCredentialsSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/IssueCredentialsSteps.kt
deleted file mode 100644
index 1688e3a585..0000000000
--- a/tests/integration-tests/src/test/kotlin/steps/credentials/IssueCredentialsSteps.kt
+++ /dev/null
@@ -1,242 +0,0 @@
-package steps.credentials
-
-import abilities.ListenToEvents
-import common.CredentialSchema
-import interactions.Post
-import interactions.body
-import io.cucumber.java.en.Then
-import io.cucumber.java.en.When
-import io.iohk.atala.automation.extensions.get
-import io.iohk.atala.automation.serenity.ensure.Ensure
-import io.iohk.atala.automation.utils.Wait
-import models.CredentialEvent
-import net.serenitybdd.rest.SerenityRest
-import net.serenitybdd.screenplay.Actor
-import org.apache.http.HttpStatus.*
-import org.hyperledger.identus.client.models.*
-import kotlin.time.Duration.Companion.seconds
-
-class IssueCredentialsSteps {
-
- private var credentialEvent: CredentialEvent? = null
-
- private fun sendCredentialOffer(
- issuer: Actor,
- holder: Actor,
- didForm: String,
- schemaGuid: String?,
- claims: Map,
- ) {
- val did: String = if (didForm == "short") {
- issuer.recall("shortFormDid")
- } else {
- issuer.recall("longFormDid")
- }
-
- val schemaId: String? = if (schemaGuid != null) {
- val baseUrl = issuer.recall("baseUrl")
- "$baseUrl/schema-registry/schemas/$schemaGuid"
- } else {
- null
- }
-
- val credentialOfferRequest = CreateIssueCredentialRecordRequest(
- schemaId = schemaId,
- claims = claims,
- issuingDID = did,
- connectionId = issuer.recall("connection-with-${holder.name}").connectionId,
- validityPeriod = 3600.0,
- credentialFormat = "JWT",
- automaticIssuance = false,
- )
-
- issuer.attemptsTo(
- Post.to("/issue-credentials/credential-offers").body(credentialOfferRequest),
- )
- }
-
- private fun saveCredentialOffer(issuer: Actor, holder: Actor) {
- val credentialRecord = SerenityRest.lastResponse().get()
-
- issuer.attemptsTo(
- Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
- )
-
- issuer.remember("thid", credentialRecord.thid)
- holder.remember("thid", credentialRecord.thid)
- }
-
- @When("{actor} offers a credential to {actor} with {string} form DID")
- fun issuerOffersACredential(issuer: Actor, holder: Actor, format: String) {
- val claims = linkedMapOf(
- "firstName" to "FirstName",
- "lastName" to "LastName",
- )
- sendCredentialOffer(issuer, holder, format, null, claims)
- saveCredentialOffer(issuer, holder)
- }
-
- @When("{actor} offers a credential to {actor} with {} form using {} schema")
- fun issuerOffersCredentialToHolderUsingSchema(
- issuer: Actor,
- holder: Actor,
- format: String,
- schema: CredentialSchema,
- ) {
- val schemaGuid = issuer.recall(schema.name)
- val claims = schema.claims
- sendCredentialOffer(issuer, holder, format, schemaGuid, claims)
- saveCredentialOffer(issuer, holder)
- }
-
- @When("{actor} offers a credential to {actor} with {} form DID with wrong claims structure using {} schema")
- fun issuerOffersCredentialToHolderWithWrongClaimStructure(
- issuer: Actor,
- holder: Actor,
- format: String,
- schema: CredentialSchema,
- ) {
- val schemaGuid = issuer.recall(schema.name)!!
- val claims = linkedMapOf(
- "name" to "Name",
- "surname" to "Surname",
- )
- sendCredentialOffer(issuer, holder, format, schemaGuid, claims)
- }
-
- @When("{actor} offers anoncred to {actor}")
- fun acmeOffersAnoncredToBob(issuer: Actor, holder: Actor) {
- val credentialOfferRequest = CreateIssueCredentialRecordRequest(
- credentialDefinitionId = issuer.recall("anoncredsCredentialDefinition").guid,
- claims = linkedMapOf(
- "name" to "Bob",
- "age" to "21",
- "sex" to "M",
- ),
- issuingDID = issuer.recall("shortFormDid"),
- connectionId = issuer.recall("connection-with-${holder.name}").connectionId,
- validityPeriod = 3600.0,
- credentialFormat = "AnonCreds",
- automaticIssuance = false,
- )
-
- issuer.attemptsTo(
- Post.to("/issue-credentials/credential-offers")
- .with {
- it.body(credentialOfferRequest)
- },
- )
-
- val credentialRecord = SerenityRest.lastResponse().get()
-
- issuer.attemptsTo(
- Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
- )
-
- issuer.remember("thid", credentialRecord.thid)
- holder.remember("thid", credentialRecord.thid)
- }
-
- @When("{actor} receives the credential offer")
- fun holderReceivesCredentialOffer(holder: Actor) {
- Wait.until(
- errorMessage = "Holder was unable to receive the credential offer from Issuer! " +
- "Protocol state did not achieve ${IssueCredentialRecord.ProtocolState.OFFER_RECEIVED} state.",
- ) {
- credentialEvent = ListenToEvents.with(holder).credentialEvents.lastOrNull {
- it.data.thid == holder.recall("thid")
- }
- credentialEvent != null &&
- credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.OFFER_RECEIVED
- }
-
- val recordId = ListenToEvents.with(holder).credentialEvents.last().data.recordId
- holder.remember("recordId", recordId)
- }
-
- @When("{actor} accepts credential offer for JWT")
- fun holderAcceptsCredentialOfferForJwt(holder: Actor) {
- holder.attemptsTo(
- Post.to("/issue-credentials/records/${holder.recall("recordId")}/accept-offer")
- .with {
- it.body(
- AcceptCredentialOfferRequest(holder.recall("longFormDid")),
- )
- },
- )
- holder.attemptsTo(
- Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
- )
- }
-
- @When("{actor} accepts credential offer for anoncred")
- fun holderAcceptsCredentialOfferForAnoncred(holder: Actor) {
- holder.attemptsTo(
- Post.to("/issue-credentials/records/${holder.recall("recordId")}/accept-offer")
- .with {
- it.body(
- "{}",
- )
- },
- )
- holder.attemptsTo(
- Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
- )
- }
-
- @When("{actor} issues the credential")
- fun acmeIssuesTheCredential(issuer: Actor) {
- Wait.until(
- errorMessage = "Issuer was unable to receive the credential request from Holder! Protocol state did not achieve RequestReceived state.",
- ) {
- credentialEvent = ListenToEvents.with(issuer).credentialEvents.lastOrNull {
- it.data.thid == issuer.recall("thid")
- }
- credentialEvent != null &&
- credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.REQUEST_RECEIVED
- }
- val recordId = credentialEvent!!.data.recordId
- issuer.attemptsTo(
- Post.to("/issue-credentials/records/$recordId/issue-credential"),
- )
- issuer.attemptsTo(
- Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
- )
-
- Wait.until(
- 10.seconds,
- errorMessage = "Issuer was unable to issue the credential! " +
- "Protocol state did not achieve ${IssueCredentialRecord.ProtocolState.CREDENTIAL_SENT} state.",
- ) {
- credentialEvent = ListenToEvents.with(issuer).credentialEvents.lastOrNull {
- it.data.thid == issuer.recall("thid")
- }
- issuer.remember("issuedCredential", credentialEvent!!.data)
-
- credentialEvent != null &&
- credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.CREDENTIAL_SENT
- }
- }
-
- @Then("{actor} receives the issued credential")
- fun bobHasTheCredentialIssued(holder: Actor) {
- Wait.until(
- errorMessage = "Holder was unable to receive the credential from Issuer! " +
- "Protocol state did not achieve ${IssueCredentialRecord.ProtocolState.CREDENTIAL_RECEIVED} state.",
- ) {
- credentialEvent = ListenToEvents.with(holder).credentialEvents.lastOrNull {
- it.data.thid == holder.recall("thid")
- }
- credentialEvent != null &&
- credentialEvent!!.data.protocolState == IssueCredentialRecord.ProtocolState.CREDENTIAL_RECEIVED
- }
- holder.remember("issuedCredential", ListenToEvents.with(holder).credentialEvents.last().data)
- }
-
- @Then("{actor} should see that credential issuance has failed")
- fun issuerShouldSeeThatCredentialIssuanceHasFailed(issuer: Actor) {
- issuer.attemptsTo(
- Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_UNPROCESSABLE_ENTITY),
- )
- }
-}
diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt
new file mode 100644
index 0000000000..b5026a7163
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt
@@ -0,0 +1,110 @@
+package steps.credentials
+
+import common.CredentialSchema
+import interactions.Post
+import interactions.body
+import io.cucumber.java.en.When
+import io.iohk.atala.automation.extensions.get
+import io.iohk.atala.automation.serenity.ensure.Ensure
+import net.serenitybdd.rest.SerenityRest
+import net.serenitybdd.screenplay.Actor
+import org.apache.http.HttpStatus.SC_CREATED
+import org.apache.http.HttpStatus.SC_OK
+import org.hyperledger.identus.client.models.*
+
+class JwtCredentialSteps {
+
+ private fun sendCredentialOffer(
+ issuer: Actor,
+ holder: Actor,
+ didForm: String,
+ schemaGuid: String?,
+ claims: Map,
+ ) {
+ val did: String = if (didForm == "short") {
+ issuer.recall("shortFormDid")
+ } else {
+ issuer.recall("longFormDid")
+ }
+
+ val schemaId: String? = if (schemaGuid != null) {
+ val baseUrl = issuer.recall("baseUrl")
+ "$baseUrl/schema-registry/schemas/$schemaGuid"
+ } else {
+ null
+ }
+
+ val credentialOfferRequest = CreateIssueCredentialRecordRequest(
+ schemaId = schemaId,
+ claims = claims,
+ issuingDID = did,
+ connectionId = issuer.recall("connection-with-${holder.name}").connectionId,
+ validityPeriod = 3600.0,
+ credentialFormat = "JWT",
+ automaticIssuance = false,
+ )
+
+ issuer.attemptsTo(
+ Post.to("/issue-credentials/credential-offers").body(credentialOfferRequest),
+ )
+ }
+
+ private fun saveCredentialOffer(issuer: Actor, holder: Actor) {
+ val credentialRecord = SerenityRest.lastResponse().get()
+
+ issuer.attemptsTo(
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
+ )
+
+ issuer.remember("thid", credentialRecord.thid)
+ holder.remember("thid", credentialRecord.thid)
+ }
+
+ @When("{actor} offers a jwt credential to {actor} with {string} form DID")
+ fun issuerOffersAJwtCredential(issuer: Actor, holder: Actor, format: String) {
+ val claims = linkedMapOf(
+ "firstName" to "FirstName",
+ "lastName" to "LastName",
+ )
+ sendCredentialOffer(issuer, holder, format, null, claims)
+ saveCredentialOffer(issuer, holder)
+ }
+
+ @When("{actor} offers a jwt credential to {actor} with {} form using {} schema")
+ fun issuerOffersJwtCredentialToHolderUsingSchema(
+ issuer: Actor,
+ holder: Actor,
+ format: String,
+ schema: CredentialSchema,
+ ) {
+ val schemaGuid = issuer.recall(schema.name)
+ val claims = schema.claims
+ sendCredentialOffer(issuer, holder, format, schemaGuid, claims)
+ saveCredentialOffer(issuer, holder)
+ }
+
+ @When("{actor} offers a jwt credential to {actor} with {} form DID with wrong claims structure using {} schema")
+ fun issuerOffersJwtCredentialToHolderWithWrongClaimStructure(
+ issuer: Actor,
+ holder: Actor,
+ format: String,
+ schema: CredentialSchema,
+ ) {
+ val schemaGuid = issuer.recall(schema.name)!!
+ val claims = linkedMapOf(
+ "name" to "Name",
+ "surname" to "Surname",
+ )
+ sendCredentialOffer(issuer, holder, format, schemaGuid, claims)
+ }
+
+ @When("{actor} accepts jwt credential offer")
+ fun holderAcceptsJwtCredentialOfferForJwt(holder: Actor) {
+ val recordId = holder.recall("recordId")
+ holder.attemptsTo(
+ Post.to("/issue-credentials/records/$recordId/accept-offer")
+ .body(AcceptCredentialOfferRequest(holder.recall("longFormDid"))),
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
+ )
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/RevokeCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/RevokeCredentialSteps.kt
index ab8d16d289..3e3d539346 100644
--- a/tests/integration-tests/src/test/kotlin/steps/credentials/RevokeCredentialSteps.kt
+++ b/tests/integration-tests/src/test/kotlin/steps/credentials/RevokeCredentialSteps.kt
@@ -6,15 +6,19 @@ import io.cucumber.java.en.When
import io.iohk.atala.automation.extensions.get
import io.iohk.atala.automation.extensions.toJsonPath
import io.iohk.atala.automation.serenity.ensure.Ensure
-import io.iohk.atala.automation.utils.Wait
+import io.iohk.atala.automation.serenity.interactions.PollingWait
import models.JwtCredential
import net.serenitybdd.rest.SerenityRest
import net.serenitybdd.screenplay.Actor
+import net.serenitybdd.screenplay.Question
import org.apache.http.HttpStatus
+import org.hamcrest.CoreMatchers.equalTo
import org.hyperledger.identus.client.models.IssueCredentialRecord
-import kotlin.time.Duration.Companion.seconds
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.minutes
class RevokeCredentialSteps {
+
@When("{actor} revokes the credential issued to {actor}")
fun issuerRevokesCredentialsIssuedToHolder(issuer: Actor, holder: Actor) {
val issuedCredential = issuer.recall("issuedCredential")
@@ -50,18 +54,20 @@ class RevokeCredentialSteps {
@Then("{actor} should see the credential was revoked")
fun credentialShouldBeRevoked(issuer: Actor) {
- Wait.until(
- timeout = 60.seconds,
- errorMessage = "Encoded Status List didn't change after revoking.",
- ) {
- val statusListId: String = issuer.recall("statusListId")
- val encodedStatusList: String = issuer.recall("encodedStatusList")
- issuer.attemptsTo(
- Get.resource("/credential-status/$statusListId"),
- )
- val actualEncodedList: String = SerenityRest.lastResponse().jsonPath().get("credentialSubject.encodedList")
- actualEncodedList != encodedStatusList
- }
+ issuer.attemptsTo(
+ PollingWait.with(1.minutes, 500.milliseconds).until(
+ Question.about("revocation status list").answeredBy {
+ val statusListId: String = issuer.recall("statusListId")
+ val encodedStatusList: String = issuer.recall("encodedStatusList")
+ issuer.attemptsTo(
+ Get.resource("/credential-status/$statusListId"),
+ )
+ val actualEncodedList: String = SerenityRest.lastResponse().jsonPath().get("credentialSubject.encodedList")
+ actualEncodedList != encodedStatusList
+ },
+ equalTo(true),
+ ),
+ )
}
@Then("{actor} should see the credential is not revoked")
diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/SdJwtCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/SdJwtCredentialSteps.kt
new file mode 100644
index 0000000000..5bdf8bcf48
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/steps/credentials/SdJwtCredentialSteps.kt
@@ -0,0 +1,137 @@
+package steps.credentials
+
+import com.google.gson.Gson
+import com.nimbusds.jose.util.Base64URL
+import interactions.Post
+import interactions.body
+import io.cucumber.java.en.Then
+import io.cucumber.java.en.When
+import io.iohk.atala.automation.extensions.get
+import io.iohk.atala.automation.serenity.ensure.Ensure
+import models.JwtCredential
+import models.SdJwtClaim
+import net.serenitybdd.rest.SerenityRest
+import net.serenitybdd.screenplay.Actor
+import org.apache.http.HttpStatus.*
+import org.hyperledger.identus.client.models.*
+
+class SdJwtCredentialSteps {
+
+ private val claims = linkedMapOf(
+ "firstName" to "Automation",
+ "lastName" to "Execution",
+ )
+
+ @When("{actor} offers a sd-jwt credential to {actor}")
+ fun issuerOffersSdJwtCredentialToHolder(issuer: Actor, holder: Actor) {
+ val connectionId = issuer.recall("connection-with-${holder.name}").connectionId
+ val did = issuer.recall("shortFormDid")
+
+ val credentialOfferRequest = CreateIssueCredentialRecordRequest(
+ claims = claims,
+ issuingDID = did,
+ connectionId = connectionId,
+ validityPeriod = 3600.0,
+ credentialFormat = "SDJWT",
+ automaticIssuance = false,
+ )
+
+ issuer.attemptsTo(
+ Post.to("/issue-credentials/credential-offers").body(credentialOfferRequest),
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
+ )
+
+ val credentialRecord = SerenityRest.lastResponse().get()
+ issuer.remember("thid", credentialRecord.thid)
+ holder.remember("thid", credentialRecord.thid)
+ }
+
+ @When("{actor} accepts credential offer for sd-jwt")
+ fun holderAcceptsSdJwtCredentialOffer(holder: Actor) {
+ holderAcceptsSdJwtCredentialOfferWithKeyBinding(holder, null)
+ }
+
+ @When("{actor} accepts credential offer for sd-jwt with '{}' key binding")
+ fun holderAcceptsSdJwtCredentialOfferWithKeyBinding(holder: Actor, key: String?) {
+ val recordId = holder.recall("recordId")
+ val did = holder.recall("longFormDid")
+ val request = AcceptCredentialOfferRequest(subjectId = did, keyId = key)
+ holder.attemptsTo(
+ Post.to("/issue-credentials/records/$recordId/accept-offer").body(request),
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
+ )
+ }
+
+ @When("{actor} tries to offer a sd-jwt credential to {actor}")
+ fun issuerTriesToOfferSdJwtCredentialToHolder(issuer: Actor, holder: Actor) {
+ val connectionId = issuer.recall("connection-with-${holder.name}").connectionId
+ val did = issuer.recall("shortFormDid")
+
+ val credentialOfferRequest = CreateIssueCredentialRecordRequest(
+ claims = claims,
+ issuingDID = did,
+ connectionId = connectionId,
+ validityPeriod = 3600.0,
+ credentialFormat = "SDJWT",
+ automaticIssuance = false,
+ )
+
+ issuer.attemptsTo(
+ Post.to("/issue-credentials/credential-offers").body(credentialOfferRequest),
+ )
+ }
+
+ @Then("{actor} checks the sd-jwt credential contents")
+ fun holderChecksTheSdJwtCredentialContents(holder: Actor) {
+ commonValidation(holder) { payload, _ ->
+ holder.attemptsTo(
+ Ensure.that(payload.containsKey("cnf")).isFalse(),
+ )
+ }
+ }
+
+ @Then("{actor} checks the sd-jwt credential contents with holder binding")
+ fun holderChecksTheSdJwtCredentialContentsWithHolderBinding(holder: Actor) {
+ commonValidation(holder) { payload, _ ->
+ holder.attemptsTo(
+ Ensure.that(payload.containsKey("cnf")).isTrue(),
+ )
+ }
+ }
+
+ @Then("{actor} should see the issuance has failed")
+ fun issuerShouldSeeTheIssuanceHasFailed(issuer: Actor) {
+ issuer.attemptsTo(
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_EXPECTATION_FAILED),
+ )
+ }
+
+ private fun commonValidation(
+ holder: Actor,
+ additionalChecks: (payload: Map, disclosedClaims: Map) -> Unit,
+ ) {
+ val issuedCredential = holder.recall("issuedCredential")
+ val jwtCredential = JwtCredential.parseBase64(issuedCredential.credential!!)
+
+ val payload = jwtCredential.payload!!.toJSONObject()
+
+ val disclosedClaims = jwtCredential.signature!!.toString().split("~")
+ .drop(1)
+ .dropLastWhile { it.isBlank() }
+ .map { Base64URL.from(it).decodeToString() }
+ .map { Gson().fromJson(it, Array::class.java) }
+ .associate { it[1] to SdJwtClaim(salt = it[0], key = it[1], value = it[2]) }
+
+ holder.attemptsTo(
+ Ensure.that(payload.containsKey("_sd")).isTrue(),
+ Ensure.that(payload.containsKey("_sd_alg")).isTrue(),
+ Ensure.that(payload.containsKey("iss")).isTrue(),
+ Ensure.that(payload.containsKey("iat")).isTrue(),
+ Ensure.that(payload.containsKey("exp")).isTrue(),
+ Ensure.that(disclosedClaims["firstName"]!!.value).isEqualTo(claims["firstName"]!!),
+ Ensure.that(disclosedClaims["lastName"]!!.value).isEqualTo(claims["lastName"]!!),
+ )
+
+ additionalChecks(payload, disclosedClaims)
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/steps/did/DeactivateDidSteps.kt b/tests/integration-tests/src/test/kotlin/steps/did/DeactivateDidSteps.kt
index 52a46869f5..5a4ab0c385 100644
--- a/tests/integration-tests/src/test/kotlin/steps/did/DeactivateDidSteps.kt
+++ b/tests/integration-tests/src/test/kotlin/steps/did/DeactivateDidSteps.kt
@@ -1,17 +1,17 @@
package steps.did
-import interactions.Get
import interactions.Post
import io.cucumber.java.en.Then
import io.cucumber.java.en.When
import io.iohk.atala.automation.extensions.get
+import io.iohk.atala.automation.matchers.RestAssuredJsonProperty
import io.iohk.atala.automation.serenity.ensure.Ensure
-import io.iohk.atala.automation.utils.Wait
+import io.iohk.atala.automation.serenity.interactions.PollingWait
+import io.iohk.atala.automation.serenity.questions.HttpRequest
import net.serenitybdd.rest.SerenityRest
import net.serenitybdd.screenplay.Actor
import org.apache.http.HttpStatus
import org.hyperledger.identus.client.models.DIDOperationResponse
-import org.hyperledger.identus.client.models.DIDResolutionResult
class DeactivateDidSteps {
@@ -37,9 +37,11 @@ class DeactivateDidSteps {
@Then("{actor} sees that PRISM DID is successfully deactivated")
fun actorSeesThatPrismDidIsSuccessfullyDeactivated(actor: Actor) {
val deactivatedDid = actor.recall("deactivatedDid")
- Wait.until(errorMessage = "ERROR: DID deactivate operation did not succeed on the ledger!") {
- actor.attemptsTo(Get.resource("/dids/$deactivatedDid"))
- SerenityRest.lastResponse().get().didDocumentMetadata.deactivated!!
- }
+ actor.attemptsTo(
+ PollingWait.until(
+ HttpRequest.get("/dids/$deactivatedDid"),
+ RestAssuredJsonProperty.toBe("didDocumentMetadata.deactivated", "true"),
+ ),
+ )
}
}
diff --git a/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt b/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt
index cd4302d194..d7c3addafb 100644
--- a/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt
+++ b/tests/integration-tests/src/test/kotlin/steps/did/PublishDidSteps.kt
@@ -8,14 +8,14 @@ import interactions.body
import io.cucumber.java.en.*
import io.iohk.atala.automation.extensions.get
import io.iohk.atala.automation.serenity.ensure.Ensure
-import io.iohk.atala.automation.utils.Wait
+import io.iohk.atala.automation.serenity.interactions.PollingWait
import net.serenitybdd.rest.SerenityRest
import net.serenitybdd.screenplay.Actor
import org.apache.http.HttpStatus
import org.apache.http.HttpStatus.SC_CREATED
import org.apache.http.HttpStatus.SC_OK
+import org.hamcrest.CoreMatchers.equalTo
import org.hyperledger.identus.client.models.*
-import kotlin.time.Duration.Companion.seconds
class PublishDidSteps {
@@ -46,7 +46,7 @@ class PublishDidSteps {
@Given("{actor} creates unpublished DID")
fun agentCreatesEmptyUnpublishedDid(actor: Actor) {
- agentCreatesUnpublishedDid(actor, DidPurpose.EMPTY)
+ agentCreatesUnpublishedDid(actor, DidPurpose.CUSTOM)
}
@Given("{actor} creates unpublished DID for {}")
@@ -75,6 +75,24 @@ class PublishDidSteps {
actor.forget("hasPublishedDid")
}
+ @When("{actor} prepares a custom PRISM DID")
+ fun actorPreparesCustomDid(actor: Actor) {
+ val customDid = DidPurpose.CUSTOM
+ actor.remember("customDid", customDid)
+ }
+
+ @When("{actor} adds a '{curve}' key for '{purpose}' purpose with '{}' name to the custom PRISM DID")
+ fun actorAddsKeyToCustomDid(actor: Actor, curve: Curve, purpose: Purpose, name: String) {
+ val customDid = actor.recall("customDid")
+ customDid.publicKeys.add(ManagedDIDKeyTemplate(name, purpose, curve))
+ }
+
+ @When("{actor} creates the custom PRISM DID")
+ fun actorCreatesTheCustomPrismDid(actor: Actor) {
+ val customDid = actor.recall("customDid")
+ agentCreatesUnpublishedDid(actor, customDid)
+ }
+
@When("{actor} publishes DID to ledger")
fun hePublishesDidToLedger(actor: Actor) {
val shortFormDid = actor.recall("shortFormDid")
@@ -87,18 +105,7 @@ class PublishDidSteps {
Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_ACCEPTED),
Ensure.that(didOperationResponse.scheduledOperation.didRef).isNotEmpty(),
Ensure.that(didOperationResponse.scheduledOperation.id).isNotEmpty(),
- )
-
- Wait.until(
- timeout = 30.seconds,
- errorMessage = "ERROR: DID was not published to ledger!",
- ) {
- val didEvent = ListenToEvents.with(actor).didEvents.lastOrNull {
- it.data.did == actor.recall("shortFormDid")
- }
- didEvent != null && didEvent.data.status == "PUBLISHED"
- }
- actor.attemptsTo(
+ PollingWait.until(ListenToEvents.didStatus(actor), equalTo("PUBLISHED")),
Get.resource("/dids/${actor.recall("shortFormDid")}"),
)
diff --git a/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt b/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt
index b2a73bf072..e9e590d319 100644
--- a/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt
+++ b/tests/integration-tests/src/test/kotlin/steps/did/UpdateDidSteps.kt
@@ -7,10 +7,12 @@ import io.cucumber.java.en.Then
import io.cucumber.java.en.When
import io.iohk.atala.automation.extensions.get
import io.iohk.atala.automation.serenity.ensure.Ensure
-import io.iohk.atala.automation.utils.Wait
+import io.iohk.atala.automation.serenity.interactions.PollingWait
import net.serenitybdd.rest.SerenityRest
import net.serenitybdd.screenplay.Actor
+import net.serenitybdd.screenplay.Question
import org.apache.http.HttpStatus
+import org.hamcrest.CoreMatchers.equalTo
import org.hyperledger.identus.client.models.*
import java.util.UUID
@@ -87,91 +89,111 @@ class UpdateDidSteps {
fun actorSeesDidSuccessfullyUpdatedWithNewKeys(actor: Actor, purpose: Purpose) {
val newDidKeyId = actor.recall("newDidKeyId")
- Wait.until(errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!") {
- actor.attemptsTo(
- Get.resource("/dids/${actor.recall("shortFormDid")}"),
- )
- val didKey = "${actor.recall("shortFormDid")}#$newDidKeyId"
- val didDocument = SerenityRest.lastResponse().get().didDocument!!
- val foundVerificationMethod = didDocument.verificationMethod!!.map { it.id }.any { it == didKey }
-
- foundVerificationMethod && when (purpose) {
- Purpose.ASSERTION_METHOD -> didDocument.assertionMethod!!.any { it == didKey }
- Purpose.AUTHENTICATION -> didDocument.authentication!!.any { it == didKey }
- Purpose.CAPABILITY_DELEGATION -> didDocument.capabilityDelegation!!.any { it == didKey }
- Purpose.CAPABILITY_INVOCATION -> didDocument.capabilityInvocation!!.any { it == didKey }
- Purpose.KEY_AGREEMENT -> didDocument.keyAgreement!!.any { it == didKey }
- }
- }
+ actor.attemptsTo(
+ PollingWait.until(
+ Question.about("did update").answeredBy {
+ actor.attemptsTo(
+ Get.resource("/dids/${actor.recall("shortFormDid")}"),
+ )
+ val didKey = "${actor.recall("shortFormDid")}#$newDidKeyId"
+ val didDocument = SerenityRest.lastResponse().get().didDocument!!
+ val foundVerificationMethod = didDocument.verificationMethod!!.map { it.id }.any { it == didKey }
+
+ foundVerificationMethod && when (purpose) {
+ Purpose.ASSERTION_METHOD -> didDocument.assertionMethod!!.any { it == didKey }
+ Purpose.AUTHENTICATION -> didDocument.authentication!!.any { it == didKey }
+ Purpose.CAPABILITY_DELEGATION -> didDocument.capabilityDelegation!!.any { it == didKey }
+ Purpose.CAPABILITY_INVOCATION -> didDocument.capabilityInvocation!!.any { it == didKey }
+ Purpose.KEY_AGREEMENT -> didDocument.keyAgreement!!.any { it == didKey }
+ }
+ },
+ equalTo(true),
+ ),
+ )
}
@Then("{actor} sees PRISM DID was successfully updated and keys removed with {purpose} purpose")
fun actorSeesDidSuccessfullyUpdatedAndKeysRemoved(actor: Actor, purpose: Purpose) {
val newDidKeyId = actor.recall("newDidKeyId")
- Wait.until(errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!") {
- actor.attemptsTo(
- Get.resource("/dids/${actor.recall("shortFormDid")}"),
- )
- val didKey = "${actor.recall("shortFormDid")}#$newDidKeyId"
- val didDocument = SerenityRest.lastResponse().get().didDocument!!
- val verificationMethodNotPresent = didDocument.verificationMethod!!.map { it.id }.none { it == didKey }
-
- verificationMethodNotPresent && when (purpose) {
- Purpose.ASSERTION_METHOD -> didDocument.assertionMethod!!.none { it == didKey }
- Purpose.AUTHENTICATION -> didDocument.authentication!!.none { it == didKey }
- Purpose.CAPABILITY_DELEGATION -> didDocument.capabilityDelegation!!.none { it == didKey }
- Purpose.CAPABILITY_INVOCATION -> didDocument.capabilityInvocation!!.none { it == didKey }
- Purpose.KEY_AGREEMENT -> didDocument.keyAgreement!!.none { it == didKey }
- }
- }
+
+ actor.attemptsTo(
+ PollingWait.until(
+ Question.about("did update").answeredBy {
+ actor.attemptsTo(
+ Get.resource("/dids/${actor.recall("shortFormDid")}"),
+ )
+ val didKey = "${actor.recall("shortFormDid")}#$newDidKeyId"
+ val didDocument = SerenityRest.lastResponse().get().didDocument!!
+ val verificationMethodNotPresent = didDocument.verificationMethod!!.map { it.id }.none { it == didKey }
+
+ verificationMethodNotPresent && when (purpose) {
+ Purpose.ASSERTION_METHOD -> didDocument.assertionMethod!!.none { it == didKey }
+ Purpose.AUTHENTICATION -> didDocument.authentication!!.none { it == didKey }
+ Purpose.CAPABILITY_DELEGATION -> didDocument.capabilityDelegation!!.none { it == didKey }
+ Purpose.CAPABILITY_INVOCATION -> didDocument.capabilityInvocation!!.none { it == didKey }
+ Purpose.KEY_AGREEMENT -> didDocument.keyAgreement!!.none { it == didKey }
+ }
+ },
+ equalTo(true),
+ ),
+ )
}
@Then("{actor} sees that PRISM DID should have the new service")
fun actorSeesDidSuccessfullyUpdatedWithNewServices(actor: Actor) {
val serviceId = actor.recall("newServiceId")
- Wait.until(
- errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!",
- ) {
- actor.attemptsTo(
- Get.resource("/dids/${actor.recall("shortFormDid")}"),
- )
- val serviceIds =
- SerenityRest.lastResponse().get().didDocument!!.service!!.map { it.id }
- serviceIds.any {
- it == "${actor.recall("shortFormDid")}#$serviceId"
- }
- }
+ actor.attemptsTo(
+ PollingWait.until(
+ Question.about("did update").answeredBy {
+ actor.attemptsTo(
+ Get.resource("/dids/${actor.recall("shortFormDid")}"),
+ )
+ val serviceIds =
+ SerenityRest.lastResponse().get().didDocument!!.service!!.map { it.id }
+ serviceIds.any {
+ it == "${actor.recall("shortFormDid")}#$serviceId"
+ }
+ },
+ equalTo(true),
+ ),
+ )
}
@Then("{actor} sees the PRISM DID should have the service removed")
fun actorSeesDidSuccessfullyUpdatedByRemovingServices(actor: Actor) {
val serviceId = actor.recall("newServiceId")
- Wait.until(
- errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!",
- ) {
- actor.attemptsTo(
- Get.resource("/dids/${actor.recall("shortFormDid")}"),
- )
- val serviceIds =
- SerenityRest.lastResponse().get().didDocument!!.service!!.map { it.id }
- serviceIds.none {
- it == "${actor.recall("shortFormDid")}#$serviceId"
- }
- }
+ actor.attemptsTo(
+ PollingWait.until(
+ Question.about("did update").answeredBy {
+ actor.attemptsTo(
+ Get.resource("/dids/${actor.recall("shortFormDid")}"),
+ )
+ val serviceIds =
+ SerenityRest.lastResponse().get().didDocument!!.service!!.map { it.id }
+ serviceIds.none {
+ it == "${actor.recall("shortFormDid")}#$serviceId"
+ }
+ },
+ equalTo(true),
+ ),
+ )
}
@Then("{actor} sees the PRISM DID should have the service updated")
fun actorSeesDidSuccessfullyUpdatedByUpdatingServices(actor: Actor) {
val serviceUrl = actor.recall("newServiceUrl")
- Wait.until(
- errorMessage = "ERROR: DID UPDATE operation did not succeed on the ledger!",
- ) {
- actor.attemptsTo(
- Get.resource("/dids/${actor.recall("shortFormDid")}"),
- )
- val service = SerenityRest.lastResponse().get().didDocument!!.service!!
- service.any { it.serviceEndpoint.value.contains(serviceUrl) }
- }
+ actor.attemptsTo(
+ PollingWait.until(
+ Question.about("did update").answeredBy {
+ actor.attemptsTo(
+ Get.resource("/dids/${actor.recall("shortFormDid")}"),
+ )
+ val service = SerenityRest.lastResponse().get().didDocument!!.service!!
+ service.any { it.serviceEndpoint.value.contains(serviceUrl) }
+ },
+ equalTo(true),
+ ),
+ )
}
private fun actorSubmitsPrismDidUpdateOperation(actor: Actor, updatePrismDidAction: UpdateManagedDIDRequestAction) {
diff --git a/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt
new file mode 100644
index 0000000000..218f233004
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/steps/oid4vci/IssueCredentialSteps.kt
@@ -0,0 +1,172 @@
+package steps.oid4vci
+
+import abilities.ListenToEvents
+import com.nimbusds.jose.JWSAlgorithm
+import com.nimbusds.jose.jwk.JWK
+import eu.europa.ec.eudi.openid4vci.*
+import interactions.Post
+import io.cucumber.java.en.Then
+import io.cucumber.java.en.When
+import io.iohk.atala.automation.extensions.get
+import io.iohk.atala.automation.serenity.ensure.Ensure
+import kotlinx.coroutines.runBlocking
+import net.serenitybdd.rest.SerenityRest
+import net.serenitybdd.screenplay.Actor
+import org.apache.http.HttpStatus
+import org.htmlunit.*
+import org.htmlunit.html.HtmlPage
+import org.htmlunit.html.HtmlPasswordInput
+import org.htmlunit.html.HtmlSubmitInput
+import org.htmlunit.html.HtmlTextInput
+import org.hyperledger.identus.apollo.base64.base64UrlEncoded
+import org.hyperledger.identus.apollo.utils.KMMECSecp256k1PrivateKey
+import org.hyperledger.identus.apollo.utils.decodeHex
+import org.hyperledger.identus.client.models.*
+import org.hyperledger.identus.client.models.CredentialOfferRequest
+import java.net.URI
+import java.net.URL
+
+class IssueCredentialSteps {
+ @When("{actor} creates an offer using {string} configuration with {string} form DID")
+ fun issuerCreateCredentialOffer(issuer: Actor, configurationId: String, didForm: String) {
+ val credentialIssuer = issuer.recall("oid4vciCredentialIssuer")
+ val claims = linkedMapOf(
+ "name" to "Alice",
+ "age" to 42,
+ )
+ val did: String = if (didForm == "short") {
+ issuer.recall("shortFormDid")
+ } else {
+ issuer.recall("longFormDid")
+ }
+ issuer.attemptsTo(
+ Post.to("/oid4vci/issuers/${credentialIssuer.id}/credential-offers")
+ .with {
+ it.body(
+ CredentialOfferRequest(
+ credentialConfigurationId = configurationId,
+ issuingDID = did,
+ claims = claims,
+ ),
+ )
+ },
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(HttpStatus.SC_CREATED),
+ )
+ val offerUri = SerenityRest.lastResponse().get().credentialOffer
+ issuer.remember("oid4vciOffer", offerUri)
+ }
+
+ @When("{actor} receives oid4vci offer from {actor}")
+ fun holderReceivesOfferFromIssuer(holder: Actor, issuer: Actor) {
+ val offerUri = issuer.recall("oid4vciOffer")
+ holder.remember("oid4vciOffer", offerUri)
+ }
+
+ @When("{actor} resolves oid4vci issuer metadata and login via front-end channel")
+ fun holderResolvesIssuerMetadata(holder: Actor) {
+ val offerUri = holder.recall("oid4vciOffer")
+ val credentialOffer = runBlocking {
+ CredentialOfferRequestResolver().resolve(offerUri).getOrThrow()
+ }
+ val redirectUrl = holder.recall("webhookUrl")
+ val openId4VCIConfig = OpenId4VCIConfig(
+ clientId = holder.recall("OID4VCI_AUTH_SERVER_CLIENT_ID"),
+ authFlowRedirectionURI = URI.create("$redirectUrl/auth-cb"),
+ keyGenerationConfig = KeyGenerationConfig.ecOnly(com.nimbusds.jose.jwk.Curve.SECP256K1),
+ credentialResponseEncryptionPolicy = CredentialResponseEncryptionPolicy.SUPPORTED,
+ parUsage = ParUsage.Never,
+ )
+ val issuer = Issuer.make(openId4VCIConfig, credentialOffer).getOrThrow()
+ val authorizationRequest = runBlocking {
+ issuer.prepareAuthorizationRequest().getOrThrow()
+ }
+ val authResponse = keycloakLoginViaBrowser(authorizationRequest.authorizationCodeURL.value, holder)
+ val authorizedRequest =
+ with(issuer) {
+ runBlocking {
+ val authCode = AuthorizationCode(authResponse.first)
+ authorizationRequest.authorizeWithAuthorizationCode(authCode, authResponse.second).getOrThrow()
+ }
+ }
+ holder.remember("eudiCredentialOffer", credentialOffer)
+ holder.remember("eudiAuthorizedRequest", authorizedRequest)
+ holder.remember("eudiIssuer", issuer)
+ }
+
+ @When("{actor} presents the access token with JWT proof on CredentialEndpoint")
+ fun holderPresentsTokenOnCredentialEdpoint(holder: Actor) {
+ val credentialOffer = holder.recall("eudiCredentialOffer")
+ val issuer = holder.recall("eudiIssuer")
+ val authorizedRequest = holder.recall("eudiAuthorizedRequest")
+ val requestPayload = IssuanceRequestPayload.ConfigurationBased(credentialOffer.credentialConfigurationIdentifiers.first(), null)
+ val submissionOutcome = with(issuer) {
+ when (authorizedRequest) {
+ is AuthorizedRequest.NoProofRequired -> throw Exception("Not supported yet")
+ is AuthorizedRequest.ProofRequired -> runBlocking {
+ authorizedRequest.requestSingle(
+ requestPayload,
+ popSigner(),
+ )
+ }
+ }.getOrThrow()
+ }
+ holder.remember("eudiSubmissionOutcome", submissionOutcome)
+ }
+
+ @Then("{actor} sees credential issued successfully from CredentialEndpoint")
+ fun holderSeesCredentialIssuedSuccessfully(holder: Actor) {
+ val credentials = when (val submissionOutcome = holder.recall("eudiSubmissionOutcome")) {
+ is SubmissionOutcome.Success -> submissionOutcome.credentials
+ else -> throw Exception("Issuance failed. $submissionOutcome")
+ }
+ holder.attemptsTo(
+ Ensure.that(credentials).hasSize(1),
+ )
+ }
+
+ private fun popSigner(): PopSigner {
+ val privateKeyHex = "d93c6485e30aad4d6522313553e58d235693f7007b822676e5e1e9a667655b69"
+ val did = "did:prism:4a2bc09be65136f604d1564e2fced1a1cdbce9deb9b64ee396afc95fc0b01c59:CnsKeRI6CgZhdXRoLTEQBEouCglzZWNwMjU2azESIQOx16yykO2nDcmM-NeQeVipxmuaF38KasIA8gycJCHWJhI7CgdtYXN0ZXIwEAFKLgoJc2VjcDI1NmsxEiECKrfbf1_p7YT5aRJspBLct5zDyL6aicEam1Gycq5xKy0"
+ val kid = "$did#auth-1"
+ val privateKey = KMMECSecp256k1PrivateKey.secp256k1FromByteArray(privateKeyHex.decodeHex())
+ val point = privateKey.getPublicKey().getCurvePoint()
+ val jwk = JWK.parse(
+ mapOf(
+ "kty" to "EC",
+ "crv" to "secp256k1",
+ "x" to point.x.base64UrlEncoded,
+ "y" to point.y.base64UrlEncoded,
+ "d" to privateKey.raw.base64UrlEncoded,
+ ),
+ )
+ return PopSigner.jwtPopSigner(
+ privateKey = jwk,
+ algorithm = JWSAlgorithm.ES256K,
+ publicKey = JwtBindingKey.Did(identity = kid),
+ )
+ }
+
+ /**
+ * @return A tuple of authorization code and authorization server state
+ */
+ private fun keycloakLoginViaBrowser(loginUrl: URL, actor: Actor): Pair {
+ val client = WebClient()
+
+ // step 1 - login with username and password
+ val loginPage = client.getPage(loginUrl)
+ val loginForm = loginPage.forms.first()
+ loginForm.getInputByName("username").type(actor.name)
+ loginForm.getInputByName("password").type(actor.name)
+ val postLoginPage = loginForm.getInputByName("login").click()
+
+ // If it is the first time user is logged in, Keycloak ask for consent by returning HtmlPage.
+ if (postLoginPage is HtmlPage) {
+ // step 2 - give client consent to access the scopes
+ val consentForm = postLoginPage.forms.first()
+ consentForm.getInputByName("accept").click()
+ }
+
+ client.close()
+ return ListenToEvents.with(actor).authCodeCallbackEvents.last()
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredsPresentProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredProofSteps.kt
similarity index 61%
rename from tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredsPresentProofSteps.kt
rename to tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredProofSteps.kt
index ace6fc890e..e41618037c 100644
--- a/tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredsPresentProofSteps.kt
+++ b/tests/integration-tests/src/test/kotlin/steps/proofs/AnoncredProofSteps.kt
@@ -1,26 +1,19 @@
package steps.proofs
-import abilities.ListenToEvents
-import interactions.Patch
-import interactions.Post
+import interactions.*
import io.cucumber.java.en.When
import io.iohk.atala.automation.extensions.get
import io.iohk.atala.automation.serenity.ensure.Ensure
-import io.iohk.atala.automation.utils.Wait
-import models.PresentationEvent
-import models.PresentationStatusAdapter
import net.serenitybdd.rest.SerenityRest
import net.serenitybdd.screenplay.Actor
import net.serenitybdd.screenplay.rest.abilities.CallAnApi
import org.apache.http.HttpStatus.SC_CREATED
import org.hyperledger.identus.client.models.*
-class AnoncredsPresentProofSteps {
-
- private var proofEvent: PresentationEvent? = null
+class AnoncredProofSteps {
@When("{actor} sends a anoncreds request for proof presentation to {actor} using credential definition issued by {actor}")
- fun faberSendsAnAnoncredsRequestForProofPresentationToBob(faber: Actor, bob: Actor, issuer: Actor) {
+ fun verifierSendsAnAnoncredRequestForProofPresentationToHolder(verifier: Actor, holder: Actor, issuer: Actor) {
val credentialDefinitionRegistryUrl =
issuer.usingAbilityTo(CallAnApi::class.java)
.resolve("/credential-definition-registry/definitions")
@@ -54,49 +47,28 @@ class AnoncredsPresentProofSteps {
version = "0.1",
)
val presentationRequest = RequestPresentationInput(
- connectionId = faber.recall("connection-with-${bob.name}").connectionId,
+ connectionId = verifier.recall("connection-with-${holder.name}").connectionId,
credentialFormat = "AnonCreds",
anoncredPresentationRequest = anoncredsPresentationRequestV1,
proofs = emptyList(),
)
- faber.attemptsTo(
- Post.to("/present-proof/presentations")
- .with {
- it.body(
- presentationRequest,
- )
- },
- )
- faber.attemptsTo(
+ verifier.attemptsTo(
+ Post.to("/present-proof/presentations").body(presentationRequest),
Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
)
val presentationStatus = SerenityRest.lastResponse().get()
- faber.remember("thid", presentationStatus.thid)
- bob.remember("thid", presentationStatus.thid)
- }
-
- @When("{actor} receives the anoncreds request")
- fun bobReceivesTheAnoncredsRequest(bob: Actor) {
- Wait.until(
- errorMessage = "ERROR: Bob did not achieve any presentation request!",
- ) {
- proofEvent = ListenToEvents.with(bob).presentationEvents.lastOrNull {
- it.data.thid == bob.recall("thid")
- }
- proofEvent != null &&
- proofEvent!!.data.status == PresentationStatusAdapter.Status.REQUEST_RECEIVED
- }
- bob.remember("presentationId", proofEvent!!.data.presentationId)
+ verifier.remember("thid", presentationStatus.thid)
+ holder.remember("thid", presentationStatus.thid)
}
@When("{actor} accepts the anoncreds presentation request")
- fun bobAcceptsTheAnoncredsPresentationWithProof(bob: Actor) {
+ fun holderAcceptsTheAnoncredsPresentationWithProof(holder: Actor) {
val requestPresentationAction = RequestPresentationAction(
anoncredPresentationRequest =
AnoncredCredentialProofsV1(
listOf(
AnoncredCredentialProofV1(
- bob.recall("issuedCredential").recordId,
+ holder.recall("issuedCredential").recordId,
listOf("sex"),
listOf("age"),
),
@@ -105,13 +77,9 @@ class AnoncredsPresentProofSteps {
action = RequestPresentationAction.Action.REQUEST_MINUS_ACCEPT,
)
- val presentationId = bob.recall("presentationId")
- bob.attemptsTo(
- Patch.to("/present-proof/presentations/$presentationId").with {
- it.body(
- requestPresentationAction,
- )
- },
+ val presentationId = holder.recall("presentationId")
+ holder.attemptsTo(
+ Patch.to("/present-proof/presentations/$presentationId").body(requestPresentationAction),
)
}
}
diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/HolderProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/HolderProofSteps.kt
new file mode 100644
index 0000000000..bdf3d078f7
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/steps/proofs/HolderProofSteps.kt
@@ -0,0 +1,35 @@
+package steps.proofs
+
+import abilities.ListenToEvents
+import interactions.*
+import io.cucumber.java.en.When
+import io.iohk.atala.automation.serenity.interactions.PollingWait
+import models.PresentationStatusAdapter.Status.REQUEST_RECEIVED
+import net.serenitybdd.screenplay.Actor
+import org.hamcrest.CoreMatchers.equalTo
+import org.hyperledger.identus.client.models.RequestPresentationAction
+
+class HolderProofSteps {
+
+ @When("{actor} rejects the proof")
+ fun holderRejectsProof(holder: Actor) {
+ val presentationId: String = holder.recall("presentationId")
+ holder.attemptsTo(
+ Patch.to("/present-proof/presentations/$presentationId").body(
+ RequestPresentationAction(action = RequestPresentationAction.Action.REQUEST_MINUS_REJECT),
+ ),
+ )
+ }
+
+ @When("{actor} receives the presentation proof request")
+ fun holderReceivesTheRequest(holder: Actor) {
+ holder.attemptsTo(
+ PollingWait.until(
+ ListenToEvents.presentationProofStatus(holder),
+ equalTo(REQUEST_RECEIVED),
+ ),
+ )
+ val presentationId = ListenToEvents.with(holder).presentationEvents.last().data.presentationId
+ holder.remember("presentationId", presentationId)
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt
new file mode 100644
index 0000000000..78985e62b0
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/steps/proofs/JwtProofSteps.kt
@@ -0,0 +1,52 @@
+package steps.proofs
+
+import interactions.*
+import io.cucumber.java.en.When
+import io.iohk.atala.automation.extensions.get
+import io.iohk.atala.automation.serenity.ensure.Ensure
+import net.serenitybdd.rest.SerenityRest
+import net.serenitybdd.screenplay.Actor
+import org.apache.http.HttpStatus.SC_CREATED
+import org.apache.http.HttpStatus.SC_OK
+import org.hyperledger.identus.client.models.*
+
+class JwtProofSteps {
+
+ @When("{actor} sends a request for jwt proof presentation to {actor}")
+ fun verifierSendsARequestForJwtProofPresentationToHolder(verifier: Actor, holder: Actor) {
+ val verifierConnectionToHolder = verifier.recall("connection-with-${holder.name}").connectionId
+ val presentationRequest = RequestPresentationInput(
+ connectionId = verifierConnectionToHolder,
+ options = Options(
+ challenge = "11c91493-01b3-4c4d-ac36-b336bab5bddf",
+ domain = "https://example-verifier.com",
+ ),
+ proofs = listOf(
+ ProofRequestAux(
+ schemaId = "https://schema.org/Person",
+ trustIssuers = listOf("did:web:atalaprism.io/users/testUser"),
+ ),
+ ),
+ )
+ verifier.attemptsTo(
+ Post.to("/present-proof/presentations").body(presentationRequest),
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
+ )
+ val presentationStatus = SerenityRest.lastResponse().get()
+ verifier.remember("thid", presentationStatus.thid)
+ holder.remember("thid", presentationStatus.thid)
+ }
+
+ @When("{actor} makes the jwt presentation of the proof")
+ fun holderMakesThePresentationOfTheProofToVerifier(holder: Actor) {
+ val requestPresentationAction = RequestPresentationAction(
+ proofId = listOf(holder.recall("issuedCredential").recordId),
+ action = RequestPresentationAction.Action.REQUEST_MINUS_ACCEPT,
+ )
+ val presentationId: String = holder.recall("presentationId")
+ holder.attemptsTo(
+ Patch.to("/present-proof/presentations/$presentationId").body(requestPresentationAction),
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
+ )
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/PresentProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/PresentProofSteps.kt
deleted file mode 100644
index b0599b7627..0000000000
--- a/tests/integration-tests/src/test/kotlin/steps/proofs/PresentProofSteps.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-package steps.proofs
-
-import abilities.ListenToEvents
-import interactions.Patch
-import interactions.Post
-import interactions.body
-import io.cucumber.java.en.Then
-import io.cucumber.java.en.When
-import io.iohk.atala.automation.extensions.get
-import io.iohk.atala.automation.serenity.ensure.Ensure
-import io.iohk.atala.automation.utils.Wait
-import models.PresentationStatusAdapter
-import net.serenitybdd.rest.SerenityRest
-import net.serenitybdd.screenplay.Actor
-import org.apache.http.HttpStatus.*
-import org.hyperledger.identus.client.models.*
-import kotlin.time.Duration.Companion.seconds
-
-class PresentProofSteps {
-
- @When("{actor} sends a request for proof presentation to {actor}")
- fun verifierSendsARequestForProofPresentationToHolder(verifier: Actor, holder: Actor) {
- val verifierConnectionToHolder = verifier.recall("connection-with-${holder.name}").connectionId
- val presentationRequest = RequestPresentationInput(
- connectionId = verifierConnectionToHolder,
- options = Options(
- challenge = "11c91493-01b3-4c4d-ac36-b336bab5bddf",
- domain = "https://example-verifier.com",
- ),
- proofs = listOf(
- ProofRequestAux(
- schemaId = "https://schema.org/Person",
- trustIssuers = listOf("did:web:atalaprism.io/users/testUser"),
- ),
- ),
- )
- verifier.attemptsTo(
- Post.to("/present-proof/presentations").body(presentationRequest),
- Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
- )
- val presentationStatus = SerenityRest.lastResponse().get()
- verifier.remember("thid", presentationStatus.thid)
- holder.remember("thid", presentationStatus.thid)
- }
-
- @When("{actor} receives the presentation proof request")
- fun holderReceivesTheRequest(holder: Actor) {
- Wait.until(
- timeout = 30.seconds,
- errorMessage = "ERROR: Bob did not achieve any presentation request!",
- ) {
- val proofEvent = ListenToEvents.with(holder).presentationEvents.lastOrNull {
- it.data.thid == holder.recall("thid")
- }
- holder.remember("presentationId", proofEvent?.data?.presentationId)
- proofEvent?.data?.status == PresentationStatusAdapter.Status.REQUEST_RECEIVED
- }
- }
-
- @When("{actor} makes the presentation of the proof")
- fun holderMakesThePresentationOfTheProofToVerifier(holder: Actor) {
- val requestPresentationAction = RequestPresentationAction(
- proofId = listOf(holder.recall("issuedCredential").recordId),
- action = RequestPresentationAction.Action.REQUEST_MINUS_ACCEPT,
- )
- val presentationId: String = holder.recall("presentationId")
- holder.attemptsTo(
- Patch.to("/present-proof/presentations/$presentationId").body(requestPresentationAction),
- Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
- )
- }
-
- @When("{actor} rejects the proof")
- fun holderRejectsProof(holder: Actor) {
- val presentationId: String = holder.recall("presentationId")
- holder.attemptsTo(
- Patch.to("/present-proof/presentations/$presentationId").with {
- it.body(
- RequestPresentationAction(action = RequestPresentationAction.Action.REQUEST_MINUS_REJECT),
- )
- },
- )
- }
-
- @Then("{actor} sees the proof is rejected")
- fun bobSeesProofIsRejected(bob: Actor) {
- Wait.until(
- timeout = 30.seconds,
- errorMessage = "ERROR: Faber did not receive presentation from Bob!",
- ) {
- val proofEvent = ListenToEvents.with(bob).presentationEvents.lastOrNull {
- it.data.thid == bob.recall("thid")
- }
- proofEvent?.data?.status == PresentationStatusAdapter.Status.REQUEST_REJECTED
- }
- }
-
- @Then("{actor} has the proof verified")
- fun faberHasTheProofVerified(faber: Actor) {
- Wait.until(
- timeout = 60.seconds,
- errorMessage = "Presentation did not achieve PresentationVerified state!",
- ) {
- val proofEvent = ListenToEvents.with(faber).presentationEvents.lastOrNull {
- it.data.thid == faber.recall("thid")
- }
- proofEvent?.data?.status == PresentationStatusAdapter.Status.PRESENTATION_VERIFIED
- }
- }
-
- @Then("{actor} sees the proof returned verification failed")
- fun verifierSeesTheProofReturnedVerificationFailed(verifier: Actor) {
- Wait.until(
- timeout = 120.seconds,
- errorMessage = "Presentation did not achieve PresentationVerificationFailed state!",
- ) {
- val proofEvent = ListenToEvents.with(verifier).presentationEvents.lastOrNull {
- it.data.thid == verifier.recall("thid")
- }
- proofEvent?.data?.status == PresentationStatusAdapter.Status.PRESENTATION_VERIFICATION_FAILED
- }
- }
-}
diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/SdJwtProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/SdJwtProofSteps.kt
new file mode 100644
index 0000000000..41eee396b0
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/steps/proofs/SdJwtProofSteps.kt
@@ -0,0 +1,99 @@
+package steps.proofs
+
+import abilities.ListenToEvents
+import com.google.gson.Gson
+import com.google.gson.JsonObject
+import com.nimbusds.jose.util.Base64URL
+import interactions.*
+import io.cucumber.java.en.Then
+import io.cucumber.java.en.When
+import io.iohk.atala.automation.extensions.get
+import io.iohk.atala.automation.serenity.ensure.Ensure
+import models.JwtCredential
+import models.SdJwtClaim
+import net.serenitybdd.rest.SerenityRest
+import net.serenitybdd.screenplay.Actor
+import org.apache.http.HttpStatus.SC_CREATED
+import org.apache.http.HttpStatus.SC_OK
+import org.hyperledger.identus.client.models.*
+
+class SdJwtProofSteps {
+
+ @When("{actor} sends a request for sd-jwt proof presentation to {actor} requesting [{}] claims")
+ fun verifierSendsARequestForSdJwtProofPresentationToHolder(verifier: Actor, holder: Actor, keys: String) {
+ val claims = JsonObject()
+ for (key in keys.split(",")) {
+ claims.addProperty(key, "{}")
+ }
+ val verifierConnectionToHolder = verifier.recall("connection-with-${holder.name}").connectionId
+ val presentationRequest = RequestPresentationInput(
+ connectionId = verifierConnectionToHolder,
+ options = Options(
+ challenge = "11c91493-01b3-4c4d-ac36-b336bab5bddf",
+ domain = "https://example-verifier.com",
+ ),
+ proofs = listOf(),
+ credentialFormat = "SDJWT",
+ claims = claims,
+ )
+ verifier.attemptsTo(
+ Post.to("/present-proof/presentations").body(presentationRequest),
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_CREATED),
+ )
+ val presentationStatus = SerenityRest.lastResponse().get()
+ verifier.remember("thid", presentationStatus.thid)
+ holder.remember("thid", presentationStatus.thid)
+ }
+
+ @When("{actor} makes the sd-jwt presentation of the proof disclosing [{}] claims")
+ fun holderMakesThePresentationOfTheProofToVerifier(holder: Actor, keys: String) {
+ val claims = JsonObject()
+ for (key in keys.split(",")) {
+ claims.addProperty(key, "{}")
+ }
+
+ val requestPresentationAction = RequestPresentationAction(
+ proofId = listOf(holder.recall("issuedCredential").recordId),
+ action = RequestPresentationAction.Action.REQUEST_MINUS_ACCEPT,
+ claims = claims,
+ credentialFormat = "SDJWT",
+ )
+
+ val presentationId: String = holder.recall("presentationId")
+ holder.attemptsTo(
+ Patch.to("/present-proof/presentations/$presentationId").body(requestPresentationAction),
+ Ensure.thatTheLastResponse().statusCode().isEqualTo(SC_OK),
+ )
+ }
+
+ @Then("{actor} has the sd-jwt proof verified")
+ fun verifierHasTheSdJwtProofVerified(verifier: Actor) {
+ VerifierProofSteps().verifierHasTheProofVerified(verifier)
+ val proofEvent = ListenToEvents.with(verifier).presentationEvents.last().data
+ val sdjwt = proofEvent.data!!.first()
+ val isBound = !sdjwt.endsWith("~") // if it ends with a ~ there's no binding
+
+ val jwt = JwtCredential.parseJwt(sdjwt.split("~").first())
+ val claims = sdjwt.split("~")
+ .drop(1)
+ .dropLast(if (isBound) 1 else 0)
+ .dropLastWhile { it.isBlank() }
+ .map { Base64URL.from(it).decodeToString() }
+ .map { Gson().fromJson(it, Array::class.java) }
+ .associate { it[1] to SdJwtClaim(salt = it[0], key = it[1], value = it[2]) }
+
+ verifier.attemptsTo(
+ Ensure.that(claims.containsKey("firstName")).isTrue(),
+ Ensure.that(claims.containsKey("lastName")).isFalse(),
+ )
+
+ if (isBound) {
+ val bindingJwt = JwtCredential.parseJwt(sdjwt.split("~").last())
+ val payload = Gson().toJsonTree(bindingJwt.payload!!.toJSONObject()).asJsonObject
+
+ verifier.attemptsTo(
+ Ensure.that(payload.get("aud").asString).isEqualTo("https://example-verifier.com"),
+ )
+ }
+ }
+}
diff --git a/tests/integration-tests/src/test/kotlin/steps/proofs/VerifierProofSteps.kt b/tests/integration-tests/src/test/kotlin/steps/proofs/VerifierProofSteps.kt
new file mode 100644
index 0000000000..e99c103c83
--- /dev/null
+++ b/tests/integration-tests/src/test/kotlin/steps/proofs/VerifierProofSteps.kt
@@ -0,0 +1,32 @@
+package steps.proofs
+
+import abilities.ListenToEvents.Companion.presentationProofStatus
+import io.cucumber.java.en.Then
+import io.iohk.atala.automation.serenity.interactions.PollingWait
+import models.PresentationStatusAdapter.Status.*
+import net.serenitybdd.screenplay.Actor
+import org.hamcrest.CoreMatchers.equalTo
+
+class VerifierProofSteps {
+
+ @Then("{actor} sees the proof returned verification failed")
+ fun verifierSeesTheProofReturnedVerificationFailed(verifier: Actor) {
+ verifier.attemptsTo(
+ PollingWait.until(presentationProofStatus(verifier), equalTo(PRESENTATION_VERIFICATION_FAILED)),
+ )
+ }
+
+ @Then("{actor} sees the proof is rejected")
+ fun verifierSeesProofIsRejected(verifier: Actor) {
+ verifier.attemptsTo(
+ PollingWait.until(presentationProofStatus(verifier), equalTo(REQUEST_REJECTED)),
+ )
+ }
+
+ @Then("{actor} has the proof verified")
+ fun verifierHasTheProofVerified(verifier: Actor) {
+ verifier.attemptsTo(
+ PollingWait.until(presentationProofStatus(verifier), equalTo(PRESENTATION_VERIFIED)),
+ )
+ }
+}
diff --git a/tests/integration-tests/src/test/resources/configs/basic.conf b/tests/integration-tests/src/test/resources/configs/basic.conf
index 954af03916..af91c7bf2a 100644
--- a/tests/integration-tests/src/test/resources/configs/basic.conf
+++ b/tests/integration-tests/src/test/resources/configs/basic.conf
@@ -6,10 +6,15 @@ services = {
}
keycloak_oid4vci = {
http_port = 9981
+ logger_name = "keycloak_oid4vci"
compose_file = "src/test/resources/containers/keycloak-oid4vci.yml"
realm = "oid4vci-holder"
+ extra_scopes = ["StudentProfile"]
extra_envs = {
- IDENTUS_URL = "${ISSUER_AGENT_URL:-http://localhost:8080}"
+ IDENTUS_URL = "${ISSUER_AGENT_URL:-http://host.docker.internal:8080}"
+ }
+ extra_clients = {
+ holder = { redirectUris = ["${HOLDER_WEBHOOK_URL:-http://host.docker.internal:9956}/*"] }
}
}
}
@@ -40,7 +45,7 @@ roles = [
url = "${ISSUER_WEBHOOK_URL:-http://host.docker.internal:9955}"
init_required = true
}
- oid4vci_auth_server = "http://localhost:9981"
+ oid4vci_auth_server = "http://host.docker.internal:9981/realms/oid4vci-holder"
},
{
name = "Holder"
diff --git a/tests/integration-tests/src/test/resources/configs/mt_keycloak.conf b/tests/integration-tests/src/test/resources/configs/mt_keycloak.conf
index b240ff1705..2e8d6da9a2 100644
--- a/tests/integration-tests/src/test/resources/configs/mt_keycloak.conf
+++ b/tests/integration-tests/src/test/resources/configs/mt_keycloak.conf
@@ -9,10 +9,15 @@ services = {
}
keycloak_oid4vci = {
http_port = 9981
+ logger_name = "keycloak_oid4vci"
compose_file = "src/test/resources/containers/keycloak-oid4vci.yml"
realm = "oid4vci-holder"
+ extra_scopes = ["StudentProfile"]
extra_envs = {
- IDENTUS_URL = "${ISSUER_AGENT_URL:-http://localhost:8080}"
+ IDENTUS_URL = "${ISSUER_AGENT_URL:-http://host.docker.internal:8080}"
+ }
+ extra_clients = {
+ holder = { redirectUris = ["${HOLDER_WEBHOOK_URL:-http://host.docker.internal:9956}/*"] }
}
}
}
@@ -43,7 +48,7 @@ roles = [
url = "${ISSUER_WEBHOOK_URL:-http://host.docker.internal:9955}"
init_required = true
}
- oid4vci_auth_server = "http://localhost:9981"
+ oid4vci_auth_server = "http://host.docker.internal:9981/realms/oid4vci-holder"
},
{
name = "Holder"
diff --git a/tests/integration-tests/src/test/resources/configs/mt_keycloak_agent_role.conf b/tests/integration-tests/src/test/resources/configs/mt_keycloak_agent_role.conf
index d3ba070f5a..41d8550074 100644
--- a/tests/integration-tests/src/test/resources/configs/mt_keycloak_agent_role.conf
+++ b/tests/integration-tests/src/test/resources/configs/mt_keycloak_agent_role.conf
@@ -7,6 +7,19 @@ services = {
keycloak = {
http_port = 9980
}
+ keycloak_oid4vci = {
+ http_port = 9981
+ logger_name = "keycloak_oid4vci"
+ compose_file = "src/test/resources/containers/keycloak-oid4vci.yml"
+ realm = "oid4vci-holder"
+ extra_scopes = ["StudentProfile"]
+ extra_envs = {
+ IDENTUS_URL = "${ISSUER_AGENT_URL:-http://host.docker.internal:8080}"
+ }
+ extra_clients = {
+ holder = { redirectUris = ["${HOLDER_WEBHOOK_URL:-http://host.docker.internal:9956}/*"] }
+ }
+ }
}
# Specify agents that are required to be created before running tests
@@ -35,6 +48,7 @@ roles = [
url = "${ISSUER_WEBHOOK_URL:-http://host.docker.internal:9955}"
init_required = true
}
+ oid4vci_auth_server = "http://host.docker.internal:9981/realms/oid4vci-holder"
},
{
name = "Holder"
diff --git a/tests/integration-tests/src/test/resources/configs/mt_keycloak_vault.conf b/tests/integration-tests/src/test/resources/configs/mt_keycloak_vault.conf
index 66957bf226..37271e2a5f 100644
--- a/tests/integration-tests/src/test/resources/configs/mt_keycloak_vault.conf
+++ b/tests/integration-tests/src/test/resources/configs/mt_keycloak_vault.conf
@@ -7,6 +7,19 @@ services = {
keycloak = {
http_port = 9980
}
+ keycloak_oid4vci = {
+ http_port = 9981
+ logger_name = "keycloak_oid4vci"
+ compose_file = "src/test/resources/containers/keycloak-oid4vci.yml"
+ realm = "oid4vci-holder"
+ extra_scopes = ["StudentProfile"]
+ extra_envs = {
+ IDENTUS_URL = "${ISSUER_AGENT_URL:-http://host.docker.internal:8080}"
+ }
+ extra_clients = {
+ holder = { redirectUris = ["${HOLDER_WEBHOOK_URL:-http://host.docker.internal:9956}/*"] }
+ }
+ }
vault = {
http_port = 8200
auth_type = "APP_ROLE"
@@ -41,6 +54,7 @@ roles = [
url = "${ISSUER_WEBHOOK_URL:-http://host.docker.internal:9955}"
init_required = true
}
+ oid4vci_auth_server = "http://host.docker.internal:9981/realms/oid4vci-holder"
},
{
name = "Holder"
diff --git a/tests/integration-tests/src/test/resources/configs/mt_vault_approle.conf b/tests/integration-tests/src/test/resources/configs/mt_vault_approle.conf
index b50656519a..aa81ec52fd 100644
--- a/tests/integration-tests/src/test/resources/configs/mt_vault_approle.conf
+++ b/tests/integration-tests/src/test/resources/configs/mt_vault_approle.conf
@@ -8,6 +8,19 @@ services = {
http_port = 8200,
vault_auth_type = "APP_ROLE"
}
+ keycloak_oid4vci = {
+ http_port = 9981
+ logger_name = "keycloak_oid4vci"
+ compose_file = "src/test/resources/containers/keycloak-oid4vci.yml"
+ realm = "oid4vci-holder"
+ extra_scopes = ["StudentProfile"]
+ extra_envs = {
+ IDENTUS_URL = "${ISSUER_AGENT_URL:-http://host.docker.internal:8080}"
+ }
+ extra_clients = {
+ holder = { redirectUris = ["${HOLDER_WEBHOOK_URL:-http://host.docker.internal:9956}/*"] }
+ }
+ }
}
# Specify agents that are required to be created before running tests
@@ -37,6 +50,7 @@ roles = [
url = "${ISSUER_WEBHOOK_URL:-http://host.docker.internal:9955}"
init_required = true
}
+ oid4vci_auth_server = "http://host.docker.internal:9981/realms/oid4vci-holder"
},
{
name = "Holder"
diff --git a/tests/integration-tests/src/test/resources/configs/mt_vault_token.conf b/tests/integration-tests/src/test/resources/configs/mt_vault_token.conf
index e6493e2126..85a52323de 100644
--- a/tests/integration-tests/src/test/resources/configs/mt_vault_token.conf
+++ b/tests/integration-tests/src/test/resources/configs/mt_vault_token.conf
@@ -8,6 +8,19 @@ services = {
http_port = 8200,
vault_auth_type = TOKEN
}
+ keycloak_oid4vci = {
+ http_port = 9981
+ logger_name = "keycloak_oid4vci"
+ compose_file = "src/test/resources/containers/keycloak-oid4vci.yml"
+ realm = "oid4vci-holder"
+ extra_scopes = ["StudentProfile"]
+ extra_envs = {
+ IDENTUS_URL = "${ISSUER_AGENT_URL:-http://host.docker.internal:8080}"
+ }
+ extra_clients = {
+ holder = { redirectUris = ["${HOLDER_WEBHOOK_URL:-http://host.docker.internal:9956}/*"] }
+ }
+ }
}
# Specify agents that are required to be created before running tests
@@ -37,6 +50,7 @@ roles = [
url = "${ISSUER_WEBHOOK_URL:-http://host.docker.internal:9955}"
init_required = true
}
+ oid4vci_auth_server = "http://host.docker.internal:9981/realms/oid4vci-holder"
},
{
name = "Holder"
diff --git a/tests/integration-tests/src/test/resources/configs/two_agents_basic.conf b/tests/integration-tests/src/test/resources/configs/two_agents_basic.conf
index 0ffcb90173..f95568f44f 100644
--- a/tests/integration-tests/src/test/resources/configs/two_agents_basic.conf
+++ b/tests/integration-tests/src/test/resources/configs/two_agents_basic.conf
@@ -4,6 +4,19 @@ services = {
http_port = 50053
version = "${PRISM_NODE_VERSION}"
}
+ keycloak_oid4vci = {
+ http_port = 9981
+ logger_name = "keycloak_oid4vci"
+ compose_file = "src/test/resources/containers/keycloak-oid4vci.yml"
+ realm = "oid4vci-holder"
+ extra_scopes = ["StudentProfile"]
+ extra_envs = {
+ IDENTUS_URL = "${ISSUER_AGENT_URL:-http://host.docker.internal:8080}"
+ }
+ extra_clients = {
+ holder = { redirectUris = ["${HOLDER_WEBHOOK_URL:-http://host.docker.internal:9956}/*"] }
+ }
+ }
}
# Specify agents that are required to be created before running tests
@@ -39,6 +52,7 @@ roles = [
url = "${ISSUER_WEBHOOK_URL:-http://host.docker.internal:9955}"
init_required = true
}
+ oid4vci_auth_server = "http://host.docker.internal:9981/realms/oid4vci-holder"
},
{
name = "Holder"
diff --git a/tests/integration-tests/src/test/resources/configs/two_agents_sharing_keycloak.conf b/tests/integration-tests/src/test/resources/configs/two_agents_sharing_keycloak.conf
index 9824e1ff4c..c67f2312f9 100644
--- a/tests/integration-tests/src/test/resources/configs/two_agents_sharing_keycloak.conf
+++ b/tests/integration-tests/src/test/resources/configs/two_agents_sharing_keycloak.conf
@@ -7,6 +7,19 @@ services = {
keycloak = {
http_port = 9980
}
+ keycloak_oid4vci = {
+ http_port = 9981
+ logger_name = "keycloak_oid4vci"
+ compose_file = "src/test/resources/containers/keycloak-oid4vci.yml"
+ realm = "oid4vci-holder"
+ extra_scopes = ["StudentProfile"]
+ extra_envs = {
+ IDENTUS_URL = "${ISSUER_AGENT_URL:-http://host.docker.internal:8080}"
+ }
+ extra_clients = {
+ holder = { redirectUris = ["${HOLDER_WEBHOOK_URL:-http://host.docker.internal:9956}/*"] }
+ }
+ }
}
# Specify agents that are required to be created before running tests
@@ -44,6 +57,7 @@ roles = [
url = "${ISSUER_WEBHOOK_URL:-http://host.docker.internal:9955}"
init_required = true
}
+ oid4vci_auth_server = "http://host.docker.internal:9981/realms/oid4vci-holder"
},
{
name = "Holder"
diff --git a/tests/integration-tests/src/test/resources/containers/keycloak-oid4vci.yml b/tests/integration-tests/src/test/resources/containers/keycloak-oid4vci.yml
index 2dae22b4ae..dcfb8c4274 100644
--- a/tests/integration-tests/src/test/resources/containers/keycloak-oid4vci.yml
+++ b/tests/integration-tests/src/test/resources/containers/keycloak-oid4vci.yml
@@ -3,11 +3,13 @@ version: "3.8"
services:
keycloak:
- image: ghcr.io/hyperledger/identus-keycloak-plugins:0.1.0
+ image: ghcr.io/hyperledger/identus-keycloak-plugins:0.2.0
ports:
- "${KEYCLOAK_HTTP_PORT}:8080"
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
IDENTUS_URL:
- command: start-dev --health-enabled=true --hostname-url=http://localhost:${KEYCLOAK_HTTP_PORT}
+ command: start-dev --health-enabled=true --hostname-url=http://host.docker.internal:${KEYCLOAK_HTTP_PORT}
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
diff --git a/tests/integration-tests/src/test/resources/features/credentials/issue_anoncred_with_published_did.feature b/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature
similarity index 78%
rename from tests/integration-tests/src/test/resources/features/credentials/issue_anoncred_with_published_did.feature
rename to tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature
index 051e8df412..712d6d7db6 100644
--- a/tests/integration-tests/src/test/resources/features/credentials/issue_anoncred_with_published_did.feature
+++ b/tests/integration-tests/src/test/resources/features/credential/anoncred/issuance.feature
@@ -1,5 +1,5 @@
-@RFC0453 @AIP20 @credentials
-Feature: Issue Anoncred with published DID
+@anoncred @issuance
+Feature: Issue Anoncred credential
Background:
Given Issuer and Holder have an existing connection
@@ -10,6 +10,6 @@ Feature: Issue Anoncred with published DID
Given Issuer has an anoncred schema definition
When Issuer offers anoncred to Holder
And Holder receives the credential offer
- And Holder accepts credential offer for anoncred
+ And Holder accepts anoncred credential offer
And Issuer issues the credential
Then Holder receives the issued credential
diff --git a/tests/integration-tests/src/test/resources/features/proofs/present_proof_anoncred.feature b/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature
similarity index 86%
rename from tests/integration-tests/src/test/resources/features/proofs/present_proof_anoncred.feature
rename to tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature
index 77c52dc572..8bc05395c0 100644
--- a/tests/integration-tests/src/test/resources/features/proofs/present_proof_anoncred.feature
+++ b/tests/integration-tests/src/test/resources/features/credential/anoncred/present_proof.feature
@@ -1,4 +1,4 @@
-@proof @anoncreds
+@anoncred @proof
Feature: Present Proof Protocol
Scenario: Holder presents anoncreds credential proof to verifier
@@ -9,10 +9,10 @@ Scenario: Holder presents anoncreds credential proof to verifier
And Issuer has an anoncred schema definition
And Issuer offers anoncred to Holder
And Holder receives the credential offer
- And Holder accepts credential offer for anoncred
+ And Holder accepts anoncred credential offer
And Issuer issues the credential
And Holder receives the issued credential
When Verifier sends a anoncreds request for proof presentation to Holder using credential definition issued by Issuer
- And Holder receives the anoncreds request
+ And Holder receives the presentation proof request
And Holder accepts the anoncreds presentation request
# Then Verifier has the proof verified FIXME
diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature
new file mode 100644
index 0000000000..2e9689f772
--- /dev/null
+++ b/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature
@@ -0,0 +1,41 @@
+@jwt @issuance
+Feature: Issue JWT credential
+
+ Scenario: Issuing jwt credential with published PRISM DID
+ Given Issuer and Holder have an existing connection
+ And Issuer has a published DID for JWT
+ And Holder has an unpublished DID for JWT
+ When Issuer offers a jwt credential to Holder with "short" form DID
+ And Holder receives the credential offer
+ And Holder accepts jwt credential offer
+ And Issuer issues the credential
+ Then Holder receives the issued credential
+
+ Scenario: Issuing jwt credential with a schema
+ Given Issuer and Holder have an existing connection
+ And Issuer has a published DID for JWT
+ And Issuer has published STUDENT_SCHEMA schema
+ And Holder has an unpublished DID for JWT
+ When Issuer offers a jwt credential to Holder with "short" form using STUDENT_SCHEMA schema
+ And Holder receives the credential offer
+ And Holder accepts jwt credential offer
+ And Issuer issues the credential
+ Then Holder receives the issued credential
+
+ Scenario: Issuing jwt credential with wrong claim structure for schema
+ Given Issuer and Holder have an existing connection
+ And Issuer has a published DID for JWT
+ And Issuer has published STUDENT_SCHEMA schema
+ And Holder has an unpublished DID for JWT
+ When Issuer offers a jwt credential to Holder with "short" form DID with wrong claims structure using STUDENT_SCHEMA schema
+ Then Issuer should see that credential issuance has failed
+
+ Scenario: Issuing jwt credential with unpublished PRISM DID
+ Given Issuer and Holder have an existing connection
+ And Issuer has an unpublished DID for JWT
+ And Holder has an unpublished DID for JWT
+ And Issuer offers a jwt credential to Holder with "long" form DID
+ And Holder receives the credential offer
+ And Holder accepts jwt credential offer
+ And Issuer issues the credential
+ Then Holder receives the issued credential
diff --git a/tests/integration-tests/src/test/resources/features/proofs/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature
similarity index 60%
rename from tests/integration-tests/src/test/resources/features/proofs/present_proof.feature
rename to tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature
index c2fa51b80d..5b0f46564c 100644
--- a/tests/integration-tests/src/test/resources/features/proofs/present_proof.feature
+++ b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature
@@ -1,26 +1,26 @@
-@proof @jwt
+@jwt @proof
Feature: Present Proof Protocol
- Scenario: Holder presents credential proof to verifier
+ Scenario: Holder presents jwt credential proof to verifier
Given Verifier and Holder have an existing connection
And Holder has a jwt issued credential from Issuer
- When Verifier sends a request for proof presentation to Holder
+ When Verifier sends a request for jwt proof presentation to Holder
And Holder receives the presentation proof request
- And Holder makes the presentation of the proof
+ And Holder makes the jwt presentation of the proof
Then Verifier has the proof verified
- Scenario: Holder presents proof to verifier which is the issuer itself
+ Scenario: Holder presents jwt proof to verifier which is the issuer itself
Given Issuer and Holder have an existing connection
And Holder has a jwt issued credential from Issuer
- When Issuer sends a request for proof presentation to Holder
+ When Issuer sends a request for jwt proof presentation to Holder
And Holder receives the presentation proof request
- And Holder makes the presentation of the proof
+ And Holder makes the jwt presentation of the proof
Then Issuer has the proof verified
Scenario: Verifier rejects holder proof
Given Verifier and Holder have an existing connection
And Holder has a jwt issued credential from Issuer
- When Verifier sends a request for proof presentation to Holder
+ When Verifier sends a request for jwt proof presentation to Holder
And Holder receives the presentation proof request
And Holder rejects the proof
Then Holder sees the proof is rejected
diff --git a/tests/integration-tests/src/test/resources/features/revocation/revoke_jwt_credential.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature
similarity index 55%
rename from tests/integration-tests/src/test/resources/features/revocation/revoke_jwt_credential.feature
rename to tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature
index b60e7d7bf9..508380402d 100644
--- a/tests/integration-tests/src/test/resources/features/revocation/revoke_jwt_credential.feature
+++ b/tests/integration-tests/src/test/resources/features/credential/jwt/revocation.feature
@@ -1,21 +1,21 @@
-@revocation @jwt
-Feature: Credential revocation - JWT
+@jwt @revocation
+Feature: JWT Credential revocation
Background:
Given Holder has a jwt issued credential from Issuer
- Scenario: Revoke issued credential
+ Scenario: Revoke jwt issued credential
When Issuer revokes the credential issued to Holder
Then Issuer should see the credential was revoked
- When Issuer sends a request for proof presentation to Holder
+ When Issuer sends a request for jwt proof presentation to Holder
And Holder receives the presentation proof request
- And Holder makes the presentation of the proof
+ And Holder makes the jwt presentation of the proof
Then Issuer sees the proof returned verification failed
- Scenario: Holder tries to revoke credential from issuer
+ Scenario: Holder tries to revoke jwt credential from issuer
When Holder tries to revoke credential from Issuer
- And Issuer sends a request for proof presentation to Holder
+ And Issuer sends a request for jwt proof presentation to Holder
And Holder receives the presentation proof request
- And Holder makes the presentation of the proof
+ And Holder makes the jwt presentation of the proof
Then Issuer has the proof verified
And Issuer should see the credential is not revoked
diff --git a/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature
new file mode 100644
index 0000000000..29af552150
--- /dev/null
+++ b/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature
@@ -0,0 +1,37 @@
+@sdjwt @issuance
+Feature: Issue SD-JWT credential
+
+ Scenario: Issuing sd-jwt credential
+ Given Issuer and Holder have an existing connection
+ And Issuer has a published DID for SD_JWT
+ And Holder has an unpublished DID for SD_JWT
+ When Issuer offers a sd-jwt credential to Holder
+ And Holder receives the credential offer
+ And Holder accepts credential offer for sd-jwt
+ And Issuer issues the credential
+ Then Holder receives the issued credential
+ And Holder checks the sd-jwt credential contents
+
+ Scenario: Issuing sd-jwt credential with holder binding
+ Given Issuer and Holder have an existing connection
+ And Issuer has a published DID for SD_JWT
+ And Holder has an unpublished DID for SD_JWT
+ When Issuer offers a sd-jwt credential to Holder
+ And Holder receives the credential offer
+ And Holder accepts credential offer for sd-jwt with 'auth-1' key binding
+ And Issuer issues the credential
+ Then Holder receives the issued credential
+ Then Holder checks the sd-jwt credential contents with holder binding
+
+# Scenario: Issuing sd-jwt with wrong algorithm
+# Given Issuer and Holder have an existing connection
+# When Issuer prepares a custom PRISM DID
+# And Issuer adds a 'secp256k1' key for 'assertionMethod' purpose with 'assert-1' name to the custom PRISM DID
+# And Issuer adds a 'secp256k1' key for 'authentication' purpose with 'auth-1' name to the custom PRISM DID
+# And Issuer creates the custom PRISM DID
+# And Holder has an unpublished DID for SD_JWT
+# And Issuer offers a sd-jwt credential to Holder
+# And Holder receives the credential offer
+# And Holder accepts credential offer for sd-jwt
+# And Issuer tries to issue the credential
+# Then Issuer should see that credential issuance has failed
diff --git a/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature
new file mode 100644
index 0000000000..e5d273bf37
--- /dev/null
+++ b/tests/integration-tests/src/test/resources/features/credential/sdjwt/present_proof.feature
@@ -0,0 +1,35 @@
+@sdjwt @proof
+Feature: Present SD-JWT Proof Protocol
+
+ Scenario Outline: Holder presents sd-jwt proof to
+ Given and Holder have an existing connection
+ And Holder has a sd-jwt issued credential from Issuer
+ When sends a request for sd-jwt proof presentation to Holder requesting [firstName] claims
+ And Holder receives the presentation proof request
+ And Holder makes the sd-jwt presentation of the proof disclosing [firstName] claims
+ Then has the proof verified
+ Examples:
+ | verifier |
+ | Verifier |
+ | Issuer |
+
+ Scenario Outline: Holder presents a bound sd-jwt proof to
+ Given and Holder have an existing connection
+ And Holder has a bound sd-jwt issued credential from Issuer
+ When sends a request for sd-jwt proof presentation to Holder requesting [firstName] claims
+ And Holder receives the presentation proof request
+ And Holder makes the sd-jwt presentation of the proof disclosing [firstName] claims
+ Then has the sd-jwt proof verified
+ Examples:
+ | verifier |
+ | Verifier |
+ | Issuer |
+
+# Scenario: Holder presents sd-jwt proof with different claims from requested
+# Given Verifier and Holder have an existing connection
+# And Holder has a bound sd-jwt issued credential from Issuer
+# When Verifier sends a request for sd-jwt proof presentation to Holder requesting [firstName] claims
+# And Holder receives the presentation proof request
+# And Holder makes the sd-jwt presentation of the proof disclosing [lastName] claims
+# Then Verifier sees the proof returned verification failed
+
diff --git a/tests/integration-tests/src/test/resources/features/credentials/issue_jwt_with_published_did.feature b/tests/integration-tests/src/test/resources/features/credentials/issue_jwt_with_published_did.feature
deleted file mode 100644
index e54fd17c2d..0000000000
--- a/tests/integration-tests/src/test/resources/features/credentials/issue_jwt_with_published_did.feature
+++ /dev/null
@@ -1,34 +0,0 @@
-@RFC0453 @AIP20 @credentials
-Feature: Issue JWT Credentials with published DID
-
- Background:
- Given Issuer and Holder have an existing connection
- And Issuer has a published DID for JWT
- And Issuer has published STUDENT_SCHEMA schema
- And Holder has an unpublished DID for JWT
-
- Scenario: Issuing credential with published PRISM DID
- When Issuer offers a credential to Holder with "short" form DID
- And Holder receives the credential offer
- And Holder accepts credential offer for JWT
- And Issuer issues the credential
- Then Holder receives the issued credential
-
- Scenario: Issuing anoncred with published PRISM DID
- Given Issuer has an anoncred schema definition
- When Issuer offers anoncred to Holder
- And Holder receives the credential offer
- And Holder accepts credential offer for anoncred
- And Issuer issues the credential
- Then Holder receives the issued credential
-
- Scenario: Issuing credential with an existing schema
- When Issuer offers a credential to Holder with "short" form using STUDENT_SCHEMA schema
- And Holder receives the credential offer
- And Holder accepts credential offer for JWT
- And Issuer issues the credential
- Then Holder receives the issued credential
-
- Scenario: Issuing credential with wrong claim structure for schema
- When Issuer offers a credential to Holder with "short" form DID with wrong claims structure using STUDENT_SCHEMA schema
- Then Issuer should see that credential issuance has failed
diff --git a/tests/integration-tests/src/test/resources/features/credentials/issue_jwt_with_unpublished_did.feature b/tests/integration-tests/src/test/resources/features/credentials/issue_jwt_with_unpublished_did.feature
deleted file mode 100644
index a658a0c453..0000000000
--- a/tests/integration-tests/src/test/resources/features/credentials/issue_jwt_with_unpublished_did.feature
+++ /dev/null
@@ -1,14 +0,0 @@
-@RFC0453 @AIP20 @credentials
-Feature: Issue JWT Credentials with unpublished DID
-
- Background:
- Given Issuer and Holder have an existing connection
- And Issuer has an unpublished DID for JWT
- And Holder has an unpublished DID for JWT
-
- Scenario: Issuing credential with unpublished PRISM DID
- And Issuer offers a credential to Holder with "long" form DID
- And Holder receives the credential offer
- And Holder accepts credential offer for JWT
- And Issuer issues the credential
- Then Holder receives the issued credential
diff --git a/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature b/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature
new file mode 100644
index 0000000000..2f30658ad8
--- /dev/null
+++ b/tests/integration-tests/src/test/resources/features/oid4vci/issue_jwt.feature
@@ -0,0 +1,22 @@
+@oid4vci
+Feature: Issue JWT Credentials using OID4VCI authorization code flow
+
+Background:
+ Given Issuer has a published DID for JWT
+ And Issuer has published STUDENT_SCHEMA schema
+ And Issuer has an existing oid4vci issuer
+ And Issuer has "StudentProfile" credential configuration created from STUDENT_SCHEMA
+
+Scenario: Issuing credential with published PRISM DID
+ When Issuer creates an offer using "StudentProfile" configuration with "short" form DID
+ And Holder receives oid4vci offer from Issuer
+ And Holder resolves oid4vci issuer metadata and login via front-end channel
+ And Holder presents the access token with JWT proof on CredentialEndpoint
+ Then Holder sees credential issued successfully from CredentialEndpoint
+
+Scenario: Issuing credential with unpublished PRISM DID
+ When Issuer creates an offer using "StudentProfile" configuration with "long" form DID
+ And Holder receives oid4vci offer from Issuer
+ And Holder resolves oid4vci issuer metadata and login via front-end channel
+ And Holder presents the access token with JWT proof on CredentialEndpoint
+ Then Holder sees credential issued successfully from CredentialEndpoint
diff --git a/tests/integration-tests/src/test/resources/logback-test.xml b/tests/integration-tests/src/test/resources/logback-test.xml
index 357d669652..b28cee7002 100644
--- a/tests/integration-tests/src/test/resources/logback-test.xml
+++ b/tests/integration-tests/src/test/resources/logback-test.xml
@@ -13,4 +13,6 @@
+
+
diff --git a/version.sbt b/version.sbt
index 2e05788ede..163d9c0802 100644
--- a/version.sbt
+++ b/version.sbt
@@ -1 +1 @@
-ThisBuild / version := "1.37.0-SNAPSHOT"
+ThisBuild / version := "1.38.0-SNAPSHOT"