From f05091847776ae7eb8937fa4792e2a22f3ca6b53 Mon Sep 17 00:00:00 2001 From: Bassam Date: Mon, 22 Jan 2024 09:50:28 -0500 Subject: [PATCH] feat: interoperable credential definition api (#852) Signed-off-by: Bassam Riman --- .../core/model/IssueCredentialRecord.scala | 7 +- .../core/model/schema/CredentialSchema.scala | 10 +-- .../CredentialRepositoryInMemory.scala | 6 +- .../CredentialDefinitionServiceImpl.scala | 13 ++- .../core/service/CredentialService.scala | 4 +- .../core/service/CredentialServiceImpl.scala | 27 +++--- .../service/CredentialServiceNotifier.scala | 8 +- .../core/service/MockCredentialService.scala | 4 +- .../service/PresentationServiceImpl.scala | 83 ++++++++----------- .../CredentialRepositorySpecSuite.scala | 3 +- .../service/CredentialServiceImplSpec.scala | 6 +- .../CredentialServiceNotifierSpec.scala | 1 + .../service/PresentationServiceSpec.scala | 36 ++++++-- .../PresentationServiceSpecHelper.scala | 3 +- .../serdes/AnoncredPresentationSpec.scala | 4 +- ...V18__issue_credential_rename_schema_id.sql | 2 + .../repository/JdbcCredentialRepository.scala | 26 +++--- .../server/jobs/IssueBackgroundJobs.scala | 10 ++- .../controller/IssueControllerImpl.scala | 16 ++-- 19 files changed, 148 insertions(+), 121 deletions(-) create mode 100644 pollux/lib/sql-doobie/src/main/resources/sql/pollux/V18__issue_credential_rename_schema_id.sql 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 1551c85f53..c803f3421f 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 @@ -21,8 +21,9 @@ final case class IssueCredentialRecord( createdAt: Instant, updatedAt: Option[Instant], thid: DidCommID, - schemaId: Option[String], + schemaUri: Option[String], credentialDefinitionId: Option[UUID], + credentialDefinitionUri: Option[String], credentialFormat: CredentialFormat, role: Role, subjectId: Option[String], @@ -77,8 +78,8 @@ final case class ValidFullIssuedCredentialRecord( id: DidCommID, issuedCredential: Option[IssueCredential], credentialFormat: CredentialFormat, - schemaId: Option[String], - credentialDefinitionId: Option[UUID], + schemaUri: Option[String], + credentialDefinitionUri: Option[String], subjectId: Option[String] ) diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialSchema.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialSchema.scala index 1881137ca9..6c05b941cb 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialSchema.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/model/schema/CredentialSchema.scala @@ -13,7 +13,6 @@ import io.iohk.atala.pollux.core.service.URIDereferencer import zio.* import zio.json.* import zio.json.ast.Json -import zio.prelude.Validation import java.net.URI import java.time.{OffsetDateTime, ZoneOffset} @@ -150,10 +149,11 @@ object CredentialSchema { for { uri <- ZIO.attempt(new URI(schemaId)).mapError(t => URISyntaxError(t.getMessage)) content <- uriDereferencer.dereference(uri).mapError(err => UnexpectedError(err.toString)) - validAttrNames <- ZIO - .fromEither(content.fromJson[AnoncredSchemaSerDesV1]) - .mapError(error => CredentialSchemaParsingError(s"AnonCreds Schema parsing error: $error")) - .map(_.attrNames) + validAttrNames <- + AnoncredSchemaSerDesV1.schemaSerDes + .deserialize(content) + .mapError(error => CredentialSchemaParsingError(s"AnonCreds Schema parsing error: $error")) + .map(_.attrNames) jsonClaims <- ZIO.fromEither(claims.fromJson[Json]).mapError(err => UnexpectedError(err)) _ <- jsonClaims match case Json.Obj(fields) => 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 c329a2548a..2bc9bce4c5 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 @@ -148,7 +148,7 @@ class CredentialRepositoryInMemory( recordId.contains( rec.id ) && rec.issueCredentialData.isDefined - && rec.schemaId.isDefined + && rec.schemaUri.isDefined && rec.credentialDefinitionId.isDefined && rec.credentialFormat == CredentialFormat.AnonCreds ) @@ -157,8 +157,8 @@ class CredentialRepositoryInMemory( rec.id, rec.issueCredentialData, rec.credentialFormat, - rec.schemaId, - rec.credentialDefinitionId, + rec.schemaUri, + rec.credentialDefinitionUri, rec.subjectId ) ) diff --git a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala index d0e6cd463c..dda76e55f8 100644 --- a/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala +++ b/pollux/lib/core/src/main/scala/io/iohk/atala/pollux/core/service/CredentialDefinitionServiceImpl.scala @@ -3,12 +3,11 @@ package io.iohk.atala.pollux.core.service import io.iohk.atala.agent.walletapi.storage import io.iohk.atala.agent.walletapi.storage.GenericSecretStorage import io.iohk.atala.pollux.anoncreds.{AnoncredLib, AnoncredSchemaDef} -import io.iohk.atala.pollux.core.model.error.CredentialSchemaError -import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.URISyntaxError +import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.{SchemaError, URISyntaxError} +import io.iohk.atala.pollux.core.model.schema.CredentialDefinition import io.iohk.atala.pollux.core.model.schema.CredentialDefinition.{Filter, FilteredEntries} import io.iohk.atala.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1 import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError -import io.iohk.atala.pollux.core.model.schema.{CredentialDefinition, CredentialSchema} import io.iohk.atala.pollux.core.model.secret.CredentialDefinitionSecret import io.iohk.atala.pollux.core.repository.CredentialDefinitionRepository import io.iohk.atala.pollux.core.repository.Repository.SearchQuery @@ -84,12 +83,10 @@ class CredentialDefinitionServiceImpl( ) } yield createdCredentialDefinition }.mapError { - case e: CredentialDefinitionCreationError => e - case j: JsonSchemaError => UnexpectedError(j.error) - case s: URISyntaxError => UnexpectedError(s.message) - case u: URIDereferencerError => UnexpectedError(u.error) - case e: CredentialSchemaError => CredentialDefinitionValidationError(e) + case u: URIDereferencerError => CredentialDefinitionValidationError(URISyntaxError(u.error)) + case j: JsonSchemaError => CredentialDefinitionValidationError(SchemaError(j)) case t: Throwable => RepositoryError(t) + case e: CredentialDefinitionCreationError => e } override def delete(guid: UUID): Result[CredentialDefinition] = 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 d6a81adcbf..47622c4335 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 @@ -32,10 +32,10 @@ trait CredentialService { pairwiseHolderDID: DidId, thid: DidCommID, credentialDefinitionGUID: UUID, + credentialDefinitionId: String, claims: io.circe.Json, validityPeriod: Option[Double] = None, - automaticIssuance: Option[Boolean], - credentialDefinitionId: String + automaticIssuance: Option[Boolean] ): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] /** Return a list of records as well as a count of all filtered items */ 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 ffddf3856d..8b273ebdb5 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 @@ -130,8 +130,9 @@ private class CredentialServiceImpl( createdAt = Instant.now, updatedAt = None, thid = thid, - schemaId = maybeSchemaId, + schemaUri = maybeSchemaId, credentialDefinitionId = None, + credentialDefinitionUri = None, credentialFormat = CredentialFormat.JWT, role = IssueCredentialRecord.Role.Issuer, subjectId = None, @@ -165,10 +166,10 @@ private class CredentialServiceImpl( pairwiseHolderDID: DidId, thid: DidCommID, credentialDefinitionGUID: UUID, + credentialDefinitionId: String, claims: Json, validityPeriod: Option[Double], - automaticIssuance: Option[Boolean], - credentialDefinitionId: String + automaticIssuance: Option[Boolean] ): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = { for { credentialDefinition <- credentialDefinitionService @@ -181,11 +182,11 @@ private class CredentialServiceImpl( offer <- createAnonCredsDidCommOfferCredential( pairwiseIssuerDID = pairwiseIssuerDID, pairwiseHolderDID = pairwiseHolderDID, - schemaId = credentialDefinition.schemaId, + schemaUri = credentialDefinition.schemaId, credentialDefinitionGUID = credentialDefinitionGUID, + credentialDefinitionId = credentialDefinitionId, claims = attributes, thid = thid, - credentialDefinitionId ) record <- ZIO.succeed( IssueCredentialRecord( @@ -193,8 +194,9 @@ private class CredentialServiceImpl( createdAt = Instant.now, updatedAt = None, thid = thid, - schemaId = Some(credentialDefinition.schemaId), + schemaUri = Some(credentialDefinition.schemaId), credentialDefinitionId = Some(credentialDefinitionGUID), + credentialDefinitionUri = Some(credentialDefinitionId), credentialFormat = CredentialFormat.AnonCreds, role = IssueCredentialRecord.Role.Issuer, subjectId = None, @@ -270,8 +272,9 @@ private class CredentialServiceImpl( createdAt = Instant.now, updatedAt = None, thid = DidCommID(offer.thid.getOrElse(offer.id)), - schemaId = None, + schemaUri = None, credentialDefinitionId = None, + credentialDefinitionUri = None, credentialFormat = credentialFormat, role = Role.Holder, subjectId = None, @@ -843,14 +846,14 @@ private class CredentialServiceImpl( private[this] def createAnonCredsDidCommOfferCredential( pairwiseIssuerDID: DidId, pairwiseHolderDID: DidId, - schemaId: String, + schemaUri: String, credentialDefinitionGUID: UUID, + credentialDefinitionId: String, claims: Seq[Attribute], - thid: DidCommID, - credentialDefinitionId: String + thid: DidCommID ) = { for { - credentialPreview <- ZIO.succeed(CredentialPreview(schema_id = Some(schemaId), attributes = claims)) + credentialPreview <- ZIO.succeed(CredentialPreview(schema_id = Some(schemaUri), attributes = claims)) body = OfferCredential.Body( goal_code = Some("Offer Credential"), credential_preview = credentialPreview, @@ -1036,7 +1039,7 @@ private class CredentialServiceImpl( issuanceDate = issuanceDate, maybeExpirationDate = record.validityPeriod.map(sec => issuanceDate.plusSeconds(sec.toLong)), maybeCredentialSchema = - record.schemaId.map(id => io.iohk.atala.pollux.vc.jwt.CredentialSchema(id, VC_JSON_SCHEMA_TYPE)), + record.schemaUri.map(id => io.iohk.atala.pollux.vc.jwt.CredentialSchema(id, VC_JSON_SCHEMA_TYPE)), credentialSubject = claims.add("id", jwtPresentation.iss.asJson).asJson, maybeCredentialStatus = None, maybeRefreshService = None, 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 2474208493..177a9c7904 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 @@ -47,10 +47,10 @@ class CredentialServiceNotifier( pairwiseHolderDID: DidId, thid: DidCommID, credentialDefinitionGUID: UUID, + credentialDefinitionId: _root_.java.lang.String, claims: Json, validityPeriod: Option[Double], - automaticIssuance: Option[Boolean], - credentialDefinitionId: _root_.java.lang.String + automaticIssuance: Option[Boolean] ): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = notifyOnSuccess( svc.createAnonCredsIssueCredentialRecord( @@ -58,10 +58,10 @@ class CredentialServiceNotifier( pairwiseHolderDID, thid, credentialDefinitionGUID, + credentialDefinitionId, claims, validityPeriod, - automaticIssuance, - credentialDefinitionId + automaticIssuance ) ) 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 b3013f4745..f7a2a72f73 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 @@ -100,10 +100,10 @@ object MockCredentialService extends Mock[CredentialService] { pairwiseHolderDID: DidId, thid: DidCommID, credentialDefinitionGUID: UUID, + credentialDefinitionId: String, claims: Json, validityPeriod: Option[Double], - automaticIssuance: Option[Boolean], - credentialDefinitionId: String + automaticIssuance: Option[Boolean] ): ZIO[WalletAccessContext, CredentialServiceError, IssueCredentialRecord] = proxy( CreateAnonCredsIssueCredentialRecord, 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 bd8d1e286f..08b25b001f 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 @@ -10,24 +10,16 @@ import io.iohk.atala.mercury.protocol.issuecredential.IssueCredentialIssuedForma import io.iohk.atala.mercury.protocol.presentproof.* import io.iohk.atala.pollux.anoncreds.* import io.iohk.atala.pollux.core.model.* -import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.CredentialSchemaParsingError import io.iohk.atala.pollux.core.model.error.PresentationError import io.iohk.atala.pollux.core.model.error.PresentationError.* import io.iohk.atala.pollux.core.model.presentation.* -import io.iohk.atala.pollux.core.model.schema.CredentialDefinition import io.iohk.atala.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1 import io.iohk.atala.pollux.core.repository.{CredentialRepository, PresentationRepository} -import io.iohk.atala.pollux.core.service.serdes.{ - AnoncredCredentialProofV1, - AnoncredCredentialProofsV1, - AnoncredPresentationRequestV1, - AnoncredPresentationV1 -} +import io.iohk.atala.pollux.core.service.serdes.* import io.iohk.atala.pollux.vc.jwt.* import io.iohk.atala.shared.models.WalletAccessContext import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect -import zio.{ZIO, *} -import zio.json.* +import zio.* import java.net.URI import java.rmi.UnexpectedException @@ -37,7 +29,6 @@ import java.util.{UUID, Base64 as JBase64} import scala.util.Try private class PresentationServiceImpl( - credentialDefinitionService: CredentialDefinitionService, uriDereferencer: URIDereferencer, linkSecretService: LinkSecretService, presentationRepository: PresentationRepository, @@ -149,8 +140,8 @@ private class PresentationServiceImpl( ) presentationPayload <- createAnoncredPresentationPayloadFromCredential( issuedCredentials, - issuedValidCredentials.flatMap(_.schemaId), - issuedValidCredentials.flatMap(_.credentialDefinitionId), + issuedValidCredentials.flatMap(_.schemaUri), + issuedValidCredentials.flatMap(_.credentialDefinitionUri), requestPresentation, anoncredCredentialProof.credentialProofs ) @@ -502,28 +493,21 @@ private class PresentationServiceImpl( private def createAnoncredPresentationPayloadFromCredential( issuedCredentialRecords: Seq[ValidFullIssuedCredentialRecord], schemaIds: Seq[String], - credentialDefinitionIds: Seq[UUID], + credentialDefinitionIds: Seq[String], requestPresentation: RequestPresentation, credentialProofs: List[AnoncredCredentialProofV1], ): ZIO[WalletAccessContext, PresentationError, AnoncredPresentation] = { for { schemaMap <- ZIO - .collectAll(schemaIds.map { schemaId => - resolveSchema(schemaId) + .collectAll(schemaIds.map { schemaUri => + resolveSchema(schemaUri) }) .map(_.toMap) credentialDefinitionMap <- ZIO - .collectAll(credentialDefinitionIds.map { credentialDefinitionId => - for { - credentialDefinition <- credentialDefinitionService - .getByGUID(credentialDefinitionId) - .mapError(e => UnexpectedError(e.toString)) - } yield ( - credentialDefinition.longId, - AnoncredCredentialDefinition(credentialDefinition.definition.toString) - ) + .collectAll(credentialDefinitionIds.map { credentialDefinitionUri => + resolveCredentialDefinition(credentialDefinitionUri) }) .map(_.toMap) credentialProofsMap = credentialProofs.map(credentialProof => (credentialProof.credential, credentialProof)).toMap @@ -593,21 +577,36 @@ private class PresentationServiceImpl( } yield presentation } - private def resolveSchema(schemaId: String): IO[UnexpectedError, (String, AnoncredSchemaDef)] = { + private def resolveSchema(schemaUri: String): IO[UnexpectedError, (String, AnoncredSchemaDef)] = { for { - uri <- ZIO.attempt(new URI(schemaId)).mapError(e => UnexpectedError(e.getMessage)) + uri <- ZIO.attempt(new URI(schemaUri)).mapError(e => UnexpectedError(e.getMessage)) content <- uriDereferencer.dereference(uri).mapError(e => UnexpectedError(e.error)) - anoncredSchema <- ZIO - .fromEither(content.fromJson[AnoncredSchemaSerDesV1]) - .mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error")) + anoncredSchema <- + AnoncredSchemaSerDesV1.schemaSerDes + .deserialize(content) + .mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error")) anoncredLibSchema = AnoncredSchemaDef( - schemaId, + schemaUri, anoncredSchema.version, anoncredSchema.attrNames, anoncredSchema.issuerId ) - } yield (schemaId, anoncredLibSchema) + } yield (schemaUri, anoncredLibSchema) + } + + private def resolveCredentialDefinition( + credentialDefinitionUri: String + ): IO[UnexpectedError, (String, AnoncredCredentialDefinition)] = { + for { + uri <- ZIO.attempt(new URI(credentialDefinitionUri)).mapError(e => UnexpectedError(e.getMessage)) + content <- uriDereferencer.dereference(uri).mapError(e => UnexpectedError(e.error)) + _ <- + PublicCredentialDefinitionSerDesV1.schemaSerDes + .validate(content) + .mapError(error => UnexpectedError(s"AnonCreds Schema parsing error: $error")) + anoncredCredentialDefinition = AnoncredCredentialDefinition(content) + } yield (credentialDefinitionUri, anoncredCredentialDefinition) } def acceptRequestPresentation( @@ -952,21 +951,7 @@ private class PresentationServiceImpl( credentialDefinitionMap <- ZIO .collectAll(credentialDefinitionIds.map { credentialDefinitionId => - for { - guid <- - ZIO - .fromOption(CredentialDefinition.extractGUID(credentialDefinitionId)) - .mapError(_ => - InvalidAnoncredPresentation(s"CredentialDefinitionId format invalid ${credentialDefinitionId}") - ) - credentialDefinition <- - credentialDefinitionService - .getByGUID(guid) - .mapError(e => UnexpectedError(e.toString)) - } yield ( - credentialDefinition.longId, - AnoncredCredentialDefinition(credentialDefinition.definition.toString) - ) + resolveCredentialDefinition(credentialDefinitionId) }) .map(_.toMap) serializedPresentationRequest <- requestPresentation.attachments.head.data match { @@ -1105,8 +1090,8 @@ private class PresentationServiceImpl( object PresentationServiceImpl { val layer: URLayer[ - CredentialDefinitionService & URIDereferencer & LinkSecretService & PresentationRepository & CredentialRepository, + URIDereferencer & LinkSecretService & PresentationRepository & CredentialRepository, PresentationService ] = - ZLayer.fromFunction(PresentationServiceImpl(_, _, _, _, _)) + ZLayer.fromFunction(PresentationServiceImpl(_, _, _, _)) } 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 ce622e7a16..0d0a1eb1c0 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 @@ -22,8 +22,9 @@ object CredentialRepositorySpecSuite { createdAt = Instant.now, updatedAt = None, thid = DidCommID(), - schemaId = None, + schemaUri = None, credentialDefinitionId = None, + credentialDefinitionUri = None, credentialFormat = credentialFormat, role = IssueCredentialRecord.Role.Issuer, subjectId = None, 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 5b26f677d1..5b2c22ac49 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 @@ -82,7 +82,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS } yield { assertTrue(record.thid == thid) && assertTrue(record.updatedAt.isEmpty) && - assertTrue(record.schemaId.isEmpty) && + assertTrue(record.schemaUri.isEmpty) && assertTrue(record.validityPeriod == validityPeriod) && assertTrue(record.automaticIssuance == automaticIssuance) && assertTrue(record.role == Role.Issuer) && @@ -158,7 +158,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS assertTrue(record.thid == thid) && assertTrue(record.updatedAt.isEmpty) && assertTrue( - record.schemaId.contains("resource:///vc-schema-example.json") + record.schemaUri.contains("resource:///vc-schema-example.json") ) && assertTrue(record.validityPeriod == validityPeriod) && assertTrue(record.automaticIssuance == automaticIssuance) && @@ -290,7 +290,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS } yield { assertTrue(holderRecord.thid.toString == offer.thid.get) && assertTrue(holderRecord.updatedAt.isEmpty) && - assertTrue(holderRecord.schemaId.isEmpty) && + assertTrue(holderRecord.schemaUri.isEmpty) && assertTrue(holderRecord.validityPeriod.isEmpty) && assertTrue(holderRecord.automaticIssuance.isEmpty) && assertTrue(holderRecord.role == Role.Holder) && diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifierSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifierSpec.scala index 61c327985a..17e3ce7a3b 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifierSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/CredentialServiceNotifierSpec.scala @@ -21,6 +21,7 @@ object CredentialServiceNotifierSpec extends MockSpecDefault with CredentialServ DidCommID(), None, None, + None, CredentialFormat.JWT, IssueCredentialRecord.Role.Issuer, None, 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 befbbdde40..f5f907d8a1 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 @@ -28,6 +28,8 @@ import zio.* import zio.test.* import zio.test.Assertion.* +import java.nio.charset.StandardCharsets +import java.nio.file.{Files, Path, Paths} import java.time.{Instant, OffsetDateTime} import java.util.{UUID, Base64 as JBase64} @@ -276,7 +278,9 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp linkSecretService <- ZIO.service[LinkSecretService] linkSecret <- linkSecretService.fetchOrCreate() credentialDefinition = AnoncredLib.createCredDefinition(issuerId, schema, "tag", supportRevocation = false) - credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionDb.longId) + file = createTempJsonFile(credentialDefinition.cd.data, "anoncred-presentation-credential-definition-example") + credentialDefinitionId = "resource:///" + file.getFileName + credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionId) credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) credential = AnoncredLib @@ -310,8 +314,9 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp createdAt = Instant.now, updatedAt = None, thid = DidCommID(), - schemaId = Some(schemaId), + schemaUri = Some(schemaId), credentialDefinitionId = Some(credentialDefinitionDb.guid), + credentialDefinitionUri = Some(credentialDefinitionId), credentialFormat = CredentialFormat.AnonCreds, role = IssueCredentialRecord.Role.Issuer, subjectId = None, @@ -331,7 +336,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp ) _ <- repo.createIssueCredentialRecord(aIssueCredentialRecord) svc <- ZIO.service[PresentationService] - aRecord <- svc.createAnoncredRecord() + aRecord <- svc.createAnoncredRecord(credentialDefinitionId = credentialDefinitionId) repo <- ZIO.service[PresentationRepository] credentialsToUse = AnoncredCredentialProofsV1( @@ -392,16 +397,18 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp repo <- ZIO.service[CredentialRepository] linkSecretService <- ZIO.service[LinkSecretService] linkSecret <- linkSecretService.fetchOrCreate() - cenericSecretStorage <- ZIO.service[GenericSecretStorage] + genericSecretStorage <- ZIO.service[GenericSecretStorage] maybeCredentialDefintionPrivate <- - cenericSecretStorage + genericSecretStorage .get[UUID, CredentialDefinitionSecret](credentialDefinitionDb.guid) credentialDefinition = AnoncredCreateCredentialDefinition( AnoncredCredentialDefinition(credentialDefinitionDb.definition.toString()), AnoncredCredentialDefinitionPrivate(maybeCredentialDefintionPrivate.get.json.toString()), AnoncredCredentialKeyCorrectnessProof(credentialDefinitionDb.keyCorrectnessProof.toString()) ) - credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionDb.longId) + file = createTempJsonFile(credentialDefinition.cd.data, "anoncred-presentation-credential-definition-example") + credentialDefinitionId = "resource:///" + file.getFileName + credentialOffer = AnoncredLib.createOffer(credentialDefinition, credentialDefinitionId) credentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition.cd, credentialOffer) processedCredential = AnoncredLib.processCredential( @@ -440,8 +447,9 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp createdAt = Instant.now, updatedAt = None, thid = DidCommID(), - schemaId = Some(schemaId), + schemaUri = Some(schemaId), credentialDefinitionId = Some(credentialDefinitionDb.guid), + credentialDefinitionUri = Some(credentialDefinitionId), credentialFormat = CredentialFormat.AnonCreds, role = IssueCredentialRecord.Role.Issuer, subjectId = None, @@ -462,7 +470,7 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp _ <- repo.createIssueCredentialRecord(aIssueCredentialRecord) svc <- ZIO.service[PresentationService] aRecord <- svc.createAnoncredRecord( - credentialDefinitionId = credentialDefinitionDb.longId + credentialDefinitionId = credentialDefinitionId ) repo <- ZIO.service[PresentationRepository] credentialsToUse = @@ -1031,4 +1039,16 @@ object PresentationServiceSpec extends ZIOSpecDefault with PresentationServiceSp } ) + def createTempJsonFile(jsonContent: String, fileName: String): Path = { + val resourceURI = this.getClass.getResource("/").toURI + val resourcePath = Paths.get(resourceURI) + + val filePath = resourcePath.resolve(fileName + ".json") + + Files.write(filePath, jsonContent.getBytes(StandardCharsets.UTF_8)) + + filePath.toFile.deleteOnExit() + filePath + } + } diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpecHelper.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpecHelper.scala index d50f4376b5..bbb32df467 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpecHelper.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/PresentationServiceSpecHelper.scala @@ -132,8 +132,9 @@ trait PresentationServiceSpecHelper { createdAt = Instant.now, updatedAt = None, thid = DidCommID(), - schemaId = None, + schemaUri = None, credentialDefinitionId = None, + credentialDefinitionUri = None, credentialFormat = credentialFormat, role = IssueCredentialRecord.Role.Issuer, subjectId = None, diff --git a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala index 6517c6bd03..275bc181be 100644 --- a/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala +++ b/pollux/lib/core/src/test/scala/io/iohk/atala/pollux/core/service/serdes/AnoncredPresentationSpec.scala @@ -93,7 +93,7 @@ object AnoncredPresentationSpec extends ZIOSpecDefault { | "identifiers": [ | { | "schema_id": "resource:///anoncred-presentation-schema-example.json", - | "cred_def_id": "did:prism:issuer/b2c8ccb8-191a-4233-9b34-3e3111a4adaf?version=1.2", + | "cred_def_id": "resource:///anoncred-presentation-credential-definition-example.json", | "rev_reg_id": null, | "timestamp": null | } @@ -189,7 +189,7 @@ object AnoncredPresentationSpec extends ZIOSpecDefault { val identifier = AnoncredIdentifierV1( schema_id = "resource:///anoncred-presentation-schema-example.json", - cred_def_id = "did:prism:issuer/b2c8ccb8-191a-4233-9b34-3e3111a4adaf?version=1.2", + cred_def_id = "resource:///anoncred-presentation-credential-definition-example.json", rev_reg_id = None, timestamp = None ) diff --git a/pollux/lib/sql-doobie/src/main/resources/sql/pollux/V18__issue_credential_rename_schema_id.sql b/pollux/lib/sql-doobie/src/main/resources/sql/pollux/V18__issue_credential_rename_schema_id.sql new file mode 100644 index 0000000000..6fc8f3c693 --- /dev/null +++ b/pollux/lib/sql-doobie/src/main/resources/sql/pollux/V18__issue_credential_rename_schema_id.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.issue_credential_records RENAME COLUMN schema_id TO schema_uri; +ALTER TABLE public.issue_credential_records ADD COLUMN credential_definition_uri VARCHAR(500); 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 3ce67f30bd..8d86a432df 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 @@ -69,8 +69,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -92,8 +93,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | ${record.createdAt}, | ${record.updatedAt}, | ${record.thid}, - | ${record.schemaId}, + | ${record.schemaUri}, | ${record.credentialDefinitionId}, + | ${record.credentialDefinitionUri}, | ${record.credentialFormat}, | ${record.role}, | ${record.subjectId}, @@ -136,8 +138,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -203,8 +206,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -253,8 +257,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -294,8 +299,9 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | created_at, | updated_at, | thid, - | schema_id, + | schema_uri, | credential_definition_id, + | credential_definition_uri, | credential_format, | role, | subject_id, @@ -460,14 +466,14 @@ class JdbcCredentialRepository(xa: Transactor[ContextAwareTask], xb: Transactor[ | id, | issue_credential_data, | credential_format, - | schema_id, - | credential_definition_id, + | schema_uri, + | credential_definition_uri, | subject_id | FROM public.issue_credential_records | WHERE 1=1 | AND issue_credential_data IS NOT NULL - | AND schema_id IS NOT NULL - | AND credential_definition_id IS NOT NULL + | AND schema_uri IS NOT NULL + | AND credential_definition_uri IS NOT NULL | AND credential_format = 'AnonCreds' | AND $inClauseFragment """.stripMargin diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/IssueBackgroundJobs.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/IssueBackgroundJobs.scala index e61ce931c1..37aaea2043 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/IssueBackgroundJobs.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/jobs/IssueBackgroundJobs.scala @@ -2,6 +2,7 @@ package io.iohk.atala.agent.server.jobs import io.iohk.atala.agent.server.config.AppConfig import io.iohk.atala.agent.server.jobs.BackgroundJobError.ErrorResponseReceivedFromPeerAgent +import io.iohk.atala.agent.walletapi.model.error.DIDSecretStorageError.WalletNotFoundError import io.iohk.atala.castor.core.model.did.* import io.iohk.atala.mercury.* import io.iohk.atala.mercury.protocol.issuecredential.* @@ -12,7 +13,6 @@ import io.iohk.atala.shared.utils.DurationOps.toMetricsSeconds import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect import zio.* import zio.metrics.* -import io.iohk.atala.agent.walletapi.model.error.DIDSecretStorageError.WalletNotFoundError object IssueBackgroundJobs extends BackgroundJobsHelper { @@ -148,6 +148,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, Role.Issuer, _, _, @@ -202,6 +203,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, CredentialFormat.JWT, Role.Holder, Some(subjectId), @@ -242,6 +244,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, CredentialFormat.AnonCreds, Role.Holder, None, @@ -284,6 +287,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, Role.Holder, _, _, @@ -339,6 +343,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, Role.Issuer, _, _, @@ -377,6 +382,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, CredentialFormat.JWT, Role.Issuer, _, @@ -418,6 +424,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, CredentialFormat.AnonCreds, Role.Issuer, _, @@ -460,6 +467,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper { _, _, _, + _, Role.Issuer, _, _, 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 a88521701b..bba5c66d68 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 @@ -78,21 +78,23 @@ class IssueControllerImpl( .mapError(_ => ErrorResponse.badRequest(detail = Some("Missing request parameter: credentialDefinitionId")) ) + credentialDefinitionId = { + val publicEndpointUrl = appConfig.agent.httpEndpoint.publicEndpointUrl + val urlSuffix = + s"credential-definition-registry/definitions/${credentialDefinitionGUID.toString}/definition" + val urlPrefix = if (publicEndpointUrl.endsWith("/")) publicEndpointUrl else publicEndpointUrl + "/" + s"$urlPrefix$urlSuffix" + } record <- credentialService .createAnonCredsIssueCredentialRecord( pairwiseIssuerDID = didIdPair.myDID, pairwiseHolderDID = didIdPair.theirDid, thid = DidCommID(), credentialDefinitionGUID = credentialDefinitionGUID, + credentialDefinitionId = credentialDefinitionId, claims = jsonClaims, validityPeriod = request.validityPeriod, - automaticIssuance = request.automaticIssuance.orElse(Some(true)), { - val publicEndpointUrl = appConfig.agent.httpEndpoint.publicEndpointUrl - val urlSuffix = - s"credential-definition-registry/definitions/${credentialDefinitionGUID.toString}/definition" - val urlPrefix = if (publicEndpointUrl.endsWith("/")) publicEndpointUrl else publicEndpointUrl + "/" - s"$urlPrefix$urlSuffix" - } + automaticIssuance = request.automaticIssuance.orElse(Some(true)) ) } yield record } yield IssueCredentialRecord.fromDomain(outcome)