From cc10ba28fd32d3da8cd62071253d8408ef667e1c Mon Sep 17 00:00:00 2001 From: Fabio Pinheiro Date: Mon, 16 Sep 2024 10:47:17 +0100 Subject: [PATCH 1/9] docs: Update README.md rempve GITHUB_TOKEN prerequisite (#1360) Signed-off-by: Fabio Pinheiro --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 8c3f541f78..068cc97803 100644 --- a/README.md +++ b/README.md @@ -77,17 +77,6 @@ The next diagrams offer a concise architectural overview, depicting a Cloud Agen - SBT (latest version) - Git (for cloning the repository) - Docker (for running the PostgreSQL database, Hashicorp Vault, APISIX, and PRISM Node) -- [GITHUB_TOKEN](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) environment variable (required for SBT plugins and access to the GitHub packages) - -#### Login to GitHub packages - -To login to GitHub packages, you need to create a personal access token and set it as an environment variable together with your GitHub username. Here is an example of how you can do this: - -```bash -export GITHUB_TOKEN=your-personal-access-token -export GITHUB_USER=your-github-username -docker login ghcr.io -u $GITHUB_USER -p $GITHUB_TOKEN -``` #### Compile, Test, create the docker image of the Cloud Agent From 6a0a3ea3deef712479420ac23ef58aaafa7df78a Mon Sep 17 00:00:00 2001 From: patlo-iog Date: Mon, 16 Sep 2024 17:02:11 +0700 Subject: [PATCH 2/9] fix: cannot reuse the same credential-offer in oid4vci (#1361) Signed-off-by: Pat Losoponkul --- .../CredentialIssuerController.scala | 7 ++++-- .../service/OIDCCredentialIssuerService.scala | 25 +++++++++++++++++-- .../OIDCCredentialIssuerServiceSpec.scala | 2 +- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/controller/CredentialIssuerController.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/controller/CredentialIssuerController.scala index ff6f64ec20..69300821ec 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/controller/CredentialIssuerController.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/controller/CredentialIssuerController.scala @@ -173,7 +173,7 @@ case class CredentialIssuerControllerImpl( nonce <- getNonceFromJwt(JWT(jwt)) .mapError(throwable => badRequestInvalidProof(jwt, throwable.getMessage)) session <- credentialIssuerService - .getIssuanceSessionByNonce(nonce) + .getPendingIssuanceSessionByNonce(nonce) .mapError(_ => badRequestInvalidProof(jwt, "nonce is not associated to the issuance session")) subjectDid <- parseDIDUrlFromKeyId(JWT(jwt)) .map(_.did) @@ -240,11 +240,14 @@ case class CredentialIssuerControllerImpl( request: NonceRequest ): IO[ErrorResponse, NonceResponse] = { credentialIssuerService - .getIssuanceSessionByIssuerState(request.issuerState) + .getPendingIssuanceSessionByIssuerState(request.issuerState) .map(session => NonceResponse(session.nonce)) .mapError(ue => internalServerError(detail = Some(s"Unexpected error while creating credential offer: ${ue.userFacingMessage}")) ) + // Ideally we don't want this here, but this is used by keycloak plugin and error is not bubbled to the user. + // We log it manually to help with debugging until we find a better way. + .tapError(error => ZIO.logWarning(error.toString())) } override def createCredentialIssuer( diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala index e9e58451cb..839f1a4d8e 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/service/OIDCCredentialIssuerService.scala @@ -63,7 +63,9 @@ trait OIDCCredentialIssuerService { def getIssuanceSessionByIssuerState(issuerState: String): IO[Error, IssuanceSession] - def getIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession] + def getPendingIssuanceSessionByIssuerState(issuerState: String): IO[Error, IssuanceSession] + + def getPendingIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession] def updateIssuanceSession(issuanceSession: IssuanceSession): IO[Error, IssuanceSession] } @@ -85,6 +87,11 @@ object OIDCCredentialIssuerService { s"Credential configuration with id $credentialConfigurationId not found for issuer $issuerId" } + case class IssuanceSessionAlreadyIssued(issuerState: String) extends Error { + override def userFacingMessage: String = + s"Issuance session with issuerState $issuerState is already issued" + } + case class CredentialSchemaError(cause: org.hyperledger.identus.pollux.core.model.error.CredentialSchemaError) extends Error { override def userFacingMessage: String = cause.userFacingMessage @@ -230,6 +237,10 @@ case class OIDCCredentialIssuerServiceImpl( .mapError(e => ServiceError(s"Failed to get issuance session: ${e.message}")) .someOrFail(ServiceError(s"The IssuanceSession with the issuerState $issuerState does not exist")) + override def getPendingIssuanceSessionByIssuerState( + issuerState: String + ): IO[Error, IssuanceSession] = getIssuanceSessionByIssuerState(issuerState).ensurePendingSession + override def createCredentialOffer( credentialIssuerBaseUrl: URL, issuerId: UUID, @@ -261,11 +272,12 @@ case class OIDCCredentialIssuerServiceImpl( ) ) - def getIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession] = { + def getPendingIssuanceSessionByNonce(nonce: String): IO[Error, IssuanceSession] = { issuanceSessionStorage .getByNonce(nonce) .mapError(e => ServiceError(s"Failed to get issuance session: ${e.message}")) .someOrFail(ServiceError(s"The IssuanceSession with the nonce $nonce does not exist")) + .ensurePendingSession } override def updateIssuanceSession(issuanceSession: IssuanceSession): IO[Error, IssuanceSession] = { @@ -295,6 +307,15 @@ case class OIDCCredentialIssuerServiceImpl( issuingDid = issuerDid, ) } + + extension [R, A](result: ZIO[R, Error, IssuanceSession]) { + def ensurePendingSession: ZIO[R, Error, IssuanceSession] = + result.flatMap { session => + if session.subjectDid.isEmpty + then ZIO.succeed(session) + else ZIO.fail(IssuanceSessionAlreadyIssued(session.issuerState)) + } + } } object OIDCCredentialIssuerServiceImpl { 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 7e37850272..ea80db437c 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 @@ -211,6 +211,6 @@ object OIDCCredentialIssuerServiceSpec MockDIDNonSecretStorage.empty, getCredentialConfigurationExpectations.toLayer, layers - ) + ), ) } From 91cb4e7f4371a651617265279a27fefe9551887c Mon Sep 17 00:00:00 2001 From: Bassam Date: Mon, 16 Sep 2024 10:09:05 -0400 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20VCVerification=20API=20support=20AR?= =?UTF-8?q?RAY=20or=20OBJECT=20as=20Credential=20Sc=E2=80=A6=20(#1355)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bassam Riman --- .../VcVerificationServiceImpl.scala | 5 +- .../resources/vc-schema-driver-license.json | 6 +- .../test/resources/vc-schema-personal.json | 4 +- .../VcVerificationServiceImplSpec.scala | 80 +++++++++++++++++++ 4 files changed, 88 insertions(+), 7 deletions(-) diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImpl.scala index 18b5370bb5..7ad3c9588d 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImpl.scala @@ -2,7 +2,8 @@ package org.hyperledger.identus.pollux.core.service.verification import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema import org.hyperledger.identus.pollux.core.service.URIDereferencer -import org.hyperledger.identus.pollux.vc.jwt.{DidResolver, JWT, JWTVerification, JwtCredential} +import org.hyperledger.identus.pollux.vc.jwt.{CredentialPayload, DidResolver, JWT, JWTVerification, JwtCredential} +import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits import zio.* import java.time.OffsetDateTime @@ -104,7 +105,7 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe CredentialSchema .validateJWTCredentialSubject( credentialSchema.id, - decodedJwt.credentialSubject.noSpaces, + CredentialPayload.Implicits.jwtVcEncoder(decodedJwt.vc).noSpaces, uriDereferencer ) .mapError(error => diff --git a/pollux/core/src/test/resources/vc-schema-driver-license.json b/pollux/core/src/test/resources/vc-schema-driver-license.json index e5a9f20cb7..f2ff164f61 100644 --- a/pollux/core/src/test/resources/vc-schema-driver-license.json +++ b/pollux/core/src/test/resources/vc-schema-driver-license.json @@ -11,16 +11,16 @@ "format": "date-time" }, "drivingLicenseID": { - "type": "string" + "type": "integer" }, "drivingClass": { "type": "integer" } }, "required": ["dateOfIssuance", "drivingLicenseID", "drivingClass"], - "additionalProperties": false + "additionalProperties": true } }, "required": ["credentialSubject"], - "additionalProperties": false + "additionalProperties": true } diff --git a/pollux/core/src/test/resources/vc-schema-personal.json b/pollux/core/src/test/resources/vc-schema-personal.json index 3f2d0e9b15..cb0cb16a4c 100644 --- a/pollux/core/src/test/resources/vc-schema-personal.json +++ b/pollux/core/src/test/resources/vc-schema-personal.json @@ -18,9 +18,9 @@ } }, "required": ["email", "userName", "age"], - "additionalProperties": false + "additionalProperties": true } }, "required": ["credentialSubject"], - "additionalProperties": false + "additionalProperties": true } diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala index 3d9f41da04..62be6e2e87 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala @@ -515,6 +515,86 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ), + test("verify subject given multiple schema") { + for { + svc <- ZIO.service[VcVerificationService] + verifier = "did:prism:verifier" + jwtCredentialPayload = W3cCredentialPayload( + `@context` = + Set("https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1"), + maybeId = Some("http://example.edu/credentials/3732"), + `type` = Set("VerifiableCredential", "UniversityDegreeCredential"), + issuer = Left(issuer.did.toString), + issuanceDate = Instant.parse("2010-01-01T00:00:00Z"), + maybeExpirationDate = Some(Instant.parse("2010-01-12T00:00:00Z")), + maybeValidFrom = Some(Instant.parse("2010-01-12T00:00:00Z")), + maybeValidUntil = Some(Instant.parse("2010-01-12T00:00:00Z")), + maybeCredentialSchema = Some( + Right( + List( + CredentialSchema( + id = "resource:///vc-schema-personal.json", + `type` = "JsonSchemaValidator2018" + ), + CredentialSchema( + id = "resource:///vc-schema-driver-license.json", + `type` = "JsonSchemaValidator2018" + ) + ) + ) + ), + credentialSubject = Json.obj( + "userName" -> Json.fromString("Alice"), + "age" -> Json.fromInt(42), + "email" -> Json.fromString("alice@wonderland.com"), + "dateOfIssuance" -> Json.fromString("2000-01-01T10:00:00Z"), + "drivingLicenseID" -> Json.fromInt(12345), + "drivingClass" -> Json.fromInt(5) + ), + maybeCredentialStatus = Some( + CredentialStatus( + id = "did:work:MDP8AsFhHzhwUvGNuYkX7T;id=06e126d1-fa44-4882-a243-1e326fbe21db;version=1.0", + `type` = "StatusList2021Entry", + statusPurpose = StatusPurpose.Revocation, + statusListIndex = 0, + statusListCredential = "https://example.com/credentials/status/3" + ) + ), + maybeRefreshService = Some( + RefreshService( + id = "https://example.edu/refresh/3732", + `type` = "ManualRefreshService2018" + ) + ), + maybeEvidence = Option.empty, + maybeTermsOfUse = Option.empty, + aud = Set(verifier) + ).toJwtCredentialPayload + signedJwtCredential = issuer.signer.encode(jwtCredentialPayload.asJson) + result <- + svc.verify( + List( + VcVerificationRequest(signedJwtCredential.value, VcVerification.SubjectVerification) + ) + ) + } yield { + assertTrue( + result.contains( + VcVerificationResult( + signedJwtCredential.value, + VcVerification.SubjectVerification, + true + ) + ) + ) + } + }.provideSomeLayer( + MockDIDService.empty ++ + MockManagedDIDService.empty ++ + ResourceURIDereferencerImpl.layer >+> + someVcVerificationServiceLayer ++ + ZLayer.succeed(WalletAccessContext(WalletId.random)) + ), test("verify nbf given valid") { for { svc <- ZIO.service[VcVerificationService] From 65679503d9078d19b8eb4678ba20915ee5932e58 Mon Sep 17 00:00:00 2001 From: Fabio Pinheiro Date: Wed, 18 Sep 2024 07:40:35 +0100 Subject: [PATCH 4/9] docs: Update README.md fix user documentation URL (#1362) Signed-off-by: Fabio Pinheiro --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 068cc97803..fd98fcea32 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ In order to use the Cloud Agent, you establish a business logic controller respo As a result, you can concentrate on crafting self-sovereign identity solutions using well-known web development tools, without the need to delve into the intricacies of lower-level cryptography and identity protocol internals. +## User documentation + +All documentation, tutorials and API references for the Identus ecosystem can be found at [https://hyperledger.github.io/identus-docs/](https://hyperledger.github.io/identus-docs/) + ## Features * Rest API @@ -178,10 +182,6 @@ The following tutorials will help you get started with the Cloud Agent and issue * [Issuing verifiable credentials (VCs)](https://docs.atalaprism.io/tutorials/credentials/issue) * [Presenting VC proofs](https://docs.atalaprism.io/tutorials/credentials/present-proof) -## User documentation - -All extended documentation, tutorials and API references for the Identus ecosystem can be found at - ## Contributing Please read our [contributions guidelines](./CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin (DCO) commit signing](./DCO.md). From 56200cfae6f62b823a74e67eb060face2ee3ecbc Mon Sep 17 00:00:00 2001 From: Fabio Pinheiro Date: Wed, 18 Sep 2024 07:55:11 +0100 Subject: [PATCH 5/9] feat: Add KID to the credential-offers API - ATL-7704 (#1320) Signed-off-by: FabioPinheiro Co-authored-by: Yurii Shynbuiev --- .../castor/core/model/ProtoModelHelper.scala | 10 ++- .../castor/core/model/did/PublicKey.scala | 5 +- .../castor/core/model/error/package.scala | 4 +- .../core/util/DIDOperationValidator.scala | 19 ++--- .../model/did/w3c/W3CModelHelperSpec.scala | 23 +++--- .../castor/core/service/MockDIDService.scala | 5 +- .../core/util/DIDOperationValidatorSpec.scala | 49 +++++------ .../identus/castor/core/util/GenUtils.scala | 5 +- .../server/jobs/BackgroundJobsHelper.scala | 46 ++++++----- .../server/jobs/PresentBackgroundJobs.scala | 81 +++++++++---------- .../agent/server/jobs/StatusListJobs.scala | 7 +- .../controller/DIDCommControllerError.scala | 6 +- .../controller/IssueControllerImpl.scala | 2 + .../CreateIssueCredentialRecordRequest.scala | 15 ++++ .../controller/IssueControllerImplSpec.scala | 2 + .../agent/walletapi/model/KeyManagement.scala | 8 +- .../model/error/DIDSecretStorageError.scala | 2 +- .../walletapi/service/ManagedDIDService.scala | 3 +- .../service/ManagedDIDServiceImpl.scala | 9 ++- .../service/handler/DIDCreateHandler.scala | 7 +- .../service/handler/DIDUpdateHandler.scala | 9 ++- .../service/handler/PublicationHandler.scala | 3 +- .../sql/JdbcDIDNonSecretStorage.scala | 9 ++- .../walletapi/sql/JdbcDIDSecretStorage.scala | 17 ++-- .../storage/DIDNonSecretStorage.scala | 5 +- .../walletapi/storage/DIDSecretStorage.scala | 9 ++- .../agent/walletapi/util/KeyResolver.scala | 5 +- .../walletapi/util/OperationFactory.scala | 19 ++--- .../vault/VaultDIDSecretStorage.scala | 17 ++-- .../service/ManagedDIDServiceSpec.scala | 35 ++++---- .../service/MockManagedDIDService.scala | 5 +- .../storage/DIDSecretStorageSpec.scala | 52 ++++++------ .../storage/MockDIDNonSecretStorage.scala | 9 ++- .../walletapi/storage/StorageSpecHelper.scala | 3 +- .../walletapi/util/OperationFactorySpec.scala | 21 ++--- .../model/error/CredentialServiceError.scala | 3 +- .../core/service/CredentialService.scala | 2 + .../core/service/CredentialServiceImpl.scala | 14 +++- .../service/CredentialServiceNotifier.scala | 4 + .../service/CredentialServiceSpecHelper.scala | 1 + .../core/service/MockCredentialService.scala | 2 + .../identus/shared/models/KeyId.scala | 4 + .../identus/shared/crypto/Apollo.scala | 1 + 43 files changed, 322 insertions(+), 235 deletions(-) diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala index f2ef3f156d..cd12aeeb4b 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala @@ -24,8 +24,10 @@ import org.hyperledger.identus.castor.core.model.did.{ UpdateDIDAction, VerificationRelationship } +import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.value import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.UriOrJsonEndpoint import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.utils.Traverse.* import zio.* @@ -122,7 +124,7 @@ private[castor] trait ProtoModelHelper { extension (publicKey: PublicKey) { def toProto: node_models.PublicKey = { node_models.PublicKey( - id = publicKey.id, + id = publicKey.id.value, usage = publicKey.purpose match { case VerificationRelationship.Authentication => node_models.KeyUsage.AUTHENTICATION_KEY case VerificationRelationship.AssertionMethod => node_models.KeyUsage.ISSUING_KEY @@ -140,7 +142,7 @@ private[castor] trait ProtoModelHelper { extension (internalPublicKey: InternalPublicKey) { def toProto: node_models.PublicKey = { node_models.PublicKey( - id = internalPublicKey.id, + id = internalPublicKey.id.value, usage = internalPublicKey.purpose match { case InternalKeyPurpose.Master => node_models.KeyUsage.MASTER_KEY case InternalKeyPurpose.Revocation => node_models.KeyUsage.REVOCATION_KEY @@ -313,13 +315,13 @@ private[castor] trait ProtoModelHelper { } yield purpose match { case purpose: VerificationRelationship => PublicKey( - id = publicKey.id, + id = KeyId(publicKey.id), purpose = purpose, publicKeyData = keyData ) case purpose: InternalKeyPurpose => InternalPublicKey( - id = publicKey.id, + id = KeyId(publicKey.id), purpose = purpose, publicKeyData = keyData ) diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala index 496b71abb3..da27a541c2 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala @@ -1,9 +1,10 @@ package org.hyperledger.identus.castor.core.model.did import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId final case class PublicKey( - id: String, + id: KeyId, purpose: VerificationRelationship, publicKeyData: PublicKeyData ) @@ -14,7 +15,7 @@ enum InternalKeyPurpose { } final case class InternalPublicKey( - id: String, + id: KeyId, purpose: InternalKeyPurpose, publicKeyData: PublicKeyData ) diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/error/package.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/error/package.scala index 001db37e68..ddad1ad8f2 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/error/package.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/error/package.scala @@ -1,5 +1,7 @@ package org.hyperledger.identus.castor.core.model +import org.hyperledger.identus.shared.models.KeyId + package object error { sealed trait DIDOperationError @@ -27,7 +29,7 @@ package object error { final case class TooManyDidPublicKeyAccess(limit: Int, access: Option[Int]) extends OperationValidationError final case class TooManyDidServiceAccess(limit: Int, access: Option[Int]) extends OperationValidationError final case class InvalidArgument(msg: String) extends OperationValidationError - final case class InvalidMasterKeyData(ids: Seq[String]) extends OperationValidationError + final case class InvalidMasterKeyData(ids: Seq[KeyId]) extends OperationValidationError } } diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidator.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidator.scala index 8de4e44269..01dca95443 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidator.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidator.scala @@ -5,6 +5,7 @@ import org.hyperledger.identus.castor.core.model.error.OperationValidationError import org.hyperledger.identus.castor.core.util.DIDOperationValidator.Config import org.hyperledger.identus.castor.core.util.Prelude.* import org.hyperledger.identus.shared.crypto.Apollo +import org.hyperledger.identus.shared.models.KeyId import zio.* import scala.collection.immutable.ArraySeq @@ -70,14 +71,14 @@ private object CreateOperationValidator extends BaseOperationValidator { else Left(OperationValidationError.InvalidArgument("create operation must contain at least 1 master key")) } - private def extractKeyIds(operation: PrismDIDOperation.Create): Seq[String] = operation.publicKeys.map { + private def extractKeyIds(operation: PrismDIDOperation.Create): Seq[KeyId] = operation.publicKeys.map { case PublicKey(id, _, _) => id case InternalPublicKey(id, _, _) => id } private def extractKeyData( operation: PrismDIDOperation.Create - ): Seq[(String, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] = + ): Seq[(KeyId, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] = operation.publicKeys.map { case PublicKey(id, purpose, data) => (id, purpose, data) case InternalPublicKey(id, purpose, data) => (id, purpose, data) @@ -139,15 +140,15 @@ private object UpdateOperationValidator extends BaseOperationValidator { ) } - private def extractKeyIds(operation: PrismDIDOperation.Update): Seq[String] = operation.actions.collect { + private def extractKeyIds(operation: PrismDIDOperation.Update): Seq[KeyId] = operation.actions.collect { case UpdateDIDAction.AddKey(pk) => pk.id case UpdateDIDAction.AddInternalKey(pk) => pk.id - case UpdateDIDAction.RemoveKey(id) => id + case UpdateDIDAction.RemoveKey(id) => KeyId(id) } private def extractKeyData( operation: PrismDIDOperation.Update - ): Seq[(String, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] = + ): Seq[(KeyId, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] = operation.actions.collect { case UpdateDIDAction.AddKey(pk) => (pk.id, pk.purpose, pk.publicKeyData) case UpdateDIDAction.AddInternalKey(pk) => (pk.id, pk.purpose, pk.publicKeyData) @@ -182,12 +183,12 @@ private object DeactivateOperationValidator extends BaseOperationValidator { private trait BaseOperationValidator { - type KeyIdExtractor[T] = T => Seq[String] + type KeyIdExtractor[T] = T => Seq[KeyId] type ServiceIdExtractor[T] = T => Seq[String] type ServiceTypeExtractor[T] = T => Seq[(String, ServiceType)] type ServiceEndpointExtractor[T] = T => Seq[(String, ServiceEndpoint)] type ContextExtractor[T] = T => Seq[Seq[String]] - type KeyDataExtractor[T] = T => Seq[(String, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] + type KeyDataExtractor[T] = T => Seq[(KeyId, VerificationRelationship | InternalKeyPurpose, PublicKeyData)] protected def validateMaxPublicKeysAccess[T <: PrismDIDOperation]( config: Config @@ -266,7 +267,7 @@ private trait BaseOperationValidator { keyIdExtractor: KeyIdExtractor[T] ): Either[OperationValidationError, Unit] = { val ids = keyIdExtractor(operation) - val invalidIds = ids.filterNot(UriUtils.isValidUriFragment) + val invalidIds = ids.map(_.value).filterNot(UriUtils.isValidUriFragment) if (invalidIds.isEmpty) Right(()) else Left( @@ -289,7 +290,7 @@ private trait BaseOperationValidator { config: Config )(operation: T, keyIdExtractor: KeyIdExtractor[T]): Either[OperationValidationError, Unit] = { val ids = keyIdExtractor(operation) - val invalidIds = ids.filter(_.length > config.maxIdSize) + val invalidIds = ids.filter(_.value.length > config.maxIdSize) if (invalidIds.isEmpty) Right(()) else Left( diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/w3c/W3CModelHelperSpec.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/w3c/W3CModelHelperSpec.scala index eaac4b58d2..f1dc30b34a 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/w3c/W3CModelHelperSpec.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/model/did/w3c/W3CModelHelperSpec.scala @@ -9,6 +9,7 @@ import org.hyperledger.identus.castor.core.model.did.{ VerificationRelationship } import org.hyperledger.identus.castor.core.util.GenUtils +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.* import zio.test.Assertion.* @@ -17,13 +18,13 @@ object W3CModelHelperSpec extends ZIOSpecDefault { import W3CModelHelper.* - private def generateInternalPublicKey(id: String, purpose: InternalKeyPurpose = InternalKeyPurpose.Master) = + private def generateInternalPublicKey(id: KeyId, purpose: InternalKeyPurpose = InternalKeyPurpose.Master) = GenUtils.internalPublicKey .map(_.copy(id = id, purpose = purpose)) .runCollectN(1) .map(_.head) - private def generatePublicKey(id: String, purpose: VerificationRelationship) = + private def generatePublicKey(id: KeyId, purpose: VerificationRelationship) = GenUtils.publicKey.map(_.copy(id = id, purpose = purpose)).runCollectN(1).map(_.head) private def generateService(id: String) = @@ -31,8 +32,8 @@ object W3CModelHelperSpec extends ZIOSpecDefault { private def generateDIDData( did: CanonicalPrismDID, - masterKeyId: String = "master-0", - keyIds: Seq[(String, VerificationRelationship)] = Seq.empty, + masterKeyId: KeyId = KeyId("master-0"), + keyIds: Seq[(KeyId, VerificationRelationship)] = Seq.empty, serviceIds: Seq[String] = Seq.empty, context: Seq[String] = Seq.empty ) = @@ -49,11 +50,11 @@ object W3CModelHelperSpec extends ZIOSpecDefault { didData <- generateDIDData( did = did, keyIds = Seq( - "auth-0" -> VerificationRelationship.Authentication, - "iss-0" -> VerificationRelationship.AssertionMethod, - "comm-0" -> VerificationRelationship.KeyAgreement, - "capinv-0" -> VerificationRelationship.CapabilityInvocation, - "capdel-0" -> VerificationRelationship.CapabilityDelegation + KeyId("auth-0") -> VerificationRelationship.Authentication, + KeyId("iss-0") -> VerificationRelationship.AssertionMethod, + KeyId("comm-0") -> VerificationRelationship.KeyAgreement, + KeyId("capinv-0") -> VerificationRelationship.CapabilityInvocation, + KeyId("capdel-0") -> VerificationRelationship.CapabilityDelegation ), serviceIds = Seq("service-0") ) @@ -92,7 +93,7 @@ object W3CModelHelperSpec extends ZIOSpecDefault { longFormDID = PrismDID.buildLongFormFromOperation(PrismDIDOperation.Create(Nil, Nil, Nil)) didData <- generateDIDData( did = did, - keyIds = Seq("auth-0" -> VerificationRelationship.Authentication) + keyIds = Seq(KeyId("auth-0") -> VerificationRelationship.Authentication) ) didDoc = didData.toW3C(longFormDID) } yield assert(didDoc.id)(equalTo(longFormDID.toString)) && @@ -104,7 +105,7 @@ object W3CModelHelperSpec extends ZIOSpecDefault { did <- ZIO.fromEither(PrismDID.buildCanonicalFromSuffix("0" * 64)) didData <- generateDIDData( did = did, - keyIds = Seq("auth-0" -> VerificationRelationship.Authentication), + keyIds = Seq(KeyId("auth-0") -> VerificationRelationship.Authentication), serviceIds = Seq("service-0"), context = Seq("user-defined-context") ) diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala index f3d4b8fd40..ae128f47ed 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.castor.core.model.error import org.hyperledger.identus.shared.crypto.{Apollo, Secp256k1KeyPair} import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import zio.{mock, IO, URLayer, ZIO, ZLayer} import zio.mock.{Expectation, Mock, Proxy} import zio.test.Assertion @@ -47,7 +48,7 @@ object MockDIDService extends Mock[DIDService] { val createOperation = PrismDIDOperation.Create( publicKeys = Seq( InternalPublicKey( - id = "master-0", + id = KeyId("master-0"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECCompressedKeyData( crv = EllipticCurve.SECP256K1, @@ -55,7 +56,7 @@ object MockDIDService extends Mock[DIDService] { ) ), PublicKey( - id = "key-0", + id = KeyId("key-0"), purpose = verificationRelationship, publicKeyData = PublicKeyData.ECCompressedKeyData( crv = EllipticCurve.SECP256K1, diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala index 6e2eee7396..72957513b0 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.castor.core.model.error.OperationValidationError import org.hyperledger.identus.castor.core.util.DIDOperationValidator.Config import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.* import zio.test.Assertion.* @@ -95,7 +96,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { publicKeys: Seq[PublicKey] = Nil, internalKeys: Seq[InternalPublicKey] = Seq( InternalPublicKey( - id = "master0", + id = KeyId("master0"), purpose = InternalKeyPurpose.Master, publicKeyData = publicKeyData ) @@ -111,8 +112,8 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("validates a Create operation successfully when using the provided ZLayer") { val operation = PrismDIDOperation.Create( publicKeys = Seq( - PublicKey("key1", VerificationRelationship.Authentication, publicKeyData), - InternalPublicKey("master0", InternalKeyPurpose.Master, publicKeyData) + PublicKey(KeyId("key1"), VerificationRelationship.Authentication, publicKeyData), + InternalPublicKey(KeyId("master0"), InternalKeyPurpose.Master, publicKeyData) ), services = Seq( Service("service1", ServiceType.Single("LinkedDomains"), "http://example.com/") @@ -130,14 +131,14 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject CreateOperation on too many DID publicKey access") { val publicKeys = (1 to 10).map(i => PublicKey( - id = s"key$i", + id = KeyId(s"key$i"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) ) val internalKeys = (1 to 10).map(i => InternalPublicKey( - id = s"master$i", + id = KeyId(s"master$i"), purpose = InternalKeyPurpose.Master, publicKeyData = publicKeyData ) @@ -150,14 +151,14 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject CreateOperation on duplicated DID public key id") { val publicKeys = (1 to 10).map(i => PublicKey( - id = s"key$i", + id = KeyId(s"key$i"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) ) val internalKeys = Seq( InternalPublicKey( - id = s"key1", + id = KeyId(s"key1"), purpose = InternalKeyPurpose.Master, publicKeyData = publicKeyData ) @@ -196,7 +197,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject CreateOperation on invalid key-id") { val publicKeys = (1 to 2).map(i => PublicKey( - id = s"key $i", + id = KeyId(s"key $i"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -221,7 +222,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { }, test("reject CreateOperation on too long key-id") { val publicKey = PublicKey( - id = s"key-${"0" * 100}", + id = KeyId(s"key-${"0" * 100}"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -313,12 +314,12 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val op = createPrismDIDOperation( publicKeys = Seq( PublicKey( - id = "key-0", + id = KeyId("key-0"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ), PublicKey( - id = "key-1", + id = KeyId("key-1"), purpose = VerificationRelationship.AssertionMethod, publicKeyData = publicKeyData ) @@ -344,7 +345,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val op = createPrismDIDOperation( internalKeys = Seq( InternalPublicKey( - id = "master0", + id = KeyId("master0"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECKeyData( crv = EllipticCurve.ED25519, @@ -353,7 +354,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) ), InternalPublicKey( - id = "master1", + id = KeyId("master1"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECKeyData( crv = EllipticCurve.SECP256K1, @@ -364,7 +365,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) ) assert(DIDOperationValidator(Config.default).validate(op))( - isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq("master0", "master1")))) + isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq(KeyId("master0"), KeyId("master1"))))) ) } ).provideLayer(testLayer) @@ -400,8 +401,10 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("accept valid UpdateOperation") { val op = updatePrismDIDOperation( Seq( - UpdateDIDAction.AddKey(PublicKey("key0", VerificationRelationship.Authentication, publicKeyData)), - UpdateDIDAction.AddInternalKey(InternalPublicKey("master0", InternalKeyPurpose.Master, publicKeyData)), + UpdateDIDAction.AddKey(PublicKey(KeyId("key0"), VerificationRelationship.Authentication, publicKeyData)), + UpdateDIDAction.AddInternalKey( + InternalPublicKey(KeyId("master0"), InternalKeyPurpose.Master, publicKeyData) + ), UpdateDIDAction.RemoveKey("key0"), UpdateDIDAction.AddService( Service("service0", ServiceType.Single("LinkedDomains"), "http://example.com/") @@ -422,7 +425,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val addKeyActions = (1 to 10).map(i => UpdateDIDAction.AddKey( PublicKey( - id = s"key$i", + id = KeyId(s"key$i"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -431,7 +434,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { val addInternalKeyActions = (1 to 10).map(i => UpdateDIDAction.AddInternalKey( InternalPublicKey( - id = s"master$i", + id = KeyId(s"master$i"), purpose = InternalKeyPurpose.Master, publicKeyData = publicKeyData ) @@ -469,7 +472,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject UpdateOperation on invalid key-id") { val action1 = UpdateDIDAction.AddKey( PublicKey( - id = "key 1", + id = KeyId("key 1"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -497,7 +500,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject UpdateOperation on too long key-id") { val action1 = UpdateDIDAction.AddKey( PublicKey( - id = s"key-${"0" * 100}", + id = KeyId(s"key-${"0" * 100}"), purpose = VerificationRelationship.Authentication, publicKeyData = publicKeyData ) @@ -591,7 +594,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { test("reject UpdateOperation when master key is not a secp256k1 key") { val action1 = UpdateDIDAction.AddInternalKey( InternalPublicKey( - id = "master0", + id = KeyId("master0"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECKeyData( crv = EllipticCurve.ED25519, @@ -602,7 +605,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) val action2 = UpdateDIDAction.AddInternalKey( InternalPublicKey( - id = "master1", + id = KeyId("master1"), purpose = InternalKeyPurpose.Master, publicKeyData = PublicKeyData.ECKeyData( crv = EllipticCurve.SECP256K1, @@ -613,7 +616,7 @@ object DIDOperationValidatorSpec extends ZIOSpecDefault { ) val op = updatePrismDIDOperation(Seq(action1, action2)) assert(DIDOperationValidator(Config.default).validate(op))( - isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq("master0", "master1")))) + isLeft(equalTo(OperationValidationError.InvalidMasterKeyData(Seq(KeyId("master0"), KeyId("master1"))))) ) } ) diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala index 2b33c0f3b6..29622b3b70 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala @@ -5,6 +5,7 @@ import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.{UriOrJsonEndpoint, UriValue} import org.hyperledger.identus.shared.crypto.Apollo import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.Gen @@ -41,14 +42,14 @@ object GenUtils { id <- uriFragment purpose <- Gen.fromIterable(VerificationRelationship.values) keyData <- publicKeyData - } yield PublicKey(id, purpose, keyData) + } yield PublicKey(KeyId(id), purpose, keyData) val internalPublicKey: Gen[Any, InternalPublicKey] = for { id <- uriFragment purpose <- Gen.fromIterable(InternalKeyPurpose.values) keyData <- publicKeyData - } yield InternalPublicKey(id, purpose, keyData) + } yield InternalPublicKey(KeyId(id), purpose, keyData) val service: Gen[Any, Service] = for { diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobsHelper.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobsHelper.scala index 2f046f99f0..1708ca1517 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobsHelper.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/BackgroundJobsHelper.scala @@ -59,9 +59,10 @@ trait BackgroundJobsHelper { } yield longFormPrismDID } - def createJwtIssuer( + def createJwtVcIssuer( jwtIssuerDID: PrismDID, - verificationRelationship: VerificationRelationship + verificationRelationship: VerificationRelationship, + kidIssuer: Option[KeyId], ): ZIO[ DIDService & ManagedDIDService & WalletAccessContext, BackgroundJobError | GetManagedDIDError | DIDResolutionError, @@ -71,19 +72,23 @@ trait BackgroundJobsHelper { managedDIDService <- ZIO.service[ManagedDIDService] didService <- ZIO.service[DIDService] // Automatically infer keyId to use by resolving DID and choose the corresponding VerificationRelationship - issuingKeyId <- didService - .resolveDID(jwtIssuerDID) - .someOrFail(BackgroundJobError.InvalidState(s"Issuing DID resolution result is not found")) - .map { case (_, didData) => - didData.publicKeys - .find(pk => pk.purpose == verificationRelationship && pk.publicKeyData.crv == EllipticCurve.SECP256K1) - .map(_.id) - } - .someOrFail( - BackgroundJobError.InvalidState( - s"Issuing DID doesn't have a key in ${verificationRelationship.name} to use: $jwtIssuerDID" + + // FIXME kidIssuer + issuingKeyId <- + // kidIssuer.orElse + didService + .resolveDID(jwtIssuerDID) + .someOrFail(BackgroundJobError.InvalidState(s"Issuing DID resolution result is not found")) + .map { case (_, didData) => + didData.publicKeys + .find(pk => pk.purpose == verificationRelationship && pk.publicKeyData.crv == EllipticCurve.SECP256K1) + .map(_.id) + } + .someOrFail( + BackgroundJobError.InvalidState( + s"Issuing DID doesn't have a key in ${verificationRelationship.name} to use: $jwtIssuerDID" + ) ) - ) jwtIssuer <- managedDIDService .findDIDKeyPair(jwtIssuerDID.asCanonical, issuingKeyId) .flatMap { @@ -93,9 +98,12 @@ trait BackgroundJobsHelper { .InvalidState(s"Issuer key-pair does not exist in the wallet: ${jwtIssuerDID.toString}#$issuingKeyId") ) case Some(Ed25519KeyPair(publicKey, privateKey)) => - ZIO.fail( - BackgroundJobError.InvalidState( - s"Issuer key-pair '$issuingKeyId' is of the type Ed25519. It's not supported by this feature in this version" + ZIO.succeed( + JwtIssuer( + jwtIssuerDID.did, + // org.hyperledger.identus.castor.core.model.did.DID.fromStringUnsafe(jwtIssuerDID.toString), + EdSigner(Ed25519KeyPair(publicKey, privateKey), Some(issuingKeyId)), + publicKey.toJava ) ) case Some(X25519KeyPair(publicKey, privateKey)) => @@ -108,7 +116,7 @@ trait BackgroundJobsHelper { ZIO.succeed( JwtIssuer( jwtIssuerDID.did, - ES256KSigner(privateKey.toJavaPrivateKey), + ES256KSigner(privateKey.toJavaPrivateKey, Some(issuingKeyId)), publicKey.toJavaPublicKey ) ) @@ -161,7 +169,7 @@ trait BackgroundJobsHelper { .map { case (_, didData) => didData.publicKeys .find(pk => - pk.id == keyId.value + pk.id == keyId && pk.purpose == verificationRelationship && pk.publicKeyData.crv == EllipticCurve.ED25519 ) .map(_.id) 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 85106e084f..297602f425 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 @@ -639,14 +639,15 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ] = { val result = credentialFormat match { - case CredentialFormat.JWT => handleJWT(id, credentialsToUse, requestPresentation) - case CredentialFormat.SDJWT => handleSDJWT(id, credentialsToUse, requestPresentation) + case CredentialFormat.JWT => handle_JWT_VC(id, credentialsToUse, requestPresentation) + case CredentialFormat.SDJWT => handle_SD_JWT_VC(id, credentialsToUse, requestPresentation) case CredentialFormat.AnonCreds => handleAnoncred(id, maybeCredentialsToUseJson, requestPresentation) } result @@ metric } - private def handleJWT( + /** prover presentation pending to generated flow */ + private def handle_JWT_VC( id: DidCommID, credentialsToUse: Option[List[String]], requestPresentation: RequestPresentation @@ -654,40 +655,36 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { CredentialService & DIDService & COMMON_RESOURCES, ERROR, Unit - ] = { - val proverPresentationPendingToGeneratedFlow = for { - walletAccessContext <- ZIO - .fromOption(requestPresentation.to) - .flatMap(buildWalletAccessContextLayer) - .mapError(_ => PresentationError.RequestPresentationMissingField(id.value, "recipient")) - _ <- for { - presentationService <- ZIO.service[PresentationService] - prover <- createPrismDIDIssuerFromPresentationCredentials(id, credentialsToUse.getOrElse(Nil)) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - presentation <- - for { - presentationPayload <- - presentationService - .createJwtPresentationPayloadFromRecord( - id, - prover, - Instant.now() - ) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - signedJwtPresentation = JwtPresentation.toEncodedJwt( - presentationPayload.toW3CPresentationPayload, - prover - ) - presentation <- createPresentation(id, requestPresentation, signedJwtPresentation) - } yield presentation - _ <- presentationService - .markPresentationGenerated(id, presentation) - .provideSomeLayer(ZLayer.succeed(walletAccessContext)) - } yield () + ] = for { + walletAccessContext <- ZIO + .fromOption(requestPresentation.to) + .flatMap(buildWalletAccessContextLayer) + .mapError(_ => PresentationError.RequestPresentationMissingField(id.value, "recipient")) + _ <- for { + presentationService <- ZIO.service[PresentationService] + prover <- createPrismDIDIssuerFromPresentationCredentials(id, credentialsToUse.getOrElse(Nil)) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + presentation <- + for { + presentationPayload <- + presentationService + .createJwtPresentationPayloadFromRecord( + id, + prover, + Instant.now() + ) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) + signedJwtPresentation = JwtPresentation.toEncodedJwt( + presentationPayload.toW3CPresentationPayload, + prover + ) + presentation <- createPresentation(id, requestPresentation, signedJwtPresentation) + } yield presentation + _ <- presentationService + .markPresentationGenerated(id, presentation) + .provideSomeLayer(ZLayer.succeed(walletAccessContext)) } yield () - - proverPresentationPendingToGeneratedFlow - } + } yield () private def createPresentation( id: DidCommID, @@ -719,7 +716,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { ) } - private def handleSDJWT( + private def handle_SD_JWT_VC( id: DidCommID, credentialsToUse: Option[List[String]], requestPresentation: RequestPresentation @@ -810,16 +807,18 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { credentialRecordUuid <- ZIO .attempt(DidCommID(credentialRecordId)) .mapError(_ => PresentationError.NotValidDidCommID(credentialRecordId)) - vcSubjectId <- credentialService + issueCredentialRecord <- credentialService .findById(credentialRecordUuid) .someOrFail(CredentialServiceError.RecordNotFound(credentialRecordUuid)) - .map(_.subjectId) - .someOrElseZIO(ZIO.dieMessage(s"VC SubjectId not found in credential record: $credentialRecordUuid")) + vcSubjectId <- issueCredentialRecord.subjectId match + case None => ZIO.dieMessage(s"VC SubjectId not found in credential record: $credentialRecordUuid") + case Some(value) => ZIO.succeed(value) proverDID <- ZIO .fromEither(PrismDID.fromString(vcSubjectId)) .mapError(e => CredentialServiceError.UnsupportedDidFormat(vcSubjectId)) longFormPrismDID <- getLongForm(proverDID, true) - jwtIssuer <- createJwtIssuer(longFormPrismDID, VerificationRelationship.Authentication) + mKidIssuer = issueCredentialRecord.keyId + jwtIssuer <- createJwtVcIssuer(longFormPrismDID, VerificationRelationship.Authentication, mKidIssuer) } yield jwtIssuer } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala index 7ba57aa3fb..d57cccb079 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala @@ -6,6 +6,7 @@ import org.hyperledger.identus.mercury.* import org.hyperledger.identus.mercury.protocol.revocationnotificaiton.RevocationNotification import org.hyperledger.identus.pollux.core.service.{CredentialService, CredentialStatusListService} import org.hyperledger.identus.pollux.vc.jwt.revocation.{VCStatusList2021, VCStatusList2021Error} +import org.hyperledger.identus.shared.models.* import org.hyperledger.identus.shared.models.WalletAccessContext import org.hyperledger.identus.shared.utils.DurationOps.toMetricsSeconds import zio.* @@ -30,7 +31,11 @@ object StatusListJobs extends BackgroundJobsHelper { vcStatusListCredJson <- ZIO .fromEither(io.circe.parser.parse(vcStatusListCredString)) .mapError(_.underlying) - issuer <- createJwtIssuer(statusListWithCreds.issuer, VerificationRelationship.AssertionMethod) + issuer <- createJwtVcIssuer( + statusListWithCreds.issuer, + VerificationRelationship.AssertionMethod, + None + ) vcStatusListCred <- VCStatusList2021 .decodeFromJson(vcStatusListCredJson, issuer) .mapError(x => new Throwable(x.msg)) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala index 2c46434de9..1bc1a644d4 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.didcomm.controller import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.models.{Failure, StatusCode} +import org.hyperledger.identus.shared.models.KeyId sealed trait DIDCommControllerError extends Failure { override def namespace = "DIDCommControllerError" @@ -34,8 +35,9 @@ object DIDCommControllerError { override def userFacingMessage: String = s"The Peer DID was not found in this agent: ${did.value}" } - final case class PeerDIDKeyNotFoundError(did: DidId, keyId: String) extends DIDCommControllerError { + final case class PeerDIDKeyNotFoundError(did: DidId, keyId: KeyId) extends DIDCommControllerError { override def statusCode: StatusCode = StatusCode.UnprocessableContent - override def userFacingMessage: String = s"The Peer DID does not contain the required key: DID=$did, keyId=$keyId" + override def userFacingMessage: String = + s"The Peer DID does not contain the required key: DID=${did.value}, keyId=${keyId.value}" } } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala index 48a5a5619d..587b3376da 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/IssueControllerImpl.scala @@ -67,6 +67,7 @@ class IssueControllerImpl( .createJWTIssueCredentialRecord( pairwiseIssuerDID = offerContext.pairwiseIssuerDID, pairwiseHolderDID = offerContext.pairwiseHolderDID, + kidIssuer = request.issuingKid, thid = DidCommID(), maybeSchemaId = request.schemaId, claims = jsonClaims, @@ -91,6 +92,7 @@ class IssueControllerImpl( .createSDJWTIssueCredentialRecord( pairwiseIssuerDID = offerContext.pairwiseIssuerDID, pairwiseHolderDID = offerContext.pairwiseHolderDID, + kidIssuer = request.issuingKid, thid = DidCommID(), maybeSchemaId = request.schemaId, claims = jsonClaims, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala index 930d8e36f2..405b302409 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/http/CreateIssueCredentialRecordRequest.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.issue.controller.http import org.hyperledger.identus.api.http.Annotation import org.hyperledger.identus.issue.controller.http.CreateIssueCredentialRecordRequest.annotations +import org.hyperledger.identus.shared.models.KeyId import sttp.tapir.{Schema, Validator} import sttp.tapir.json.zio.schemaForZioJsonValue import sttp.tapir.Schema.annotations.{description, encodedExample} @@ -48,6 +49,9 @@ final case class CreateIssueCredentialRecordRequest( @description(annotations.issuingDID.description) @encodedExample(annotations.issuingDID.example) issuingDID: Option[String], + @description(annotations.issuingKid.description) + @encodedExample(annotations.issuingKid.example) + issuingKid: Option[KeyId], @description(annotations.connectionId.description) @encodedExample(annotations.connectionId.example) connectionId: Option[UUID], @@ -133,6 +137,16 @@ object CreateIssueCredentialRecordRequest { example = Some("did:prism:3bb0505d13fcb04d28a48234edb27b0d4e6d7e18a81e2c1abab58f3bbc21ce6f") ) + object issuingKid + extends Annotation[Option[String]]( + description = """ + |Specified the key ID (kid) of the DID, it will be used to sign credential. + |User should specify just the partial identifier of the key. The full id of the kid MUST be "#" + |Note the cryto algorithm used with depend type of the key. + |""".stripMargin, + example = Some("kid1") // TODO 20240902 get the defualt name of the key we generete. + ) + object connectionId extends Annotation[Option[UUID]]( description = """ @@ -170,6 +184,7 @@ object CreateIssueCredentialRecordRequest { given decoder: JsonDecoder[CreateIssueCredentialRecordRequest] = DeriveJsonDecoder.gen[CreateIssueCredentialRecordRequest] + given schemaJson: Schema[KeyId] = Schema.schemaForString.map[KeyId](v => Some(KeyId(v)))(KeyId.value) given schema: Schema[CreateIssueCredentialRecordRequest] = Schema.derived } diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala index 75327b35b7..edcb5f1411 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala @@ -22,6 +22,7 @@ import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation import org.hyperledger.identus.pollux.core.model.{CredentialFormat, DidCommID, IssueCredentialRecord} import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.{ProtocolState, Role} import org.hyperledger.identus.pollux.core.service.MockCredentialService +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletId import sttp.client3.{basicRequest, DeserializationException, UriContext} import sttp.client3.ziojson.* @@ -56,6 +57,7 @@ object IssueControllerImplSpec extends ZIOSpecDefault with IssueControllerTestTo issuingDID = Some( "did:prism:332518729a7b7805f73a788e0944802527911901d9b7c16152281be9bc62d944:CosBCogBEkkKFW15LWtleS1hdXRoZW50aWNhdGlvbhAESi4KCXNlY3AyNTZrMRIhAuYoRIefsLhkvYwHz8gDtkG2b0kaZTDOLj_SExWX1fOXEjsKB21hc3RlcjAQAUouCglzZWNwMjU2azESIQLOzab8f0ibt1P0zdMfoWDQTSlPc8_tkV9Jk5BBsXB8fA" ), + issuingKid = Some(KeyId("some_kid_id")), connectionId = Some(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")) ) private val issueCredentialRecord = IssueCredentialRecord( diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala index d3aa382dc2..f4a84785f8 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/model/KeyManagement.scala @@ -161,15 +161,15 @@ final case class ManagedDIDRandKeyMeta( } private[walletapi] final case class CreateDIDKey( - hdKeys: Map[String, ManagedDIDHdKeyPath], - randKeys: Map[String, ManagedDIDRandKeyPair] + hdKeys: Map[String, ManagedDIDHdKeyPath], // TODO use type KeyId + randKeys: Map[String, ManagedDIDRandKeyPair] // TODO use type KeyId ) { def randKeyMeta: Map[String, ManagedDIDRandKeyMeta] = randKeys.map { case (k, v) => k -> v.meta } } private[walletapi] final case class UpdateDIDKey( - hdKeys: Map[String, ManagedDIDHdKeyPath], - randKeys: Map[String, ManagedDIDRandKeyPair], + hdKeys: Map[String, ManagedDIDHdKeyPath], // TODO use type KeyId + randKeys: Map[String, ManagedDIDRandKeyPair], // TODO use type KeyId counter: HdKeyIndexCounter ) { def randKeyMeta: Map[String, ManagedDIDRandKeyMeta] = randKeys.map { case (k, v) => k -> v.meta } 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 9112f841c0..f8e4f75569 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 @@ -11,7 +11,7 @@ sealed trait DIDSecretStorageError( } object DIDSecretStorageError { - case class KeyNotFoundError(didId: DidId, keyId: String) + case class KeyNotFoundError(didId: DidId, keyId: KeyId) extends DIDSecretStorageError( StatusCode.NotFound, s"The not found: keyId='$keyId', didId='$didId'" diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala index e8cba848c1..c233844df1 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala @@ -7,6 +7,7 @@ import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.mercury.model.* import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -23,7 +24,7 @@ trait ManagedDIDService { def findDIDKeyPair( did: CanonicalPrismDID, - keyId: String + keyId: KeyId ): URIO[WalletAccessContext, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] def getManagedDIDState(did: CanonicalPrismDID): ZIO[WalletAccessContext, GetManagedDIDError, Option[ManagedDIDState]] diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala index b021c28d33..c93806c040 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala @@ -14,6 +14,7 @@ import org.hyperledger.identus.castor.core.util.DIDOperationValidator import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -33,13 +34,13 @@ class ManagedDIDServiceImpl private[walletapi] ( createDIDSem: Semaphore ) extends ManagedDIDService { - private val AGREEMENT_KEY_ID = "agreement" - private val AUTHENTICATION_KEY_ID = "authentication" + private val AGREEMENT_KEY_ID = KeyId("agreement") + private val AUTHENTICATION_KEY_ID = KeyId("authentication") private val keyResolver = KeyResolver(apollo, nonSecretStorage, secretStorage, walletSecretStorage) private val publicationHandler = PublicationHandler(didService, keyResolver)(DEFAULT_MASTER_KEY_ID) private val didCreateHandler = - DIDCreateHandler(apollo, nonSecretStorage, secretStorage, walletSecretStorage)(DEFAULT_MASTER_KEY_ID) + DIDCreateHandler(apollo, nonSecretStorage, secretStorage, walletSecretStorage)(KeyId(DEFAULT_MASTER_KEY_ID)) private val didUpdateHandler = DIDUpdateHandler(apollo, nonSecretStorage, secretStorage, walletSecretStorage, publicationHandler) @@ -56,7 +57,7 @@ class ManagedDIDServiceImpl private[walletapi] ( override def findDIDKeyPair( did: CanonicalPrismDID, - keyId: String + keyId: KeyId ): URIO[WalletAccessContext, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] = nonSecretStorage .getManagedDIDState(did) 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 1c2f239b55..479b34ed2b 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 @@ -12,6 +12,7 @@ import org.hyperledger.identus.agent.walletapi.storage.{DIDNonSecretStorage, DID import org.hyperledger.identus.agent.walletapi.util.OperationFactory import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -21,7 +22,7 @@ private[walletapi] class DIDCreateHandler( secretStorage: DIDSecretStorage, walletSecretStorage: WalletSecretStorage, )( - masterKeyId: String + masterKeyId: KeyId ) { def materialize( didTemplate: ManagedDIDTemplate @@ -64,8 +65,8 @@ private[walletapi] class DIDCreateMaterialImpl(nonSecretStorage: DIDNonSecretSto .mapError(CreateManagedDIDError.WalletStorageError.apply) _ <- ZIO.foreach(keys.randKeys.toList) { case (keyId, key) => key.keyPair match { - case kp: Ed25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, keyId, operationHash, kp) - case kp: X25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, keyId, operationHash, kp) + case kp: Ed25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, KeyId(keyId), operationHash, kp) + case kp: X25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, KeyId(keyId), operationHash, kp) } } } yield () 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 de517075dd..b93510bc69 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 @@ -19,6 +19,7 @@ import org.hyperledger.identus.castor.core.model.did.{ } import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation.Update import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -103,16 +104,16 @@ private class HdKeyUpdateMaterial(nonSecretStorage: DIDNonSecretStorage, secretS for { _ <- ZIO.foreach(keys.hdKeys.toList) { case (keyId, keyPath) => val meta = ManagedDIDKeyMeta.HD(keyPath) - nonSecretStorage.insertKeyMeta(did, keyId, meta, operationHash) + nonSecretStorage.insertKeyMeta(did, KeyId(keyId), meta, operationHash) } _ <- ZIO.foreach(keys.randKeyMeta.toList) { case (keyId, rand) => val meta = ManagedDIDKeyMeta.Rand(rand) - nonSecretStorage.insertKeyMeta(did, keyId, meta, operationHash) + nonSecretStorage.insertKeyMeta(did, KeyId(keyId), meta, operationHash) } _ <- ZIO.foreach(keys.randKeys.toList) { case (keyId, key) => key.keyPair match { - case kp: Ed25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, keyId, operationHash, kp) - case kp: X25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, keyId, operationHash, kp) + case kp: Ed25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, KeyId(keyId), operationHash, kp) + case kp: X25519KeyPair => secretStorage.insertPrismDIDKeyPair(did, KeyId(keyId), operationHash, kp) } } } yield () diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala index 41014a3c22..1807e9efd9 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala @@ -11,6 +11,7 @@ import org.hyperledger.identus.castor.core.model.did.{ import org.hyperledger.identus.castor.core.model.error.DIDOperationError import org.hyperledger.identus.castor.core.service.DIDService import org.hyperledger.identus.shared.crypto.Secp256k1KeyPair +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -25,7 +26,7 @@ class PublicationHandler(didService: DIDService, keyResolver: KeyResolver)(maste for { masterKeyPair <- keyResolver - .getKey(state.did, masterKeyId) + .getKey(state.did, KeyId(masterKeyId)) .someOrFail(Exception("master-key must exists in the wallet for signing DID operation and submit to Node")) .collect(Exception("master-key must be secp256k1 key")) { case keyPair: Secp256k1KeyPair => keyPair } .orDie 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 4bc6154035..85cada9985 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 @@ -17,6 +17,7 @@ import org.hyperledger.identus.shared.db.ContextAwareTask import org.hyperledger.identus.shared.db.Implicits.* import org.hyperledger.identus.shared.db.Implicits.given import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.interop.catz.* @@ -199,7 +200,7 @@ class JdbcDIDNonSecretStorage(xa: Transactor[ContextAwareTask], xb: Transactor[T override def getKeyMeta( did: PrismDID, - keyId: String + keyId: KeyId ): RIO[WalletAccessContext, Option[(ManagedDIDKeyMeta, Array[Byte])]] = { val status: ScheduledDIDOperationStatus = ScheduledDIDOperationStatus.Confirmed val cxnIO = @@ -216,7 +217,7 @@ class JdbcDIDNonSecretStorage(xa: Transactor[ContextAwareTask], xb: Transactor[T | LEFT JOIN public.prism_did_update_lineage ul ON hd.operation_hash = ul.operation_hash | WHERE | hd.did = $did - | AND hd.key_id = $keyId + | AND hd.key_id = ${keyId.value} | AND (ul.status = $status OR (ul.status IS NULL AND hd.operation_hash = sha256(ws.atala_operation_content))) """.stripMargin .query[ @@ -245,7 +246,7 @@ class JdbcDIDNonSecretStorage(xa: Transactor[ContextAwareTask], xb: Transactor[T override def insertKeyMeta( did: PrismDID, - keyId: String, + keyId: KeyId, meta: ManagedDIDKeyMeta, operationHash: Array[Byte] ): RIO[WalletAccessContext, Unit] = { @@ -258,7 +259,7 @@ class JdbcDIDNonSecretStorage(xa: Transactor[ContextAwareTask], xb: Transactor[T | VALUES | ( | $did, - | $keyId, + | ${keyId.value}, | ${keyUsage}, | ${keyIndex}, | $now, diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala index 0ab551a07d..76f9e2650f 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala @@ -10,6 +10,7 @@ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.crypto.jwk.{FromJWK, JWK} import org.hyperledger.identus.shared.db.ContextAwareTask import org.hyperledger.identus.shared.db.Implicits.* +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -28,13 +29,13 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt given didIdPut: Put[DidId] = Put[String].contramap(_.value) - override def getKey(did: DidId, keyId: String): RIO[WalletAccessContext, Option[OctetKeyPair]] = { + override def getKey(did: DidId, keyId: KeyId): RIO[WalletAccessContext, Option[OctetKeyPair]] = { val cxnIO = sql""" | SELECT key_pair | FROM public.peer_did_rand_key | WHERE | did = $did - | AND key_id = $keyId + | AND key_id = ${keyId.value} """.stripMargin .query[OctetKeyPair] .option @@ -42,7 +43,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt cxnIO.transactWallet(xa) } - override def insertKey(did: DidId, keyId: String, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] = { + override def insertKey(did: DidId, keyId: KeyId, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] = { val cxnIO = (now: InstantAsBigInt) => sql""" | INSERT INTO public.peer_did_rand_key( | did, @@ -52,7 +53,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt | ) values ( | ${did}, | ${now}, - | ${keyId}, + | ${keyId.value}, | ${keyPair} | ) """.stripMargin.update @@ -65,7 +66,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt override def insertPrismDIDKeyPair[K]( did: PrismDID, - keyId: String, + keyId: KeyId, operationHash: Array[Byte], keyPair: K )(using c: Conversion[K, JWK]): URIO[WalletAccessContext, Unit] = { @@ -80,7 +81,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt | ) values ( | ${did}, | ${now}, - | ${keyId}, + | ${keyId.value}, | ${operationHash}, | ${jwk} | ) @@ -92,7 +93,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt } yield () } - override def getPrismDIDKeyPair[K](did: PrismDID, keyId: String, operationHash: Array[Byte])(using + override def getPrismDIDKeyPair[K](did: PrismDID, keyId: KeyId, operationHash: Array[Byte])(using c: FromJWK[K] ): URIO[WalletAccessContext, Option[K]] = { val cxnIO = sql""" @@ -101,7 +102,7 @@ class JdbcDIDSecretStorage(xa: Transactor[ContextAwareTask]) extends DIDSecretSt | WHERE | did = $did | AND operation_hash = $operationHash - | AND key_id = $keyId + | AND key_id = ${keyId.value} """.stripMargin .query[JWK] .option diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala index 8527cff127..e1f84efb4b 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.agent.walletapi.model.* import org.hyperledger.identus.castor.core.model.did.{PrismDID, ScheduledDIDOperationStatus} import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} +import org.hyperledger.identus.shared.models.KeyId import zio.* trait DIDNonSecretStorage { @@ -24,11 +25,11 @@ trait DIDNonSecretStorage { def getHdKeyCounter(did: PrismDID): RIO[WalletAccessContext, Option[HdKeyIndexCounter]] /** Return a tuple of key metadata and the operation hash */ - def getKeyMeta(did: PrismDID, keyId: String): RIO[WalletAccessContext, Option[(ManagedDIDKeyMeta, Array[Byte])]] + def getKeyMeta(did: PrismDID, keyId: KeyId): RIO[WalletAccessContext, Option[(ManagedDIDKeyMeta, Array[Byte])]] def insertKeyMeta( did: PrismDID, - keyId: String, + keyId: KeyId, meta: ManagedDIDKeyMeta, operationHash: Array[Byte] ): RIO[WalletAccessContext, Unit] diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala index a0becbea46..4726e5a015 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala @@ -4,22 +4,23 @@ import com.nimbusds.jose.jwk.OctetKeyPair import org.hyperledger.identus.castor.core.model.did.PrismDID import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.crypto.jwk.{FromJWK, JWK} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* /** A simple single-user DID key storage */ trait DIDSecretStorage { - def insertKey(did: DidId, keyId: String, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] - def getKey(did: DidId, keyId: String): RIO[WalletAccessContext, Option[OctetKeyPair]] + def insertKey(did: DidId, keyId: KeyId, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] + def getKey(did: DidId, keyId: KeyId): RIO[WalletAccessContext, Option[OctetKeyPair]] def insertPrismDIDKeyPair[K]( did: PrismDID, - keyId: String, + keyId: KeyId, operationHash: Array[Byte], keyPair: K )(using c: Conversion[K, JWK]): URIO[WalletAccessContext, Unit] - def getPrismDIDKeyPair[K](did: PrismDID, keyId: String, operationHash: Array[Byte])(using + def getPrismDIDKeyPair[K](did: PrismDID, keyId: KeyId, operationHash: Array[Byte])(using c: FromJWK[K] ): URIO[WalletAccessContext, Option[K]] } 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 81a5fa8035..6460af34f5 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 @@ -9,6 +9,7 @@ import org.hyperledger.identus.agent.walletapi.model.{ import org.hyperledger.identus.agent.walletapi.storage.{DIDNonSecretStorage, DIDSecretStorage, WalletSecretStorage} import org.hyperledger.identus.castor.core.model.did.{EllipticCurve, PrismDID} import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.shared.models.WalletAccessContext import zio.* @@ -20,7 +21,7 @@ class KeyResolver( ) { def getKey( did: PrismDID, - keyId: String + keyId: KeyId, ): RIO[WalletAccessContext, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] = nonSecretStorage.getKeyMeta(did, keyId).flatMap { case None => ZIO.none @@ -36,7 +37,7 @@ class KeyResolver( private def getRandKey( did: PrismDID, - keyId: String, + keyId: KeyId, meta: ManagedDIDRandKeyMeta, operationHash: Array[Byte] ): RIO[WalletAccessContext, Option[Ed25519KeyPair | X25519KeyPair]] = { diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala index 5eb2d4614c..acabbb413d 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala @@ -11,6 +11,7 @@ import org.hyperledger.identus.shared.crypto.{ X25519PublicKey } import org.hyperledger.identus.shared.models.Base64UrlString +import org.hyperledger.identus.shared.models.KeyId import zio.* import scala.collection.immutable.ArraySeq @@ -41,7 +42,7 @@ class OperationFactory(apollo: Apollo) { * The index of the DID to be used for the key derivation */ def makeCreateOperation( - masterKeyId: String, + masterKeyId: KeyId, seed: Array[Byte] )( didIndex: Int, @@ -66,9 +67,9 @@ class OperationFactory(apollo: Apollo) { context = didTemplate.contexts ) keys = CreateDIDKey( - hdKeys = hdKeysWithCounter._1.map(i => i.publicKey.id -> i.path).toMap ++ - Map(masterKeyOutcome.publicKey.id -> masterKeyOutcome.path), - randKeys = randKeys.map(i => i.publicKey.id -> i.key).toMap, + hdKeys = hdKeysWithCounter._1.map(i => i.publicKey.id.value -> i.path).toMap ++ + Map(masterKeyOutcome.publicKey.id.value -> masterKeyOutcome.path), + randKeys = randKeys.map(i => i.publicKey.id.value -> i.key).toMap, ) } yield operation -> keys } @@ -110,8 +111,8 @@ class OperationFactory(apollo: Apollo) { } keys = actionWithKey.collect { case (UpdateManagedDIDAction.AddKey(_), Some(secret)) => secret } (randKeys, hdKeys) = keys.partitionMap { - case (pk, hdPath: ManagedDIDHdKeyPath) => Right(pk.id -> hdPath) - case (pk, keyPair: ManagedDIDRandKeyPair) => Left(pk.id -> keyPair) + case (pk, hdPath: ManagedDIDHdKeyPath) => Right(pk.id.value -> hdPath) + case (pk, keyPair: ManagedDIDRandKeyPair) => Left(pk.id.value -> keyPair) } operation = PrismDIDOperation.Update( did = did, @@ -163,7 +164,7 @@ class OperationFactory(apollo: Apollo) { toPublicKeyData(kp.publicKey) -> kp } KeyGenerationOutcome( - publicKey = PublicKey(template.id, template.purpose, publicKeyData), + publicKey = PublicKey(KeyId(template.id), template.purpose, publicKeyData), key = ManagedDIDRandKeyPair(template.purpose, keyPair) ) }.orDie @@ -177,12 +178,12 @@ class OperationFactory(apollo: Apollo) { val keyPath = keyCounter.path(purpose) for { keyPair <- deriveSecp256k1KeyPair(seed, keyPath) - publicKey = PublicKey(template.id, purpose, toPublicKeyData(keyPair.publicKey)) + publicKey = PublicKey(KeyId(template.id), purpose, toPublicKeyData(keyPair.publicKey)) } yield KeyDerivationOutcome(publicKey, keyPath, keyCounter.next(purpose)) } private def deriveInternalPublicKey(seed: Array[Byte])( - id: String, + id: KeyId, purpose: InternalKeyPurpose, keyCounter: HdKeyIndexCounter ): UIO[KeyDerivationOutcome[InternalPublicKey]] = { diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala index c40c6f4b57..1cf98aa9a1 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala @@ -7,13 +7,14 @@ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.crypto.jwk.{FromJWK, JWK} import org.hyperledger.identus.shared.crypto.Sha256Hash import org.hyperledger.identus.shared.models.{HexString, WalletAccessContext, WalletId} +import org.hyperledger.identus.shared.models.KeyId import zio.* import java.nio.charset.StandardCharsets class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) extends DIDSecretStorage { - override def insertKey(did: DidId, keyId: String, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] = { + override def insertKey(did: DidId, keyId: KeyId, keyPair: OctetKeyPair): RIO[WalletAccessContext, Int] = { for { walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId) (path, metadata) = peerDidKeyPath(walletId)(did, keyId) @@ -25,7 +26,7 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex } yield 1 } - override def getKey(did: DidId, keyId: String): RIO[WalletAccessContext, Option[OctetKeyPair]] = { + override def getKey(did: DidId, keyId: KeyId): RIO[WalletAccessContext, Option[OctetKeyPair]] = { for { walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId) (path, _) = peerDidKeyPath(walletId)(did, keyId) @@ -35,7 +36,7 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex override def insertPrismDIDKeyPair[K]( did: PrismDID, - keyId: String, + keyId: KeyId, operationHash: Array[Byte], keyPair: K )(using c: Conversion[K, JWK]): URIO[WalletAccessContext, Unit] = { @@ -51,7 +52,7 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex } yield () }.orDie - override def getPrismDIDKeyPair[K](did: PrismDID, keyId: String, operationHash: Array[Byte])(using + override def getPrismDIDKeyPair[K](did: PrismDID, keyId: KeyId, operationHash: Array[Byte])(using c: FromJWK[K] ): URIO[WalletAccessContext, Option[K]] = { for { @@ -65,9 +66,9 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex }.orDie /** @return A tuple of secret path and a secret custom_metadata */ - private def peerDidKeyPath(walletId: WalletId)(did: DidId, keyId: String): (String, Map[String, String]) = { + private def peerDidKeyPath(walletId: WalletId)(did: DidId, keyId: KeyId): (String, Map[String, String]) = { val basePath = s"${walletBasePath(walletId)}/dids/peer" - val relativePath = s"${did.value}/keys/$keyId" + val relativePath = s"${did.value}/keys/${keyId.value}" if (useSemanticPath) { s"$basePath/$relativePath" -> Map.empty } else { @@ -79,9 +80,9 @@ class VaultDIDSecretStorage(vaultKV: VaultKVClient, useSemanticPath: Boolean) ex /** @return A tuple of secret path and a secret custom_metadata */ private def prismDIDKeyPath( walletId: WalletId - )(did: PrismDID, keyId: String, operationHash: Array[Byte]): (String, Map[String, String]) = { + )(did: PrismDID, keyId: KeyId, operationHash: Array[Byte]): (String, Map[String, String]) = { val basePath = s"${walletBasePath(walletId)}/dids/prism" - val relativePath = s"${did.asCanonical}/keys/$keyId/${HexString.fromByteArray(operationHash)}" + val relativePath = s"${did.asCanonical}/keys/${keyId.value}/${HexString.fromByteArray(operationHash)}" if (useSemanticPath) { s"$basePath/$relativePath" -> Map.empty } else { diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala index 4ab90ff8e1..8448ae4131 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala @@ -16,6 +16,7 @@ import org.hyperledger.identus.castor.core.service.DIDService import org.hyperledger.identus.castor.core.util.DIDOperationValidator import org.hyperledger.identus.shared.crypto.{ApolloSpecHelper, Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport import org.hyperledger.identus.test.container.{DBTestUtils, VaultTestContainerSupport} import zio.* @@ -246,16 +247,16 @@ object ManagedDIDServiceSpec for { svc <- ZIO.service[ManagedDIDService] did <- svc.createAndStoreDID(template).map(_.asCanonical) - masterKey <- svc.nonSecretStorage.getKeyMeta(did, ManagedDIDService.DEFAULT_MASTER_KEY_ID).some.map(_._1) - key1 <- svc.nonSecretStorage.getKeyMeta(did, "key1").some.map(_._1) - key2 <- svc.nonSecretStorage.getKeyMeta(did, "key2").some.map(_._1) - key3 <- svc.nonSecretStorage.getKeyMeta(did, "key3").some.map(_._1) - key4 <- svc.nonSecretStorage.getKeyMeta(did, "key4").some.map(_._1) - masterKeyPair <- svc.findDIDKeyPair(did, ManagedDIDService.DEFAULT_MASTER_KEY_ID).some - key1KeyPair <- svc.findDIDKeyPair(did, "key1").some - key2KeyPair <- svc.findDIDKeyPair(did, "key2").some - key3KeyPair <- svc.findDIDKeyPair(did, "key3").some - key4KeyPair <- svc.findDIDKeyPair(did, "key4").some + masterKey <- svc.nonSecretStorage.getKeyMeta(did, KeyId(ManagedDIDService.DEFAULT_MASTER_KEY_ID)).some.map(_._1) + key1 <- svc.nonSecretStorage.getKeyMeta(did, KeyId("key1")).some.map(_._1) + key2 <- svc.nonSecretStorage.getKeyMeta(did, KeyId("key2")).some.map(_._1) + key3 <- svc.nonSecretStorage.getKeyMeta(did, KeyId("key3")).some.map(_._1) + key4 <- svc.nonSecretStorage.getKeyMeta(did, KeyId("key4")).some.map(_._1) + masterKeyPair <- svc.findDIDKeyPair(did, KeyId(ManagedDIDService.DEFAULT_MASTER_KEY_ID)).some + key1KeyPair <- svc.findDIDKeyPair(did, KeyId("key1")).some + key2KeyPair <- svc.findDIDKeyPair(did, KeyId("key2")).some + key3KeyPair <- svc.findDIDKeyPair(did, KeyId("key3")).some + key4KeyPair <- svc.findDIDKeyPair(did, KeyId("key4")).some } yield assert(masterKey)(isSubtype[ManagedDIDKeyMeta.HD](anything)) && assert(key1)(isSubtype[ManagedDIDKeyMeta.HD](anything)) && assert(key2)(isSubtype[ManagedDIDKeyMeta.HD](anything)) && @@ -425,9 +426,9 @@ object ManagedDIDServiceSpec ) _ <- svc.updateManagedDID(did, actions) _ <- svc.syncUnconfirmedUpdateOperations - key1KeyPair <- svc.findDIDKeyPair(did, "key-1").some - key2KeyPair <- svc.findDIDKeyPair(did, "key-2").some - key3KeyPair <- svc.findDIDKeyPair(did, "key-3").some + key1KeyPair <- svc.findDIDKeyPair(did, KeyId("key-1")).some + key2KeyPair <- svc.findDIDKeyPair(did, KeyId("key-2")).some + key3KeyPair <- svc.findDIDKeyPair(did, KeyId("key-3")).some } yield assert(key1KeyPair)(isSubtype[Secp256k1KeyPair](anything)) && assert(key2KeyPair)(isSubtype[Ed25519KeyPair](anything)) && assert(key3KeyPair)(isSubtype[X25519KeyPair](anything)) @@ -449,13 +450,13 @@ object ManagedDIDServiceSpec _ <- svc.updateManagedDID(did, actions) // 1st update _ <- testDIDSvc.setOperationStatus(ScheduledDIDOperationStatus.Confirmed) _ <- svc.syncUnconfirmedUpdateOperations - key1KeyPair1 <- svc.findDIDKeyPair(did, "key-1").some - key2KeyPair1 <- svc.findDIDKeyPair(did, "key-2").some + key1KeyPair1 <- svc.findDIDKeyPair(did, KeyId("key-1")).some + key2KeyPair1 <- svc.findDIDKeyPair(did, KeyId("key-2")).some _ <- svc.updateManagedDID(did, actions) // 2nd update _ <- testDIDSvc.setOperationStatus(ScheduledDIDOperationStatus.Rejected) _ <- svc.syncUnconfirmedUpdateOperations - key1KeyPair2 <- svc.findDIDKeyPair(did, "key-1").some - key2KeyPair2 <- svc.findDIDKeyPair(did, "key-2").some + key1KeyPair2 <- svc.findDIDKeyPair(did, KeyId("key-1")).some + key2KeyPair2 <- svc.findDIDKeyPair(did, KeyId("key-2")).some } yield assert(key1KeyPair1)(isSubtype[Secp256k1KeyPair](anything)) && assert(key2KeyPair1)(isSubtype[Ed25519KeyPair](anything)) && // 2nd update with rejected status does not update the key pair diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/MockManagedDIDService.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/MockManagedDIDService.scala index 98c2d01969..2e37e35faf 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/MockManagedDIDService.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/MockManagedDIDService.scala @@ -12,6 +12,7 @@ import org.hyperledger.identus.castor.core.model.did.{ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.mock.* import zio.test.Assertion @@ -20,7 +21,7 @@ object MockManagedDIDService extends Mock[ManagedDIDService] { object GetManagedDIDState extends Effect[CanonicalPrismDID, GetManagedDIDError, Option[ManagedDIDState]] object FindDIDKeyPair - extends Effect[(CanonicalPrismDID, String), Nothing, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] + extends Effect[(CanonicalPrismDID, KeyId), Nothing, Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] override val compose: URLayer[mock.Proxy, ManagedDIDService] = ZLayer { @@ -35,7 +36,7 @@ object MockManagedDIDService extends Mock[ManagedDIDService] { override def findDIDKeyPair( did: CanonicalPrismDID, - keyId: String + keyId: KeyId ): UIO[Option[Secp256k1KeyPair | Ed25519KeyPair | X25519KeyPair]] = proxy(FindDIDKeyPair, (did, keyId)) diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala index 60986e44cf..0ff4217449 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala @@ -13,6 +13,7 @@ import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Apollo, ApolloSpecHelper, Ed25519KeyPair, X25519KeyPair} import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} +import org.hyperledger.identus.shared.models.KeyId import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport import org.hyperledger.identus.test.container.{DBTestUtils, VaultTestContainerSupport} import zio.* @@ -90,10 +91,10 @@ object DIDSecretStorageSpec secretStorage <- ZIO.service[DIDSecretStorage] peerDID = PeerDID.makePeerDid() _ <- nonSecretStorage.createPeerDIDRecord(peerDID.did) - n1 <- secretStorage.insertKey(peerDID.did, "agreement", peerDID.jwkForKeyAgreement) - n2 <- secretStorage.insertKey(peerDID.did, "authentication", peerDID.jwkForKeyAuthentication) - key1 <- secretStorage.getKey(peerDID.did, "agreement") - key2 <- secretStorage.getKey(peerDID.did, "authentication") + n1 <- secretStorage.insertKey(peerDID.did, KeyId("agreement"), peerDID.jwkForKeyAgreement) + n2 <- secretStorage.insertKey(peerDID.did, KeyId("authentication"), peerDID.jwkForKeyAuthentication) + key1 <- secretStorage.getKey(peerDID.did, KeyId("agreement")) + key2 <- secretStorage.getKey(peerDID.did, KeyId("authentication")) } yield assert(n1)(equalTo(1)) && assert(n2)(equalTo(1)) && assert(key1)(isSome(equalTo(peerDID.jwkForKeyAgreement))) && @@ -105,11 +106,11 @@ object DIDSecretStorageSpec secretStorage <- ZIO.service[DIDSecretStorage] peerDID = PeerDID.makePeerDid() _ <- nonSecretStorage.createPeerDIDRecord(peerDID.did) - n1 <- secretStorage.insertKey(peerDID.did, "agreement", peerDID.jwkForKeyAgreement) + n1 <- secretStorage.insertKey(peerDID.did, KeyId("agreement"), peerDID.jwkForKeyAgreement) exit <- secretStorage - .insertKey(peerDID.did, "agreement", peerDID.jwkForKeyAuthentication) + .insertKey(peerDID.did, KeyId("agreement"), peerDID.jwkForKeyAuthentication) .exit - key1 <- secretStorage.getKey(peerDID.did, "agreement") + key1 <- secretStorage.getKey(peerDID.did, KeyId("agreement")) } yield assert(n1)(equalTo(1)) && assert(exit)(fails(anything)) && assert(key1)(isSome(equalTo(peerDID.jwkForKeyAgreement))) @@ -118,7 +119,7 @@ object DIDSecretStorageSpec for { secretStorage <- ZIO.service[DIDSecretStorage] peerDID = PeerDID.makePeerDid() - key1 <- secretStorage.getKey(peerDID.did, "agreement") + key1 <- secretStorage.getKey(peerDID.did, KeyId("agreement")) } yield assert(key1)(isNone) }, test("insert with long DID does not fail") { @@ -127,8 +128,8 @@ object DIDSecretStorageSpec secretStorage <- ZIO.service[DIDSecretStorage] peerDID = PeerDID.makePeerDid(serviceEndpoint = Some("http://localhost/" + ("a" * 100))) _ <- nonSecretStorage.createPeerDIDRecord(peerDID.did) - _ <- secretStorage.insertKey(peerDID.did, "agreement", peerDID.jwkForKeyAgreement) - _ <- secretStorage.insertKey(peerDID.did, "authentication", peerDID.jwkForKeyAuthentication) + _ <- secretStorage.insertKey(peerDID.did, KeyId("agreement"), peerDID.jwkForKeyAgreement) + _ <- secretStorage.insertKey(peerDID.did, KeyId("authentication"), peerDID.jwkForKeyAuthentication) } yield assertCompletes }, test("insert and get the same key for Prism KeyPair") { @@ -141,13 +142,13 @@ object DIDSecretStorageSpec did = createOperation.did state = ManagedDIDState(createOperation, 0, PublicationState.Created()) _ <- nonSecretStorage.insertManagedDID(did, state, Map.empty, Map.empty) - _ <- secretStorage.insertPrismDIDKeyPair(did, "key-1", createOperation.toAtalaOperationHash, key1) - _ <- secretStorage.insertPrismDIDKeyPair(did, "key-2", createOperation.toAtalaOperationHash, key2) + _ <- secretStorage.insertPrismDIDKeyPair(did, KeyId("key-1"), createOperation.toAtalaOperationHash, key1) + _ <- secretStorage.insertPrismDIDKeyPair(did, KeyId("key-2"), createOperation.toAtalaOperationHash, key2) getKey1 <- secretStorage - .getPrismDIDKeyPair[Ed25519KeyPair](did, "key-1", createOperation.toAtalaOperationHash) + .getPrismDIDKeyPair[Ed25519KeyPair](did, KeyId("key-1"), createOperation.toAtalaOperationHash) .some getKey2 <- secretStorage - .getPrismDIDKeyPair[X25519KeyPair](did, "key-2", createOperation.toAtalaOperationHash) + .getPrismDIDKeyPair[X25519KeyPair](did, KeyId("key-2"), createOperation.toAtalaOperationHash) .some } yield assert(key1)(equalTo(getKey1)) && assert(key2)(equalTo(getKey2)) @@ -162,10 +163,12 @@ object DIDSecretStorageSpec did = createOperation.did state = ManagedDIDState(createOperation, 0, PublicationState.Created()) _ <- nonSecretStorage.insertManagedDID(did, state, Map.empty, Map.empty) - _ <- secretStorage.insertPrismDIDKeyPair(did, "key-1", createOperation.toAtalaOperationHash, key1) - exit <- secretStorage.insertPrismDIDKeyPair(did, "key-1", createOperation.toAtalaOperationHash, key2).exit + _ <- secretStorage.insertPrismDIDKeyPair(did, KeyId("key-1"), createOperation.toAtalaOperationHash, key1) + exit <- secretStorage + .insertPrismDIDKeyPair(did, KeyId("key-1"), createOperation.toAtalaOperationHash, key2) + .exit getKey1 <- secretStorage - .getPrismDIDKeyPair[Ed25519KeyPair](did, "key-1", createOperation.toAtalaOperationHash) + .getPrismDIDKeyPair[Ed25519KeyPair](did, KeyId("key-1"), createOperation.toAtalaOperationHash) .some } yield assert(key1)(equalTo(getKey1)) && assert(exit)(dies(anything)) }, @@ -174,7 +177,8 @@ object DIDSecretStorageSpec secretStorage <- ZIO.service[DIDSecretStorage] createOperation = PrismDIDOperation.Create(Nil, Nil, Nil) did = createOperation.did - key1 <- secretStorage.getPrismDIDKeyPair[Ed25519KeyPair](did, "key-1", createOperation.toAtalaOperationHash) + key1 <- secretStorage + .getPrismDIDKeyPair[Ed25519KeyPair](did, KeyId("key-1"), createOperation.toAtalaOperationHash) } yield assert(key1)(isNone) }, ).globalWallet @@ -193,7 +197,7 @@ object DIDSecretStorageSpec .createPeerDIDRecord(peerDID1.did) .provide(ZLayer.succeed(WalletAccessContext(walletId1))) _ <- secretStorage - .insertKey(peerDID1.did, "key-1", peerDID1.jwkForKeyAgreement) + .insertKey(peerDID1.did, KeyId("key-1"), peerDID1.jwkForKeyAgreement) .provide(ZLayer.succeed(WalletAccessContext(walletId1))) // wallet2 setup peerDID2 = PeerDID.makePeerDid() @@ -201,20 +205,20 @@ object DIDSecretStorageSpec .createPeerDIDRecord(peerDID2.did) .provide(ZLayer.succeed(WalletAccessContext(walletId2))) _ <- secretStorage - .insertKey(peerDID2.did, "key-1", peerDID2.jwkForKeyAgreement) + .insertKey(peerDID2.did, KeyId("key-1"), peerDID2.jwkForKeyAgreement) .provide(ZLayer.succeed(WalletAccessContext(walletId2))) // assertions ownWallet1 <- secretStorage - .getKey(peerDID1.did, "key-1") + .getKey(peerDID1.did, KeyId("key-1")) .provide(ZLayer.succeed(WalletAccessContext(walletId1))) ownWallet2 <- secretStorage - .getKey(peerDID2.did, "key-1") + .getKey(peerDID2.did, KeyId("key-1")) .provide(ZLayer.succeed(WalletAccessContext(walletId2))) crossWallet1 <- secretStorage - .getKey(peerDID1.did, "key-1") + .getKey(peerDID1.did, KeyId("key-1")) .provide(ZLayer.succeed(WalletAccessContext(walletId2))) crossWallet2 <- secretStorage - .getKey(peerDID2.did, "key-1") + .getKey(peerDID2.did, KeyId("key-1")) .provide(ZLayer.succeed(WalletAccessContext(walletId1))) } yield assert(ownWallet1)(isSome(equalTo(peerDID1.jwkForKeyAgreement))) && assert(ownWallet2)(isSome(equalTo(peerDID2.jwkForKeyAgreement))) && diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala index 004660d9ec..e20c1a245a 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.agent.walletapi.model.* import org.hyperledger.identus.castor.core.model.did.{PrismDID, ScheduledDIDOperationStatus} import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.mock.{Expectation, Mock, Proxy} import zio.test.Assertion.equalTo @@ -54,13 +55,13 @@ case class MockDIDNonSecretStorage(proxy: Proxy) extends DIDNonSecretStorage { override def getKeyMeta( did: PrismDID, - keyId: String + keyId: KeyId ): RIO[WalletAccessContext, Option[(ManagedDIDKeyMeta, Array[Byte])]] = proxy(MockDIDNonSecretStorage.GetKeyMeta, (did, keyId)) override def insertKeyMeta( did: PrismDID, - keyId: String, + keyId: KeyId, meta: ManagedDIDKeyMeta, operationHash: Array[Byte] ): RIO[WalletAccessContext, Unit] = @@ -90,8 +91,8 @@ object MockDIDNonSecretStorage extends Mock[DIDNonSecretStorage] { object UpdateManagedDID extends Effect[(PrismDID, ManagedDIDStatePatch), Throwable, Unit] object GetMaxDIDIndex extends Effect[Unit, Throwable, Option[Int]] object GetHdKeyCounter extends Effect[PrismDID, Throwable, Option[HdKeyIndexCounter]] - object GetKeyMeta extends Effect[(PrismDID, String), Throwable, Option[(ManagedDIDKeyMeta, Array[Byte])]] - object InsertHdKeyMeta extends Effect[(PrismDID, String, ManagedDIDKeyMeta, Array[Byte]), Throwable, Unit] + object GetKeyMeta extends Effect[(PrismDID, KeyId), Throwable, Option[(ManagedDIDKeyMeta, Array[Byte])]] + object InsertHdKeyMeta extends Effect[(PrismDID, KeyId, ManagedDIDKeyMeta, Array[Byte]), Throwable, Unit] object ListHdKeyPath extends Effect[PrismDID, Throwable, Seq[(String, ArraySeq[Byte], ManagedDIDHdKeyPath)]] object ListManagedDID extends Effect[(Option[Int], Option[Int]), Throwable, (Seq[(PrismDID, ManagedDIDState)], Int)] diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala index 9fe8e79427..ef6a3b05e4 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala @@ -19,6 +19,7 @@ import org.hyperledger.identus.castor.core.model.did.{ } import org.hyperledger.identus.shared.crypto.ApolloSpecHelper import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.* @@ -44,7 +45,7 @@ trait StorageSpecHelper extends ApolloSpecHelper { protected def generateKeyPair() = apollo.secp256k1.generateKeyPair protected def generateCreateOperation(keyIds: Seq[String], didIndex: Int) = - OperationFactory(apollo).makeCreateOperation("master0", Array.fill(64)(0))( + OperationFactory(apollo).makeCreateOperation(KeyId("master0"), Array.fill(64)(0))( didIndex, ManagedDIDTemplate( publicKeys = diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala index 1e0642fe8f..8131d80f18 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.agent.walletapi.model.* import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.shared.crypto.{ApolloSpecHelper, Ed25519KeyPair, X25519KeyPair} import org.hyperledger.identus.shared.models.HexString +import org.hyperledger.identus.shared.models.KeyId import zio.* import zio.test.* import zio.test.Assertion.* @@ -31,9 +32,9 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { test("make CrateOperation from same seed is deterministic") { val didTemplate = ManagedDIDTemplate(Nil, Nil, Nil) for { - result1 <- operationFactory.makeCreateOperation("master0", seed)(0, didTemplate) + result1 <- operationFactory.makeCreateOperation(KeyId("master0"), seed)(0, didTemplate) (op1, hdKey1) = result1 - result2 <- operationFactory.makeCreateOperation("master0", seed)(0, didTemplate) + result2 <- operationFactory.makeCreateOperation(KeyId("master0"), seed)(0, didTemplate) (op2, hdKey2) = result2 } yield assert(op1)(equalTo(op2)) && assert(hdKey1)(equalTo(hdKey2)) @@ -41,7 +42,7 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { test("make CreateOperation must contain 1 master key") { val didTemplate = ManagedDIDTemplate(Nil, Nil, Nil) for { - result <- operationFactory.makeCreateOperation("master-0", seed)(0, didTemplate) + result <- operationFactory.makeCreateOperation(KeyId("master-0"), seed)(0, didTemplate) (op, hdKey) = result pk = op.publicKeys.head.asInstanceOf[InternalPublicKey] } yield assert(op.publicKeys)(hasSize(equalTo(1))) && @@ -59,7 +60,7 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { Nil ) for { - result <- operationFactory.makeCreateOperation("master-0", seed)(0, didTemplate) + result <- operationFactory.makeCreateOperation(KeyId("master-0"), seed)(0, didTemplate) (op, keys) = result } yield assert(op.publicKeys.length)(equalTo(4)) && assert(keys.hdKeys.size)(equalTo(4)) && @@ -80,26 +81,26 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { Nil ) for { - result <- operationFactory.makeCreateOperation("master-0", seed)(0, didTemplate) + result <- operationFactory.makeCreateOperation(KeyId("master-0"), seed)(0, didTemplate) (op, keys) = result publicKeyData = op.publicKeys.map { case PublicKey(id, _, publicKeyData) => id -> publicKeyData case InternalPublicKey(id, _, publicKeyData) => id -> publicKeyData }.toMap } yield assert(publicKeyData.size)(equalTo(4)) && - assert(publicKeyData.get("auth-0").get)( + assert(publicKeyData.get(KeyId("auth-0")).get)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(33)) && hasField("crv", _.crv, equalTo(EllipticCurve.SECP256K1)) ) ) && - assert(publicKeyData.get("auth-1").get)( + assert(publicKeyData.get(KeyId("auth-1")).get)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(32)) && hasField("crv", _.crv, equalTo(EllipticCurve.ED25519)) ) ) && - assert(publicKeyData.get("comm-0").get)( + assert(publicKeyData.get(KeyId("comm-0")).get)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(32)) && hasField("crv", _.crv, equalTo(EllipticCurve.X25519)) @@ -212,13 +213,13 @@ object OperationFactorySpec extends ZIOSpecDefault, ApolloSpecHelper { assert(keys.randKeys.get("comm-42").get.keyPair)(isSubtype[X25519KeyPair](anything)) && // operation is correct assert(op.actions)(hasSize(equalTo(2))) && - assert(addKeyActions.find(_.id == "auth-42").get.publicKeyData)( + assert(addKeyActions.find(_.id == KeyId("auth-42")).get.publicKeyData)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(32)) && hasField("crv", _.crv, equalTo(EllipticCurve.ED25519)) ) ) && - assert(addKeyActions.find(_.id == "comm-42").get.publicKeyData)( + assert(addKeyActions.find(_.id == KeyId("comm-42")).get.publicKeyData)( isSubtype[PublicKeyData.ECCompressedKeyData]( hasField[PublicKeyData.ECCompressedKeyData, Int]("data", _.data.toByteArray.length, equalTo(32)) && hasField("crv", _.crv, equalTo(EllipticCurve.X25519)) diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala index 2c5950dfbc..09d221ed25 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala @@ -5,6 +5,7 @@ import org.hyperledger.identus.castor.core.model.did.{PrismDID, VerificationRela import org.hyperledger.identus.pollux.core.model.DidCommID import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.ProtocolState import org.hyperledger.identus.shared.models.{Failure, StatusCode} +import org.hyperledger.identus.shared.models.KeyId import java.util.UUID @@ -97,7 +98,7 @@ object CredentialServiceError { s"The requested DID does not exist in the wallet: did=${did.toString}" ) - final case class KeyPairNotFoundInWallet(did: PrismDID, keyId: String, algo: String) + final case class KeyPairNotFoundInWallet(did: PrismDID, keyId: KeyId, algo: String) extends CredentialServiceError( StatusCode.NotFound, s"The requested key pair does not exist in the wallet: did=${did.toString}, keyId=$keyId, algo=$algo" diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialService.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialService.scala index 166047ceba..e1341a3dbb 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialService.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialService.scala @@ -25,6 +25,7 @@ trait CredentialService { def createJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: io.circe.Json, @@ -40,6 +41,7 @@ trait CredentialService { def createSDJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: io.circe.Json, diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala index d83b8390ec..ec86a7fa87 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImpl.scala @@ -26,7 +26,7 @@ import org.hyperledger.identus.pollux.core.repository.{CredentialRepository, Cre import org.hyperledger.identus.pollux.prex.{ClaimFormat, Jwt, PresentationDefinition} import org.hyperledger.identus.pollux.sdjwt.* import org.hyperledger.identus.pollux.vc.jwt.{Issuer as JwtIssuer, *} -import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Ed25519PublicKey, Secp256k1KeyPair} +import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair} import org.hyperledger.identus.shared.http.{DataUrlResolver, GenericUriResolver} import org.hyperledger.identus.shared.models.* import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect @@ -125,6 +125,7 @@ class CredentialServiceImpl( private def createIssueCredentialRecord( pairwiseIssuerDID: DidId, + kidIssuer: Option[KeyId], thid: DidCommID, schemaUri: Option[String], validityPeriod: Option[Double], @@ -167,7 +168,7 @@ class CredentialServiceImpl( invitation = invitation, role = IssueCredentialRecord.Role.Issuer, subjectId = None, - keyId = None, + keyId = kidIssuer, validityPeriod = validityPeriod, automaticIssuance = automaticIssuance, protocolState = invitation.fold(IssueCredentialRecord.ProtocolState.OfferPending)(_ => @@ -193,6 +194,7 @@ class CredentialServiceImpl( override def createJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: Json, @@ -219,6 +221,7 @@ class CredentialServiceImpl( ) record <- createIssueCredentialRecord( pairwiseIssuerDID = pairwiseIssuerDID, + kidIssuer = kidIssuer, thid = thid, schemaUri = maybeSchemaId, validityPeriod = validityPeriod, @@ -239,6 +242,7 @@ class CredentialServiceImpl( override def createSDJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: io.circe.Json, @@ -265,6 +269,7 @@ class CredentialServiceImpl( ) record <- createIssueCredentialRecord( pairwiseIssuerDID = pairwiseIssuerDID, + kidIssuer = kidIssuer, thid = thid, schemaUri = maybeSchemaId, validityPeriod = validityPeriod, @@ -313,6 +318,7 @@ class CredentialServiceImpl( ) record <- createIssueCredentialRecord( pairwiseIssuerDID = pairwiseIssuerDID, + kidIssuer = None, thid = thid, schemaUri = Some(credentialDefinition.schemaId), validityPeriod = validityPeriod, @@ -532,7 +538,7 @@ class CredentialServiceImpl( did: PrismDID, verificationRelationship: VerificationRelationship, ellipticCurve: EllipticCurve - ): UIO[String] = { + ): UIO[KeyId] = { for { maybeDidData <- didService .resolveDID(did) @@ -612,7 +618,7 @@ class CredentialServiceImpl( JwtIssuer( jwtIssuerDID.did, EdSigner(ed25519keyPair, keyId), - Ed25519PublicKey.toJavaEd25519PublicKey(ed25519keyPair.publicKey.getEncoded) + ed25519keyPair.publicKey.toJava ) } } diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifier.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifier.scala index d2dd0e0c5a..5046688d45 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifier.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifier.scala @@ -26,6 +26,7 @@ class CredentialServiceNotifier( override def createJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: Json, @@ -41,6 +42,7 @@ class CredentialServiceNotifier( svc.createJWTIssueCredentialRecord( pairwiseIssuerDID, pairwiseHolderDID, + kidIssuer, thid, maybeSchemaId, claims, @@ -57,6 +59,7 @@ class CredentialServiceNotifier( override def createSDJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: io.circe.Json, @@ -72,6 +75,7 @@ class CredentialServiceNotifier( svc.createSDJWTIssueCredentialRecord( pairwiseIssuerDID, pairwiseHolderDID, + kidIssuer, thid, maybeSchemaId, claims, diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceSpecHelper.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceSpecHelper.scala index e8d9fcf907..4f61c5e123 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceSpecHelper.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceSpecHelper.scala @@ -128,6 +128,7 @@ trait CredentialServiceSpecHelper { record <- svc.createJWTIssueCredentialRecord( pairwiseIssuerDID = pairwiseIssuerDID, pairwiseHolderDID = pairwiseHolderDID, + kidIssuer = None, thid = thid, maybeSchemaId = maybeSchemaId, claims = claims, diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockCredentialService.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockCredentialService.scala index a33dc064f7..41b7b472bc 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockCredentialService.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockCredentialService.scala @@ -128,6 +128,7 @@ object MockCredentialService extends Mock[CredentialService] { override def createJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: Json, @@ -158,6 +159,7 @@ object MockCredentialService extends Mock[CredentialService] { override def createSDJWTIssueCredentialRecord( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], + kidIssuer: Option[KeyId], thid: DidCommID, maybeSchemaId: Option[String], claims: Json, diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala index 67f686971f..f6cbc60efe 100644 --- a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala +++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala @@ -1,6 +1,10 @@ package org.hyperledger.identus.shared.models +import zio.json._ + opaque type KeyId = String object KeyId: def apply(value: String): KeyId = value extension (id: KeyId) def value: String = id + given decoder: JsonDecoder[KeyId] = JsonDecoder.string.map(KeyId(_)) + given encoder: JsonEncoder[KeyId] = JsonEncoder.string.contramap[KeyId](_.value) diff --git a/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Apollo.scala b/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Apollo.scala index ccade3471b..b7e674c212 100644 --- a/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Apollo.scala +++ b/shared/crypto/src/main/scala/org/hyperledger/identus/shared/crypto/Apollo.scala @@ -163,6 +163,7 @@ trait Ed25519PublicKey extends PublicKey, Verifiable { case _ => false } + def toJava = Ed25519PublicKey.toJavaEd25519PublicKey(this.getEncoded) } trait Ed25519PrivateKey extends PrivateKey, Signable { type Pub = Ed25519PublicKey From 960c155a135968dd23bb293719633b86348b8e15 Mon Sep 17 00:00:00 2001 From: Fabio Pinheiro Date: Wed, 18 Sep 2024 08:09:47 +0100 Subject: [PATCH 6/9] docs: Update README.md with credential types that are supported (#1359) Signed-off-by: Fabio Pinheiro --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fd98fcea32..4d56f8ce02 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,9 @@ All documentation, tutorials and API references for the Identus ecosystem can be * DIDComm V2 * W3C-compliant `did:prism` and `did:peer` methods * Credential types - * JWT - * AnonCreds (coming soon) + * JWT-VC + * SD-JWT-VC + * AnonCreds * HTTP events notification * Cardano as a distributed ledger * Secrets management with Hashicorp vault From c18385c8fdbbb0e5dbde9a03e21f4600bf5e6890 Mon Sep 17 00:00:00 2001 From: Bassam Date: Wed, 18 Sep 2024 13:35:21 -0400 Subject: [PATCH 7/9] fix: Preserve Presentation Format (#1363) Signed-off-by: Bassam Riman --- .../server/jobs/PresentBackgroundJobs.scala | 11 +++- .../PresentProofControllerImpl.scala | 7 ++- .../http/RequestPresentationInput.scala | 20 +++++++ .../presentproof/PresentFormats.scala | 11 ++-- .../core/service/PresentationService.scala | 3 ++ .../service/PresentationServiceImpl.scala | 52 +++++++++++++------ .../service/PresentationServiceNotifier.scala | 13 ++--- .../service/MockPresentationService.scala | 10 ++++ .../PresentationServiceNotifierSpec.scala | 1 + .../service/PresentationServiceSpec.scala | 2 + .../PresentationServiceSpecHelper.scala | 4 +- 11 files changed, 106 insertions(+), 28 deletions(-) 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 297602f425..9c35fa6449 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 @@ -703,11 +703,18 @@ object PresentBackgroundJobs extends BackgroundJobsHelper { goal_code = requestPresentation.body.goal_code, comment = requestPresentation.body.comment ), - attachments = Seq( + attachments = requestPresentation.attachments.map(attachment => AttachmentDescriptor .buildBase64Attachment( payload = signedJwtPresentation.value.getBytes(), - mediaType = Some(PresentCredentialFormat.JWT.name) + mediaType = attachment.media_type, + format = attachment.format.map { + case PresentCredentialRequestFormat.JWT.name => PresentCredentialFormat.JWT.name + case format => + throw throw RuntimeException( + s"Unexpected PresentCredentialRequestFormat=$format. Expecting: ${PresentCredentialRequestFormat.JWT.name}" + ) + } ) ), thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala index 11adbd36f6..0d1a8ff81b 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/PresentProofControllerImpl.scala @@ -8,7 +8,7 @@ import org.hyperledger.identus.api.http.model.PaginationInput import org.hyperledger.identus.connect.core.model.error.ConnectionServiceError import org.hyperledger.identus.connect.core.service.ConnectionService import org.hyperledger.identus.mercury.model.DidId -import org.hyperledger.identus.mercury.protocol.presentproof.ProofType +import org.hyperledger.identus.mercury.protocol.presentproof.{PresentCredentialRequestFormat, ProofType} import org.hyperledger.identus.pollux.core.model.{CredentialFormat, DidCommID, PresentationRecord} import org.hyperledger.identus.pollux.core.model.error.PresentationError import org.hyperledger.identus.pollux.core.model.presentation.Options @@ -80,6 +80,7 @@ class PresentProofControllerImpl( request.options.map(o => Options(o.challenge, o.domain)), request.claims, request.anoncredPresentationRequest, + request.presentationFormat, request.goalCode, request.goal, expirationDuration @@ -95,6 +96,7 @@ class PresentProofControllerImpl( options: Option[Options], claims: Option[zio.json.ast.Json.Obj], anoncredPresentationRequest: Option[AnoncredPresentationRequestV1], + presentationFormat: Option[PresentCredentialRequestFormat], goalCode: Option[String], goal: Option[String], expirationDuration: Option[Duration], @@ -115,6 +117,7 @@ class PresentProofControllerImpl( ) }, options = options, + presentationFormat = presentationFormat.getOrElse(PresentCredentialRequestFormat.JWT), goalCode = goalCode, goal = goal, expirationDuration = expirationDuration, @@ -136,6 +139,7 @@ class PresentProofControllerImpl( }, claimsToDisclose = claimsToDisclose, options = options, + presentationFormat = presentationFormat.getOrElse(PresentCredentialRequestFormat.SDJWT), goalCode = goalCode, goal = goal, expirationDuration = expirationDuration, @@ -156,6 +160,7 @@ class PresentProofControllerImpl( thid = DidCommID(), connectionId = connectionId, presentationRequest = presentationRequest, + presentationFormat = presentationFormat.getOrElse(PresentCredentialRequestFormat.Anoncred), goalCode = goalCode, goal = goal, expirationDuration = expirationDuration, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/RequestPresentationInput.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/RequestPresentationInput.scala index acf486f33d..695094e2e1 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/RequestPresentationInput.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/presentproof/controller/http/RequestPresentationInput.scala @@ -1,6 +1,7 @@ package org.hyperledger.identus.presentproof.controller.http import org.hyperledger.identus.api.http.Annotation +import org.hyperledger.identus.mercury.protocol.presentproof.PresentCredentialRequestFormat import org.hyperledger.identus.pollux.core.service.serdes.* import org.hyperledger.identus.presentproof.controller.http.RequestPresentationInput.annotations import sttp.tapir.{Schema, Validator} @@ -29,6 +30,9 @@ final case class RequestPresentationInput( @description(annotations.anoncredPresentationRequest.description) @encodedExample(annotations.anoncredPresentationRequest.example) anoncredPresentationRequest: Option[AnoncredPresentationRequestV1], + @description(annotations.presentationFormat.description) + @encodedExample(annotations.presentationFormat.example) + presentationFormat: Option[PresentCredentialRequestFormat], @description(annotations.claims.description) @encodedExample(annotations.claims.example) claims: Option[zio.json.ast.Json.Obj], @@ -128,6 +132,20 @@ object RequestPresentationInput { ) ) ) + + object presentationFormat + extends Annotation[Option[String]]( + description = + "The presentation format to display in Didcomm messages (default to 'prism/jwt', vc+sd-jwt or anoncreds/proof-request@v1.0)", + example = Some("prism/jwt"), + validator = Validator.enumeration( + List( + Some("prism/jwt"), + Some("vc+sd-jwt"), + Some("anoncreds/proof-request@v1.0") + ) + ) + ) object claims extends Annotation[Option[zio.json.ast.Json.Obj]]( description = """ @@ -163,6 +181,8 @@ object RequestPresentationInput { import AnoncredPresentationRequestV1.given + given Schema[PresentCredentialRequestFormat] = Schema.derivedEnumeration.defaultStringBased + given Schema[AnoncredPresentationRequestV1] = Schema.derived given Schema[AnoncredRequestedAttributeV1] = Schema.derived diff --git a/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentFormats.scala b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentFormats.scala index 602d46a479..c7dda85da6 100644 --- a/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentFormats.scala +++ b/mercury/protocol-present-proof/src/main/scala/org/hyperledger/identus/mercury/protocol/presentproof/PresentFormats.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.mercury.protocol.presentproof import io.circe.{Decoder, Encoder} import io.circe.generic.semiauto.* +import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} /* Present Credential Formats: @@ -72,8 +73,6 @@ object PresentCredentialProposeFormat { * - dif/presentation-exchange/definitions@v1.0 */ enum PresentCredentialRequestFormat(val name: String) { - case Unsupported(other: String) extends PresentCredentialRequestFormat(other) - // case JWT extends PresentCredentialRequestFormat("jwt/proof-request@v1.0") // TODO FOLLOW specs for JWT VC case JWT extends PresentCredentialRequestFormat("prism/jwt") // TODO REMOVE case SDJWT extends PresentCredentialRequestFormat("vc+sd-jwt") case Anoncred extends PresentCredentialRequestFormat("anoncreds/proof-request@v1.0") @@ -82,6 +81,13 @@ enum PresentCredentialRequestFormat(val name: String) { object PresentCredentialRequestFormat { given Encoder[PresentCredentialRequestFormat] = deriveEncoder[PresentCredentialRequestFormat] given Decoder[PresentCredentialRequestFormat] = deriveDecoder[PresentCredentialRequestFormat] + + given JsonEncoder[PresentCredentialRequestFormat] = + DeriveJsonEncoder.gen[PresentCredentialRequestFormat] + + given JsonDecoder[PresentCredentialRequestFormat] = + DeriveJsonDecoder.gen[PresentCredentialRequestFormat] + } /** Present Credential: @@ -98,7 +104,6 @@ object PresentCredentialRequestFormat { * - dif/presentation-exchange/submission@v1.0 */ enum PresentCredentialFormat(val name: String) { - case Unsupported(other: String) extends PresentCredentialFormat(other) // case JWT extends PresentCredentialFormat("jwt/proof-request@v1.0") // TODO FOLLOW specs for JWT VC case JWT extends PresentCredentialFormat("prism/jwt") // TODO REMOVE case SDJWT extends PresentCredentialFormat("vc+sd-jwt") 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 d3c3781cf2..94aa1af79b 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 @@ -26,6 +26,7 @@ trait PresentationService { connectionId: Option[String], proofTypes: Seq[ProofType], options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String], goal: Option[String], expirationDuration: Option[Duration], @@ -39,6 +40,7 @@ trait PresentationService { proofTypes: Seq[ProofType], claimsToDisclose: ast.Json.Obj, options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String], goal: Option[String], expirationDuration: Option[Duration], @@ -50,6 +52,7 @@ trait PresentationService { thid: DidCommID, connectionId: Option[String], presentationRequest: AnoncredPresentationRequestV1, + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String], goal: Option[String], expirationDuration: Option[Duration], 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 edecea22e8..67d68b928c 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 @@ -189,11 +189,18 @@ private class PresentationServiceImpl( goal_code = requestPresentation.body.goal_code, comment = requestPresentation.body.comment ), - attachments = Seq( + attachments = requestPresentation.attachments.map(attachment => AttachmentDescriptor .buildBase64Attachment( - payload = presentationPayload.compact.getBytes, - mediaType = Some(PresentCredentialFormat.SDJWT.name) + payload = presentationPayload.compact.getBytes(), + mediaType = attachment.media_type, + format = attachment.format.map { + case PresentCredentialRequestFormat.SDJWT.name => PresentCredentialFormat.SDJWT.name + case format => + throw throw RuntimeException( + s"Unexpected PresentCredentialRequestFormat=$format. Expecting: ${PresentCredentialRequestFormat.SDJWT.name}" + ) + } ) ), thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), @@ -259,12 +266,18 @@ private class PresentationServiceImpl( goal_code = requestPresentation.body.goal_code, comment = requestPresentation.body.comment ), - attachments = Seq( + attachments = requestPresentation.attachments.map(attachment => AttachmentDescriptor .buildBase64Attachment( payload = presentationPayload.data.getBytes(), - mediaType = Some(PresentCredentialFormat.Anoncred.name), - format = Some(PresentCredentialFormat.Anoncred.name), + mediaType = attachment.media_type, + format = attachment.format.map { + case PresentCredentialRequestFormat.Anoncred.name => PresentCredentialFormat.Anoncred.name + case format => + throw throw RuntimeException( + s"Unexpected PresentCredentialRequestFormat=$format. Expecting: ${PresentCredentialRequestFormat.Anoncred.name}" + ) + } ) ), thid = requestPresentation.thid.orElse(Some(requestPresentation.id)), @@ -310,6 +323,7 @@ private class PresentationServiceImpl( connectionId: Option[String], proofTypes: Seq[ProofType], options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String] = None, goal: Option[String] = None, expirationDuration: Option[Duration] = None, @@ -321,7 +335,7 @@ private class PresentationServiceImpl( connectionId, CredentialFormat.JWT, proofTypes, - options.map(o => Seq(toJWTAttachment(o))).getOrElse(Seq.empty), + options.map(o => Seq(toJWTAttachment(o, presentationFormat))).getOrElse(Seq.empty), goalCode, goal, expirationDuration @@ -336,6 +350,7 @@ private class PresentationServiceImpl( proofTypes: Seq[ProofType], claimsToDisclose: ast.Json.Obj, options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String] = None, goal: Option[String] = None, expirationDuration: Option[Duration] = None, @@ -347,7 +362,7 @@ private class PresentationServiceImpl( connectionId, CredentialFormat.SDJWT, proofTypes, - attachments = Seq(toSDJWTAttachment(options, claimsToDisclose)), + attachments = Seq(toSDJWTAttachment(options, claimsToDisclose, presentationFormat)), goalCode, goal, expirationDuration @@ -360,6 +375,7 @@ private class PresentationServiceImpl( thid: DidCommID, connectionId: Option[String], presentationRequest: AnoncredPresentationRequestV1, + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String] = None, goal: Option[String] = None, expirationDuration: Option[Duration] = None, @@ -371,7 +387,7 @@ private class PresentationServiceImpl( connectionId, CredentialFormat.AnonCreds, Seq.empty, - Seq(toAnoncredAttachment(presentationRequest)), + Seq(toAnoncredAttachment(presentationRequest, presentationFormat)), goalCode, goal, expirationDuration @@ -1160,30 +1176,36 @@ private class PresentationServiceImpl( } yield record } - private def toJWTAttachment(options: Options): AttachmentDescriptor = { + private def toJWTAttachment( + options: Options, + presentationFormat: PresentCredentialRequestFormat + ): AttachmentDescriptor = { AttachmentDescriptor.buildJsonAttachment( payload = PresentationAttachment.build(Some(options)), - format = Some(PresentCredentialRequestFormat.JWT.name) + format = Some(presentationFormat.name), + mediaType = Some("application/json") ) } private def toSDJWTAttachment( options: Option[Options], - claimsToDsiclose: ast.Json.Obj + claimsToDsiclose: ast.Json.Obj, + presentationFormat: PresentCredentialRequestFormat ): AttachmentDescriptor = { AttachmentDescriptor.buildBase64Attachment( mediaType = Some("application/json"), - format = Some(PresentCredentialRequestFormat.SDJWT.name), + format = Some(presentationFormat.name), payload = SDJwtPresentation(options, claimsToDsiclose).toJson.getBytes ) } private def toAnoncredAttachment( - presentationRequest: AnoncredPresentationRequestV1 + presentationRequest: AnoncredPresentationRequestV1, + presentationFormat: PresentCredentialRequestFormat ): AttachmentDescriptor = { AttachmentDescriptor.buildBase64Attachment( mediaType = Some("application/json"), - format = Some(PresentCredentialRequestFormat.Anoncred.name), + format = Some(presentationFormat.name), payload = AnoncredPresentationRequestV1.schemaSerDes.serializeToJsonString(presentationRequest).getBytes() ) } 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 ce4b3ab333..80d358cdbf 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 @@ -2,12 +2,7 @@ package org.hyperledger.identus.pollux.core.service import org.hyperledger.identus.event.notification.{Event, EventNotificationService} import org.hyperledger.identus.mercury.model.DidId -import org.hyperledger.identus.mercury.protocol.presentproof.{ - Presentation, - ProofType, - ProposePresentation, - RequestPresentation -} +import org.hyperledger.identus.mercury.protocol.presentproof.* import org.hyperledger.identus.pollux.anoncreds.AnoncredPresentation import org.hyperledger.identus.pollux.core.model.{DidCommID, PresentationRecord} import org.hyperledger.identus.pollux.core.model.error.PresentationError @@ -38,6 +33,7 @@ class PresentationServiceNotifier( connectionId: Option[String], proofTypes: Seq[ProofType], options: Option[Options], + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String], goal: Option[String], expirationTime: Option[Duration], @@ -50,6 +46,7 @@ class PresentationServiceNotifier( connectionId, proofTypes, options, + presentationFormat, goalCode, goal, expirationTime @@ -64,6 +61,7 @@ class PresentationServiceNotifier( proofTypes: Seq[ProofType], claimsToDisclose: ast.Json.Obj, options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String], goal: Option[String], expirationTime: Option[Duration], @@ -77,6 +75,7 @@ class PresentationServiceNotifier( proofTypes, claimsToDisclose, options, + presentationFormat, goalCode, goal, expirationTime @@ -89,6 +88,7 @@ class PresentationServiceNotifier( thid: DidCommID, connectionId: Option[String], presentationRequest: AnoncredPresentationRequestV1, + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String], goal: Option[String], expirationTime: Option[Duration], @@ -100,6 +100,7 @@ class PresentationServiceNotifier( thid, connectionId, presentationRequest, + presentationFormat, goalCode, goal, expirationTime diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockPresentationService.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockPresentationService.scala index ba9549a691..4f58c2f570 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockPresentationService.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/MockPresentationService.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.pollux.core.service import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.mercury.protocol.presentproof.{ + PresentCredentialRequestFormat, Presentation, ProofType, ProposePresentation, @@ -33,6 +34,7 @@ object MockPresentationService extends Mock[PresentationService] { Option[String], Seq[ProofType], Option[Options], + PresentCredentialRequestFormat, Option[String], Option[String], Option[Duration] @@ -50,6 +52,7 @@ object MockPresentationService extends Mock[PresentationService] { Seq[ProofType], ast.Json.Obj, Option[Options], + PresentCredentialRequestFormat, Option[String], Option[String], Option[Duration] @@ -66,6 +69,7 @@ object MockPresentationService extends Mock[PresentationService] { DidCommID, Option[String], AnoncredPresentationRequestV1, + PresentCredentialRequestFormat, Option[String], Option[String], Option[Duration] @@ -129,6 +133,7 @@ object MockPresentationService extends Mock[PresentationService] { connectionId: Option[String], proofTypes: Seq[ProofType], options: Option[Options], + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String], goal: Option[String], expirationTime: Option[Duration] @@ -142,6 +147,7 @@ object MockPresentationService extends Mock[PresentationService] { connectionId, proofTypes, options, + presentationFormat, goalCode, goal, expirationTime @@ -156,6 +162,7 @@ object MockPresentationService extends Mock[PresentationService] { proofTypes: Seq[ProofType], claimsToDisclose: ast.Json.Obj, options: Option[org.hyperledger.identus.pollux.core.model.presentation.Options], + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String], goal: Option[String], expirationTime: Option[Duration] @@ -170,6 +177,7 @@ object MockPresentationService extends Mock[PresentationService] { proofTypes, claimsToDisclose, options, + presentationFormat, goalCode, goal, expirationTime @@ -182,6 +190,7 @@ object MockPresentationService extends Mock[PresentationService] { thid: DidCommID, connectionId: Option[String], presentationRequest: AnoncredPresentationRequestV1, + presentationFormat: PresentCredentialRequestFormat, goalCode: Option[String], goal: Option[String], expirationTime: Option[Duration] @@ -194,6 +203,7 @@ object MockPresentationService extends Mock[PresentationService] { thid, connectionId, presentationRequest, + presentationFormat, goalCode, goal, expirationTime diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifierSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifierSpec.scala index eeb66d593d..8f4197a57a 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifierSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceNotifierSpec.scala @@ -117,6 +117,7 @@ object PresentationServiceNotifierSpec extends ZIOSpecDefault with PresentationS None, Seq.empty, None, + PresentCredentialRequestFormat.JWT, None, None, None diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpec.scala index 1b84f45743..b1feb03cd2 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpec.scala @@ -75,6 +75,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp Some(connectionId), proofTypes, options, + PresentCredentialRequestFormat.JWT, None, None, None, @@ -143,6 +144,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp thid, Some(connectionId), anoncredPresentationRequestV1, + PresentCredentialRequestFormat.Anoncred, None, None, None diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala index e6ea65d26c..8e08c6445f 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala @@ -171,6 +171,7 @@ trait PresentationServiceSpecHelper { connectionId = Some("connectionId"), proofTypes = Seq(proofType), options = options, + presentationFormat = PresentCredentialRequestFormat.JWT, goalCode = None, goal = None, expirationDuration = None @@ -215,7 +216,8 @@ trait PresentationServiceSpecHelper { pairwiseVerifierDID = pairwiseVerifierDID, pairwiseProverDID = Some(pairwiseProverDID), connectionId = Some("connectionId"), - anoncredPresentationRequestV1, + presentationRequest = anoncredPresentationRequestV1, + presentationFormat = PresentCredentialRequestFormat.Anoncred, goalCode = None, goal = None, expirationDuration = None From 49f7ab3d0473d820c37dc7f4f944cf1c2cae2a25 Mon Sep 17 00:00:00 2001 From: patlo-iog Date: Thu, 19 Sep 2024 12:01:44 +0700 Subject: [PATCH 8/9] feat: add presentation-exchange endpoints (#1365) Signed-off-by: Pat Losoponkul --- .../castor/core/model/ProtoModelHelper.scala | 6 +- .../castor/core/model/did/PublicKey.scala | 3 +- .../castor/core/service/MockDIDService.scala | 3 +- .../core/util/DIDOperationValidatorSpec.scala | 3 +- .../identus/castor/core/util/GenUtils.scala | 3 +- .../identus/agent/server/CloudAgentApp.scala | 6 +- .../identus/agent/server/MainApp.scala | 7 ++ .../identus/agent/server/http/DocModels.scala | 4 +- .../agent/server/jobs/StatusListJobs.scala | 1 - .../controller/DIDController.scala | 0 .../controller/DIDEndpoints.scala | 0 .../controller/DIDRegistrarController.scala | 0 .../controller/DIDRegistrarEndpoints.scala | 0 .../DIDRegistrarServerEndpoints.scala | 0 .../controller/DIDServerEndpoints.scala | 0 .../controller/http/DIDDocument.scala | 0 .../controller/http/DIDDocumentMetadata.scala | 0 .../controller/http/DIDInput.scala | 0 .../http/DIDResolutionMetadata.scala | 0 .../controller/http/DIDResolutionResult.scala | 0 .../controller/http/ManagedDID.scala | 0 .../controller/http/PublicKeyJwk.scala | 0 .../controller/http/ScheduledOperation.scala | 0 .../controller/http/Service.scala | 0 .../controller/http/UpdateManagedDID.scala | 0 .../controller/http/VerificationMethod.scala | 0 .../controller/DIDCommControllerError.scala | 3 +- .../prex/PresentationExchangeEndpoints.scala | 97 +++++++++++++++++++ .../PresentationExchangeServerEndpoints.scala | 61 ++++++++++++ .../PresentationExchangeController.scala | 62 ++++++++++++ .../prex/http/PresentationDefinition.scala | 42 ++++++++ .../PresentationExchangeTapirSchemas.scala | 24 +++++ .../identus/api/util/Tapir2StaticOAS.scala | 2 + .../controller/IssueControllerImplSpec.scala | 3 +- .../walletapi/service/ManagedDIDService.scala | 3 +- .../service/ManagedDIDServiceImpl.scala | 3 +- .../service/handler/DIDCreateHandler.scala | 3 +- .../service/handler/DIDUpdateHandler.scala | 3 +- .../service/handler/PublicationHandler.scala | 3 +- .../sql/JdbcDIDNonSecretStorage.scala | 3 +- .../walletapi/sql/JdbcDIDSecretStorage.scala | 3 +- .../storage/DIDNonSecretStorage.scala | 3 +- .../walletapi/storage/DIDSecretStorage.scala | 3 +- .../agent/walletapi/util/KeyResolver.scala | 3 +- .../walletapi/util/OperationFactory.scala | 3 +- .../vault/VaultDIDSecretStorage.scala | 3 +- .../service/ManagedDIDServiceSpec.scala | 3 +- .../storage/DIDSecretStorageSpec.scala | 3 +- .../storage/MockDIDNonSecretStorage.scala | 3 +- .../walletapi/storage/StorageSpecHelper.scala | 3 +- .../walletapi/util/OperationFactorySpec.scala | 3 +- .../model/error/CredentialServiceError.scala | 3 +- .../PresentationExchangeRepository.scala | 16 +++ .../service/PresentationExchangeService.scala | 44 +++++++++ .../PresentationExchangeServiceImpl.scala | 41 ++++++++ .../pollux/prex/PresentationDefinition.scala | 29 ++++++ .../PresentationDefinitionValidator.scala | 3 +- .../V27__presentation_definition_table.sql | 16 +++ .../sql/model/db/PresentationDefinition.scala | 69 +++++++++++++ .../JdbcPresentationExchangeRepository.scala | 67 +++++++++++++ .../identus/shared/models/KeyId.scala | 2 +- 61 files changed, 614 insertions(+), 57 deletions(-) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/DIDController.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/DIDEndpoints.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/DIDRegistrarController.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/DIDRegistrarEndpoints.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/DIDRegistrarServerEndpoints.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/DIDServerEndpoints.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/DIDDocument.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/DIDDocumentMetadata.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/DIDInput.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/DIDResolutionMetadata.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/DIDResolutionResult.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/ManagedDID.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/PublicKeyJwk.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/ScheduledOperation.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/Service.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/UpdateManagedDID.scala (100%) rename cloud-agent/service/server/src/main/scala/org/hyperledger/identus/{ => castor}/controller/http/VerificationMethod.scala (100%) create mode 100644 cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationExchangeEndpoints.scala create mode 100644 cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationExchangeServerEndpoints.scala create mode 100644 cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/controller/PresentationExchangeController.scala create mode 100644 cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/http/PresentationDefinition.scala create mode 100644 cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/http/PresentationExchangeTapirSchemas.scala create mode 100644 pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationExchangeRepository.scala create mode 100644 pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationExchangeService.scala create mode 100644 pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationExchangeServiceImpl.scala create mode 100644 pollux/sql-doobie/src/main/resources/sql/pollux/V27__presentation_definition_table.sql create mode 100644 pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/model/db/PresentationDefinition.scala create mode 100644 pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcPresentationExchangeRepository.scala diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala index cd12aeeb4b..2e66913279 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/ProtoModelHelper.scala @@ -24,10 +24,8 @@ import org.hyperledger.identus.castor.core.model.did.{ UpdateDIDAction, VerificationRelationship } -import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.value -import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.UriOrJsonEndpoint -import org.hyperledger.identus.shared.models.Base64UrlString -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.{value, UriOrJsonEndpoint} +import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId} import org.hyperledger.identus.shared.utils.Traverse.* import zio.* diff --git a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala index da27a541c2..ec79f7fd2b 100644 --- a/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala +++ b/castor/src/main/scala/org/hyperledger/identus/castor/core/model/did/PublicKey.scala @@ -1,7 +1,6 @@ package org.hyperledger.identus.castor.core.model.did -import org.hyperledger.identus.shared.models.Base64UrlString -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId} final case class PublicKey( id: KeyId, diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala index ae128f47ed..b911ff9a92 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/service/MockDIDService.scala @@ -3,8 +3,7 @@ package org.hyperledger.identus.castor.core.service import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.castor.core.model.error import org.hyperledger.identus.shared.crypto.{Apollo, Secp256k1KeyPair} -import org.hyperledger.identus.shared.models.Base64UrlString -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId} import zio.{mock, IO, URLayer, ZIO, ZLayer} import zio.mock.{Expectation, Mock, Proxy} import zio.test.Assertion diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala index 72957513b0..7e2e8fee99 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/DIDOperationValidatorSpec.scala @@ -3,8 +3,7 @@ package org.hyperledger.identus.castor.core.util import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.castor.core.model.error.OperationValidationError import org.hyperledger.identus.castor.core.util.DIDOperationValidator.Config -import org.hyperledger.identus.shared.models.Base64UrlString -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId} import zio.* import zio.test.* import zio.test.Assertion.* diff --git a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala index 29622b3b70..e25519ebc2 100644 --- a/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala +++ b/castor/src/test/scala/org/hyperledger/identus/castor/core/util/GenUtils.scala @@ -4,8 +4,7 @@ import io.circe.Json import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.{UriOrJsonEndpoint, UriValue} import org.hyperledger.identus.shared.crypto.Apollo -import org.hyperledger.identus.shared.models.Base64UrlString -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId} import zio.* import zio.test.Gen diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala index 5503393c1a..ced294305e 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala @@ -26,6 +26,7 @@ import org.hyperledger.identus.pollux.credentialschema.{ SchemaRegistryServerEndpoints, VerificationPolicyServerEndpoints } +import org.hyperledger.identus.pollux.prex.PresentationExchangeServerEndpoints import org.hyperledger.identus.pollux.vc.jwt.DidResolver as JwtDidResolver import org.hyperledger.identus.presentproof.controller.PresentProofServerEndpoints import org.hyperledger.identus.resolvers.DIDResolver @@ -47,7 +48,7 @@ object CloudAgentApp { _ <- syncRevocationStatusListsJob.debug.fork _ <- AgentHttpServer.run.tapDefect(e => ZIO.logErrorCause("Agent HTTP Server failure", e)).fork fiber <- DidCommHttpServer.run.tapDefect(e => ZIO.logErrorCause("DIDComm HTTP Server failure", e)).fork - _ <- WebhookPublisher.layer.build.map(_.get[WebhookPublisher]).flatMap(_.run.debug.fork) + _ <- WebhookPublisher.layer.build.map(_.get[WebhookPublisher]).flatMap(_.run.fork) _ <- fiber.join *> ZIO.log(s"Server End") _ <- ZIO.never } yield () @@ -137,6 +138,7 @@ object AgentHttpServer { allWalletManagementEndpoints <- WalletManagementServerEndpoints.all allEventEndpoints <- EventServerEndpoints.all allOIDCEndpoints <- CredentialIssuerServerEndpoints.all + allPresentationExchangeEndpoints <- PresentationExchangeServerEndpoints.all } yield allCredentialDefinitionRegistryEndpoints ++ allSchemaRegistryEndpoints ++ allVerificationPolicyEndpoints ++ @@ -147,11 +149,13 @@ object AgentHttpServer { allStatusListEndpoints ++ allPresentProofEndpoints ++ allVcVerificationEndpoints ++ + allPresentationExchangeEndpoints ++ allSystemEndpoints ++ allEntityEndpoints ++ allWalletManagementEndpoints ++ allEventEndpoints ++ allOIDCEndpoints + def run = for { allEndpoints <- agentRESTServiceEndpoints diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala index a2a69e8d9e..10b4928d25 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala @@ -46,12 +46,15 @@ import org.hyperledger.identus.pollux.credentialschema.controller.{ CredentialSchemaControllerImpl, VerificationPolicyControllerImpl } +import org.hyperledger.identus.pollux.prex.controller.PresentationExchangeControllerImpl +import org.hyperledger.identus.pollux.prex.PresentationDefinitionValidatorImpl import org.hyperledger.identus.pollux.sql.repository.{ JdbcCredentialDefinitionRepository, JdbcCredentialRepository, JdbcCredentialSchemaRepository, JdbcCredentialStatusListRepository, JdbcOID4VCIIssuerMetadataRepository, + JdbcPresentationExchangeRepository, JdbcPresentationRepository, JdbcVerificationPolicyRepository, Migrations as PolluxMigrations @@ -169,12 +172,14 @@ object MainApp extends ZIOAppDefault { WalletManagementControllerImpl.layer, EventControllerImpl.layer, DIDCommControllerImpl.layer, + PresentationExchangeControllerImpl.layer, // domain AppModule.apolloLayer, AppModule.didJwtResolverLayer, DIDOperationValidator.layer(), DIDResolver.layer, HttpURIDereferencerImpl.layer, + PresentationDefinitionValidatorImpl.layer, // service ConnectionServiceImpl.layer >>> ConnectionServiceNotifier.layer, CredentialSchemaServiceImpl.layer, @@ -188,6 +193,7 @@ object MainApp extends ZIOAppDefault { VerificationPolicyServiceImpl.layer, WalletManagementServiceImpl.layer, VcVerificationServiceImpl.layer, + PresentationExchangeServiceImpl.layer, // authentication AppModule.builtInAuthenticatorLayer, AppModule.keycloakAuthenticatorLayer, @@ -211,6 +217,7 @@ object MainApp extends ZIOAppDefault { RepoModule.polluxContextAwareTransactorLayer ++ RepoModule.polluxTransactorLayer >>> JdbcCredentialDefinitionRepository.layer, RepoModule.polluxContextAwareTransactorLayer ++ RepoModule.polluxTransactorLayer >>> JdbcPresentationRepository.layer, RepoModule.polluxContextAwareTransactorLayer ++ RepoModule.polluxTransactorLayer >>> JdbcOID4VCIIssuerMetadataRepository.layer, + RepoModule.polluxContextAwareTransactorLayer ++ RepoModule.polluxTransactorLayer >>> JdbcPresentationExchangeRepository.layer, RepoModule.polluxContextAwareTransactorLayer >>> JdbcVerificationPolicyRepository.layer, // oidc CredentialIssuerControllerImpl.layer, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/DocModels.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/DocModels.scala index 0faceda3cf..e13321a552 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/DocModels.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/http/DocModels.scala @@ -8,6 +8,7 @@ import org.hyperledger.identus.iam.wallet.http.WalletManagementEndpoints import org.hyperledger.identus.issue.controller.IssueEndpoints import org.hyperledger.identus.pollux.credentialdefinition.CredentialDefinitionRegistryEndpoints import org.hyperledger.identus.pollux.credentialschema.{SchemaRegistryEndpoints, VerificationPolicyEndpoints} +import org.hyperledger.identus.pollux.prex.PresentationExchangeEndpoints import org.hyperledger.identus.system.controller.SystemEndpoints import sttp.apispec.{SecurityScheme, Tag} import sttp.apispec.openapi.* @@ -122,7 +123,8 @@ object DocModels { WalletManagementEndpoints.tag, SystemEndpoints.tag, EventEndpoints.tag, - EntityEndpoints.tag + EntityEndpoints.tag, + PresentationExchangeEndpoints.tag ) ) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala index d57cccb079..71d02db3e2 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/StatusListJobs.scala @@ -7,7 +7,6 @@ import org.hyperledger.identus.mercury.protocol.revocationnotificaiton.Revocatio import org.hyperledger.identus.pollux.core.service.{CredentialService, CredentialStatusListService} import org.hyperledger.identus.pollux.vc.jwt.revocation.{VCStatusList2021, VCStatusList2021Error} import org.hyperledger.identus.shared.models.* -import org.hyperledger.identus.shared.models.WalletAccessContext import org.hyperledger.identus.shared.utils.DurationOps.toMetricsSeconds import zio.* import zio.metrics.Metric diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDController.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDController.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDController.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDController.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDEndpoints.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDEndpoints.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDEndpoints.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDRegistrarController.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDRegistrarController.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDRegistrarController.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDRegistrarController.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDRegistrarEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDRegistrarEndpoints.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDRegistrarEndpoints.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDRegistrarEndpoints.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDRegistrarServerEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDRegistrarServerEndpoints.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDRegistrarServerEndpoints.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDRegistrarServerEndpoints.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDServerEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDServerEndpoints.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/DIDServerEndpoints.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/DIDServerEndpoints.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDDocument.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDDocument.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDDocument.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDDocument.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDDocumentMetadata.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDDocumentMetadata.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDDocumentMetadata.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDDocumentMetadata.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDInput.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDInput.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDInput.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDInput.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDResolutionMetadata.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDResolutionMetadata.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDResolutionMetadata.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDResolutionMetadata.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDResolutionResult.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDResolutionResult.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/DIDResolutionResult.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/DIDResolutionResult.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/ManagedDID.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/ManagedDID.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/ManagedDID.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/ManagedDID.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/PublicKeyJwk.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/PublicKeyJwk.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/PublicKeyJwk.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/PublicKeyJwk.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/ScheduledOperation.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/ScheduledOperation.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/ScheduledOperation.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/ScheduledOperation.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/Service.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/Service.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/Service.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/Service.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/UpdateManagedDID.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/UpdateManagedDID.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/UpdateManagedDID.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/UpdateManagedDID.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/VerificationMethod.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/VerificationMethod.scala similarity index 100% rename from cloud-agent/service/server/src/main/scala/org/hyperledger/identus/controller/http/VerificationMethod.scala rename to cloud-agent/service/server/src/main/scala/org/hyperledger/identus/castor/controller/http/VerificationMethod.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala index 1bc1a644d4..5a27032b75 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/didcomm/controller/DIDCommControllerError.scala @@ -1,8 +1,7 @@ package org.hyperledger.identus.didcomm.controller import org.hyperledger.identus.mercury.model.DidId -import org.hyperledger.identus.shared.models.{Failure, StatusCode} -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{Failure, KeyId, StatusCode} sealed trait DIDCommControllerError extends Failure { override def namespace = "DIDCommControllerError" diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationExchangeEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationExchangeEndpoints.scala new file mode 100644 index 0000000000..435042b052 --- /dev/null +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationExchangeEndpoints.scala @@ -0,0 +1,97 @@ +package org.hyperledger.identus.pollux.prex + +import org.hyperledger.identus.api.http.{EndpointOutputs, ErrorResponse, RequestContext} +import org.hyperledger.identus.api.http.model.PaginationInput +import org.hyperledger.identus.iam.authentication.apikey.ApiKeyCredentials +import org.hyperledger.identus.iam.authentication.apikey.ApiKeyEndpointSecurityLogic.apiKeyHeader +import org.hyperledger.identus.iam.authentication.oidc.JwtCredentials +import org.hyperledger.identus.iam.authentication.oidc.JwtSecurityLogic.jwtAuthHeader +import org.hyperledger.identus.pollux.prex.http.{CreatePresentationDefinition, PresentationDefinitionPage} +import org.hyperledger.identus.pollux.prex.http.PresentationExchangeTapirSchemas.given +import sttp.apispec.Tag +import sttp.model.StatusCode +import sttp.tapir.* +import sttp.tapir.json.zio.jsonBody + +import java.util.UUID + +object PresentationExchangeEndpoints { + + private val tagName = "Presentation Exchange" + private val tagDescription = + s""" + |The __${tagName}__ endpoints offers a way to manage resources related to [presentation exchange protocol](https://identity.foundation/presentation-exchange/spec/v2.1.1/). + | + |The verifier can create the resources such as `presentation-definition` that can be publicly referenced + |in various protocols such as [OpenID for Verificable Presentation](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). + |""".stripMargin + + val tag = Tag(tagName, Some(tagDescription)) + + private val paginationInput: EndpointInput[PaginationInput] = EndpointInput.derived[PaginationInput] + + private val baseEndpoint = endpoint + .tag(tagName) + .in("presentation-exchange") + .in(extractFromRequest[RequestContext](RequestContext.apply)) + + private val basePrivateEndpoint = baseEndpoint + .securityIn(apiKeyHeader) + .securityIn(jwtAuthHeader) + + val getPresentationDefinition: Endpoint[ + Unit, + (RequestContext, UUID), + ErrorResponse, + PresentationDefinition, + Any + ] = + baseEndpoint.get + .in("presentation-definitions" / path[UUID]("id")) + .out(statusCode(StatusCode.Ok).description("Presentation Definition retrieved successfully")) + .out(jsonBody[PresentationDefinition]) + .errorOut(EndpointOutputs.basicFailuresAndNotFound) + .name("getPresentationDefinition") + .summary("Get a presentation-definition") + + val listPresentationDefinition: Endpoint[ + (ApiKeyCredentials, JwtCredentials), + (RequestContext, PaginationInput), + ErrorResponse, + PresentationDefinitionPage, + Any, + ] = + basePrivateEndpoint.get + .in("presentation-definitions") + .in(paginationInput) + .out(statusCode(StatusCode.Ok).description("Presentation Definitions retrieved successfully")) + .out(jsonBody[PresentationDefinitionPage]) + .errorOut(EndpointOutputs.basicFailuresAndForbidden) + .name("listPresentationDefinition") + .summary("List all presentation-definitions") + .description( + """List all `presentation-definitions` in the wallet. + |Return a paginated items ordered by created timestamp.""".stripMargin + ) + + val createPresentationDefinition: Endpoint[ + (ApiKeyCredentials, JwtCredentials), + (RequestContext, CreatePresentationDefinition), + ErrorResponse, + PresentationDefinition, + Any + ] = + basePrivateEndpoint.post + .in("presentation-definitions") + .in(jsonBody[CreatePresentationDefinition]) + .out(statusCode(StatusCode.Created).description("Presentation Definition created successfully")) + .out(jsonBody[PresentationDefinition]) + .errorOut(EndpointOutputs.basicFailureAndNotFoundAndForbidden) + .name("createPresentationDefinition") + .summary("Create a new presentation-definition") + .description( + """Create a `presentation-definition` object according to the [presentation exchange protocol](https://identity.foundation/presentation-exchange/spec/v2.1.1/). + |The `POST` endpoint is restricted to the owner of the wallet. The `presentation-definition` object, however can be referenced by publicly by `id` returned in the response.""".stripMargin + ) + +} diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationExchangeServerEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationExchangeServerEndpoints.scala new file mode 100644 index 0000000000..e5daa287d1 --- /dev/null +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationExchangeServerEndpoints.scala @@ -0,0 +1,61 @@ +package org.hyperledger.identus.pollux.prex + +import org.hyperledger.identus.agent.walletapi.model.BaseEntity +import org.hyperledger.identus.iam.authentication.{Authenticator, Authorizer, DefaultAuthenticator, SecurityLogic} +import org.hyperledger.identus.pollux.prex.controller.PresentationExchangeController +import org.hyperledger.identus.LogUtils.* +import sttp.tapir.ztapir.* +import zio.* + +class PresentationExchangeServerEndpoints( + controller: PresentationExchangeController, + authenticator: Authenticator[BaseEntity], + authorizer: Authorizer[BaseEntity] +) { + + private val getPresentationDefinitionServerEndpoint: ZServerEndpoint[Any, Any] = + PresentationExchangeEndpoints.getPresentationDefinition + .zServerLogic { case (rc, id) => + controller.getPresentationDefinition(id).logTrace(rc) + } + + private val listPresentationDefinitionServerEndpoint: ZServerEndpoint[Any, Any] = + PresentationExchangeEndpoints.listPresentationDefinition + .zServerSecurityLogic(SecurityLogic.authorizeWalletAccessWith(_)(authenticator, authorizer)) + .serverLogic { wac => + { case (rc, pagination) => + controller + .listPresentationDefinition(pagination)(rc) + .provideSomeLayer(ZLayer.succeed(wac)) + .logTrace(rc) + } + } + + private val createPresentationDefinitionServerEndpoint: ZServerEndpoint[Any, Any] = + PresentationExchangeEndpoints.createPresentationDefinition + .zServerSecurityLogic(SecurityLogic.authorizeWalletAccessWith(_)(authenticator, authorizer)) + .serverLogic { wac => + { case (rc, pd) => + controller + .createPresentationDefinition(pd) + .provideSomeLayer(ZLayer.succeed(wac)) + .logTrace(rc) + } + } + + val all: List[ZServerEndpoint[Any, Any]] = List( + getPresentationDefinitionServerEndpoint, + listPresentationDefinitionServerEndpoint, + createPresentationDefinitionServerEndpoint + ) +} + +object PresentationExchangeServerEndpoints { + def all: URIO[DefaultAuthenticator & PresentationExchangeController, List[ZServerEndpoint[Any, Any]]] = { + for { + controller <- ZIO.service[PresentationExchangeController] + authenticator <- ZIO.service[DefaultAuthenticator] + endpoints = PresentationExchangeServerEndpoints(controller, authenticator, authenticator) + } yield endpoints.all + } +} diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/controller/PresentationExchangeController.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/controller/PresentationExchangeController.scala new file mode 100644 index 0000000000..0376544d9b --- /dev/null +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/controller/PresentationExchangeController.scala @@ -0,0 +1,62 @@ +package org.hyperledger.identus.pollux.prex.controller + +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.pollux.core.service.PresentationExchangeService +import org.hyperledger.identus.pollux.prex.http.{CreatePresentationDefinition, PresentationDefinitionPage} +import org.hyperledger.identus.pollux.prex.PresentationDefinition +import org.hyperledger.identus.shared.models.WalletAccessContext +import zio.* + +import java.util.UUID +import scala.language.implicitConversions + +trait PresentationExchangeController { + def createPresentationDefinition( + cpd: CreatePresentationDefinition + ): ZIO[WalletAccessContext, ErrorResponse, PresentationDefinition] + + def getPresentationDefinition(id: UUID): IO[ErrorResponse, PresentationDefinition] + + def listPresentationDefinition(paginationInput: PaginationInput)(implicit + rc: RequestContext + ): ZIO[WalletAccessContext, ErrorResponse, PresentationDefinitionPage] +} + +class PresentationExchangeControllerImpl(service: PresentationExchangeService) extends PresentationExchangeController { + + override def createPresentationDefinition( + cpd: CreatePresentationDefinition + ): ZIO[WalletAccessContext, ErrorResponse, PresentationDefinition] = { + val pd: PresentationDefinition = cpd + service.createPresentationDefinititon(pd).as(pd) + } + + override def getPresentationDefinition(id: UUID): IO[ErrorResponse, PresentationDefinition] = + service.getPresentationDefinition(id) + + override def listPresentationDefinition( + paginationInput: PaginationInput + )(implicit rc: RequestContext): ZIO[WalletAccessContext, ErrorResponse, PresentationDefinitionPage] = { + val uri = rc.request.uri + val pagination = paginationInput.toPagination + for { + pageResult <- service.listPresentationDefinition(offset = paginationInput.offset, limit = paginationInput.limit) + (items, totalCount) = pageResult + stats = CollectionStats(totalCount = totalCount, filteredCount = totalCount) + } yield PresentationDefinitionPage( + self = uri.toString(), + pageOf = PaginationUtils.composePageOfUri(uri).toString, + next = PaginationUtils.composeNextUri(uri, items, pagination, stats).map(_.toString), + previous = PaginationUtils.composePreviousUri(uri, items, pagination, stats).map(_.toString), + contents = items, + ) + } + +} + +object PresentationExchangeControllerImpl { + def layer: URLayer[PresentationExchangeService, PresentationExchangeController] = + ZLayer.fromFunction(PresentationExchangeControllerImpl(_)) +} diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/http/PresentationDefinition.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/http/PresentationDefinition.scala new file mode 100644 index 0000000000..fb3c3066b2 --- /dev/null +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/http/PresentationDefinition.scala @@ -0,0 +1,42 @@ +package org.hyperledger.identus.pollux.prex.http + +import org.hyperledger.identus.pollux.prex.{ClaimFormat, InputDescriptor, PresentationDefinition} +import org.hyperledger.identus.pollux.prex.http.PresentationExchangeTapirSchemas.given +import sttp.tapir.Schema +import zio.json.{JsonDecoder, JsonEncoder} + +case class CreatePresentationDefinition( + input_descriptors: Seq[InputDescriptor] = Seq.empty, + name: Option[String] = None, + purpose: Option[String] = None, + format: Option[ClaimFormat] = None +) + +object CreatePresentationDefinition { + given Schema[CreatePresentationDefinition] = Schema.derived + given JsonEncoder[CreatePresentationDefinition] = JsonEncoder.derived + given JsonDecoder[CreatePresentationDefinition] = JsonDecoder.derived + + given Conversion[CreatePresentationDefinition, PresentationDefinition] = cpd => + PresentationDefinition( + input_descriptors = cpd.input_descriptors, + name = cpd.name, + purpose = cpd.purpose, + format = cpd.format + ) +} + +case class PresentationDefinitionPage( + self: String, + kind: String = "PresentationDefinitionPage", + pageOf: String, + next: Option[String] = None, + previous: Option[String] = None, + contents: Seq[PresentationDefinition] +) + +object PresentationDefinitionPage { + given Schema[PresentationDefinitionPage] = Schema.derived + given JsonEncoder[PresentationDefinitionPage] = JsonEncoder.derived + given JsonDecoder[PresentationDefinitionPage] = JsonDecoder.derived +} diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/http/PresentationExchangeTapirSchemas.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/http/PresentationExchangeTapirSchemas.scala new file mode 100644 index 0000000000..91b129f5f2 --- /dev/null +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/pollux/prex/http/PresentationExchangeTapirSchemas.scala @@ -0,0 +1,24 @@ +package org.hyperledger.identus.pollux.prex.http + +import org.hyperledger.identus.pollux.prex.* +import sttp.tapir.json.zio.* +import sttp.tapir.Schema +import zio.json.ast.Json + +import scala.language.implicitConversions + +object PresentationExchangeTapirSchemas { + given Schema[PresentationDefinition] = Schema + .derived[PresentationDefinition] + .description( + "*Presentation Definition* object according to the [PresentationExchange spec](https://identity.foundation/presentation-exchange/spec/v2.1.1/#presentation-definition)" + ) + given Schema[InputDescriptor] = Schema.derived + given Schema[ClaimFormat] = Schema.derived + given Schema[Constraints] = Schema.derived + given Schema[Jwt] = Schema.derived + given Schema[Ldp] = Schema.derived + given Schema[Field] = Schema.derived + given Schema[JsonPathValue] = Schema.schemaForString.map[JsonPathValue](Some(_))(_.value) + given Schema[FieldFilter] = Schema.derived[Json].map[FieldFilter](Some(_))(_.asJsonZio) +} diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/api/util/Tapir2StaticOAS.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/api/util/Tapir2StaticOAS.scala index 42f55edbc5..05b217c71e 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/api/util/Tapir2StaticOAS.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/api/util/Tapir2StaticOAS.scala @@ -16,6 +16,7 @@ import org.hyperledger.identus.pollux.credentialschema.controller.{ CredentialSchemaController, VerificationPolicyController } +import org.hyperledger.identus.pollux.prex.controller.PresentationExchangeController import org.hyperledger.identus.presentproof.controller.PresentProofController import org.hyperledger.identus.system.controller.SystemController import org.hyperledger.identus.verification.controller.VcVerificationController @@ -58,6 +59,7 @@ object Tapir2StaticOAS extends ZIOAppDefault { ZLayer.succeed(mock[DefaultAuthenticator]) ++ ZLayer.succeed(mock[EventController]) ++ ZLayer.succeed(mock[CredentialIssuerController]) ++ + ZLayer.succeed(mock[PresentationExchangeController]) ++ ZLayer.succeed(mock[Oid4vciAuthenticatorFactory]) ) } diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala index edcb5f1411..422928756c 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala @@ -22,8 +22,7 @@ import org.hyperledger.identus.mercury.protocol.invitation.v2.Invitation import org.hyperledger.identus.pollux.core.model.{CredentialFormat, DidCommID, IssueCredentialRecord} import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.{ProtocolState, Role} import org.hyperledger.identus.pollux.core.service.MockCredentialService -import org.hyperledger.identus.shared.models.KeyId -import org.hyperledger.identus.shared.models.WalletId +import org.hyperledger.identus.shared.models.{KeyId, WalletId} import sttp.client3.{basicRequest, DeserializationException, UriContext} import sttp.client3.ziojson.* import sttp.model.StatusCode diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala index c233844df1..935a38244f 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDService.scala @@ -7,8 +7,7 @@ import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.mercury.model.* import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} -import org.hyperledger.identus.shared.models.KeyId -import org.hyperledger.identus.shared.models.WalletAccessContext +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext} import zio.* /** A wrapper around Castor's DIDService providing key-management capability. Analogous to the secretAPI in diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala index c93806c040..4e8763bdad 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceImpl.scala @@ -14,8 +14,7 @@ import org.hyperledger.identus.castor.core.util.DIDOperationValidator import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} -import org.hyperledger.identus.shared.models.KeyId -import org.hyperledger.identus.shared.models.WalletAccessContext +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext} import zio.* import scala.collection.immutable.ArraySeq 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 479b34ed2b..66fec256bb 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 @@ -12,8 +12,7 @@ import org.hyperledger.identus.agent.walletapi.storage.{DIDNonSecretStorage, DID import org.hyperledger.identus.agent.walletapi.util.OperationFactory import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, X25519KeyPair} -import org.hyperledger.identus.shared.models.KeyId -import org.hyperledger.identus.shared.models.WalletAccessContext +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext} import zio.* private[walletapi] class DIDCreateHandler( 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 b93510bc69..dd0af4256a 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 @@ -19,8 +19,7 @@ import org.hyperledger.identus.castor.core.model.did.{ } import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation.Update import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, X25519KeyPair} -import org.hyperledger.identus.shared.models.KeyId -import org.hyperledger.identus.shared.models.WalletAccessContext +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext} import zio.* import scala.collection.immutable.ArraySeq diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala index 1807e9efd9..b1656f0679 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/handler/PublicationHandler.scala @@ -11,8 +11,7 @@ import org.hyperledger.identus.castor.core.model.did.{ import org.hyperledger.identus.castor.core.model.error.DIDOperationError import org.hyperledger.identus.castor.core.service.DIDService import org.hyperledger.identus.shared.crypto.Secp256k1KeyPair -import org.hyperledger.identus.shared.models.KeyId -import org.hyperledger.identus.shared.models.WalletAccessContext +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext} import zio.* import scala.collection.immutable.ArraySeq 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 85cada9985..bfdca44f73 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 @@ -16,8 +16,7 @@ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.db.ContextAwareTask import org.hyperledger.identus.shared.db.Implicits.* import org.hyperledger.identus.shared.db.Implicits.given -import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext, WalletId} import zio.* import zio.interop.catz.* diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala index 76f9e2650f..7ff1e935b7 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/sql/JdbcDIDSecretStorage.scala @@ -10,8 +10,7 @@ import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.crypto.jwk.{FromJWK, JWK} import org.hyperledger.identus.shared.db.ContextAwareTask import org.hyperledger.identus.shared.db.Implicits.* -import org.hyperledger.identus.shared.models.KeyId -import org.hyperledger.identus.shared.models.WalletAccessContext +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext} import zio.* import java.time.Instant diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala index e1f84efb4b..1830dc1600 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDNonSecretStorage.scala @@ -3,8 +3,7 @@ package org.hyperledger.identus.agent.walletapi.storage import org.hyperledger.identus.agent.walletapi.model.* import org.hyperledger.identus.castor.core.model.did.{PrismDID, ScheduledDIDOperationStatus} import org.hyperledger.identus.mercury.model.DidId -import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext, WalletId} import zio.* trait DIDNonSecretStorage { diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala index 4726e5a015..0056e0d0e6 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorage.scala @@ -4,8 +4,7 @@ import com.nimbusds.jose.jwk.OctetKeyPair import org.hyperledger.identus.castor.core.model.did.PrismDID import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.crypto.jwk.{FromJWK, JWK} -import org.hyperledger.identus.shared.models.KeyId -import org.hyperledger.identus.shared.models.WalletAccessContext +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext} import zio.* /** A simple single-user DID key storage */ 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 6460af34f5..76a28a853f 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 @@ -9,8 +9,7 @@ import org.hyperledger.identus.agent.walletapi.model.{ import org.hyperledger.identus.agent.walletapi.storage.{DIDNonSecretStorage, DIDSecretStorage, WalletSecretStorage} import org.hyperledger.identus.castor.core.model.did.{EllipticCurve, PrismDID} import org.hyperledger.identus.shared.crypto.{Apollo, Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} -import org.hyperledger.identus.shared.models.KeyId -import org.hyperledger.identus.shared.models.WalletAccessContext +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext} import zio.* class KeyResolver( diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala index acabbb413d..9cf16b6a42 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactory.scala @@ -10,8 +10,7 @@ import org.hyperledger.identus.shared.crypto.{ Secp256k1PublicKey, X25519PublicKey } -import org.hyperledger.identus.shared.models.Base64UrlString -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId} import zio.* import scala.collection.immutable.ArraySeq diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala index 1cf98aa9a1..2ca1faf466 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/vault/VaultDIDSecretStorage.scala @@ -6,8 +6,7 @@ import org.hyperledger.identus.castor.core.model.did.PrismDID import org.hyperledger.identus.mercury.model.DidId import org.hyperledger.identus.shared.crypto.jwk.{FromJWK, JWK} import org.hyperledger.identus.shared.crypto.Sha256Hash -import org.hyperledger.identus.shared.models.{HexString, WalletAccessContext, WalletId} -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{HexString, KeyId, WalletAccessContext, WalletId} import zio.* import java.nio.charset.StandardCharsets diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala index 8448ae4131..7950a38265 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceSpec.scala @@ -15,8 +15,7 @@ import org.hyperledger.identus.castor.core.model.error import org.hyperledger.identus.castor.core.service.DIDService import org.hyperledger.identus.castor.core.util.DIDOperationValidator import org.hyperledger.identus.shared.crypto.{ApolloSpecHelper, Ed25519KeyPair, Secp256k1KeyPair, X25519KeyPair} -import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext, WalletAdministrationContext} import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport import org.hyperledger.identus.test.container.{DBTestUtils, VaultTestContainerSupport} import zio.* diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala index 0ff4217449..9288c39cd9 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/DIDSecretStorageSpec.scala @@ -12,8 +12,7 @@ import org.hyperledger.identus.agent.walletapi.vault.{VaultDIDSecretStorage, Vau import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation import org.hyperledger.identus.mercury.PeerDID import org.hyperledger.identus.shared.crypto.{Apollo, ApolloSpecHelper, Ed25519KeyPair, X25519KeyPair} -import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext, WalletAdministrationContext} import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport import org.hyperledger.identus.test.container.{DBTestUtils, VaultTestContainerSupport} import zio.* diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala index e20c1a245a..f14df4d00d 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/MockDIDNonSecretStorage.scala @@ -3,8 +3,7 @@ package org.hyperledger.identus.agent.walletapi.storage import org.hyperledger.identus.agent.walletapi.model.* import org.hyperledger.identus.castor.core.model.did.{PrismDID, ScheduledDIDOperationStatus} import org.hyperledger.identus.mercury.model.DidId -import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext, WalletId} import zio.* import zio.mock.{Expectation, Mock, Proxy} import zio.test.Assertion.equalTo diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala index ef6a3b05e4..15f348ca1d 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/storage/StorageSpecHelper.scala @@ -18,8 +18,7 @@ import org.hyperledger.identus.castor.core.model.did.{ VerificationRelationship } import org.hyperledger.identus.shared.crypto.ApolloSpecHelper -import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletAdministrationContext} -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{KeyId, WalletAccessContext, WalletAdministrationContext} import zio.* import zio.test.* diff --git a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala index 8131d80f18..6d24a9c403 100644 --- a/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala +++ b/cloud-agent/service/wallet-api/src/test/scala/org/hyperledger/identus/agent/walletapi/util/OperationFactorySpec.scala @@ -3,8 +3,7 @@ package org.hyperledger.identus.agent.walletapi.util import org.hyperledger.identus.agent.walletapi.model.* import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.shared.crypto.{ApolloSpecHelper, Ed25519KeyPair, X25519KeyPair} -import org.hyperledger.identus.shared.models.HexString -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{HexString, KeyId} import zio.* import zio.test.* import zio.test.Assertion.* diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala index 09d221ed25..a0ffdbe944 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialServiceError.scala @@ -4,8 +4,7 @@ import org.hyperledger.identus.agent.walletapi.model.PublicationState import org.hyperledger.identus.castor.core.model.did.{PrismDID, VerificationRelationship} import org.hyperledger.identus.pollux.core.model.DidCommID import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.ProtocolState -import org.hyperledger.identus.shared.models.{Failure, StatusCode} -import org.hyperledger.identus.shared.models.KeyId +import org.hyperledger.identus.shared.models.{Failure, KeyId, StatusCode} import java.util.UUID diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationExchangeRepository.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationExchangeRepository.scala new file mode 100644 index 0000000000..b94026af86 --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/PresentationExchangeRepository.scala @@ -0,0 +1,16 @@ +package org.hyperledger.identus.pollux.core.repository + +import org.hyperledger.identus.pollux.prex.PresentationDefinition +import org.hyperledger.identus.shared.models.WalletAccessContext +import zio.* + +import java.util.UUID + +trait PresentationExchangeRepository { + def createPresentationDefinition(pd: PresentationDefinition): URIO[WalletAccessContext, Unit] + def findPresentationDefinition(id: UUID): UIO[Option[PresentationDefinition]] + def listPresentationDefinition( + offset: Option[Int] = None, + limit: Option[Int] = None + ): URIO[WalletAccessContext, (Seq[PresentationDefinition], Int)] +} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationExchangeService.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationExchangeService.scala new file mode 100644 index 0000000000..9096c2510e --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationExchangeService.scala @@ -0,0 +1,44 @@ +package org.hyperledger.identus.pollux.core.service + +import org.hyperledger.identus.pollux.core.service.PresentationExchangeServiceError.{ + PresentationDefinitionNotFound, + PresentationDefinitionValidationError +} +import org.hyperledger.identus.pollux.prex.{PresentationDefinition, PresentationDefinitionError} +import org.hyperledger.identus.shared.models.{Failure, StatusCode, WalletAccessContext} +import zio.* + +import java.util.UUID + +sealed trait PresentationExchangeServiceError( + val statusCode: StatusCode, + val userFacingMessage: String +) extends Failure { + override val namespace = "PresentationExchangeServiceError" +} + +object PresentationExchangeServiceError { + case class PresentationDefinitionNotFound(id: UUID) + extends PresentationExchangeServiceError(StatusCode.NotFound, s"PresentationDefinition not found with id: $id") + + case class PresentationDefinitionValidationError(error: PresentationDefinitionError) + extends PresentationExchangeServiceError( + StatusCode.BadRequest, + s"PresentationDefinition validation failed: ${error.userFacingMessage}" + ) +} + +trait PresentationExchangeService { + def createPresentationDefinititon( + pd: PresentationDefinition + ): ZIO[WalletAccessContext, PresentationDefinitionValidationError, Unit] + + def getPresentationDefinition( + id: UUID + ): IO[PresentationDefinitionNotFound, PresentationDefinition] + + def listPresentationDefinition( + limit: Option[Int], + offset: Option[Int] + ): URIO[WalletAccessContext, (Seq[PresentationDefinition], Int)] +} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationExchangeServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationExchangeServiceImpl.scala new file mode 100644 index 0000000000..4af0203c07 --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationExchangeServiceImpl.scala @@ -0,0 +1,41 @@ +package org.hyperledger.identus.pollux.core.service + +import org.hyperledger.identus.pollux.core.repository.PresentationExchangeRepository +import org.hyperledger.identus.pollux.core.service.PresentationExchangeServiceError.{ + PresentationDefinitionNotFound, + PresentationDefinitionValidationError +} +import org.hyperledger.identus.pollux.prex.{PresentationDefinition, PresentationDefinitionValidator} +import org.hyperledger.identus.shared.models.WalletAccessContext +import zio.* + +import java.util.UUID + +class PresentationExchangeServiceImpl(validator: PresentationDefinitionValidator, repo: PresentationExchangeRepository) + extends PresentationExchangeService { + + override def createPresentationDefinititon( + pd: PresentationDefinition + ): ZIO[WalletAccessContext, PresentationDefinitionValidationError, Unit] = + for { + _ <- validator.validate(pd).mapError(PresentationDefinitionValidationError(_)) + _ <- repo.createPresentationDefinition(pd) + } yield () + + override def getPresentationDefinition( + id: UUID + ): IO[PresentationDefinitionNotFound, PresentationDefinition] = + repo.findPresentationDefinition(id).someOrFail(PresentationDefinitionNotFound(id)) + + override def listPresentationDefinition( + limit: Option[Int], + offset: Option[Int] + ): URIO[WalletAccessContext, (Seq[PresentationDefinition], Int)] = + repo.listPresentationDefinition(offset = offset, limit = limit) + +} + +object PresentationExchangeServiceImpl { + def layer: URLayer[PresentationDefinitionValidator & PresentationExchangeRepository, PresentationExchangeService] = + ZLayer.fromFunction(PresentationExchangeServiceImpl(_, _)) +} diff --git a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinition.scala b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinition.scala index 21ded1e947..ea16960cc9 100644 --- a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinition.scala +++ b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinition.scala @@ -6,6 +6,7 @@ import io.circe.generic.semiauto.* import io.circe.Json as CirceJson import org.hyperledger.identus.shared.json.{JsonInterop, JsonPath, JsonPathError, JsonSchemaError, JsonSchemaUtils} import zio.* +import zio.json.{JsonDecoder, JsonEncoder} import zio.json.ast.Json as ZioJson opaque type JsonPathValue = String @@ -15,6 +16,9 @@ object JsonPathValue { given Decoder[JsonPathValue] = Decoder.decodeString given Conversion[String, JsonPathValue] = identity + given JsonEncoder[JsonPathValue] = JsonEncoder.string + given JsonDecoder[JsonPathValue] = JsonDecoder.string + extension (jpv: JsonPathValue) { def toJsonPath: Either[JsonPathError, JsonPath] = JsonPath.compile(jpv) def value: String = jpv @@ -26,6 +30,10 @@ opaque type FieldFilter = ZioJson object FieldFilter { given Encoder[FieldFilter] = Encoder.encodeJson.contramap(JsonInterop.toCirceJsonAst) given Decoder[FieldFilter] = Decoder.decodeJson.map(JsonInterop.toZioJsonAst) + given Conversion[ZioJson, FieldFilter] = identity + + given JsonEncoder[FieldFilter] = ZioJson.encoder + given JsonDecoder[FieldFilter] = ZioJson.decoder extension (f: FieldFilter) def asJsonZio: ZioJson = f @@ -49,6 +57,9 @@ case class Field( object Field { given Encoder[Field] = deriveEncoder[Field] given Decoder[Field] = deriveDecoder[Field] + + given JsonEncoder[Field] = JsonEncoder.derived + given JsonDecoder[Field] = JsonDecoder.derived } case class Jwt(alg: Seq[String]) @@ -56,6 +67,9 @@ case class Jwt(alg: Seq[String]) object Jwt { given Encoder[Jwt] = deriveEncoder[Jwt] given Decoder[Jwt] = deriveDecoder[Jwt] + + given JsonEncoder[Jwt] = JsonEncoder.derived + given JsonDecoder[Jwt] = JsonDecoder.derived } case class Ldp(proof_type: Seq[String]) @@ -63,6 +77,9 @@ case class Ldp(proof_type: Seq[String]) object Ldp { given Encoder[Ldp] = deriveEncoder[Ldp] given Decoder[Ldp] = deriveDecoder[Ldp] + + given JsonEncoder[Ldp] = JsonEncoder.derived + given JsonDecoder[Ldp] = JsonDecoder.derived } enum ClaimFormatValue(val value: String) { @@ -89,6 +106,9 @@ case class ClaimFormat( object ClaimFormat { given Encoder[ClaimFormat] = deriveEncoder[ClaimFormat] given Decoder[ClaimFormat] = deriveDecoder[ClaimFormat] + + given JsonEncoder[ClaimFormat] = JsonEncoder.derived + given JsonDecoder[ClaimFormat] = JsonDecoder.derived } case class Constraints(fields: Option[Seq[Field]]) @@ -96,6 +116,9 @@ case class Constraints(fields: Option[Seq[Field]]) object Constraints { given Encoder[Constraints] = deriveEncoder[Constraints] given Decoder[Constraints] = deriveDecoder[Constraints] + + given JsonEncoder[Constraints] = JsonEncoder.derived + given JsonDecoder[Constraints] = JsonDecoder.derived } /** Refer to Input Descriptors @@ -111,6 +134,9 @@ case class InputDescriptor( object InputDescriptor { given Encoder[InputDescriptor] = deriveEncoder[InputDescriptor] given Decoder[InputDescriptor] = deriveDecoder[InputDescriptor] + + given JsonEncoder[InputDescriptor] = JsonEncoder.derived + given JsonDecoder[InputDescriptor] = JsonDecoder.derived } /** Refer to Presentation @@ -127,4 +153,7 @@ case class PresentationDefinition( object PresentationDefinition { given Encoder[PresentationDefinition] = deriveEncoder[PresentationDefinition] given Decoder[PresentationDefinition] = deriveDecoder[PresentationDefinition] + + given JsonEncoder[PresentationDefinition] = JsonEncoder.derived + given JsonDecoder[PresentationDefinition] = JsonDecoder.derived } diff --git a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinitionValidator.scala b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinitionValidator.scala index 9b21a17d94..d0fba75b67 100644 --- a/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinitionValidator.scala +++ b/pollux/prex/src/main/scala/org/hyperledger/identus/pollux/prex/PresentationDefinitionValidator.scala @@ -56,10 +56,11 @@ trait PresentationDefinitionValidator { } object PresentationDefinitionValidatorImpl { - def layer: Layer[JsonSchemaError, PresentationDefinitionValidator] = + def layer: ULayer[PresentationDefinitionValidator] = ZLayer.scoped { JsonSchemaValidatorImpl.draft7Meta .map(PresentationDefinitionValidatorImpl(_)) + .orDieWith(e => Exception(s"Failed to load JSON schema draft-7 meta schema: $e")) } } diff --git a/pollux/sql-doobie/src/main/resources/sql/pollux/V27__presentation_definition_table.sql b/pollux/sql-doobie/src/main/resources/sql/pollux/V27__presentation_definition_table.sql new file mode 100644 index 0000000000..5faf02007a --- /dev/null +++ b/pollux/sql-doobie/src/main/resources/sql/pollux/V27__presentation_definition_table.sql @@ -0,0 +1,16 @@ +CREATE TABLE public.presentation_definition ( + id UUID PRIMARY KEY, + input_descriptors json NOT NULL, + name VARCHAR(300), + purpose VARCHAR(100), + format json, + wallet_id UUID NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL +); + +ALTER TABLE public.presentation_definition + ENABLE ROW LEVEL SECURITY; + +CREATE POLICY presentation_definition_wallet_isolation + ON public.presentation_definition + USING (wallet_id = current_setting('app.current_wallet_id')::UUID); diff --git a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/model/db/PresentationDefinition.scala b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/model/db/PresentationDefinition.scala new file mode 100644 index 0000000000..5b6d436a89 --- /dev/null +++ b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/model/db/PresentationDefinition.scala @@ -0,0 +1,69 @@ +package org.hyperledger.identus.pollux.sql.model.db + +import io.getquill.* +import io.getquill.context.json.PostgresJsonExtensions +import io.getquill.doobie.DoobieContext +import io.getquill.idiom.* +import org.hyperledger.identus.pollux.prex +import org.hyperledger.identus.shared.models.WalletId + +import java.time.Instant +import java.util.UUID + +case class PresentationDefinition( + id: UUID, + input_descriptors: JsonValue[Seq[prex.InputDescriptor]], + name: Option[String], + purpose: Option[String], + format: Option[JsonValue[prex.ClaimFormat]], + createdAt: Instant, + walletId: WalletId +) + +object PresentationDefinition { + def fromModel(pd: prex.PresentationDefinition, walletId: WalletId, createdAt: Instant): PresentationDefinition = { + PresentationDefinition( + id = UUID.fromString(pd.id), + input_descriptors = JsonValue(pd.input_descriptors), + name = pd.name, + purpose = pd.purpose, + format = pd.format.map(JsonValue(_)), + createdAt = createdAt, + walletId = walletId + ) + } + + extension (pd: PresentationDefinition) { + def toModel: prex.PresentationDefinition = { + prex.PresentationDefinition( + id = pd.id.toString(), + input_descriptors = pd.input_descriptors.value, + name = pd.name, + purpose = pd.purpose, + format = pd.format.map(_.value) + ) + } + } +} + +object PresentationDefinitionSql extends DoobieContext.Postgres(SnakeCase) with PostgresJsonExtensions { + def insert(pd: PresentationDefinition) = run { + quote { + query[PresentationDefinition].insertValue(lift(pd)) + } + } + + def findById(id: UUID) = run { + quote { + query[PresentationDefinition].filter(_.id == lift(id)) + } + } + + def lookupCount() = run { quote(query[PresentationDefinition].size) } + + def lookup(offset: Int, limit: Int) = run { + quote { + query[PresentationDefinition].sortBy(_.createdAt).drop(lift(offset)).take(lift(limit)) + } + } +} diff --git a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcPresentationExchangeRepository.scala b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcPresentationExchangeRepository.scala new file mode 100644 index 0000000000..2c58dd6245 --- /dev/null +++ b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcPresentationExchangeRepository.scala @@ -0,0 +1,67 @@ +package org.hyperledger.identus.pollux.sql.repository + +import doobie.* +import doobie.implicits.* +import doobie.util.transactor.Transactor +import org.hyperledger.identus.pollux.core.repository.PresentationExchangeRepository +import org.hyperledger.identus.pollux.prex.PresentationDefinition +import org.hyperledger.identus.pollux.sql.model.db +import org.hyperledger.identus.pollux.sql.model.db.PresentationDefinitionSql +import org.hyperledger.identus.shared.db.ContextAwareTask +import org.hyperledger.identus.shared.db.Implicits.* +import org.hyperledger.identus.shared.models.WalletAccessContext +import zio.* +import zio.interop.catz.* + +import java.util.UUID + +class JdbcPresentationExchangeRepository(xa: Transactor[ContextAwareTask], xb: Transactor[Task]) + extends PresentationExchangeRepository { + + override def createPresentationDefinition(pd: PresentationDefinition): URIO[WalletAccessContext, Unit] = { + for { + now <- Clock.instant + walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId) + row = db.PresentationDefinition.fromModel(pd, walletId, now) + _ <- PresentationDefinitionSql + .insert(row) + .transactWallet(xa) + .orDie + } yield () + } + + override def findPresentationDefinition(id: UUID): UIO[Option[PresentationDefinition]] = { + PresentationDefinitionSql + .findById(id) + .transact(xb) + .orDie + .map(_.headOption.map(_.toModel)) + } + + override def listPresentationDefinition( + offset: Option[Int], + limit: Option[Int] + ): URIO[WalletAccessContext, (Seq[PresentationDefinition], Int)] = { + val countCxnIO = PresentationDefinitionSql.lookupCount() + val pdCxnIO = PresentationDefinitionSql.lookup( + offset = offset.getOrElse(0), + limit = limit.getOrElse(100) + ) + + val effect = + for { + totalCount <- countCxnIO + rows <- pdCxnIO.map(_.map(_.toModel)) + } yield (rows, totalCount.toInt) + + effect + .transactWallet(xa) + .orDie + } + +} + +object JdbcPresentationExchangeRepository { + def layer: URLayer[Transactor[ContextAwareTask] & Transactor[Task], PresentationExchangeRepository] = + ZLayer.fromFunction(JdbcPresentationExchangeRepository(_, _)) +} diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala index f6cbc60efe..185cd59732 100644 --- a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala +++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/KeyId.scala @@ -1,6 +1,6 @@ package org.hyperledger.identus.shared.models -import zio.json._ +import zio.json.* opaque type KeyId = String object KeyId: From dd6243355cc590fe8ec3fb2dcdada953a438f922 Mon Sep 17 00:00:00 2001 From: Fabio Pinheiro Date: Thu, 19 Sep 2024 11:29:17 +0100 Subject: [PATCH 9/9] docs: Update README.md add link to Quickstart guide (#1364) Signed-off-by: Fabio Pinheiro --- README.md | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/README.md b/README.md index 4d56f8ce02..455080b730 100644 --- a/README.md +++ b/README.md @@ -122,34 +122,7 @@ System requirements can vary depending on the use case. The following are the mi #### Running locally in demo mode -Here is a general example of running a Cloud Agent locally: -```bash -PORT=${PORT} AGENT_VERSION=${AGENT_VERSION} PRISM_NODE_VERSION=${PRISM_NODE_VERSION} \ - docker compose \ - -p "${AGENT_ROLE}" \ - -f ./infrastructure/shared/docker-compose-demo.yml \ - up --wait -``` - -The `PORT` variable is used to specify the port number for the Cloud Agent to listen on. The `AGENT_VERSION` and `PRISM_NODE_VERSION` variables are used to specify the versions of the Cloud Agent and PRISM Node to use. The `AGENT_ROLE` variable is used to specify the role of the Cloud Agent. The `AGENT_ROLE` variable can be set to `issuer`, `verifier` or `holder`. - -In real life, you will need to start at least two Cloud Agent instances with different roles. For example, you can start one instance with the `issuer` role and another one with the `holder` role. The `issuer` instance will be used to issue verifiable credentials (VCs) and the `holder` instance will be used to hold VCs. Here is an example of how you can do this: - -```bash -PORT=8080 AGENT_VERSION=${AGENT_VERSION} PRISM_NODE_VERSION=2.3.0 \ - docker compose \ - -p "issuer" \ - -f ./infrastructure/shared/docker-compose-demo.yml \ - up --wait -``` - -```bash -PORT=8090 AGENT_VERSION=${AGENT_VERSION} PRISM_NODE_VERSION=2.3.0 \ - docker compose \ - -p "holder" \ - -f ./infrastructure/shared/docker-compose-demo.yml \ - up --wait -``` +To run Identus locally you should follow the instructions in the [Quickstart guide](https://hyperledger.github.io/identus-docs/docs/quick-start/) If the Cloud Agent is started successfully, all the running containers should achieve `Healthy` state, and Cloud Agent Rest API should be available at the specified port, for example: * `http://localhost:8080/cloud-agent` for the `issuer` instance @@ -161,8 +134,6 @@ $ curl http://localhost:8080/cloud-agent/_system/health {"version":"1.19.1"} ``` -> For more information about all available configuration parameters, please, check [Cloud Agent configuration](https://docs.atalaprism.io/docs/atala-prism/prism-cloud-agent/environment-variables) section at the documentation portal and edit the `docker-compose-demo.yml` file accordingly. - #### Compatibility between Cloud Agent and PRISM Node There could be some incompatibilities between the most latest versions of Cloud Agent and PRISM Node. Please, use the following table to check the compatibility between the versions: