From c6a3e0ced12927f5a819af9f4f56e8e381293d15 Mon Sep 17 00:00:00 2001 From: Yurii Shynbuiev Date: Wed, 4 Dec 2024 16:38:06 +0700 Subject: [PATCH] feat: align the credential schema property name according to the VCDM 1.1 (#1467) Signed-off-by: Yurii Shynbuiev --- .../http/StatusListCredential.scala | 2 +- ...redentialSchemaReferenceParsingLogic.scala | 62 +++++ .../controller/IssueControllerImpl.scala | 30 +-- .../CreateIssueCredentialRecordRequest.scala | 221 +++++++++++++++++- .../service/OIDCCredentialIssuerService.scala | 8 +- .../issuecredential/CredentialPreview.scala | 1 + .../core/model/primitives/UriString.scala | 29 +++ .../core/model/primitives/UrlString.scala | 26 +++ .../core/model/schema/CredentialSchema.scala | 10 +- .../model/schema/CredentialSchemaRef.scala | 10 + .../core/service/CredentialService.scala | 5 +- .../core/service/CredentialServiceImpl.scala | 48 ++-- .../service/CredentialServiceNotifier.scala | 9 +- .../OID4VCIIssuerMetadataService.scala | 11 +- .../VcVerificationServiceImpl.scala | 15 +- .../service/CredentialServiceImplSpec.scala | 22 +- .../service/CredentialServiceSpecHelper.scala | 5 +- .../core/service/MockCredentialService.scala | 13 +- .../vc/jwt/VerifiableCredentialPayload.scala | 1 + .../test/kotlin/common/CredentialSchema.kt | 31 +++ .../connectionless/ConnectionLessSteps.kt | 26 ++- .../steps/credentials/JwtCredentialSteps.kt | 29 ++- .../steps/credentials/SdJwtCredentialSteps.kt | 58 ++--- .../features/credential/jwt/issuance.feature | 19 +- .../credential/jwt/present_proof.feature | 3 +- .../credential/sdjwt/issuance.feature | 5 +- .../agent-performance-tests-k6/README.md | 2 +- .../agent-performance-tests-k6/package.json | 2 +- .../src/actors/Holder.ts | 2 +- .../src/actors/Issuer.ts | 2 +- .../src/actors/Verifier.ts | 2 +- .../src/common/ConnectionService.ts | 2 +- .../src/common/CredentialsService.ts | 4 +- .../src/common/DidService.ts | 2 +- .../src/common/ProofsService.ts | 2 +- .../credentials/credential-definition-test.ts | 2 +- .../credentials/credential-offer-test.ts | 2 +- .../src/tests/flows/issuance-flow-test.ts | 2 +- .../tests/flows/present-proof-flow-test.ts | 2 +- .../agent-performance-tests-k6/yarn.lock | 2 +- 40 files changed, 585 insertions(+), 144 deletions(-) create mode 100644 cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/CredentialSchemaReferenceParsingLogic.scala create mode 100644 pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/primitives/UriString.scala create mode 100644 pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/primitives/UrlString.scala create mode 100644 pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/schema/CredentialSchemaRef.scala diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/credentialstatus/controller/http/StatusListCredential.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/credentialstatus/controller/http/StatusListCredential.scala index 0206b60827..f270b4500b 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/credentialstatus/controller/http/StatusListCredential.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/credentialstatus/controller/http/StatusListCredential.scala @@ -165,7 +165,7 @@ object StatusListCredential { given stringOrCredentialIssuerDecoder: JsonDecoder[String | CredentialIssuer] = JsonDecoder[CredentialIssuer] .map(issuer => issuer: String | CredentialIssuer) - .orElse(JsonDecoder[String].map(schemaId => schemaId: String | CredentialIssuer)) + .orElse(JsonDecoder[String].map(issuerId => issuerId: String | CredentialIssuer)) given statusListCredentialEncoder: JsonEncoder[StatusListCredential] = DeriveJsonEncoder.gen[StatusListCredential] diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/CredentialSchemaReferenceParsingLogic.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/CredentialSchemaReferenceParsingLogic.scala new file mode 100644 index 0000000000..642581a2b3 --- /dev/null +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/issue/controller/CredentialSchemaReferenceParsingLogic.scala @@ -0,0 +1,62 @@ +package org.hyperledger.identus.issue.controller + +import org.hyperledger.identus.api.http.ErrorResponse +import org.hyperledger.identus.issue.controller.http.CredentialSchemaRef as HTTPCredentialSchemaRef +import org.hyperledger.identus.pollux.core.model.primitives.UriString +import org.hyperledger.identus.pollux.core.model.schema.{ + CredentialSchemaRef as DomainCredentialSchemaRef, + CredentialSchemaRefType +} +import zio.{IO, ZIO} + +trait CredentialSchemaReferenceParsingLogic { + + // According to VCDM 1.1, the property "credentialSchema" is required to issue JWT, JSON, and JSON-LD credentials. + // The "id" property in the "credentialSchema" object is a URI that points to the schema of the credential. + // The "type" property in the "credentialSchema" object must be "JsonSchemaValidator2018". + // Multiple schemas are not allowed in VCDM 1.1. + def parseCredentialSchemaRef_VCDM1_1( + deprecatedSchemaIdProperty: Option[String | List[String]], + credentialSchemaRefOption: Option[HTTPCredentialSchemaRef] + ): IO[ErrorResponse, DomainCredentialSchemaRef] = { + credentialSchemaRefOption match { + case Some(csr) if csr.`type` == "JsonSchemaValidator2018" => + makeDomainCredentialSchemaRef(csr.id) + case Some(csr) => + ZIO.fail(ErrorResponse.badRequest(detail = Some(s"Invalid credentialSchema type: ${csr.`type`}."))) + case None => + handleDeprecatedSchemaId(deprecatedSchemaIdProperty) + .flatMap(makeDomainCredentialSchemaRef) + } + } + + def parseSchemaIdForAnonCredsModelV1( + deprecatedSchemaIdProperty: Option[String | List[String]], + schemaIdProperty: Option[String] + ): IO[ErrorResponse, UriString] = { + schemaIdProperty + .map(makeUriStringOrErrorResponse) + .getOrElse(handleDeprecatedSchemaId(deprecatedSchemaIdProperty).flatMap(makeUriStringOrErrorResponse)) + } + + private def handleDeprecatedSchemaId( + deprecatedSchemaIdProperty: Option[String | List[String]] + ): IO[ErrorResponse, String] = { + deprecatedSchemaIdProperty match { + case Some(schemaId: String) => + ZIO.succeed(schemaId) + case Some(_: List[String]) => + ZIO.fail(ErrorResponse.badRequest(detail = Some("Multiple credential schemas are not allowed."))) + case None => + ZIO.fail(ErrorResponse.badRequest(detail = Some("Credential schema property missed."))) + } + } + + private def makeDomainCredentialSchemaRef(input: String): IO[ErrorResponse, DomainCredentialSchemaRef] = + makeUriStringOrErrorResponse(input).map( + DomainCredentialSchemaRef(CredentialSchemaRefType.JsonSchemaValidator2018, _) + ) + + private def makeUriStringOrErrorResponse(input: String): IO[ErrorResponse, UriString] = + UriString.make(input).toZIO.mapError(uriParseError => ErrorResponse.badRequest(detail = Some(uriParseError))) +} 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 69cfc710bf..6778d3ce7f 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 @@ -35,7 +35,8 @@ class IssueControllerImpl( managedDIDService: ManagedDIDService, appConfig: AppConfig ) extends IssueController - with ControllerHelper { + with ControllerHelper + with CredentialSchemaReferenceParsingLogic { private case class OfferContext( pairwiseIssuerDID: DidId, @@ -55,9 +56,10 @@ class IssueControllerImpl( ) for { - jsonClaims <- ZIO // TODO: Get read of Circe and use zio-json all the way down + deprecatedJsonClaims <- ZIO // TODO: Get read of Circe and use zio-json all the way down .fromEither(io.circe.parser.parse(request.claims.toString())) .mapError(e => ErrorResponse.badRequest(detail = Some(e.getMessage))) + deprecatedSchemaId = request.schemaId credentialFormat = request.credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT) outcome <- credentialFormat match @@ -65,17 +67,18 @@ class IssueControllerImpl( for { issuingDID <- getIssuingDidFromRequest(request) _ <- validatePrismDID(issuingDID, allowUnpublished = true, Role.Issuer) + credentialSchemaRef <- parseCredentialSchemaRef_VCDM1_1( + deprecatedSchemaId, + request.jwtVcPropertiesV1.map(_.credentialSchema) + ) record <- credentialService .createJWTIssueCredentialRecord( pairwiseIssuerDID = offerContext.pairwiseIssuerDID, pairwiseHolderDID = offerContext.pairwiseHolderDID, kidIssuer = request.issuingKid, thid = DidCommID(), - maybeSchemaIds = request.schemaId.map { - case schemaId: String => List(schemaId) - case schemaIds: List[String] => schemaIds - }, - claims = jsonClaims, + credentialSchemaRef = Some(credentialSchemaRef), + claims = deprecatedJsonClaims, validityPeriod = request.validityPeriod, automaticIssuance = request.automaticIssuance.orElse(Some(true)), issuingDID = issuingDID.asCanonical, @@ -89,17 +92,18 @@ class IssueControllerImpl( for { issuingDID <- getIssuingDidFromRequest(request) _ <- validatePrismDID(issuingDID, allowUnpublished = true, Role.Issuer) + credentialSchemaRef <- parseCredentialSchemaRef_VCDM1_1( + deprecatedSchemaId, + request.sdJwtVcPropertiesV1.map(_.credentialSchema) + ) record <- credentialService .createSDJWTIssueCredentialRecord( pairwiseIssuerDID = offerContext.pairwiseIssuerDID, pairwiseHolderDID = offerContext.pairwiseHolderDID, kidIssuer = request.issuingKid, thid = DidCommID(), - maybeSchemaIds = request.schemaId.map { - case schemaId: String => List(schemaId) - case schemaIds: List[String] => schemaIds - }, - claims = jsonClaims, + credentialSchemaRef = Option(credentialSchemaRef), + claims = deprecatedJsonClaims, validityPeriod = request.validityPeriod, automaticIssuance = request.automaticIssuance.orElse(Some(true)), issuingDID = issuingDID.asCanonical, @@ -160,7 +164,7 @@ class IssueControllerImpl( thid = DidCommID(), credentialDefinitionGUID = credentialDefinitionGUID, credentialDefinitionId = credentialDefinitionId, - claims = jsonClaims, + claims = deprecatedJsonClaims, validityPeriod = request.validityPeriod, automaticIssuance = request.automaticIssuance.orElse(Some(true)), goalCode = offerContext.goalCode, 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 5c530b194d..cb71c54a8e 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,10 +2,11 @@ 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.pollux.core.model.primitives.UriString 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} +import sttp.tapir.Schema.annotations.{description, encodedExample, validate} import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} import java.util.UUID @@ -31,27 +32,33 @@ import scala.language.implicitConversions final case class CreateIssueCredentialRecordRequest( @description(annotations.validityPeriod.description) @encodedExample(annotations.validityPeriod.example) + @deprecated("Use jwtVcPropertiesV1.validityPeriod instead", "2.0.0") validityPeriod: Option[Double] = None, @description(annotations.schemaId.description) @encodedExample(annotations.schemaId.example) + @deprecated("Use anoncredsVcPropertiesV1.schemaId instead", "2.0.0") schemaId: Option[String | List[String]] = None, @description(annotations.credentialDefinitionId.description) @encodedExample(annotations.credentialDefinitionId.example) + @deprecated("Use anoncredsVcPropertiesV1.credentialDefinitionId instead", "2.0.0") credentialDefinitionId: Option[UUID], @description(annotations.credentialFormat.description) @encodedExample(annotations.credentialFormat.example) credentialFormat: Option[String], @description(annotations.claims.description) @encodedExample(annotations.claims.example) + @deprecated("Use specific properties of the verifiable credentials *.claims instead", "2.0.0") claims: zio.json.ast.Json, @description(annotations.automaticIssuance.description) @encodedExample(annotations.automaticIssuance.example) automaticIssuance: Option[Boolean] = None, @description(annotations.issuingDID.description) @encodedExample(annotations.issuingDID.example) + @deprecated("Use specific properties of the verifiable credentials *.issuingDID instead", "2.0.0") issuingDID: String, @description(annotations.issuingKid.description) @encodedExample(annotations.issuingKid.example) + @deprecated("Use specific jwtVcPropertiesV1.issuingKid instead", "2.0.0") issuingKid: Option[KeyId], @description(annotations.connectionId.description) @encodedExample(annotations.connectionId.example) @@ -62,8 +69,196 @@ final case class CreateIssueCredentialRecordRequest( @description(annotations.goal.description) @encodedExample(annotations.goal.example) goal: Option[String] = None, + @description(annotations.jwtVcPropertiesV1.description) + jwtVcPropertiesV1: Option[JwtVCPropertiesV1] = None, + @description(annotations.anoncredsVcPropertiesV1.description) + anoncredsVcPropertiesV1: Option[AnonCredsVCPropertiesV1] = None, + @description(annotations.sdJwtVcPropertiesV1.description) + sdJwtVcPropertiesV1: Option[SDJWTVCPropertiesV1] = None ) +case class CredentialSchemaRef( + @description(CredentialSchemaRef.annotations.id.description) + @encodedExample(CredentialSchemaRef.annotations.id.example) + id: String, + @description(CredentialSchemaRef.annotations.`type`.description) + @encodedExample(CredentialSchemaRef.annotations.`type`.example) + `type`: String +) + +object CredentialSchemaRef { + given schema: Schema[CredentialSchemaRef] = Schema.derived + given encoder: JsonEncoder[CredentialSchemaRef] = DeriveJsonEncoder.gen + given decoder: JsonDecoder[CredentialSchemaRef] = DeriveJsonDecoder.gen + + object annotations { + object id + extends Annotation[String]( + description = """ + |The URL or DIDURL pointing to the credential schema that will be used for this offer. + |""".stripMargin, + example = "https://agent-host.com/cloud-agent/schema-registry/schemas/d9569cec-c81e-4779-aa86-0d5994d82676" + ) + object `type` + extends Annotation[String]( + description = """ + |The type of the credential schema that will be used for this offer. + |""".stripMargin, + example = "JsonSchema" + ) + } + import org.hyperledger.identus.pollux.core.model.schema as domain + + def toDomain(ref: CredentialSchemaRef): Either[String, domain.CredentialSchemaRef] = { + domain.CredentialSchemaRefType.values + for { + `type` <- ref.`type` match { + case "JsonSchema" => Right(domain.CredentialSchemaRefType.JsonSchema) + case "JsonSchemaValidator2018" => Right(domain.CredentialSchemaRefType.JsonSchemaValidator2018) + case _ => Left("Invalid CredentialSchemaRefType") + } + id <- UriString.make(ref.id).toEither.left.map(nec => nec.mkString(", ")) + } yield domain.CredentialSchemaRef(`type`, id) + } + +} + +case class JwtVCPropertiesV1( + @description(JwtVCPropertiesV1.annotations.issuingDID.description) + @encodedExample(JwtVCPropertiesV1.annotations.issuingDID.example) + issuingDID: String, + @description(JwtVCPropertiesV1.annotations.validityPeriod.description) + @encodedExample(JwtVCPropertiesV1.annotations.validityPeriod.example) + validityPeriod: Double, + @description(JwtVCPropertiesV1.annotations.claims.description) + @encodedExample(JwtVCPropertiesV1.annotations.claims.example) + claims: zio.json.ast.Json, + credentialSchema: CredentialSchemaRef +) + +object JwtVCPropertiesV1 { + given schema: Schema[JwtVCPropertiesV1] = Schema.derived + given encoder: JsonEncoder[JwtVCPropertiesV1] = DeriveJsonEncoder.gen + given decoder: JsonDecoder[JwtVCPropertiesV1] = DeriveJsonDecoder.gen + + object annotations { + object validityPeriod + extends Annotation[Double]( + description = "The validity period in seconds of the verifiable credential that will be issued.", + example = 3600 + ) + object issuingDID + extends Annotation[String]( + description = """ + |The issuer Prism DID by which the verifiable credential will be issued. DID can be short for or long form. + |""".stripMargin, + example = "did:prism:3bb0505d13fcb04d28a48234edb27b0d4e6d7e18a81e2c1abab58f3bbc21ce6f" + ) + object claims + extends Annotation[zio.json.ast.Json]( + description = """ + |The set of claims that will be included in the issued credential. + |The JSON object should comply with the schema applicable for this offer (i.e. 'schemaId' or 'credentialDefinitionId'). + |""".stripMargin, + example = zio.json.ast.Json.Obj( + "firstname" -> zio.json.ast.Json.Str("Alice"), + "lastname" -> zio.json.ast.Json.Str("Wonderland"), + ) + ) + object credentialSchema + extends Annotation[CredentialSchemaRef]( + description = """ + |The properties of the JWT verifiable credential that will be issued complied with VCDM 1.1. + |""".stripMargin, + example = CredentialSchemaRef( + "https://agent-host.com/cloud-agent/schema-registry/schemas/d9569cec-c81e-4779-aa86-0d5994d82676", + "JsonSchemaValidator2018" + ) + ) + } +} + +case class AnonCredsVCPropertiesV1( + @description(annotations.issuingDID.description) + @encodedExample(annotations.issuingDID.example) + issuingDID: String, + @description(AnonCredsVCPropertiesV1.annotations.schemaId.description) + @encodedExample(AnonCredsVCPropertiesV1.annotations.schemaId.example) + schemaId: String, + @description(AnonCredsVCPropertiesV1.annotations.credentialDefinitionId.description) + @encodedExample(AnonCredsVCPropertiesV1.annotations.credentialDefinitionId.example) + credentialDefinitionId: String, + @description(AnonCredsVCPropertiesV1.annotations.claims.description) + @encodedExample(AnonCredsVCPropertiesV1.annotations.claims.example) + claims: zio.json.ast.Json +) + +object AnonCredsVCPropertiesV1 { + given schema: Schema[AnonCredsVCPropertiesV1] = Schema.derived + given encoder: JsonEncoder[AnonCredsVCPropertiesV1] = DeriveJsonEncoder.gen + given decoder: JsonDecoder[AnonCredsVCPropertiesV1] = DeriveJsonDecoder.gen + + object annotations { + object schemaId + extends Annotation[String]( + description = """ + |The URL or DIDURL pointing to the AnonCreds schema that will be used for this offer. + |When dereferenced, the returned content should be a JSON schema compliant with the '[AnonCreds v1.0 schema](https://hyperledger.github.io/anoncreds-spec/#term:schema)' version of the specification. + |""".stripMargin, + example = + "https://agent-host.com/cloud-agent/schema-registry/schemas/d9569cec-c81e-4779-aa86-0d5994d82676/schema" + ) + object credentialDefinitionId + extends Annotation[UUID]( + description = """ + |The unique identifier (UUID) of the credential definition that will be used for this offer. + |It should be the identifier of a credential definition that exists in the issuer agent's database. + |""".stripMargin, + example = UUID.fromString("d9569cec-c81e-4779-aa86-0d5994d82676") + ) + object claims + extends Annotation[zio.json.ast.Json]( + description = """ + |The set of claims that will be included in the issued credential. + |The object should comply with the schema applicable for this offer (i.e. 'schemaId' or 'credentialDefinitionId'). + |""".stripMargin, + example = zio.json.ast.Json.Obj.apply( + "firstname" -> zio.json.ast.Json.Str("Alice"), + "lastname" -> zio.json.ast.Json.Str("Wonderland") + ) + ) + } +} + +case class SDJWTVCPropertiesV1(issuingDID: String, credentialSchema: CredentialSchemaRef, claims: zio.json.ast.Json) + +object SDJWTVCPropertiesV1 { + given schema: Schema[SDJWTVCPropertiesV1] = Schema.derived + given encoder: JsonEncoder[SDJWTVCPropertiesV1] = DeriveJsonEncoder.gen + given decoder: JsonDecoder[SDJWTVCPropertiesV1] = DeriveJsonDecoder.gen + + object annotations { + object issuingDID + extends Annotation[String]( + description = """ + |The issuer Prism DID by which the verifiable credential will be issued. + |""".stripMargin, + example = "did:prism:3bb0505d13fcb04d28a48234edb27b0d4e6d7e18a81e2c1abab58f3bbc21ce6f" + ) + object claims + extends Annotation[zio.json.ast.Json]( + description = """ + |The set of claims that will be included in the issued credential. + |The JSON object should comply with the schema applicable for this offer. + |""".stripMargin, + example = zio.json.ast.Json.Obj( + "firstname" -> zio.json.ast.Json.Str("Alice"), + "lastname" -> zio.json.ast.Json.Str("Wonderland"), + ) + ) + } +} + object CreateIssueCredentialRecordRequest { object annotations { @@ -176,6 +371,30 @@ object CreateIssueCredentialRecordRequest { |""".stripMargin, example = Some("To issue a Faber College Graduate credential") ) + + object jwtVcPropertiesV1 + extends Annotation[Option[JwtVCPropertiesV1]]( + description = """ + |The properties of the JWT verifiable credential that will be issued complied with VCDM 1.1. + |""".stripMargin, + example = None + ) + + object anoncredsVcPropertiesV1 + extends Annotation[Option[AnonCredsVCPropertiesV1]]( + description = """ + |The properties of the AnonCreds verifiable credential that will be issued complied with AnonCreds 1.0. + |""".stripMargin, + example = None + ) + + object sdJwtVcPropertiesV1 + extends Annotation[Option[SDJWTVCPropertiesV1]]( + description = """ + |The properties of the SDJWT verifiable credential that will be issued complied with SD-JWT specification and VCDM 1.1. + |""".stripMargin, + example = None + ) } given schemaIdEncoder: JsonEncoder[String | List[String]] = 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 340dda1622..40d52b5c55 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 @@ -7,6 +7,8 @@ import org.hyperledger.identus.castor.core.model.did.{DID, DIDUrl, PrismDID, Ver import org.hyperledger.identus.oid4vci.domain.{IssuanceSession, Openid4VCIProofJwtOps} import org.hyperledger.identus.oid4vci.http.* import org.hyperledger.identus.oid4vci.storage.IssuanceSessionStorage +import org.hyperledger.identus.pollux.core.model.primitives.UriString +import org.hyperledger.identus.pollux.core.model.primitives.UriString.toUriString import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema import org.hyperledger.identus.pollux.core.service.{ CredentialService, @@ -249,16 +251,16 @@ case class OIDCCredentialIssuerServiceImpl( claims: zio.json.ast.Json ): ZIO[WalletAccessContext, Error, CredentialOffer] = for { - schemaId <- issuerMetadataService + credentialSchemaUri <- issuerMetadataService .getCredentialConfigurationById(issuerId, credentialConfigurationId) .mapError { case _: OID4VCIIssuerMetadataServiceError.CredentialConfigurationNotFound => CredentialConfigurationNotFound(issuerId, credentialConfigurationId) } .map(_.schemaId) _ <- CredentialSchema - .validateJWTCredentialSubject(schemaId.toString(), simpleZioToCirce(claims).noSpaces, uriResolver) + .validateJWTCredentialSubject(credentialSchemaUri.toUriString, simpleZioToCirce(claims).noSpaces, uriResolver) .mapError(e => CredentialSchemaError(e)) - session <- buildNewIssuanceSession(issuerId, issuingDID, claims, schemaId) + session <- buildNewIssuanceSession(issuerId, issuingDID, claims, credentialSchemaUri) _ <- issuanceSessionStorage .start(session) .mapError(e => ServiceError(s"Failed to start issuance session: ${e.message}")) diff --git a/mercury/protocol-issue-credential/src/main/scala/org/hyperledger/identus/mercury/protocol/issuecredential/CredentialPreview.scala b/mercury/protocol-issue-credential/src/main/scala/org/hyperledger/identus/mercury/protocol/issuecredential/CredentialPreview.scala index 4351ca6367..886e3c2256 100644 --- a/mercury/protocol-issue-credential/src/main/scala/org/hyperledger/identus/mercury/protocol/issuecredential/CredentialPreview.scala +++ b/mercury/protocol-issue-credential/src/main/scala/org/hyperledger/identus/mercury/protocol/issuecredential/CredentialPreview.scala @@ -25,6 +25,7 @@ import io.circe.generic.semiauto.* * } * }}} */ +//TODO: Discuss with Fabio or Shailesh if we can change the preview by adding credentialSchema property final case class CredentialPreview( `type`: String = "https://didcomm.org/issue-credential/3.0/credential-credential", schema_ids: Option[List[String]] = None, diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/primitives/UriString.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/primitives/UriString.scala new file mode 100644 index 0000000000..7d5921a054 --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/primitives/UriString.scala @@ -0,0 +1,29 @@ +package org.hyperledger.identus.pollux.core.model.primitives + +import zio.prelude.Validation + +import java.net.URI +import scala.util.Try + +opaque type UriString = String + +object UriString { + + def make(value: String): Validation[String, UriString] = { + Try(URI(value)).fold( + _ => Validation.fail(s"Invalid URI: $value"), + _ => Validation.succeed(value) + ) + } + + extension (uriString: UriString) { + def toUri: java.net.URI = new java.net.URI(uriString) + def value: String = uriString + } + + extension (uri: java.net.URI) { + def toUriString: UriString = uri.toString + } + + given CanEqual[UriString, UriString] = CanEqual.derived +} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/primitives/UrlString.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/primitives/UrlString.scala new file mode 100644 index 0000000000..095cc63954 --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/primitives/UrlString.scala @@ -0,0 +1,26 @@ +package org.hyperledger.identus.pollux.core.model.primitives + +import zio.prelude.Validation + +import java.net.URI +import scala.util.Try + +opaque type UrlString = String + +object UrlString { + def make(value: String): Validation[String, UrlString] = { + Try(URI(value).toURL).fold( + _ => Validation.fail(s"Invalid URL: $value"), + _ => Validation.succeed(value) + ) + } + + extension (urlString: UrlString) { + def toUrl: java.net.URL = new java.net.URI(urlString).toURL + def value: String = urlString + } + extension (url: java.net.URL) { + def toUrlString: UrlString = url.toString + } + given CanEqual[UrlString, UrlString] = CanEqual.derived +} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/schema/CredentialSchema.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/schema/CredentialSchema.scala index 092fb1043b..0d99807808 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/schema/CredentialSchema.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/schema/CredentialSchema.scala @@ -2,6 +2,7 @@ package org.hyperledger.identus.pollux.core.model.schema import org.hyperledger.identus.pollux.core.model.error.CredentialSchemaError import org.hyperledger.identus.pollux.core.model.error.CredentialSchemaError.* +import org.hyperledger.identus.pollux.core.model.primitives.UriString import org.hyperledger.identus.pollux.core.model.schema.`type`.{ AnoncredSchemaType, CredentialJsonSchemaType, @@ -43,6 +44,7 @@ type Schema = zio.json.ast.Json * Internal schema object that depends on concrete implementation For W3C JsonSchema it is a JsonSchema object For * AnonCreds schema is a AnonCreds schema */ +//TODO: refactor `id` to use UriString case class CredentialSchema( guid: UUID, id: UUID, @@ -135,11 +137,11 @@ object CredentialSchema { } def validSchemaValidator( - schemaId: String, + schemaId: UriString, uriResolver: UriResolver - ): IO[InvalidURI | CredentialSchemaParsingError | SchemaDereferencingError, JsonSchemaValidator] = { + ): IO[CredentialSchemaParsingError | SchemaDereferencingError, JsonSchemaValidator] = { for { - uri <- ZIO.attempt(new URI(schemaId)).mapError(_ => InvalidURI(schemaId)) + uri <- ZIO.succeed(schemaId.toUri) json <- resolveJWTSchema(uri, uriResolver) schemaValidator <- JsonSchemaValidatorImpl .from(json) @@ -155,7 +157,7 @@ object CredentialSchema { } def validateJWTCredentialSubject( - schemaId: String, + schemaId: UriString, credentialSubject: String, uriResolver: UriResolver ): IO[ diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/schema/CredentialSchemaRef.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/schema/CredentialSchemaRef.scala new file mode 100644 index 0000000000..ac8aa519cd --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/schema/CredentialSchemaRef.scala @@ -0,0 +1,10 @@ +package org.hyperledger.identus.pollux.core.model.schema + +import org.hyperledger.identus.pollux.core.model.primitives.UriString + +enum CredentialSchemaRefType: + case JsonSchema // according to W3C VCDM 2.0 + case JsonSchemaValidator2018 // according to W3C VCDM 1.1 + +// Represents the credentialSchema properly of the VC according to W3C VCDM 1.1 +case class CredentialSchemaRef(`type`: CredentialSchemaRefType, id: UriString) 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 3414f8a351..b07e0979a2 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 @@ -13,6 +13,7 @@ import org.hyperledger.identus.mercury.protocol.issuecredential.{ import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError.* +import org.hyperledger.identus.pollux.core.model.schema.CredentialSchemaRef import org.hyperledger.identus.pollux.vc.jwt.Issuer import org.hyperledger.identus.shared.models.* import zio.{Duration, IO, UIO, URIO, ZIO} @@ -27,7 +28,7 @@ trait CredentialService { pairwiseHolderDID: Option[DidId], kidIssuer: Option[KeyId], thid: DidCommID, - maybeSchemaIds: Option[List[String]], + credentialSchemaRef: Option[CredentialSchemaRef], claims: io.circe.Json, validityPeriod: Option[Double] = None, automaticIssuance: Option[Boolean], @@ -43,7 +44,7 @@ trait CredentialService { pairwiseHolderDID: Option[DidId], kidIssuer: Option[KeyId], thid: DidCommID, - maybeSchemaIds: Option[List[String]], + credentialSchemaRef: Option[CredentialSchemaRef], claims: io.circe.Json, validityPeriod: Option[Double] = None, automaticIssuance: Option[Boolean], 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 257fc3b604..0bc3d31564 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 @@ -18,7 +18,8 @@ import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError.* import org.hyperledger.identus.pollux.core.model.presentation.* -import org.hyperledger.identus.pollux.core.model.schema.{CredentialDefinition, CredentialSchema} +import org.hyperledger.identus.pollux.core.model.primitives.UriString +import org.hyperledger.identus.pollux.core.model.schema.{CredentialDefinition, CredentialSchema, CredentialSchemaRef} import org.hyperledger.identus.pollux.core.model.secret.CredentialDefinitionSecret import org.hyperledger.identus.pollux.core.model.CredentialFormat.AnonCreds import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.ProtocolState.OfferReceived @@ -31,6 +32,7 @@ import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Secp256k1KeyPair} import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.messaging.{Producer, WalletIdAndRecordId} import org.hyperledger.identus.shared.models.* +import org.hyperledger.identus.shared.models.Failure.orDieAsUnmanagedFailure import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect import org.hyperledger.identus.shared.utils.Base64Utils import zio.* @@ -131,7 +133,7 @@ class CredentialServiceImpl( pairwiseIssuerDID: DidId, kidIssuer: Option[KeyId], thid: DidCommID, - schemaUris: Option[List[String]], + schemaUris: Option[List[UriString]], validityPeriod: Option[Double], automaticIssuance: Option[Boolean], issuingDID: Option[CanonicalPrismDID], @@ -165,7 +167,7 @@ class CredentialServiceImpl( createdAt = Instant.now, updatedAt = None, thid = thid, - schemaUris = schemaUris, + schemaUris = schemaUris.map(uris => uris.map(uri => uri.toString)), credentialDefinitionId = credentialDefinitionGUID, credentialDefinitionUri = credentialDefinitionId, credentialFormat = credentialFormat, @@ -204,7 +206,7 @@ class CredentialServiceImpl( pairwiseHolderDID: Option[DidId], kidIssuer: Option[KeyId], thid: DidCommID, - maybeSchemaIds: Option[List[String]], + credentialSchemaRef: Option[CredentialSchemaRef], claims: Json, validityPeriod: Option[Double], automaticIssuance: Option[Boolean], @@ -215,12 +217,12 @@ class CredentialServiceImpl( connectionId: Option[UUID], ): URIO[WalletAccessContext, IssueCredentialRecord] = { for { - _ <- validateClaimsAgainstSchemaIfAny(claims, maybeSchemaIds) + _ <- validateClaimsAgainstSchemaIfAny(claims, credentialSchemaRef.map(List(_))) attributes <- CredentialService.convertJsonClaimsToAttributes(claims) offer <- createDidCommOfferCredential( pairwiseIssuerDID = pairwiseIssuerDID, pairwiseHolderDID = pairwiseHolderDID, - maybeSchemaIds = maybeSchemaIds, + credentialSchemaRef = credentialSchemaRef.map(List(_)), claims = attributes, thid = thid, UUID.randomUUID().toString, @@ -231,7 +233,7 @@ class CredentialServiceImpl( pairwiseIssuerDID = pairwiseIssuerDID, kidIssuer = kidIssuer, thid = thid, - schemaUris = maybeSchemaIds, + schemaUris = credentialSchemaRef.map(ref => List(ref.id)), validityPeriod = validityPeriod, automaticIssuance = automaticIssuance, issuingDID = Some(issuingDID), @@ -252,7 +254,7 @@ class CredentialServiceImpl( pairwiseHolderDID: Option[DidId], kidIssuer: Option[KeyId], thid: DidCommID, - maybeSchemaIds: Option[List[String]], + credentialSchemaRef: Option[CredentialSchemaRef], claims: io.circe.Json, validityPeriod: Option[Double] = None, automaticIssuance: Option[Boolean], @@ -262,13 +264,14 @@ class CredentialServiceImpl( expirationDuration: Option[Duration], connectionId: Option[UUID], ): URIO[WalletAccessContext, IssueCredentialRecord] = { + val maybeSchemaIds = credentialSchemaRef.map(ref => List(ref.id)) for { - _ <- validateClaimsAgainstSchemaIfAny(claims, maybeSchemaIds) - attributes <- CredentialService.convertJsonClaimsToAttributes(claims) + _ <- validateClaimsAgainstSchemaIfAny(claims, credentialSchemaRef.map(List(_))) + attributes <- CredentialService.convertJsonClaimsToAttributes(claims).orDieAsUnmanagedFailure offer <- createDidCommOfferCredential( pairwiseIssuerDID = pairwiseIssuerDID, pairwiseHolderDID = pairwiseHolderDID, - maybeSchemaIds = maybeSchemaIds, + credentialSchemaRef = credentialSchemaRef.map(List(_)), claims = attributes, thid = thid, UUID.randomUUID().toString, @@ -328,11 +331,16 @@ class CredentialServiceImpl( claims = attributes, thid = thid, ) + schemaUris <- UriString + .make(credentialDefinition.schemaId) + .toZIO + .orDieWith(error => RuntimeException(s"The schemaIs is not a valid URI: $error")) + .map(uri => Option(List(uri))) record <- createIssueCredentialRecord( pairwiseIssuerDID = pairwiseIssuerDID, kidIssuer = None, thid = thid, - schemaUris = Some(List(credentialDefinition.schemaId)), + schemaUris = schemaUris, validityPeriod = validityPeriod, automaticIssuance = automaticIssuance, issuingDID = None, @@ -448,18 +456,21 @@ class CredentialServiceImpl( .fromEither(PrismDID.fromString(did)) .mapError(_ => UnsupportedDidFormat(did)) + // TODO: Refactor this method in order to use more strict signatures private[this] def validateClaimsAgainstSchemaIfAny( claims: Json, - maybeSchemaIds: Option[List[String]] + maybeSchemaIds: Option[List[CredentialSchemaRef]] ): UIO[Unit] = maybeSchemaIds match case Some(schemaIds) => for { _ <- ZIO .collectAll( - schemaIds.map(schemaId => - CredentialSchema - .validateJWTCredentialSubject(schemaId, claims.noSpaces, uriResolver) - ) + schemaIds + .map(_.id) + .map(schemaId => + CredentialSchema + .validateJWTCredentialSubject(schemaId, claims.noSpaces, uriResolver) + ) ) .orDieAsUnmanagedFailure } yield ZIO.unit @@ -1022,13 +1033,14 @@ class CredentialServiceImpl( private def createDidCommOfferCredential( pairwiseIssuerDID: DidId, pairwiseHolderDID: Option[DidId], - maybeSchemaIds: Option[List[String]], + credentialSchemaRef: Option[List[CredentialSchemaRef]], claims: Seq[Attribute], thid: DidCommID, challenge: String, domain: String, offerFormat: IssueCredentialOfferFormat ): UIO[OfferCredential] = { + val maybeSchemaIds = credentialSchemaRef.map(_.map(_.id.toString)) for { credentialPreview <- ZIO.succeed(CredentialPreview(schema_ids = maybeSchemaIds, attributes = claims)) body = OfferCredential.Body( 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 500fdf4c29..74af3e1bcc 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 @@ -8,6 +8,7 @@ import org.hyperledger.identus.mercury.protocol.issuecredential.{IssueCredential import org.hyperledger.identus.pollux.core.model.{DidCommID, IssueCredentialRecord} import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError.* +import org.hyperledger.identus.pollux.core.model.schema.CredentialSchemaRef import org.hyperledger.identus.pollux.core.repository.CredentialRepository import org.hyperledger.identus.pollux.vc.jwt.Issuer import org.hyperledger.identus.shared.models.* @@ -28,7 +29,7 @@ class CredentialServiceNotifier( pairwiseHolderDID: Option[DidId], kidIssuer: Option[KeyId], thid: DidCommID, - maybeSchemaIds: Option[List[String]], + credentialSchemaRef: Option[CredentialSchemaRef], claims: Json, validityPeriod: Option[Double], automaticIssuance: Option[Boolean], @@ -44,7 +45,7 @@ class CredentialServiceNotifier( pairwiseHolderDID, kidIssuer, thid, - maybeSchemaIds, + credentialSchemaRef, claims, validityPeriod, automaticIssuance, @@ -61,7 +62,7 @@ class CredentialServiceNotifier( pairwiseHolderDID: Option[DidId], kidIssuer: Option[KeyId], thid: DidCommID, - maybeSchemaIds: Option[List[String]], + credentialSchemaRef: Option[CredentialSchemaRef], claims: io.circe.Json, validityPeriod: Option[Double] = None, automaticIssuance: Option[Boolean], @@ -77,7 +78,7 @@ class CredentialServiceNotifier( pairwiseHolderDID, kidIssuer, thid, - maybeSchemaIds, + credentialSchemaRef, claims, validityPeriod, automaticIssuance, diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataService.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataService.scala index e9be6a2cb5..af6008bfab 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataService.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataService.scala @@ -2,10 +2,10 @@ package org.hyperledger.identus.pollux.core.service import org.hyperledger.identus.pollux.core.model.error.CredentialSchemaError.{ CredentialSchemaParsingError, - InvalidURI, SchemaDereferencingError } import org.hyperledger.identus.pollux.core.model.oid4vci.{CredentialConfiguration, CredentialIssuer} +import org.hyperledger.identus.pollux.core.model.primitives.UriString import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema import org.hyperledger.identus.pollux.core.model.CredentialFormat import org.hyperledger.identus.pollux.core.repository.OID4VCIIssuerMetadataRepository @@ -21,7 +21,7 @@ import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.models.{Failure, StatusCode, WalletAccessContext} import zio.* -import java.net.{URI, URL} +import java.net.URL import java.util.UUID sealed trait OID4VCIIssuerMetadataServiceError( @@ -156,11 +156,10 @@ class OID4VCIIssuerMetadataServiceImpl(repository: OID4VCIIssuerMetadataReposito case CredentialFormat.JWT => ZIO.unit case f => ZIO.fail(UnsupportedCredentialFormat(f)) } - schemaUri <- ZIO.attempt(new URI(schemaId)).mapError(t => InvalidSchemaId(schemaId, t.getMessage)) + schemaUri <- UriString.make(schemaId).toZIO.mapError(msg => InvalidSchemaId(schemaId, msg)) _ <- CredentialSchema - .validSchemaValidator(schemaUri.toString(), uriResolver) + .validSchemaValidator(schemaUri, uriResolver) .catchAll { - case e: InvalidURI => ZIO.fail(InvalidSchemaId(schemaId, e.userFacingMessage)) case e: SchemaDereferencingError => ZIO.fail(InvalidSchemaId(schemaId, e.userFacingMessage)) case e: CredentialSchemaParsingError => ZIO.fail(InvalidSchemaId(schemaId, e.cause)) } @@ -168,7 +167,7 @@ class OID4VCIIssuerMetadataServiceImpl(repository: OID4VCIIssuerMetadataReposito config = CredentialConfiguration( configurationId = configurationId, format = CredentialFormat.JWT, - schemaId = schemaUri, + schemaId = schemaUri.toUri, // TODO: stopped refactoring here createdAt = now ) _ <- repository.createCredentialConfiguration(issuerId, config) 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 2d43eb120f..f944750108 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 @@ -1,5 +1,6 @@ package org.hyperledger.identus.pollux.core.service.verification +import org.hyperledger.identus.pollux.core.model.primitives.UriString import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema import org.hyperledger.identus.pollux.vc.jwt.{ CredentialPayload, @@ -63,12 +64,15 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriResolver: UriResolv case schema: JwtCredentialSchema => List(schema) case schemaList: List[JwtCredentialSchema] => schemaList } + schemaUris <- ZIO + .collectAll(credentialSchemas.map(cs => UriString.make(cs.id).toZIO)) + .mapError(error => VcVerificationServiceError.UnexpectedError(s"Invalid schema URI: $error")) result <- ZIO.collectAll( - credentialSchemas.map(credentialSchema => + schemaUris.map(schemaUri => CredentialSchema .validSchemaValidator( - credentialSchema.id, + schemaUri, uriResolver ) .mapError(error => VcVerificationServiceError.UnexpectedError(s"Schema Validator Failed: $error")) @@ -110,12 +114,15 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriResolver: UriResolv case schema: JwtCredentialSchema => List(schema) case schemaList: List[JwtCredentialSchema] => schemaList } + schemaUris <- ZIO + .collectAll(credentialSchemas.map(cs => UriString.make(cs.id).toZIO)) + .mapError(error => VcVerificationServiceError.UnexpectedError(s"Invalid schema URI: $error")) result <- ZIO.collectAll( - credentialSchemas.map(credentialSchema => + schemaUris.map(schemaUri => CredentialSchema .validateJWTCredentialSubject( - credentialSchema.id, + schemaUri, CredentialPayload.Implicits.jwtVcEncoder(decodedJwt.vc).noSpaces, uriResolver ) diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImplSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImplSpec.scala index ab9f7b051b..4d85b20554 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImplSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceImplSpec.scala @@ -13,7 +13,10 @@ import org.hyperledger.identus.pollux.anoncreds.AnoncredCredential import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError.* -import org.hyperledger.identus.pollux.core.model.schema.CredentialDefinition +import org.hyperledger.identus.pollux.core.model.primitives.UriString +import org.hyperledger.identus.pollux.core.model.primitives.UriString.toUriString +import org.hyperledger.identus.pollux.core.model.schema.{CredentialDefinition, CredentialSchemaRef} +import org.hyperledger.identus.pollux.core.model.schema.CredentialSchemaRefType import org.hyperledger.identus.pollux.core.model.IssueCredentialRecord.{ProtocolState, Role} import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver import org.hyperledger.identus.pollux.core.service.CredentialServiceImplSpec.test @@ -24,6 +27,7 @@ import zio.mock.MockSpecDefault import zio.test.* import zio.test.Assertion.* +import java.net.URI import java.nio.charset.StandardCharsets import java.security.Security import java.util.{Base64, UUID} @@ -79,7 +83,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS thid = thid, pairwiseIssuerDID = pairwiseIssuerDid, pairwiseHolderDID = pairwiseHolderDid, - maybeSchemaIds = None, + credentialSchemaRef = None, validityPeriod = validityPeriod, automaticIssuance = automaticIssuance ) @@ -152,7 +156,12 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS thid = thid, pairwiseIssuerDID = pairwiseIssuerDid, pairwiseHolderDID = pairwiseHolderDid, - maybeSchemaIds = Some(List("resource:///vc-schema-example.json")), + credentialSchemaRef = Some( + CredentialSchemaRef( + `type` = CredentialSchemaRefType.JsonSchemaValidator2018, + id = new URI("resource:///vc-schema-example.json").toUriString + ) + ), claims = claims, validityPeriod = validityPeriod, automaticIssuance = automaticIssuance @@ -212,7 +221,12 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS thid = thid, pairwiseIssuerDID = pairwiseIssuerDid, pairwiseHolderDID = pairwiseHolderDid, - maybeSchemaIds = Some(List("resource:///vc-schema-example.json")), + credentialSchemaRef = Some( + CredentialSchemaRef( + `type` = CredentialSchemaRefType.JsonSchemaValidator2018, + id = new URI("resource:///vc-schema-example.json").toUriString + ) + ), claims = claims, validityPeriod = validityPeriod, automaticIssuance = automaticIssuance 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 4d872c31b1..bca11ad862 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 @@ -9,6 +9,7 @@ import org.hyperledger.identus.mercury.model.{AttachmentDescriptor, DidId} import org.hyperledger.identus.mercury.protocol.issuecredential.* import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.model.presentation.Options +import org.hyperledger.identus.pollux.core.model.schema.CredentialSchemaRef import org.hyperledger.identus.pollux.core.repository.{ CredentialDefinitionRepositoryInMemory, CredentialRepositoryInMemory, @@ -110,7 +111,7 @@ trait CredentialServiceSpecHelper { pairwiseIssuerDID: DidId = DidId("did:prism:issuer"), pairwiseHolderDID: Option[DidId] = Some(DidId("did:prism:holder-pairwise")), thid: DidCommID = DidCommID(), - maybeSchemaIds: Option[List[String]] = None, + credentialSchemaRef: Option[CredentialSchemaRef] = None, claims: Json = io.circe.parser .parse(""" |{ @@ -134,7 +135,7 @@ trait CredentialServiceSpecHelper { pairwiseHolderDID = pairwiseHolderDID, kidIssuer = kidIssuer, thid = thid, - maybeSchemaIds = maybeSchemaIds, + credentialSchemaRef = credentialSchemaRef, claims = claims, validityPeriod = validityPeriod, automaticIssuance = automaticIssuance, 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 eff67638bc..6d7167d0fa 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 @@ -7,6 +7,7 @@ import org.hyperledger.identus.mercury.protocol.issuecredential.{IssueCredential import org.hyperledger.identus.pollux.core.model.{DidCommID, IssueCredentialRecord} import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError import org.hyperledger.identus.pollux.core.model.error.CredentialServiceError.* +import org.hyperledger.identus.pollux.core.model.schema.CredentialSchemaRef import org.hyperledger.identus.pollux.vc.jwt.Issuer import org.hyperledger.identus.shared.models.* import zio.{mock, Duration, IO, UIO, URIO, URLayer, ZIO, ZLayer} @@ -22,7 +23,7 @@ object MockCredentialService extends Mock[CredentialService] { DidId, Option[DidId], DidCommID, - Option[List[String]], + Option[CredentialSchemaRef], Json, Option[Double], Option[Boolean], @@ -41,7 +42,7 @@ object MockCredentialService extends Mock[CredentialService] { DidId, Option[DidId], DidCommID, - Option[List[String]], + Option[CredentialSchemaRef], Json, Option[Double], Option[Boolean], @@ -130,7 +131,7 @@ object MockCredentialService extends Mock[CredentialService] { pairwiseHolderDID: Option[DidId], kidIssuer: Option[KeyId], thid: DidCommID, - maybeSchemaIds: Option[List[String]], + credentialSchemaRef: Option[CredentialSchemaRef], claims: Json, validityPeriod: Option[Double], automaticIssuance: Option[Boolean], @@ -145,7 +146,7 @@ object MockCredentialService extends Mock[CredentialService] { pairwiseIssuerDID, pairwiseHolderDID, thid, - maybeSchemaIds, + credentialSchemaRef, claims, validityPeriod, automaticIssuance, @@ -161,7 +162,7 @@ object MockCredentialService extends Mock[CredentialService] { pairwiseHolderDID: Option[DidId], kidIssuer: Option[KeyId], thid: DidCommID, - maybeSchemaIds: Option[List[String]], + credentialSchemaRef: Option[CredentialSchemaRef], claims: Json, validityPeriod: Option[Double], automaticIssuance: Option[Boolean], @@ -176,7 +177,7 @@ object MockCredentialService extends Mock[CredentialService] { pairwiseIssuerDID, pairwiseHolderDID, thid, - maybeSchemaIds, + credentialSchemaRef, claims, validityPeriod, automaticIssuance, diff --git a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala index 5638ba028a..e243dfee54 100644 --- a/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala +++ b/pollux/vc-jwt/src/main/scala/org/hyperledger/identus/pollux/vc/jwt/VerifiableCredentialPayload.scala @@ -48,6 +48,7 @@ case class RefreshService( `type`: String ) +//TODO: refactor to use the new CredentialSchemaRef case class CredentialSchema( id: String, `type`: String diff --git a/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt b/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt index b128ddce39..21776c129b 100644 --- a/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt +++ b/tests/integration-tests/src/test/kotlin/common/CredentialSchema.kt @@ -35,6 +35,37 @@ enum class CredentialSchema { "age" to 18, ) }, + + ID_SCHEMA { + override val credentialSchemaType: String = + "https://w3c-ccg.github.io/vc-json-schemas/schema/2.0/schema.json" + override val schemaType: String = "https://json-schema.org/draft/2020-12/schema" + override val schema: JsonSchema = JsonSchema( + id = "https://example.com/student-schema-1.0", + schema = schemaType, + description = "ID schema", + type = "object", + properties = mutableMapOf( + "firstName" to JsonSchemaProperty(type = "string"), + "lastName" to JsonSchemaProperty(type = "string"), + ), + required = listOf("firstName", "lastName"), + ) + override val credentialSchema: CredentialSchemaInput = CredentialSchemaInput( + author = "did:prism:agent", + name = UUID.randomUUID().toString(), + description = "ID credentials schema", + type = credentialSchemaType, + schema = schema, + tags = listOf("id", "personal"), + version = "1.0.0", + ) + override val claims: Map = linkedMapOf( + "firstName" to "John", + "lastName" to "Doe", + ) + }, + EMPLOYEE_SCHEMA { override val credentialSchemaType: String = "https://w3c-ccg.github.io/vc-json-schemas/schema/2.0/schema.json" diff --git a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt index 04d6772c38..15445055b6 100644 --- a/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/connectionless/ConnectionLessSteps.kt @@ -1,6 +1,7 @@ package steps.connectionless import com.google.gson.JsonObject +import common.CredentialSchema import interactions.Post import interactions.body import io.cucumber.java.en.* @@ -14,19 +15,30 @@ import org.hyperledger.identus.client.models.* class ConnectionLessSteps { - @When("{actor} creates a '{}' credential offer invitation with '{}' form DID") - fun inviterGeneratesACredentialOfferInvitation(issuer: Actor, credentialFormat: String, didForm: String) { - val claims = linkedMapOf( - "firstName" to "Automation", - "lastName" to "Execution", - "email" to "email@example.com", - ) + @When("{actor} creates a {string} credential offer invitation with {string} form DID and {} schema") + fun inviterGeneratesACredentialOfferInvitation( + issuer: Actor, + credentialFormat: String, + didForm: String, + schema: CredentialSchema, + ) { + val claims = schema.claims + val schemaGuid = issuer.recall(schema.name) + + val schemaId: String? = if (schemaGuid != null) { + val baseUrl = issuer.recall("baseUrl") + "$baseUrl/schema-registry/schemas/$schemaGuid" + } else { + null + } + val did: String = if (didForm == "short") { issuer.recall("shortFormDid") } else { issuer.recall("longFormDid") } val credentialOfferRequest = CreateIssueCredentialRecordRequest( + schemaId = schemaId?.let { listOf(it) }, claims = claims, issuingDID = did, issuingKid = "assertion-1", diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt index 551662d448..2ce655a5c2 100644 --- a/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/JwtCredentialSteps.kt @@ -64,21 +64,26 @@ class JwtCredentialSteps { @When("{actor} offers a jwt credential to {actor} with '{}' form DID") fun issuerOffersAJwtCredential(issuer: Actor, holder: Actor, format: String) { - val claims = linkedMapOf( - "firstName" to "FirstName", - "lastName" to "LastName", - ) - sendCredentialOffer(issuer, holder, format, null, claims, "assertion-1") + val claims = CredentialSchema.STUDENT_SCHEMA.claims + + val schemaGuid = issuer.recall(CredentialSchema.STUDENT_SCHEMA.name) + + sendCredentialOffer(issuer, holder, format, schemaGuid, claims, "assertion-1") saveCredentialOffer(issuer, holder) } - @When("{actor} offers a jwt credential to {actor} with '{}' form DID using issuingKid '{}'") - fun issuerOffersAJwtCredentialWithIssuingKeyId(issuer: Actor, holder: Actor, format: String, issuingKid: String?) { - val claims = linkedMapOf( - "firstName" to "FirstName", - "lastName" to "LastName", - ) - sendCredentialOffer(issuer, holder, format, null, claims, issuingKid) + @When("{actor} offers a jwt credential to {actor} with {string} form DID using issuingKid {string} and {} schema") + fun issuerOffersAJwtCredentialWithIssuingKeyId( + issuer: Actor, + holder: Actor, + format: String, + issuingKid: String?, + schema: CredentialSchema, + ) { + val claims = schema.claims + val schemaGuid = issuer.recall(schema.name) + + sendCredentialOffer(issuer, holder, format, schemaGuid, claims, issuingKid) saveCredentialOffer(issuer, holder) } diff --git a/tests/integration-tests/src/test/kotlin/steps/credentials/SdJwtCredentialSteps.kt b/tests/integration-tests/src/test/kotlin/steps/credentials/SdJwtCredentialSteps.kt index 5bdf8bcf48..5f34116b09 100644 --- a/tests/integration-tests/src/test/kotlin/steps/credentials/SdJwtCredentialSteps.kt +++ b/tests/integration-tests/src/test/kotlin/steps/credentials/SdJwtCredentialSteps.kt @@ -2,6 +2,7 @@ package steps.credentials import com.google.gson.Gson import com.nimbusds.jose.util.Base64URL +import common.CredentialSchema import interactions.Post import interactions.body import io.cucumber.java.en.Then @@ -17,18 +18,22 @@ import org.hyperledger.identus.client.models.* class SdJwtCredentialSteps { - private val claims = linkedMapOf( - "firstName" to "Automation", - "lastName" to "Execution", - ) - @When("{actor} offers a sd-jwt credential to {actor}") fun issuerOffersSdJwtCredentialToHolder(issuer: Actor, holder: Actor) { val connectionId = issuer.recall("connection-with-${holder.name}").connectionId val did = issuer.recall("shortFormDid") + val schemaGuid = issuer.recall(CredentialSchema.ID_SCHEMA.name) + + val schemaId: String? = if (schemaGuid != null) { + val baseUrl = issuer.recall("baseUrl") + "$baseUrl/schema-registry/schemas/$schemaGuid" + } else { + null + } val credentialOfferRequest = CreateIssueCredentialRecordRequest( - claims = claims, + schemaId = schemaId?.let { listOf(it) }, + claims = CredentialSchema.ID_SCHEMA.claims, issuingDID = did, connectionId = connectionId, validityPeriod = 3600.0, @@ -62,24 +67,24 @@ class SdJwtCredentialSteps { ) } - @When("{actor} tries to offer a sd-jwt credential to {actor}") - fun issuerTriesToOfferSdJwtCredentialToHolder(issuer: Actor, holder: Actor) { - val connectionId = issuer.recall("connection-with-${holder.name}").connectionId - val did = issuer.recall("shortFormDid") - - val credentialOfferRequest = CreateIssueCredentialRecordRequest( - claims = claims, - issuingDID = did, - connectionId = connectionId, - validityPeriod = 3600.0, - credentialFormat = "SDJWT", - automaticIssuance = false, - ) - - issuer.attemptsTo( - Post.to("/issue-credentials/credential-offers").body(credentialOfferRequest), - ) - } +// @When("{actor} tries to offer a sd-jwt credential to {actor}") +// fun issuerTriesToOfferSdJwtCredentialToHolder(issuer: Actor, holder: Actor) { +// val connectionId = issuer.recall("connection-with-${holder.name}").connectionId +// val did = issuer.recall("shortFormDid") +// +// val credentialOfferRequest = CreateIssueCredentialRecordRequest( +// claims = claims, +// issuingDID = did, +// connectionId = connectionId, +// validityPeriod = 3600.0, +// credentialFormat = "SDJWT", +// automaticIssuance = false, +// ) +// +// issuer.attemptsTo( +// Post.to("/issue-credentials/credential-offers").body(credentialOfferRequest), +// ) +// } @Then("{actor} checks the sd-jwt credential contents") fun holderChecksTheSdJwtCredentialContents(holder: Actor) { @@ -112,6 +117,7 @@ class SdJwtCredentialSteps { ) { val issuedCredential = holder.recall("issuedCredential") val jwtCredential = JwtCredential.parseBase64(issuedCredential.credential!!) + val actualClaims = CredentialSchema.ID_SCHEMA.claims.mapValues { it.value.toString() } val payload = jwtCredential.payload!!.toJSONObject() @@ -128,8 +134,8 @@ class SdJwtCredentialSteps { Ensure.that(payload.containsKey("iss")).isTrue(), Ensure.that(payload.containsKey("iat")).isTrue(), Ensure.that(payload.containsKey("exp")).isTrue(), - Ensure.that(disclosedClaims["firstName"]!!.value).isEqualTo(claims["firstName"]!!), - Ensure.that(disclosedClaims["lastName"]!!.value).isEqualTo(claims["lastName"]!!), + Ensure.that(disclosedClaims["firstName"]!!.value).isEqualTo(actualClaims["firstName"]!!), + Ensure.that(disclosedClaims["lastName"]!!.value).isEqualTo(actualClaims["lastName"]!!), ) additionalChecks(payload, disclosedClaims) diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature index 5e15b98f8f..a8f9082b67 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/issuance.feature @@ -5,10 +5,11 @@ Feature: Issue JWT credential Given Issuer and Holder have an existing connection And Holder creates unpublished DID for 'JWT' When Issuer prepares a custom PRISM DID + And Issuer has published 'STUDENT_SCHEMA' schema And Issuer adds a '' key for 'assertionMethod' purpose with '' name to the custom PRISM DID And Issuer creates the custom PRISM DID And Issuer publishes DID to ledger - When Issuer offers a jwt credential to Holder with 'short' form DID using issuingKid '' + When Issuer offers a jwt credential to Holder with 'short' form DID using issuingKid '' and STUDENT_SCHEMA schema And Holder receives the credential offer And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential @@ -24,17 +25,7 @@ Feature: Issue JWT credential | secp256k1 | assert-1 | | ed25519 | assert-1 | - Scenario: Issuing jwt credential with published PRISM DID - Given Issuer and Holder have an existing connection - And Issuer has a published DID for 'JWT' - And Holder has an unpublished DID for 'JWT' - When Issuer offers a jwt credential to Holder with 'short' form DID - And Holder receives the credential offer - And Holder accepts jwt credential offer using 'auth-1' key id - And Issuer issues the credential - Then Holder receives the issued credential - - Scenario: Issuing jwt credential with a schema + Scenario: Issuing jwt credential with published PRISM DID and student schema Given Issuer and Holder have an existing connection And Issuer has a published DID for 'JWT' And Issuer has published 'STUDENT_SCHEMA' schema @@ -56,6 +47,7 @@ Feature: Issue JWT credential Scenario: Issuing jwt credential with unpublished PRISM DID Given Issuer and Holder have an existing connection And Issuer has an unpublished DID for 'JWT' + And Issuer has published 'STUDENT_SCHEMA' schema And Holder has an unpublished DID for 'JWT' And Issuer offers a jwt credential to Holder with 'long' form DID And Holder receives the credential offer @@ -65,8 +57,9 @@ Feature: Issue JWT credential Scenario: Connectionless issuance of JWT credential using OOB invitation Given Issuer has a published DID for 'JWT' + And Issuer has published 'STUDENT_SCHEMA' schema And Holder has an unpublished DID for 'JWT' - When Issuer creates a 'JWT' credential offer invitation with 'short' form DID + When Issuer creates a 'JWT' credential offer invitation with 'short' form DID and STUDENT_SCHEMA schema And Holder accepts the credential offer invitation from Issuer And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential diff --git a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature index b9ffebb5d7..a0bf77f18e 100644 --- a/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature +++ b/tests/integration-tests/src/test/resources/features/credential/jwt/present_proof.feature @@ -25,7 +25,6 @@ Feature: Present Proof Protocol And Holder rejects the proof Then Holder sees the proof is rejected - @RunThis Scenario Outline: Verifying jwt credential using assertion Given Issuer and Holder have an existing connection And Verifier and Holder have an existing connection @@ -34,7 +33,7 @@ Feature: Present Proof Protocol And Issuer adds a '' key for 'assertionMethod' purpose with '' name to the custom PRISM DID And Issuer creates the custom PRISM DID And Issuer publishes DID to ledger - When Issuer offers a jwt credential to Holder with 'short' form DID using issuingKid '' + When Issuer offers a jwt credential to Holder with 'short' form DID using issuingKid '' and STUDENT_SCHEMA schema And Holder receives the credential offer And Holder accepts jwt credential offer using 'auth-1' key id And Issuer issues the credential diff --git a/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature b/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature index 2edc63f073..9a0d982a20 100644 --- a/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature +++ b/tests/integration-tests/src/test/resources/features/credential/sdjwt/issuance.feature @@ -9,6 +9,7 @@ Feature: Issue SD-JWT credential And Issuer adds a '' key for 'authentication' purpose with '' name to the custom PRISM DID And Issuer creates the custom PRISM DID And Issuer publishes DID to ledger + And Issuer has published 'ID_SCHEMA' schema When Issuer offers a sd-jwt credential to Holder And Holder receives the credential offer And Holder accepts credential offer for sd-jwt @@ -22,6 +23,7 @@ Feature: Issue SD-JWT credential Scenario: Issuing sd-jwt credential with holder binding Given Issuer and Holder have an existing connection And Issuer has a published DID for 'SD_JWT' + And Issuer has published 'ID_SCHEMA' schema And Holder has an unpublished DID for 'SD_JWT' When Issuer offers a sd-jwt credential to Holder And Holder receives the credential offer @@ -32,8 +34,9 @@ Feature: Issue SD-JWT credential Scenario: Connectionless issuance of sd-jwt credential with holder binding And Issuer has a published DID for 'SD_JWT' + And Issuer has published 'ID_SCHEMA' schema And Holder has an unpublished DID for 'SD_JWT' - When Issuer creates a 'SDJWT' credential offer invitation with 'short' form DID + When Issuer creates a 'SDJWT' credential offer invitation with 'short' form DID and ID_SCHEMA schema And Holder accepts the credential offer invitation from Issuer And Holder accepts credential offer for sd-jwt with 'auth-1' key binding And Issuer issues the credential diff --git a/tests/performance-tests/agent-performance-tests-k6/README.md b/tests/performance-tests/agent-performance-tests-k6/README.md index c95ba88c2d..a7ec37b0e7 100644 --- a/tests/performance-tests/agent-performance-tests-k6/README.md +++ b/tests/performance-tests/agent-performance-tests-k6/README.md @@ -10,7 +10,7 @@ Clone the generated repository on your local machine, move to the project root folder and install the dependencies defined in [`package.json`](./package.json) -*NOTE*: The Project has a dependency on `input-output-hk/prism-typescript-client` which is a private repository. +*NOTE*: The Project has a dependency on `hyperledger/identus-cloud-agent-client-ts` which is the GitHub packages repository. To install this dependency, you need to have an environment variable `GITHUB_TOKEN` with the scope `read:packages` set, you can install the dependency by running the following command: ```bash diff --git a/tests/performance-tests/agent-performance-tests-k6/package.json b/tests/performance-tests/agent-performance-tests-k6/package.json index 6e41d3a533..4e3d689676 100644 --- a/tests/performance-tests/agent-performance-tests-k6/package.json +++ b/tests/performance-tests/agent-performance-tests-k6/package.json @@ -26,7 +26,7 @@ "webpack": "webpack" }, "dependencies": { - "@hyperledger/identus-cloud-agent-client-ts": "^1.39.1-19ab426", + "@hyperledger/identus-cloud-agent-client-ts": "^1.40.0", "uuid": "^9.0.0" } } diff --git a/tests/performance-tests/agent-performance-tests-k6/src/actors/Holder.ts b/tests/performance-tests/agent-performance-tests-k6/src/actors/Holder.ts index e574af988d..49419c419f 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/actors/Holder.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/actors/Holder.ts @@ -1,4 +1,4 @@ -import { Connection, ConnectionInvitation, IssueCredentialRecord } from "@input-output-hk/prism-typescript-client"; +import { Connection, ConnectionInvitation, IssueCredentialRecord } from "@hyperledger/identus-cloud-agent-client-ts"; import { Actor } from "./Actor"; import { HOLDER_AGENT_API_KEY, HOLDER_AGENT_URL } from "../common/Config"; diff --git a/tests/performance-tests/agent-performance-tests-k6/src/actors/Issuer.ts b/tests/performance-tests/agent-performance-tests-k6/src/actors/Issuer.ts index 2d51f89aad..1949a6ecd6 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/actors/Issuer.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/actors/Issuer.ts @@ -1,4 +1,4 @@ -import { Connection, CredentialSchemaResponse, IssueCredentialRecord } from "@input-output-hk/prism-typescript-client"; +import { Connection, CredentialSchemaResponse, IssueCredentialRecord } from "@hyperledger/identus-cloud-agent-client-ts"; import { Actor } from "./Actor"; import { ISSUER_AGENT_API_KEY, ISSUER_AGENT_URL } from "../common/Config"; diff --git a/tests/performance-tests/agent-performance-tests-k6/src/actors/Verifier.ts b/tests/performance-tests/agent-performance-tests-k6/src/actors/Verifier.ts index 7c051719d1..db126a3701 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/actors/Verifier.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/actors/Verifier.ts @@ -1,4 +1,4 @@ -import { Connection, PresentationStatus } from "@input-output-hk/prism-typescript-client"; +import { Connection, PresentationStatus } from "@hyperledger/identus-cloud-agent-client-ts"; import { Actor } from "./Actor"; import { VERIFIER_AGENT_API_KEY, VERIFIER_AGENT_URL } from "../common/Config"; diff --git a/tests/performance-tests/agent-performance-tests-k6/src/common/ConnectionService.ts b/tests/performance-tests/agent-performance-tests-k6/src/common/ConnectionService.ts index 4361a5c199..ef5db474ba 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/common/ConnectionService.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/common/ConnectionService.ts @@ -1,6 +1,6 @@ /*global __ENV*/ -import { Connection, ConnectionInvitation, ConnectionStateEnum } from "@input-output-hk/prism-typescript-client"; +import { Connection, ConnectionInvitation, ConnectionStateEnum } from "@hyperledger/identus-cloud-agent-client-ts"; import { WAITING_LOOP_MAX_ITERATIONS, WAITING_LOOP_PAUSE_INTERVAL } from "./Config"; import { HttpService, statusChangeTimeouts } from "./HttpService"; import { sleep, fail } from "k6"; diff --git a/tests/performance-tests/agent-performance-tests-k6/src/common/CredentialsService.ts b/tests/performance-tests/agent-performance-tests-k6/src/common/CredentialsService.ts index da946e66bf..c411dc745d 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/common/CredentialsService.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/common/CredentialsService.ts @@ -1,7 +1,7 @@ import { fail, sleep } from "k6"; import { HttpService, statusChangeTimeouts } from "./HttpService"; import { ISSUER_AGENT_URL, WAITING_LOOP_MAX_ITERATIONS, WAITING_LOOP_PAUSE_INTERVAL } from "./Config"; -import { IssueCredentialRecord, Connection, CredentialSchemaResponse } from "@input-output-hk/prism-typescript-client"; +import { IssueCredentialRecord, Connection, CredentialSchemaResponse } from "@hyperledger/identus-cloud-agent-client-ts"; import { crypto } from "k6/experimental/webcrypto"; /** @@ -21,12 +21,12 @@ export class CredentialsService extends HttpService { "claims": { "emailAddress": "${crypto.randomUUID()}-@atala.io", "familyName": "Test", - "schemaId": "${ISSUER_AGENT_URL.replace("localhost", "host.docker.internal")}/schema-registry/schemas/${schema.guid}/schema", "dateOfIssuance": "${new Date()}", "drivingLicenseID": "Test", "drivingClass": 1 }, "issuingDID": "${issuingDid}", + "schemaId": "${ISSUER_AGENT_URL.replace("localhost", "host.docker.internal")}/schema-registry/schemas/${schema.guid}", "connectionId": "${connection.connectionId}", "automaticIssuance": false }`; diff --git a/tests/performance-tests/agent-performance-tests-k6/src/common/DidService.ts b/tests/performance-tests/agent-performance-tests-k6/src/common/DidService.ts index b0c5bddd41..fd294eb95c 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/common/DidService.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/common/DidService.ts @@ -7,7 +7,7 @@ import { DIDDocument, DidOperationSubmission, ManagedDID -} from "@input-output-hk/prism-typescript-client"; +} from "@hyperledger/identus-cloud-agent-client-ts"; import {fail, sleep} from "k6"; diff --git a/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts b/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts index 2be18cce6f..f8ed643924 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/common/ProofsService.ts @@ -1,6 +1,6 @@ import { HttpService, statusChangeTimeouts } from "./HttpService"; import {fail, sleep} from "k6"; -import {Connection, PresentationStatus} from "@input-output-hk/prism-typescript-client"; +import {Connection, PresentationStatus} from "@hyperledger/identus-cloud-agent-client-ts"; import { WAITING_LOOP_MAX_ITERATIONS, WAITING_LOOP_PAUSE_INTERVAL } from "./Config"; import vu from "k6/execution"; diff --git a/tests/performance-tests/agent-performance-tests-k6/src/tests/credentials/credential-definition-test.ts b/tests/performance-tests/agent-performance-tests-k6/src/tests/credentials/credential-definition-test.ts index 3ced12d299..7fe44e05f1 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/tests/credentials/credential-definition-test.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/tests/credentials/credential-definition-test.ts @@ -2,7 +2,7 @@ import { Options } from "k6/options"; import { Issuer } from "../../actors"; import { defaultOptions } from "../../scenarios/default"; import merge from "ts-deepmerge"; -import { CredentialSchemaResponse } from "@input-output-hk/prism-typescript-client"; +import { CredentialSchemaResponse } from "@hyperledger/identus-cloud-agent-client-ts"; import { describe } from "../../k6chaijs.js"; export const localOptions: Options = { diff --git a/tests/performance-tests/agent-performance-tests-k6/src/tests/credentials/credential-offer-test.ts b/tests/performance-tests/agent-performance-tests-k6/src/tests/credentials/credential-offer-test.ts index b09549572e..5f72e1025c 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/tests/credentials/credential-offer-test.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/tests/credentials/credential-offer-test.ts @@ -1,6 +1,6 @@ import { Options } from 'k6/options'; import { Issuer, Holder } from '../../actors'; -import { Connection, CredentialSchemaResponse } from '@input-output-hk/prism-typescript-client'; +import { Connection, CredentialSchemaResponse } from '@hyperledger/identus-cloud-agent-client-ts'; import { defaultOptions } from "../../scenarios/default"; import merge from "ts-deepmerge"; import { describe } from "../../k6chaijs.js"; diff --git a/tests/performance-tests/agent-performance-tests-k6/src/tests/flows/issuance-flow-test.ts b/tests/performance-tests/agent-performance-tests-k6/src/tests/flows/issuance-flow-test.ts index ac7f2aba65..63c0c2555f 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/tests/flows/issuance-flow-test.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/tests/flows/issuance-flow-test.ts @@ -1,6 +1,6 @@ import { Options } from "k6/options"; import { issuer, holder } from "../common"; -import { CredentialSchemaResponse } from "@input-output-hk/prism-typescript-client"; +import { CredentialSchemaResponse } from "@hyperledger/identus-cloud-agent-client-ts"; import { defaultOptions } from "../../scenarios/default"; import merge from "ts-deepmerge"; import { describe } from "../../k6chaijs.js"; diff --git a/tests/performance-tests/agent-performance-tests-k6/src/tests/flows/present-proof-flow-test.ts b/tests/performance-tests/agent-performance-tests-k6/src/tests/flows/present-proof-flow-test.ts index 4d733540ff..51950d8db8 100644 --- a/tests/performance-tests/agent-performance-tests-k6/src/tests/flows/present-proof-flow-test.ts +++ b/tests/performance-tests/agent-performance-tests-k6/src/tests/flows/present-proof-flow-test.ts @@ -1,6 +1,6 @@ import { Options } from 'k6/options' import { Issuer, Holder, Verifier } from '../../actors' -import { CredentialSchemaResponse } from '@input-output-hk/prism-typescript-client' +import { CredentialSchemaResponse } from '@hyperledger/identus-cloud-agent-client-ts' import { defaultOptions } from '../../scenarios/default' import merge from 'ts-deepmerge' import { describe } from '../../k6chaijs.js' diff --git a/tests/performance-tests/agent-performance-tests-k6/yarn.lock b/tests/performance-tests/agent-performance-tests-k6/yarn.lock index 36b63cb43f..43f3b8e0f1 100644 --- a/tests/performance-tests/agent-performance-tests-k6/yarn.lock +++ b/tests/performance-tests/agent-performance-tests-k6/yarn.lock @@ -993,7 +993,7 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@hyperledger/identus-cloud-agent-client-ts@^1.39.1-19ab426": +"@hyperledger/identus-cloud-agent-client-ts@^1.40.0": version "1.40.0" resolved "https://npm.pkg.github.com/download/@hyperledger/identus-cloud-agent-client-ts/1.40.0/ce8120a0483b02cda6aa348dd7e2f175c7a5b846#ce8120a0483b02cda6aa348dd7e2f175c7a5b846" integrity sha512-uvxDYP8lJM7Dl7HMSX44fodVOxUaqsXy4WfNL2pdVCNBOksGFyAmxcAOlU8VNoCTCtKowyoPBnCK6zl9jyhgIw==