Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: align the credential schema property name according to the VCDM 1.1 #1467

Merged
merged 12 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
@@ -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)))
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class IssueControllerImpl(
managedDIDService: ManagedDIDService,
appConfig: AppConfig
) extends IssueController
with ControllerHelper {
with ControllerHelper
with CredentialSchemaReferenceParsingLogic {

private case class OfferContext(
pairwiseIssuerDID: DidId,
Expand All @@ -55,27 +56,29 @@ 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
case JWT =>
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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading