From 3a55455bd06c4beb7409ebd677c2397e839cb46f Mon Sep 17 00:00:00 2001 From: bvoiturier Date: Mon, 2 Oct 2023 12:47:15 +0200 Subject: [PATCH] feat(prism-agent): check issuing DID validity when creating a VC offer + return 'metaRetries' (#740) Signed-off-by: Benjamin Voiturier Signed-off-by: Milos Backonja Signed-off-by: Anton Baliasnikov Signed-off-by: Pat Losoponkul Co-authored-by: Milos Backonja <35807060+milosbackonja@users.noreply.github.com> Co-authored-by: atala-dev Co-authored-by: patlo-iog Signed-off-by: Shota Jolbordi --- build.sbt | 1 + .../castor/core/service/MockDIDService.scala | 94 +++++++++++++++++++ .../core/model/IssueCredentialRecord.scala | 9 +- .../repository/CredentialRepository.scala | 4 +- .../CredentialRepositoryInMemory.scala | 28 +++--- .../repository/PresentationRepository.scala | 2 +- .../PresentationRepositoryInMemory.scala | 2 +- .../core/service/CredentialService.scala | 4 +- .../core/service/CredentialServiceImpl.scala | 8 +- .../service/CredentialServiceNotifier.scala | 8 +- .../core/service/MockCredentialService.scala | 4 +- .../service/MockPresentationService.scala | 4 +- .../core/service/PresentationService.scala | 4 +- .../service/PresentationServiceImpl.scala | 6 +- .../service/PresentationServiceNotifier.scala | 6 +- .../CredentialRepositorySpecSuite.scala | 27 +++--- .../PresentationRepositorySpecSuite.scala | 6 +- .../service/CredentialServiceImplSpec.scala | 2 +- .../service/PresentationServiceSpec.scala | 2 +- .../repository/JdbcCredentialRepository.scala | 4 +- .../JdbcPresentationRepository.scala | 2 +- .../atala/agent/server/ControllerHelper.scala | 20 +++- .../connect/controller/http/Connection.scala | 10 ++ .../controller/IssueControllerImpl.scala | 61 ++++++++++-- .../issue/controller/IssueEndpoints.scala | 2 +- .../http/IssueCredentialRecord.scala | 14 ++- .../PresentProofControllerImpl.scala | 2 +- .../controller/http/PresentationStatus.scala | 14 ++- .../controller/IssueControllerImplSpec.scala | 8 +- .../src/main/kotlin/api_models/Connection.kt | 1 + .../src/main/kotlin/api_models/Credential.kt | 1 + .../kotlin/api_models/PresentationProof.kt | 1 + 32 files changed, 286 insertions(+), 75 deletions(-) create mode 100644 castor/lib/core/src/main/scala/io/iohk/atala/castor/core/service/MockDIDService.scala diff --git a/build.sbt b/build.sbt index 3b64090e3f..ebbea32142 100644 --- a/build.sbt +++ b/build.sbt @@ -189,6 +189,7 @@ lazy val D_Castor = new { Seq( D.zio, D.zioTest, + D.zioMock, D.zioTestSbt, D.zioTestMagnolia, D.circeCore, diff --git a/castor/lib/core/src/main/scala/io/iohk/atala/castor/core/service/MockDIDService.scala b/castor/lib/core/src/main/scala/io/iohk/atala/castor/core/service/MockDIDService.scala new file mode 100644 index 0000000000..94b1bd9439 --- /dev/null +++ b/castor/lib/core/src/main/scala/io/iohk/atala/castor/core/service/MockDIDService.scala @@ -0,0 +1,94 @@ +package io.iohk.atala.castor.core.service + +import io.iohk.atala.castor.core.model.did.* +import io.iohk.atala.castor.core.model.error +import io.iohk.atala.prism.crypto.EC +import io.iohk.atala.prism.crypto.keys.ECKeyPair +import io.iohk.atala.shared.models.Base64UrlString +import zio.mock.{Expectation, Mock, Proxy} +import zio.test.Assertion +import zio.{IO, URLayer, ZIO, ZLayer, mock} + +import scala.collection.immutable.ArraySeq + +object MockDIDService extends Mock[DIDService] { + + object ScheduleOperation extends Effect[SignedPrismDIDOperation, error.DIDOperationError, ScheduleDIDOperationOutcome] + // FIXME leaving this out for now as it gives a "java.lang.AssertionError: assertion failed: class Array" compilation error + // object GetScheduledDIDOperationDetail extends Effect[Array[Byte], error.DIDOperationError, Option[ScheduledDIDOperationDetail]] + object ResolveDID extends Effect[PrismDID, error.DIDResolutionError, Option[(DIDMetadata, DIDData)]] + + override val compose: URLayer[mock.Proxy, DIDService] = + ZLayer { + for { + proxy <- ZIO.service[Proxy] + } yield new DIDService { + override def scheduleOperation( + operation: SignedPrismDIDOperation + ): IO[error.DIDOperationError, ScheduleDIDOperationOutcome] = + proxy(ScheduleOperation, operation) + + override def getScheduledDIDOperationDetail( + operationId: Array[Byte] + ): IO[error.DIDOperationError, Option[ScheduledDIDOperationDetail]] = + ??? + + override def resolveDID(did: PrismDID): IO[error.DIDResolutionError, Option[(DIDMetadata, DIDData)]] = + proxy(ResolveDID, did) + } + } + + def createDID( + verificationRelationship: VerificationRelationship + ): (PrismDIDOperation.Create, ECKeyPair, DIDMetadata, DIDData) = { + val masterKeyPair = EC.INSTANCE.generateKeyPair() + val keyPair = EC.INSTANCE.generateKeyPair() + val createOperation = PrismDIDOperation.Create( + publicKeys = Seq( + InternalPublicKey( + id = "master-0", + purpose = InternalKeyPurpose.Master, + publicKeyData = PublicKeyData.ECCompressedKeyData( + crv = EllipticCurve.SECP256K1, + data = Base64UrlString.fromByteArray(masterKeyPair.getPublicKey.getEncodedCompressed) + ) + ), + PublicKey( + id = "key-0", + purpose = verificationRelationship, + publicKeyData = PublicKeyData.ECCompressedKeyData( + crv = EllipticCurve.SECP256K1, + data = Base64UrlString.fromByteArray(keyPair.getPublicKey.getEncodedCompressed) + ) + ), + ), + services = Nil, + context = Nil, + ) + val longFormDid = PrismDID.buildLongFormFromOperation(createOperation) + // val canonicalDid = longFormDid.asCanonical + + val didMetadata = + DIDMetadata( + lastOperationHash = ArraySeq.from(longFormDid.stateHash.toByteArray), + canonicalId = None, // unpublished DID must not contain canonicalId + deactivated = false, // unpublished DID cannot be deactivated + created = None, // unpublished DID cannot have timestamp + updated = None // unpublished DID cannot have timestamp + ) + val didData = DIDData( + id = longFormDid.asCanonical, + publicKeys = createOperation.publicKeys.collect { case pk: PublicKey => pk }, + services = createOperation.services, + internalKeys = createOperation.publicKeys.collect { case pk: InternalPublicKey => pk }, + context = createOperation.context + ) + (createOperation, keyPair, didMetadata, didData) + } + + def resolveDIDExpectation(didMetadata: DIDMetadata, didData: DIDData): Expectation[DIDService] = + MockDIDService.ResolveDID( + assertion = Assertion.anything, + result = Expectation.value(Some(didMetadata, didData)) + ) +} diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/IssueCredentialRecord.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/IssueCredentialRecord.scala index 96040264e0..e045a78c1e 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/IssueCredentialRecord.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/IssueCredentialRecord.scala @@ -1,11 +1,10 @@ package io.iohk.atala.pollux.core.model -import io.iohk.atala.mercury.protocol.issuecredential.OfferCredential -import io.iohk.atala.mercury.protocol.issuecredential.RequestCredential -import io.iohk.atala.mercury.protocol.issuecredential.IssueCredential -import IssueCredentialRecord._ -import java.time.Instant import io.iohk.atala.castor.core.model.did.CanonicalPrismDID +import io.iohk.atala.mercury.protocol.issuecredential.{IssueCredential, OfferCredential, RequestCredential} +import io.iohk.atala.pollux.core.model.IssueCredentialRecord.* + +import java.time.Instant final case class IssueCredentialRecord( id: DidCommID, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepository.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepository.scala index 9ca25aad0f..b42c77a775 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepository.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepository.scala @@ -11,7 +11,7 @@ import zio.* trait CredentialRepository { def createIssueCredentialRecord(record: IssueCredentialRecord): RIO[WalletAccessContext, Int] def getIssueCredentialRecords( - ignoreWithZeroRetries: Boolean = true, + ignoreWithZeroRetries: Boolean, offset: Option[Int] = None, limit: Option[Int] = None ): RIO[WalletAccessContext, (Seq[IssueCredentialRecord], Int)] @@ -27,7 +27,7 @@ trait CredentialRepository { def getIssueCredentialRecordByThreadId( thid: DidCommID, - ignoreWithZeroRetries: Boolean = true, + ignoreWithZeroRetries: Boolean, ): RIO[WalletAccessContext, Option[IssueCredentialRecord]] def updateCredentialRecordProtocolState( diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepositoryInMemory.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepositoryInMemory.scala index 0bc53c3b8e..eca9f52291 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepositoryInMemory.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/CredentialRepositoryInMemory.scala @@ -1,17 +1,14 @@ package io.iohk.atala.pollux.core.repository -import io.iohk.atala.mercury.protocol.issuecredential.IssueCredential -import io.iohk.atala.mercury.protocol.issuecredential.RequestCredential -import io.iohk.atala.pollux.core.model.IssueCredentialRecord.ProtocolState -import io.iohk.atala.pollux.core.model.IssueCredentialRecord.PublicationState -import io.iohk.atala.pollux.core.model._ -import io.iohk.atala.pollux.core.model.error.CredentialRepositoryError._ +import io.iohk.atala.mercury.protocol.issuecredential.{IssueCredential, RequestCredential} +import io.iohk.atala.pollux.core.model.* +import io.iohk.atala.pollux.core.model.IssueCredentialRecord.{ProtocolState, PublicationState} +import io.iohk.atala.pollux.core.model.error.CredentialRepositoryError.* import io.iohk.atala.prism.crypto.MerkleInclusionProof -import io.iohk.atala.shared.models.WalletId +import io.iohk.atala.shared.models.{WalletAccessContext, WalletId} import zio.* import java.time.Instant -import io.iohk.atala.shared.models.WalletAccessContext class CredentialRepositoryInMemory( walletRefs: Ref[Map[WalletId, Ref[Map[DidCommID, IssueCredentialRecord]]]], @@ -76,14 +73,15 @@ class CredentialRepositoryInMemory( } override def getIssueCredentialRecords( - ignoreWithZeroRetries: Boolean = true, + ignoreWithZeroRetries: Boolean, offset: Option[Int], limit: Option[Int] ): RIO[WalletAccessContext, (Seq[IssueCredentialRecord], Int)] = { for { storeRef <- walletStoreRef store <- storeRef.get - paginated = store.values.toSeq.drop(offset.getOrElse(0)).take(limit.getOrElse(Int.MaxValue)) + records = if (ignoreWithZeroRetries) store.values.filter(_.metaRetries > 0) else store.values + paginated = records.toSeq.drop(offset.getOrElse(0)).take(limit.getOrElse(Int.MaxValue)) } yield paginated -> store.values.size } @@ -209,20 +207,22 @@ class CredentialRepositoryInMemory( for { storeRef <- walletStoreRef store <- storeRef.get - } yield store.values - .filter(rec => states.contains(rec.protocolState) & (!ignoreWithZeroRetries | rec.metaRetries > 0)) + records = if (ignoreWithZeroRetries) store.values.filter(_.metaRetries > 0) else store.values + } yield records + .filter(rec => states.contains(rec.protocolState)) .take(limit) .toSeq } override def getIssueCredentialRecordByThreadId( thid: DidCommID, - ignoreWithZeroRetries: Boolean = true, + ignoreWithZeroRetries: Boolean, ): RIO[WalletAccessContext, Option[IssueCredentialRecord]] = { for { storeRef <- walletStoreRef store <- storeRef.get - } yield store.values.find(_.thid == thid).filter(!ignoreWithZeroRetries | _.metaRetries > 0) + records = if (ignoreWithZeroRetries) store.values.filter(_.metaRetries > 0) else store.values + } yield records.find(_.thid == thid) } override def updateWithSubjectId( diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepository.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepository.scala index b7893be9b7..544e600978 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepository.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepository.scala @@ -8,7 +8,7 @@ import zio.* trait PresentationRepository { def createPresentationRecord(record: PresentationRecord): RIO[WalletAccessContext, Int] - def getPresentationRecords(ignoreWithZeroRetries: Boolean = true): RIO[WalletAccessContext, Seq[PresentationRecord]] + def getPresentationRecords(ignoreWithZeroRetries: Boolean): RIO[WalletAccessContext, Seq[PresentationRecord]] def getPresentationRecord(recordId: DidCommID): RIO[WalletAccessContext, Option[PresentationRecord]] def getPresentationRecordsByStates( ignoreWithZeroRetries: Boolean, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepositoryInMemory.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepositoryInMemory.scala index fd722a1ea5..43b967e1e9 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepositoryInMemory.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/repository/PresentationRepositoryInMemory.scala @@ -49,7 +49,7 @@ class PresentationRepositoryInMemory( } override def getPresentationRecords( - ignoreWithZeroRetries: Boolean = true, + ignoreWithZeroRetries: Boolean, ): RIO[WalletAccessContext, Seq[PresentationRecord]] = { for { storeRef <- walletStoreRef diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialService.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialService.scala index bc40b036e9..2745228f44 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialService.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialService.scala @@ -34,6 +34,7 @@ trait CredentialService { /** Return a list of records as well as a count of all filtered items */ def getIssueCredentialRecords( + ignoreWithZeroRetries: Boolean, offset: Option[Int] = None, limit: Option[Int] = None ): ZIO[WalletAccessContext, CredentialServiceError, (Seq[IssueCredentialRecord], Int)] @@ -49,7 +50,8 @@ trait CredentialService { ): ZIO[WalletAccessContext, CredentialServiceError, Option[IssueCredentialRecord]] def getIssueCredentialRecordByThreadId( - thid: DidCommID + thid: DidCommID, + ignoreWithZeroRetries: Boolean ): ZIO[WalletAccessContext, CredentialServiceError, Option[IssueCredentialRecord]] def receiveCredentialOffer( diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala index a1fb05fc48..d3d3c17bfe 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceImpl.scala @@ -50,22 +50,24 @@ private class CredentialServiceImpl( credential.maybeId.map(_.split("/").last).map(DidCommID(_)) override def getIssueCredentialRecords( + ignoreWithZeroRetries: Boolean, offset: Option[Int], limit: Option[Int] ): ZIO[WalletAccessContext, CredentialServiceError, (Seq[IssueCredentialRecord], Int)] = { for { records <- credentialRepository - .getIssueCredentialRecords(offset = offset, limit = limit) + .getIssueCredentialRecords(ignoreWithZeroRetries = ignoreWithZeroRetries, offset = offset, limit = limit) .mapError(RepositoryError.apply) } yield records } override def getIssueCredentialRecordByThreadId( - thid: DidCommID + thid: DidCommID, + ignoreWithZeroRetries: Boolean ): ZIO[WalletAccessContext, CredentialServiceError, Option[IssueCredentialRecord]] = for { record <- credentialRepository - .getIssueCredentialRecordByThreadId(thid) + .getIssueCredentialRecordByThreadId(thid, ignoreWithZeroRetries) .mapError(RepositoryError.apply) } yield record diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifier.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifier.scala index 32249b5bb6..9430f0a44b 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifier.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifier.scala @@ -168,15 +168,17 @@ class CredentialServiceNotifier( svc.getIssueCredentialRecord(recordId) override def getIssueCredentialRecordByThreadId( - thid: DidCommID + thid: DidCommID, + ignoreWithZeroRetries: Boolean ): ZIO[WalletAccessContext, CredentialServiceError, Option[IssueCredentialRecord]] = - svc.getIssueCredentialRecordByThreadId(thid) + svc.getIssueCredentialRecordByThreadId(thid, ignoreWithZeroRetries) override def getIssueCredentialRecords( + ignoreWithZeroRetries: Boolean, offset: Option[Int] = None, limit: Option[Int] = None ): ZIO[WalletAccessContext, CredentialServiceError, (Seq[IssueCredentialRecord], Int)] = - svc.getIssueCredentialRecords(offset, limit) + svc.getIssueCredentialRecords(ignoreWithZeroRetries, offset, limit) override def getIssueCredentialRecordsByStates( ignoreWithZeroRetries: Boolean, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockCredentialService.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockCredentialService.scala index 26d9257cb3..5777cc99bb 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockCredentialService.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockCredentialService.scala @@ -172,6 +172,7 @@ object MockCredentialService extends Mock[CredentialService] { ??? override def getIssueCredentialRecords( + ignoreWithZeroRetries: Boolean, offset: Option[Int] = None, limit: Option[Int] = None ): IO[CredentialServiceError, (Seq[IssueCredentialRecord], Int)] = @@ -190,7 +191,8 @@ object MockCredentialService extends Mock[CredentialService] { ??? override def getIssueCredentialRecordByThreadId( - thid: DidCommID + thid: DidCommID, + ignoreWithZeroRetries: Boolean ): IO[CredentialServiceError, Option[IssueCredentialRecord]] = ??? } } diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockPresentationService.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockPresentationService.scala index 9319bfcb2d..6144719f6c 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockPresentationService.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/MockPresentationService.scala @@ -116,7 +116,9 @@ object MockPresentationService extends Mock[PresentationService] { override def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID] = ??? - override def getPresentationRecords(): IO[PresentationError, Seq[PresentationRecord]] = ??? + override def getPresentationRecords( + ignoreWithZeroRetries: Boolean + ): IO[PresentationError, Seq[PresentationRecord]] = ??? override def createPresentationPayloadFromRecord( record: DidCommID, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationService.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationService.scala index cdd86faada..0e01e9c12a 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationService.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationService.scala @@ -26,7 +26,9 @@ trait PresentationService { options: Option[io.iohk.atala.pollux.core.model.presentation.Options] ): ZIO[WalletAccessContext, PresentationError, PresentationRecord] - def getPresentationRecords(): ZIO[WalletAccessContext, PresentationError, Seq[PresentationRecord]] + def getPresentationRecords( + ignoreWithZeroRetries: Boolean + ): ZIO[WalletAccessContext, PresentationError, Seq[PresentationRecord]] def createPresentationPayloadFromRecord( record: DidCommID, diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala index d9b3beb73f..104f83acb7 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceImpl.scala @@ -99,10 +99,12 @@ private class PresentationServiceImpl( override def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID] = credential.maybeId.map(_.split("/").last).map(UUID.fromString) - override def getPresentationRecords(): ZIO[WalletAccessContext, PresentationError, Seq[PresentationRecord]] = { + override def getPresentationRecords( + ignoreWithZeroRetries: Boolean + ): ZIO[WalletAccessContext, PresentationError, Seq[PresentationRecord]] = { for { records <- presentationRepository - .getPresentationRecords() + .getPresentationRecords(ignoreWithZeroRetries) .mapError(RepositoryError.apply) } yield records } diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifier.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifier.scala index 94aabafdce..2f49837284 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifier.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/PresentationServiceNotifier.scala @@ -113,8 +113,10 @@ class PresentationServiceNotifier( override def extractIdFromCredential(credential: W3cCredentialPayload): Option[UUID] = svc.extractIdFromCredential(credential) - override def getPresentationRecords(): ZIO[WalletAccessContext, PresentationError, Seq[PresentationRecord]] = - svc.getPresentationRecords() + override def getPresentationRecords( + ignoreWithZeroRetries: Boolean + ): ZIO[WalletAccessContext, PresentationError, Seq[PresentationRecord]] = + svc.getPresentationRecords(ignoreWithZeroRetries) override def createPresentationPayloadFromRecord( record: DidCommID, diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala index dc9ba8b4bb..ca9d337c2f 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/CredentialRepositorySpecSuite.scala @@ -6,8 +6,7 @@ import io.iohk.atala.mercury.protocol.issuecredential.{IssueCredential, RequestC import io.iohk.atala.pollux.core.model.* import io.iohk.atala.pollux.core.model.IssueCredentialRecord.* import io.iohk.atala.pollux.core.model.error.CredentialRepositoryError.* -import io.iohk.atala.shared.models.WalletAccessContext -import io.iohk.atala.shared.models.WalletId +import io.iohk.atala.shared.models.{WalletAccessContext, WalletId} import zio.test.* import zio.test.Assertion.* import zio.{Exit, ZIO, ZLayer} @@ -103,7 +102,7 @@ object CredentialRepositorySpecSuite { bRecord = issueCredentialRecord _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) - records <- repo.getIssueCredentialRecords().map(_._1) + records <- repo.getIssueCredentialRecords(false).map(_._1) } yield { assertTrue(records.size == 2) && assertTrue(records.contains(aRecord)) && @@ -117,7 +116,7 @@ object CredentialRepositorySpecSuite { bRecord = issueCredentialRecord _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) - records <- repo.getIssueCredentialRecords(offset = Some(1)).map(_._1) + records <- repo.getIssueCredentialRecords(false, offset = Some(1)).map(_._1) } yield { assertTrue(records.size == 1) && assertTrue(records.contains(bRecord)) @@ -130,7 +129,7 @@ object CredentialRepositorySpecSuite { bRecord = issueCredentialRecord _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) - records <- repo.getIssueCredentialRecords(limit = Some(1)).map(_._1) + records <- repo.getIssueCredentialRecords(false, limit = Some(1)).map(_._1) } yield { assertTrue(records.size == 1) && assertTrue(records.contains(aRecord)) @@ -145,7 +144,9 @@ object CredentialRepositorySpecSuite { _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) _ <- repo.createIssueCredentialRecord(cRecord) - records <- repo.getIssueCredentialRecords(offset = Some(1), limit = Some(1)).map(_._1) + records <- repo + .getIssueCredentialRecords(false, offset = Some(1), limit = Some(1)) + .map(_._1) } yield { assertTrue(records.size == 1) && assertTrue(records.contains(bRecord)) @@ -159,7 +160,7 @@ object CredentialRepositorySpecSuite { _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) count <- repo.deleteIssueCredentialRecord(aRecord.id) - records <- repo.getIssueCredentialRecords().map(_._1) + records <- repo.getIssueCredentialRecords(false).map(_._1) } yield { assertTrue(count == 1) && assertTrue(records.size == 1) && @@ -174,7 +175,7 @@ object CredentialRepositorySpecSuite { _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) count <- repo.deleteIssueCredentialRecord(DidCommID()) - records <- repo.getIssueCredentialRecords().map(_._1) + records <- repo.getIssueCredentialRecords(false).map(_._1) } yield { assertTrue(count == 0) && assertTrue(records.size == 2) && @@ -190,7 +191,7 @@ object CredentialRepositorySpecSuite { bRecord = issueCredentialRecord _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) - record <- repo.getIssueCredentialRecordByThreadId(thid) + record <- repo.getIssueCredentialRecordByThreadId(thid, false) } yield assertTrue(record.contains(aRecord)) }, test("getIssueCredentialRecordByThreadId returns nothing for an unknown thid") { @@ -200,7 +201,7 @@ object CredentialRepositorySpecSuite { bRecord = issueCredentialRecord _ <- repo.createIssueCredentialRecord(aRecord) _ <- repo.createIssueCredentialRecord(bRecord) - record <- repo.getIssueCredentialRecordByThreadId(DidCommID()) + record <- repo.getIssueCredentialRecordByThreadId(DidCommID(), false) } yield assertTrue(record.isEmpty) }, test("getIssueCredentialRecordsByStates returns valid records") { @@ -481,10 +482,10 @@ object CredentialRepositorySpecSuite { record2 = issueCredentialRecord count1 <- repo.createIssueCredentialRecord(record1).provide(wallet1) count2 <- repo.createIssueCredentialRecord(record2).provide(wallet2) - ownWalletRecords1 <- repo.getIssueCredentialRecords().provide(wallet1) - ownWalletRecords2 <- repo.getIssueCredentialRecords().provide(wallet2) + ownWalletRecords1 <- repo.getIssueCredentialRecords(false).provide(wallet1) + ownWalletRecords2 <- repo.getIssueCredentialRecords(false).provide(wallet2) crossWalletRecordById <- repo.getIssueCredentialRecord(record2.id).provide(wallet1) - crossWalletRecordByThid <- repo.getIssueCredentialRecordByThreadId(record2.thid).provide(wallet1) + crossWalletRecordByThid <- repo.getIssueCredentialRecordByThreadId(record2.thid, false).provide(wallet1) } yield assert(count1)(equalTo(1)) && assert(count2)(equalTo(1)) && assert(ownWalletRecords1._1)(hasSameElements(Seq(record1))) && diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala index 800813425f..4373c4df58 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/repository/PresentationRepositorySpecSuite.scala @@ -94,7 +94,7 @@ object PresentationRepositorySpecSuite { bRecord = presentationRecord _ <- repo.createPresentationRecord(aRecord) _ <- repo.createPresentationRecord(bRecord) - records <- repo.getPresentationRecords() + records <- repo.getPresentationRecords(false) } yield { assertTrue(records.size == 2) && assertTrue(records.contains(aRecord)) && @@ -363,8 +363,8 @@ object PresentationRepositorySpecSuite { record2 = presentationRecord count1 <- repo.createPresentationRecord(record1).provide(wallet1) count2 <- repo.createPresentationRecord(record2).provide(wallet2) - ownWalletRecords1 <- repo.getPresentationRecords().provide(wallet1) - ownWalletRecords2 <- repo.getPresentationRecords().provide(wallet2) + ownWalletRecords1 <- repo.getPresentationRecords(false).provide(wallet1) + ownWalletRecords2 <- repo.getPresentationRecords(false).provide(wallet2) crossWalletRecordById <- repo.getPresentationRecord(record2.id).provide(wallet1) crossWalletRecordByThid <- repo.getPresentationRecordByThreadId(record2.thid).provide(wallet1) } yield assert(count1)(equalTo(1)) && diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala index d7fe0195e6..e070bb59da 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceImplSpec.scala @@ -215,7 +215,7 @@ object CredentialServiceImplSpec extends ZIOSpecDefault with CredentialServiceSp svc <- ZIO.service[CredentialService] aRecord <- svc.createRecord() bRecord <- svc.createRecord() - records <- svc.getIssueCredentialRecords().map(_._1) + records <- svc.getIssueCredentialRecords(false).map(_._1) } yield { assertTrue(records.size == 2) && assertTrue(records.contains(aRecord)) && diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala index 056d2d3456..5df1a5670a 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpec.scala @@ -105,7 +105,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp pairwiseProverDid = DidId("did:peer:Prover") record1 <- svc.createRecord() record2 <- svc.createRecord() - records <- svc.getPresentationRecords() + records <- svc.getPresentationRecords(false) } yield { assertTrue(records.size == 2) } diff --git a/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcCredentialRepository.scala b/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcCredentialRepository.scala index ff08d28ad6..601c2fa94c 100644 --- a/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcCredentialRepository.scala +++ b/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcCredentialRepository.scala @@ -126,7 +126,7 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], maxRetries: Int } override def getIssueCredentialRecords( - ignoreWithZeroRetries: Boolean = true, + ignoreWithZeroRetries: Boolean, offset: Option[Int], limit: Option[Int] ): RIO[WalletAccessContext, (Seq[IssueCredentialRecord], Int)] = { @@ -271,7 +271,7 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], maxRetries: Int override def getIssueCredentialRecordByThreadId( thid: DidCommID, - ignoreWithZeroRetries: Boolean = true, + ignoreWithZeroRetries: Boolean, ): RIO[WalletAccessContext, Option[IssueCredentialRecord]] = { val conditionFragment = Fragments.whereAndOpt( Some(fr"thid = $thid"), diff --git a/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcPresentationRepository.scala b/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcPresentationRepository.scala index 76678c906a..e56b866c33 100644 --- a/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcPresentationRepository.scala +++ b/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcPresentationRepository.scala @@ -127,7 +127,7 @@ class JdbcPresentationRepository( } override def getPresentationRecords( - ignoreWithZeroRetries: Boolean = true + ignoreWithZeroRetries: Boolean ): RIO[WalletAccessContext, Seq[PresentationRecord]] = { val conditionFragment = Fragments.whereAndOpt( Option.when(ignoreWithZeroRetries)(fr"meta_retries > 0") diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/ControllerHelper.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/ControllerHelper.scala index 4c05eb795c..f617bb6657 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/ControllerHelper.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/ControllerHelper.scala @@ -1,7 +1,11 @@ package io.iohk.atala.agent.server +import io.iohk.atala.agent.walletapi.model.PublicationState.Published +import io.iohk.atala.agent.walletapi.model.error.GetManagedDIDError +import io.iohk.atala.agent.walletapi.model.{ManagedDIDState, PublicationState} +import io.iohk.atala.agent.walletapi.service.ManagedDIDService import io.iohk.atala.api.http.ErrorResponse -import io.iohk.atala.castor.core.model.did.PrismDID +import io.iohk.atala.castor.core.model.did.{LongFormPrismDID, PrismDID} import io.iohk.atala.connect.core.model.ConnectionRecord import io.iohk.atala.connect.core.model.ConnectionRecord.{ProtocolState, Role} import io.iohk.atala.connect.core.model.error.ConnectionServiceError @@ -59,4 +63,18 @@ trait ControllerHelper { .fromEither(PrismDID.fromString(maybeDid)) .mapError(e => ErrorResponse.badRequest(detail = Some(s"Error parsing string as PrismDID: ${e}"))) + protected def getLongFormPrismDID( + did: PrismDID, + allowUnpublishedIssuingDID: Boolean = false + ): ZIO[WalletAccessContext & ManagedDIDService, GetManagedDIDError, Option[LongFormPrismDID]] = { + for { + managedDIDService <- ZIO.service[ManagedDIDService] + maybeDIDState <- managedDIDService.getManagedDIDState(did.asCanonical).map { + case Some(s) if !allowUnpublishedIssuingDID && s.publicationState != Published => None + case s => s + } + longFormPrismDID = maybeDIDState.map(ds => PrismDID.buildLongFormFromOperation(ds.createOperation)) + } yield longFormPrismDID + } + } diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/connect/controller/http/Connection.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/connect/controller/http/Connection.scala index c143e9dade..4b02c9e332 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/connect/controller/http/Connection.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/connect/controller/http/Connection.scala @@ -45,6 +45,9 @@ case class Connection( @description(annotations.updatedAt.description) @encodedExample(annotations.updatedAt.example) updatedAt: Option[OffsetDateTime] = None, + @description(annotations.metaRetries.description) + @encodedExample(annotations.metaRetries.example) + metaRetries: Int, @description(annotations.self.description) @encodedExample(annotations.self.example) self: String = "", @@ -80,6 +83,7 @@ object Connection { invitation = ConnectionInvitation.fromDomain(domain.invitation), createdAt = domain.createdAt.atOffset(ZoneOffset.UTC), updatedAt = domain.updatedAt.map(_.atOffset(ZoneOffset.UTC)), + metaRetries = domain.metaRetries, self = domain.id.toString, kind = "Connection", ) @@ -169,6 +173,12 @@ object Connection { example = OffsetDateTime.parse("2022-03-10T12:00:00Z") ) + object metaRetries + extends Annotation[Int]( + description = "The maximum background processing attempts remaining for this record", + example = 5 + ) + object self extends Annotation[String]( description = "The reference to the connection resource.", diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueControllerImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueControllerImpl.scala index f9917424df..059be32578 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueControllerImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueControllerImpl.scala @@ -1,10 +1,14 @@ package io.iohk.atala.issue.controller import io.iohk.atala.agent.server.ControllerHelper -import io.iohk.atala.api.http.model.CollectionStats -import io.iohk.atala.api.http.model.PaginationInput +import io.iohk.atala.agent.walletapi.model.PublicationState +import io.iohk.atala.agent.walletapi.model.PublicationState.{Created, PublicationPending, Published} +import io.iohk.atala.agent.walletapi.service.ManagedDIDService +import io.iohk.atala.api.http.model.{CollectionStats, PaginationInput} import io.iohk.atala.api.http.{ErrorResponse, RequestContext} import io.iohk.atala.api.util.PaginationUtils +import io.iohk.atala.castor.core.model.did.PrismDID +import io.iohk.atala.castor.core.service.DIDService import io.iohk.atala.connect.controller.ConnectionController import io.iohk.atala.connect.core.model.error.ConnectionServiceError import io.iohk.atala.connect.core.service.ConnectionService @@ -23,7 +27,9 @@ import zio.{URLayer, ZIO, ZLayer} class IssueControllerImpl( credentialService: CredentialService, - connectionService: ConnectionService + connectionService: ConnectionService, + didService: DIDService, + managedDIDService: ManagedDIDService ) extends IssueController with ControllerHelper { override def createCredentialOffer( @@ -36,6 +42,7 @@ class IssueControllerImpl( ] = for { didIdPair <- getPairwiseDIDs(request.connectionId).provideSomeLayer(ZLayer.succeed(connectionService)) issuingDID <- extractPrismDIDFromString(request.issuingDID) + _ <- validatePrismDID(issuingDID, allowUnpublished = true) jsonClaims <- ZIO .fromEither(io.circe.parser.parse(request.claims.toString())) .mapError(e => ErrorResponse.badRequest(detail = Some(e.getMessage))) @@ -64,10 +71,14 @@ class IssueControllerImpl( pageResult <- thid match case None => credentialService - .getIssueCredentialRecords(offset = Some(pagination.offset), limit = Some(pagination.limit)) + .getIssueCredentialRecords( + ignoreWithZeroRetries = false, + offset = Some(pagination.offset), + limit = Some(pagination.limit) + ) case Some(thid) => credentialService - .getIssueCredentialRecordByThreadId(DidCommID(thid)) + .getIssueCredentialRecordByThreadId(DidCommID(thid), ignoreWithZeroRetries = false) .map(_.toSeq) .map(records => records -> records.length) (records, totalCount) = pageResult @@ -99,6 +110,8 @@ class IssueControllerImpl( rc: RequestContext ): ZIO[WalletAccessContext, ErrorResponse, IssueCredentialRecord] = { val result: ZIO[WalletAccessContext, CredentialServiceError | ErrorResponse, IssueCredentialRecord] = for { + subjectDID <- extractPrismDIDFromString(request.subjectId) + _ <- validatePrismDID(subjectDID, allowUnpublished = true) id <- extractDidCommIdFromString(recordId) outcome <- credentialService.acceptCredentialOffer(id, request.subjectId) } yield IssueCredentialRecord.fromDomain(outcome) @@ -115,6 +128,40 @@ class IssueControllerImpl( mapIssueErrors(result) } + private def validatePrismDID( + prismDID: PrismDID, + allowUnpublished: Boolean + ): ZIO[WalletAccessContext, ErrorResponse, Unit] = { + val result = for { + maybeDIDState <- managedDIDService.getManagedDIDState(prismDID.asCanonical) + maybeMetadata <- didService.resolveDID(prismDID).map(_.map(_._1)) + result <- (maybeDIDState.map(_.publicationState), maybeMetadata.map(_.deactivated)) match { + case (None, _) => + ZIO.fail(ErrorResponse.badRequest(detail = Some("The provided DID can't be found in the agent wallet"))) + + case (Some(Created() | PublicationPending(_)), _) if allowUnpublished => + ZIO.succeed(()) + + case (Some(Created() | PublicationPending(_)), _) => + ZIO.fail(ErrorResponse.badRequest(detail = Some("The provided DID is not published"))) + + case (Some(Published(_)), None) => + ZIO.succeed(()) + + case (Some(Published(_)), Some(true)) => + ZIO.fail(ErrorResponse.badRequest(detail = Some("The provided DID is published but deactivated"))) + + case (Some(Published(_)), Some(false)) => + ZIO.succeed(()) + } + } yield result + result.mapError { + case e: ErrorResponse => e + case _ => + ErrorResponse.internalServerError(detail = Some(s"Unexpected error while loading the PrismDID: $prismDID")) + } + } + private def mapIssueErrors[R, T]( result: ZIO[R, CredentialServiceError | ConnectionServiceError | ErrorResponse, T] ): ZIO[R, ErrorResponse, T] = { @@ -128,6 +175,6 @@ class IssueControllerImpl( } object IssueControllerImpl { - val layer: URLayer[CredentialService & ConnectionService, IssueController] = - ZLayer.fromFunction(IssueControllerImpl(_, _)) + val layer: URLayer[CredentialService & ConnectionService & DIDService & ManagedDIDService, IssueController] = + ZLayer.fromFunction(IssueControllerImpl(_, _, _, _)) } diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueEndpoints.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueEndpoints.scala index a34845796f..7391baf0a5 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueEndpoints.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/IssueEndpoints.scala @@ -26,7 +26,7 @@ object IssueEndpoints { .in(extractFromRequest[RequestContext](RequestContext.apply)) .in("issue-credentials" / "credential-offers") .in(jsonBody[CreateIssueCredentialRecordRequest].description("The credential offer object.")) - .errorOut(basicFailuresAndForbidden) + .errorOut(basicFailuresWith(FailureVariant.forbidden, FailureVariant.badRequest)) .out(statusCode(StatusCode.Created)) .out(jsonBody[IssueCredentialRecord].description("The issue credential record.")) .tag("Issue Credentials Protocol") diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/http/IssueCredentialRecord.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/http/IssueCredentialRecord.scala index 453db1e5e1..aa95a75974 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/http/IssueCredentialRecord.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/issue/controller/http/IssueCredentialRecord.scala @@ -79,7 +79,10 @@ final case class IssueCredentialRecord( jwtCredential: Option[String] = None, @description(annotations.issuingDID.description) @encodedExample(annotations.issuingDID.example) - issuingDID: Option[String] = None + issuingDID: Option[String] = None, + @description(annotations.metaRetries.description) + @encodedExample(annotations.metaRetries.example) + metaRetries: Int ) object IssueCredentialRecord { @@ -114,7 +117,8 @@ object IssueCredentialRecord { issueCredential.attachments.collectFirst { case AttachmentDescriptor(_, _, Base64(jwt), _, _, _, _) => jwt } - }) + }), + metaRetries = domain.metaRetries ) given Conversion[PolluxIssueCredentialRecord, IssueCredentialRecord] = fromDomain @@ -222,6 +226,12 @@ object IssueCredentialRecord { example = Some("did:prism:issuerofverifiablecredentials") ) + object metaRetries + extends Annotation[Int]( + description = "The maximum background processing attempts remaining for this record", + example = 5 + ) + } given encoder: JsonEncoder[IssueCredentialRecord] = diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala index 30c2247c6f..24700263f4 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/PresentProofControllerImpl.scala @@ -57,7 +57,7 @@ class PresentProofControllerImpl( ): ZIO[WalletAccessContext, ErrorResponse, PresentationStatusPage] = { val result = for { records <- thid match - case None => presentationService.getPresentationRecords() + case None => presentationService.getPresentationRecords(ignoreWithZeroRetries = false) case Some(thid) => presentationService.getPresentationRecordByThreadId(DidCommID(thid)).map(_.toSeq) } yield PresentationStatusPage( records.map(PresentationStatus.fromDomain) diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/PresentationStatus.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/PresentationStatus.scala index df0dc162fe..4e4aa9a4cc 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/PresentationStatus.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/presentproof/controller/http/PresentationStatus.scala @@ -31,7 +31,10 @@ final case class PresentationStatus( data: Seq[String], @description(annotations.connectionId.description) @encodedExample(annotations.connectionId.example) - connectionId: Option[String] = None + connectionId: Option[String] = None, + @description(annotations.metaRetries.description) + @encodedExample(annotations.metaRetries.example) + metaRetries: Int ) object PresentationStatus { @@ -53,7 +56,8 @@ object PresentationStatus { status = domain.protocolState.toString, proofs = Seq.empty, data = data, - connectionId = domain.connectionId + connectionId = domain.connectionId, + metaRetries = domain.metaRetries ) } @@ -122,6 +126,12 @@ object PresentationStatus { description = "The unique identifier of an established connection between the verifier and the prover.", example = "bc528dc8-69f1-4c5a-a508-5f8019047900" ) + + object metaRetries + extends Annotation[Int]( + description = "The maximum background processing attempts remaining for this record", + example = 5 + ) } given encoder: JsonEncoder[PresentationStatus] = diff --git a/prism-agent/service/server/src/test/scala/io/iohk/atala/issue/controller/IssueControllerImplSpec.scala b/prism-agent/service/server/src/test/scala/io/iohk/atala/issue/controller/IssueControllerImplSpec.scala index 3c6b87b6f6..d9ddaf8962 100644 --- a/prism-agent/service/server/src/test/scala/io/iohk/atala/issue/controller/IssueControllerImplSpec.scala +++ b/prism-agent/service/server/src/test/scala/io/iohk/atala/issue/controller/IssueControllerImplSpec.scala @@ -1,6 +1,8 @@ package io.iohk.atala.issue.controller +import io.iohk.atala.agent.walletapi.service.{ManagedDIDService, MockManagedDIDService} import io.iohk.atala.api.http.ErrorResponse +import io.iohk.atala.castor.core.service.MockDIDService import io.iohk.atala.container.util.MigrationAspects.migrate import io.iohk.atala.iam.authentication.Authenticator import io.iohk.atala.issue.controller.http.AcceptCredentialOfferRequest @@ -17,7 +19,7 @@ object IssueControllerImplSpec extends ZIOSpecDefault with IssueControllerTestTo def spec = (httpErrorResponses @@ migrate( schema = "public", paths = "classpath:sql/pollux" - )).provideSomeLayerShared(testEnvironmentLayer) + )).provideSomeLayerShared(MockDIDService.empty ++ MockManagedDIDService.empty >>> testEnvironmentLayer) private val httpErrorResponses = suite("IssueControllerImp http failure cases")( test("provide incorrect subjectId to endpoint") { @@ -37,8 +39,8 @@ object IssueControllerImplSpec extends ZIOSpecDefault with IssueControllerTestTo isSubtype[ErrorResponse]( equalTo( ErrorResponse.badRequest( - "Unsupported DID format", - Some(s"The following DID is not supported: subjectId") + "BadRequest", + Some(s"Error parsing string as PrismDID: DID syntax must start with 'did:' prefix") ) ) ) diff --git a/tests/e2e-tests/src/main/kotlin/api_models/Connection.kt b/tests/e2e-tests/src/main/kotlin/api_models/Connection.kt index 7a1c6fd6e1..37096a7609 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/Connection.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/Connection.kt @@ -16,6 +16,7 @@ data class Connection( var myDid: String = "", var theirDid: String = "", var role: String = "", + var metaRetries: Int = 0, ): JsonEncoded object ConnectionState { diff --git a/tests/e2e-tests/src/main/kotlin/api_models/Credential.kt b/tests/e2e-tests/src/main/kotlin/api_models/Credential.kt index b8ce7da55d..04f5af414d 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/Credential.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/Credential.kt @@ -19,6 +19,7 @@ data class Credential( var jwtCredential: String = "", var issuingDID: String = "", var connectionId: String = "", + var metaRetries: Int = 0, ): JsonEncoded object CredentialState { diff --git a/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt b/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt index 52cc0d0e32..2f617a3571 100644 --- a/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt +++ b/tests/e2e-tests/src/main/kotlin/api_models/PresentationProof.kt @@ -11,6 +11,7 @@ data class PresentationProof( var proofs: List? = null, var data: List? = null, var role: String? = null, + var metaRetries: Int = 0, ): JsonEncoded object PresentationProofStatus {