Skip to content

Commit

Permalink
feat: change all resource URLs in VCs from http URLs to DID URLs (#1249)
Browse files Browse the repository at this point in the history
Signed-off-by: Shota Jolbordi <[email protected]>
Signed-off-by: Hyperledger Bot <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Hyperledger Bot <[email protected]>
  • Loading branch information
3 people authored Jul 16, 2024
1 parent 73958b0 commit 3cc1f24
Show file tree
Hide file tree
Showing 45 changed files with 387 additions and 326 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pollux {
}
credentialSdJwtExpirationTime = 30 days // Default exp claim duration in days sd jwt token if not provided in credential offer
statusListRegistry {
serviceName = "status-list-registry"
# defaults to the exposed AGENT_HTTP_PORT port
publicEndpointUrl = "http://localhost:"${agent.httpEndpoint.http.port}
publicEndpointUrl = ${?POLLUX_STATUS_LIST_REGISTRY_PUBLIC_URL}
Expand Down Expand Up @@ -89,6 +90,7 @@ agent {
port = 8085
port =${?AGENT_HTTP_PORT}
}
serviceName = "agent-base-url"
publicEndpointUrl = "https://host.docker.internal:8080/cloud-agent"
publicEndpointUrl = ${?REST_SERVICE_URL}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.hyperledger.identus.pollux.credentialschema.{
import org.hyperledger.identus.pollux.vc.jwt.DidResolver as JwtDidResolver
import org.hyperledger.identus.presentproof.controller.PresentProofServerEndpoints
import org.hyperledger.identus.resolvers.DIDResolver
import org.hyperledger.identus.shared.http.UriResolver
import org.hyperledger.identus.shared.models.{HexString, WalletAccessContext, WalletAdministrationContext, WalletId}
import org.hyperledger.identus.shared.utils.DurationOps.toMetricsSeconds
import org.hyperledger.identus.system.controller.SystemServerEndpoints
Expand Down Expand Up @@ -67,8 +68,8 @@ object CloudAgentApp {
} yield ()

private val presentProofExchangeJob: RIO[
AppConfig & DidOps & DIDResolver & JwtDidResolver & HttpClient & PresentationService & CredentialService &
DIDNonSecretStorage & DIDService & ManagedDIDService,
AppConfig & DidOps & UriResolver & DIDResolver & JwtDidResolver & HttpClient & PresentationService &
CredentialService & DIDNonSecretStorage & DIDService & ManagedDIDService,
Unit
] =
for {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ trait ControllerHelper {
protected def extractPrismDIDFromString(maybeDid: String): IO[ErrorResponse, PrismDID] =
ZIO
.fromEither(PrismDID.fromString(maybeDid))
.mapError(e => ErrorResponse.badRequest(detail = Some(s"Error parsing string as PrismDID: ${e}")))
.mapError(e => ErrorResponse.badRequest(detail = Some(s"Error parsing string as PrismDID: $e")))

protected def getLongFormPrismDID(
did: PrismDID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ object MainApp extends ZIOAppDefault {
AppModule.didJwtResolverLayer,
DIDOperationValidator.layer(),
DIDResolver.layer,
HttpURIDereferencerImpl.layer,
GenericUriResolverImpl.layer,
// service
ConnectionServiceImpl.layer >>> ConnectionServiceNotifier.layer,
CredentialSchemaServiceImpl.layer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ final case class PrismNodeConfig(service: GrpcServiceConfig)

final case class GrpcServiceConfig(host: String, port: Int, usePlainText: Boolean)

final case class StatusListRegistryConfig(publicEndpointUrl: java.net.URL)
final case class StatusListRegistryConfig(serviceName: String, publicEndpointUrl: java.net.URL)

final case class DatabaseConfig(
host: String,
Expand All @@ -107,7 +107,7 @@ final case class DatabaseConfig(
DbConfig(
username = if (appUser) appUsername else username,
password = if (appUser) appPassword else password,
jdbcUrl = s"jdbc:postgresql://${host}:${port}/${databaseName}",
jdbcUrl = s"jdbc:postgresql://$host:$port/${databaseName}",
awaitConnectionThreads = awaitConnectionThreads
)
}
Expand Down Expand Up @@ -185,7 +185,7 @@ final case class AgentConfig(

}

final case class HttpEndpointConfig(http: HttpConfig, publicEndpointUrl: java.net.URL)
final case class HttpEndpointConfig(http: HttpConfig, serviceName: String, publicEndpointUrl: java.net.URL)

final case class DidCommEndpointConfig(http: HttpConfig, publicEndpointUrl: java.net.URL)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper {
credentialService <- ZIO.service[CredentialService]
config <- ZIO.service[AppConfig]
_ <- credentialService
.generateJWTCredential(id, config.pollux.statusListRegistry.publicEndpointUrl.toExternalForm)
.generateJWTCredential(id, config.pollux.statusListRegistry.serviceName)
.provideSomeLayer(ZLayer.succeed(walletAccessContext))
} yield ()).mapError(e => (walletAccessContext, e))
} yield result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import zio.json.*
import zio.json.ast.Json
import zio.metrics.*
import zio.prelude.Validation
import zio.prelude.ZValidation.{Failure => ZFailure, *}
import zio.prelude.ZValidation.{Failure as ZFailure, *}

import java.time.{Clock, Instant, ZoneId}

Expand All @@ -47,8 +47,8 @@ object PresentBackgroundJobs extends BackgroundJobsHelper {
/*DIDSecretStorageError | PresentationError | CredentialServiceError | BackgroundJobError | TransportError | */
CastorDIDResolutionError | GetManagedDIDError | Failure

private type RESOURCES = COMMON_RESOURCES & CredentialService & JwtDidResolver & DIDService & AppConfig &
MESSAGING_RESOURCES
private type RESOURCES = COMMON_RESOURCES & CredentialService & JwtDidResolver & UriResolver & DIDService &
AppConfig & MESSAGING_RESOURCES

private type COMMON_RESOURCES = PresentationService & DIDNonSecretStorage & ManagedDIDService

Expand Down Expand Up @@ -800,7 +800,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper {
presentation: Presentation,
credentialFormat: CredentialFormat
): ZIO[
AppConfig & JwtDidResolver & COMMON_RESOURCES & MESSAGING_RESOURCES,
AppConfig & JwtDidResolver & UriResolver & COMMON_RESOURCES & MESSAGING_RESOURCES,
Failure,
Unit
] = {
Expand All @@ -814,7 +814,7 @@ object PresentBackgroundJobs extends BackgroundJobsHelper {
}

private def handleJWT(id: DidCommID, requestPresentation: RequestPresentation, presentation: Presentation): ZIO[
AppConfig & JwtDidResolver & COMMON_RESOURCES & MESSAGING_RESOURCES,
AppConfig & JwtDidResolver & UriResolver & COMMON_RESOURCES & MESSAGING_RESOURCES,
Failure,
Unit
] = {
Expand Down Expand Up @@ -858,28 +858,12 @@ object PresentBackgroundJobs extends BackgroundJobsHelper {
// https://www.w3.org/TR/vc-data-model/#proofs-signatures-0
// A proof is typically attached to a verifiable presentation for authentication purposes
// and to a verifiable credential as a method of assertion.
httpLayer <- ZIO.service[HttpClient]
httpUrlResolver = new UriResolver {
override def resolve(uri: String): IO[GenericUriResolverError, String] = {
val res = HttpClient
.get(uri)
.map(x => x.bodyAsString)
.provideSomeLayer(ZLayer.succeed(httpLayer))
res.mapError(err => SchemaSpecificResolutionError("http", err))
}
}
genericUriResolver = GenericUriResolver(
Map(
"data" -> DataUrlResolver(),
"http" -> httpUrlResolver,
"https" -> httpUrlResolver
)
)
uriResolver <- ZIO.service[UriResolver]
result <- JwtPresentation
.verify(
JWT(base64Decoded),
verificationConfig.toPresentationVerificationOptions()
)(didResolverService, genericUriResolver)(clock)
)(didResolverService, uriResolver)(clock)
.mapError(error => PresentationError.PresentationVerificationError(error.mkString))
} yield result

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.hyperledger.identus.issue.controller

import io.lemonlabs.uri.Url
import org.hyperledger.identus.agent.server.config.AppConfig
import org.hyperledger.identus.agent.server.ControllerHelper
import org.hyperledger.identus.agent.walletapi.model.PublicationState
Expand Down Expand Up @@ -38,20 +39,22 @@ class IssueControllerImpl(
override def createCredentialOffer(
request: CreateIssueCredentialRecordRequest
)(implicit rc: RequestContext): ZIO[WalletAccessContext, ErrorResponse, IssueCredentialRecord] = {

def getIssuingDidFromRequest(request: CreateIssueCredentialRecordRequest) = extractPrismDIDFromString(
request.issuingDID
)

for {
didIdPair <- getPairwiseDIDs(request.connectionId).provideSomeLayer(ZLayer.succeed(connectionService))
jsonClaims <- ZIO // TODO Get read of Circe and use zio-json all the way down
jsonClaims <- 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)))
credentialFormat = request.credentialFormat.map(CredentialFormat.valueOf).getOrElse(CredentialFormat.JWT)
outcome <-
credentialFormat match
case JWT =>
for {
issuingDID <- ZIO
.fromOption(request.issuingDID)
.mapError(_ => ErrorResponse.badRequest(detail = Some("Missing request parameter: issuingDID")))
.flatMap(extractPrismDIDFromString)
issuingDID <- getIssuingDidFromRequest(request)
_ <- validatePrismDID(issuingDID, allowUnpublished = true, Role.Issuer)
record <- credentialService
.createJWTIssueCredentialRecord(
Expand All @@ -67,10 +70,7 @@ class IssueControllerImpl(
} yield record
case SDJWT =>
for {
issuingDID <- ZIO
.fromOption(request.issuingDID)
.mapError(_ => ErrorResponse.badRequest(detail = Some("Missing request parameter: issuingDID")))
.flatMap(extractPrismDIDFromString)
issuingDID <- getIssuingDidFromRequest(request)
_ <- validatePrismDID(issuingDID, allowUnpublished = true, Role.Issuer)
record <- credentialService
.createSDJWTIssueCredentialRecord(
Expand All @@ -86,17 +86,22 @@ class IssueControllerImpl(
} yield record
case AnonCreds =>
for {
issuingDID <- getIssuingDidFromRequest(request)
credentialDefinitionGUID <- ZIO
.fromOption(request.credentialDefinitionId)
.mapError(_ =>
ErrorResponse.badRequest(detail = Some("Missing request parameter: credentialDefinitionId"))
)
credentialDefinitionId = {
val publicEndpointUrl = appConfig.agent.httpEndpoint.publicEndpointUrl.toExternalForm
val urlSuffix =

val publicEndpointServiceName = appConfig.agent.httpEndpoint.serviceName
val resourcePath =
s"credential-definition-registry/definitions/${credentialDefinitionGUID.toString}/definition"
val urlPrefix = if (publicEndpointUrl.endsWith("/")) publicEndpointUrl else publicEndpointUrl + "/"
s"$urlPrefix$urlSuffix"
val didUrl = Url
.parse(s"$issuingDID?resourceService=$publicEndpointServiceName&resourcePath=$resourcePath")
.toString

didUrl
}
record <- credentialService
.createAnonCredsIssueCredentialRecord(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ final case class CreateIssueCredentialRecordRequest(
automaticIssuance: Option[Boolean] = None,
@description(annotations.issuingDID.description)
@encodedExample(annotations.issuingDID.example)
issuingDID: Option[String],
issuingDID: String,
@description(annotations.connectionId.description)
@encodedExample(annotations.connectionId.example)
connectionId: UUID
Expand Down Expand Up @@ -119,12 +119,11 @@ object CreateIssueCredentialRecordRequest {
)

object issuingDID
extends Annotation[Option[String]](
extends Annotation[String](
description = """
|The short-form issuer Prism DID by which the JWT verifiable credential will be issued.
|Note that this parameter only applies when the offer is type 'JWT'.
|The issuer Prism DID by which the verifiable credential will be issued. DID can be short for or long form.
|""".stripMargin,
example = Some("did:prism:3bb0505d13fcb04d28a48234edb27b0d4e6d7e18a81e2c1abab58f3bbc21ce6f")
example = "did:prism:3bb0505d13fcb04d28a48234edb27b0d4e6d7e18a81e2c1abab58f3bbc21ce6f"
)

object connectionId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema
import org.hyperledger.identus.pollux.core.service.{
CredentialService,
OID4VCIIssuerMetadataService,
OID4VCIIssuerMetadataServiceError,
URIDereferencer
OID4VCIIssuerMetadataServiceError
}
import org.hyperledger.identus.pollux.vc.jwt.{
DID as PolluxDID,
Expand All @@ -24,6 +23,7 @@ import org.hyperledger.identus.pollux.vc.jwt.{
W3cCredentialPayload,
*
}
import org.hyperledger.identus.shared.http.UriResolver
import org.hyperledger.identus.shared.models.*
import zio.*

Expand Down Expand Up @@ -105,7 +105,7 @@ case class OIDCCredentialIssuerServiceImpl(
issuerMetadataService: OID4VCIIssuerMetadataService,
issuanceSessionStorage: IssuanceSessionStorage,
didResolver: DidResolver,
uriDereferencer: URIDereferencer,
uriResolver: UriResolver,
) extends OIDCCredentialIssuerService
with Openid4VCIProofJwtOps {

Expand Down Expand Up @@ -244,7 +244,7 @@ case class OIDCCredentialIssuerServiceImpl(
}
.map(_.schemaId)
_ <- CredentialSchema
.validateJWTCredentialSubject(schemaId.toString(), simpleZioToCirce(claims).noSpaces, uriDereferencer)
.validateJWTCredentialSubject(schemaId.toString(), simpleZioToCirce(claims).noSpaces, uriResolver)
.mapError(e => CredentialSchemaError(e))
session <- buildNewIssuanceSession(issuerId, issuingDID, claims, schemaId)
_ <- issuanceSessionStorage
Expand Down Expand Up @@ -298,7 +298,7 @@ case class OIDCCredentialIssuerServiceImpl(

object OIDCCredentialIssuerServiceImpl {
val layer: URLayer[
DIDNonSecretStorage & CredentialService & IssuanceSessionStorage & DidResolver & URIDereferencer &
DIDNonSecretStorage & CredentialService & IssuanceSessionStorage & DidResolver & UriResolver &
OID4VCIIssuerMetadataService,
OIDCCredentialIssuerService
] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ object IssueControllerImplSpec extends ZIOSpecDefault with IssueControllerTestTo
credentialFormat = Some("JWT"),
claims = json.toJsonAST.toOption.get,
automaticIssuance = Some(true),
issuingDID = Some(
"did:prism:332518729a7b7805f73a788e0944802527911901d9b7c16152281be9bc62d944:CosBCogBEkkKFW15LWtleS1hdXRoZW50aWNhdGlvbhAESi4KCXNlY3AyNTZrMRIhAuYoRIefsLhkvYwHz8gDtkG2b0kaZTDOLj_SExWX1fOXEjsKB21hc3RlcjAQAUouCglzZWNwMjU2azESIQLOzab8f0ibt1P0zdMfoWDQTSlPc8_tkV9Jk5BBsXB8fA"
),
issuingDID =
"did:prism:332518729a7b7805f73a788e0944802527911901d9b7c16152281be9bc62d944:CosBCogBEkkKFW15LWtleS1hdXRoZW50aWNhdGlvbhAESi4KCXNlY3AyNTZrMRIhAuYoRIefsLhkvYwHz8gDtkG2b0kaZTDOLj_SExWX1fOXEjsKB21hc3RlcjAQAUouCglzZWNwMjU2azESIQLOzab8f0ibt1P0zdMfoWDQTSlPc8_tkV9Jk5BBsXB8fA",
connectionId = UUID.fromString("123e4567-e89b-12d3-a456-426614174000")
)
private val issueCredentialRecord = IssueCredentialRecord(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import org.hyperledger.identus.pollux.core.repository.{
CredentialStatusListRepositoryInMemory
}
import org.hyperledger.identus.pollux.core.service.*
import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver
import org.hyperledger.identus.pollux.vc.jwt.PrismDidResolver
import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId}
import zio.{Clock, Random, URLayer, ZIO, ZLayer}
Expand Down Expand Up @@ -49,7 +50,7 @@ object OIDCCredentialIssuerServiceSpec
CredentialRepositoryInMemory.layer,
CredentialStatusListRepositoryInMemory.layer,
PrismDidResolver.layer,
ResourceURIDereferencerImpl.layer,
ResourceUrlResolver.layer,
credentialDefinitionServiceLayer,
GenericSecretStorageInMemory.layer,
LinkSecretServiceImpl.layer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ import org.hyperledger.identus.api.http.ErrorResponse
import org.hyperledger.identus.castor.core.model.did.PrismDIDOperation
import org.hyperledger.identus.iam.authentication.{AuthenticatorWithAuthZ, DefaultEntityAuthenticator}
import org.hyperledger.identus.pollux.core.repository.CredentialDefinitionRepository
import org.hyperledger.identus.pollux.core.service.{
CredentialDefinitionService,
CredentialDefinitionServiceImpl,
ResourceURIDereferencerImpl
}
import org.hyperledger.identus.pollux.core.service.{CredentialDefinitionService, CredentialDefinitionServiceImpl}
import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver
import org.hyperledger.identus.pollux.credentialdefinition.controller.{
CredentialDefinitionController,
CredentialDefinitionControllerImpl
Expand Down Expand Up @@ -56,7 +53,7 @@ trait CredentialDefinitionTestTools extends PostgresTestContainerSupport {
private val controllerLayer =
GenericSecretStorageInMemory.layer >+>
systemTransactorLayer >+> contextAwareTransactorLayer >+> JdbcCredentialDefinitionRepository.layer >+>
ResourceURIDereferencerImpl.layer >+>
ResourceUrlResolver.layer >+>
CredentialDefinitionServiceImpl.layer >+>
CredentialDefinitionControllerImpl.layer

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.hyperledger.identus.castor.core.model.did.VerificationRelationship
import org.hyperledger.identus.castor.core.service.MockDIDService
import org.hyperledger.identus.iam.authentication.{AuthenticatorWithAuthZ, DefaultEntityAuthenticator}
import org.hyperledger.identus.pollux.core.service.*
import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver
import org.hyperledger.identus.pollux.core.service.verification.{VcVerificationService, VcVerificationServiceImpl}
import org.hyperledger.identus.pollux.vc.jwt.*
import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId}
Expand Down Expand Up @@ -59,7 +60,7 @@ trait VcVerificationControllerTestTools extends PostgresTestContainerSupport {
VcVerificationController & VcVerificationService & AuthenticatorWithAuthZ[BaseEntity]
](
didResolverLayer,
ResourceURIDereferencerImpl.layer,
ResourceUrlResolver.layer,
VcVerificationControllerImpl.layer,
VcVerificationServiceImpl.layer,
DefaultEntityAuthenticator.layer
Expand Down
6 changes: 4 additions & 2 deletions docs/docusaurus/schemas/credential-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@ The locally unique identifier of the schema.

### longId (String)

Resource identifier of the given credential schema composed from the author's [DID]((/docs/concepts/glossary#decentralized-identifier) reference, id, and version fields.
Resource identifier of the given credential schema composed from the author's DID reference, id, and version fields.
**Example:** `{author}/{id}?version={version}`

> **Note:** According to the [W3C specification](https://w3c-ccg.github.io/vc-json-schemas/#id), this field is locally unique and combines the Issuer `DID`, `uuid`, and `version`.
**For **example:** `did:example:MDP8AsFhHzhwUvGNuYkX7T/06e126d1-fa44-4882-a243-1e326fbe21db?version=1.0`
**For example:** `did:example:MDP8AsFhHzhwUvGNuYkX7T/06e126d1-fa44-4882-a243-1e326fbe21db?version=1.0`



---
Expand Down
Loading

0 comments on commit 3cc1f24

Please sign in to comment.