diff --git a/cloud-agent/service/server/src/main/resources/application.conf b/cloud-agent/service/server/src/main/resources/application.conf index be4f6a0328..e15ac3187c 100644 --- a/cloud-agent/service/server/src/main/resources/application.conf +++ b/cloud-agent/service/server/src/main/resources/application.conf @@ -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} @@ -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} } diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala index 5503393c1a..c3bf7d0e76 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/CloudAgentApp.scala @@ -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 @@ -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 { diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala index 8a5aa712ec..4a4ed708de 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/ControllerHelper.scala @@ -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, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala index a2a69e8d9e..b0309e4e94 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/MainApp.scala @@ -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, diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/config/AppConfig.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/config/AppConfig.scala index 78fd4eb455..3ff588e925 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/config/AppConfig.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/config/AppConfig.scala @@ -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, @@ -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 ) } @@ -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) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/IssueBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/IssueBackgroundJobs.scala index d85a275e74..0d4ed6fd55 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/IssueBackgroundJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/IssueBackgroundJobs.scala @@ -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 diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala index b89cd14d2a..15fc1b22d7 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/agent/server/jobs/PresentBackgroundJobs.scala @@ -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} @@ -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 @@ -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 ] = { @@ -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 ] = { @@ -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 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 d9046ccfec..bd8ec73851 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 @@ -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 @@ -38,9 +39,14 @@ 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) @@ -48,10 +54,7 @@ class IssueControllerImpl( 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( @@ -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( @@ -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( 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 fb405c38e9..e6a9fd9d4c 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 @@ -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 @@ -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 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 bb90856cb0..1e472ee96a 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 @@ -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, @@ -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.* @@ -105,7 +105,7 @@ case class OIDCCredentialIssuerServiceImpl( issuerMetadataService: OID4VCIIssuerMetadataService, issuanceSessionStorage: IssuanceSessionStorage, didResolver: DidResolver, - uriDereferencer: URIDereferencer, + uriResolver: UriResolver, ) extends OIDCCredentialIssuerService with Openid4VCIProofJwtOps { @@ -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 @@ -298,7 +298,7 @@ case class OIDCCredentialIssuerServiceImpl( object OIDCCredentialIssuerServiceImpl { val layer: URLayer[ - DIDNonSecretStorage & CredentialService & IssuanceSessionStorage & DidResolver & URIDereferencer & + DIDNonSecretStorage & CredentialService & IssuanceSessionStorage & DidResolver & UriResolver & OID4VCIIssuerMetadataService, OIDCCredentialIssuerService ] = diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala index 4762ad6522..aa2710a01e 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/issue/controller/IssueControllerImplSpec.scala @@ -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( diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/oid4vci/domain/OIDCCredentialIssuerServiceSpec.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/oid4vci/domain/OIDCCredentialIssuerServiceSpec.scala index ad4d0e5a19..eba646e4b8 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/oid4vci/domain/OIDCCredentialIssuerServiceSpec.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/oid4vci/domain/OIDCCredentialIssuerServiceSpec.scala @@ -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} @@ -49,7 +50,7 @@ object OIDCCredentialIssuerServiceSpec CredentialRepositoryInMemory.layer, CredentialStatusListRepositoryInMemory.layer, PrismDidResolver.layer, - ResourceURIDereferencerImpl.layer, + ResourceUrlResolver.layer, credentialDefinitionServiceLayer, GenericSecretStorageInMemory.layer, LinkSecretServiceImpl.layer, diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/pollux/credentialdefinition/CredentialDefinitionTestTools.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/pollux/credentialdefinition/CredentialDefinitionTestTools.scala index 541a95a3af..1f659e5431 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/pollux/credentialdefinition/CredentialDefinitionTestTools.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/pollux/credentialdefinition/CredentialDefinitionTestTools.scala @@ -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 @@ -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 diff --git a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerTestTools.scala b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerTestTools.scala index 96b5d7cabf..d6d8739d40 100644 --- a/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerTestTools.scala +++ b/cloud-agent/service/server/src/test/scala/org/hyperledger/identus/verification/controller/VcVerificationControllerTestTools.scala @@ -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} @@ -59,7 +60,7 @@ trait VcVerificationControllerTestTools extends PostgresTestContainerSupport { VcVerificationController & VcVerificationService & AuthenticatorWithAuthZ[BaseEntity] ]( didResolverLayer, - ResourceURIDereferencerImpl.layer, + ResourceUrlResolver.layer, VcVerificationControllerImpl.layer, VcVerificationServiceImpl.layer, DefaultEntityAuthenticator.layer diff --git a/docs/docusaurus/schemas/credential-schema.md b/docs/docusaurus/schemas/credential-schema.md index c992b1afd0..77c826038b 100644 --- a/docs/docusaurus/schemas/credential-schema.md +++ b/docs/docusaurus/schemas/credential-schema.md @@ -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` + --- diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala index 7150fa8605..049e67cc6d 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/PresentationError.scala @@ -2,7 +2,7 @@ package org.hyperledger.identus.pollux.core.model.error import org.hyperledger.identus.pollux.core.model.schema.validator.JsonSchemaError import org.hyperledger.identus.pollux.core.model.DidCommID -import org.hyperledger.identus.pollux.core.service.URIDereferencerError +import org.hyperledger.identus.shared.http.GenericUriResolverError import org.hyperledger.identus.shared.models.{Failure, StatusCode} sealed trait PresentationError( @@ -50,25 +50,13 @@ object PresentationError { "Issued credential not found" ) - final case class InvalidSchemaURIError(schemaUri: String, error: Throwable) - extends PresentationError( - StatusCode.BadRequest, - s"Invalid Schema Uri: $schemaUri, Error: ${error.getMessage}" - ) - - final case class InvalidCredentialDefinitionURIError(credentialDefinitionUri: String, error: Throwable) - extends PresentationError( - StatusCode.BadRequest, - s"Invalid Credential Definition Uri: $credentialDefinitionUri, Error: ${error.getMessage}" - ) - - final case class SchemaURIDereferencingError(error: URIDereferencerError) + final case class SchemaURIDereferencingError(error: GenericUriResolverError) extends PresentationError( error.statusCode, error.userFacingMessage ) - final case class CredentialDefinitionURIDereferencingError(error: URIDereferencerError) + final case class CredentialDefinitionURIDereferencingError(error: GenericUriResolverError) extends PresentationError( error.statusCode, error.userFacingMessage 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 2486c17cf2..853bc99a3f 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 @@ -9,7 +9,7 @@ import org.hyperledger.identus.pollux.core.model.schema.`type`.{ } import org.hyperledger.identus.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1 import org.hyperledger.identus.pollux.core.model.schema.validator.{JsonSchemaValidator, JsonSchemaValidatorImpl} -import org.hyperledger.identus.pollux.core.service.URIDereferencer +import org.hyperledger.identus.shared.http.UriResolver import zio.* import zio.json.* import zio.json.ast.Json @@ -116,9 +116,9 @@ object CredentialSchema { given JsonEncoder[CredentialSchema] = DeriveJsonEncoder.gen[CredentialSchema] given JsonDecoder[CredentialSchema] = DeriveJsonDecoder.gen[CredentialSchema] - def resolveJWTSchema(uri: URI, uriDereferencer: URIDereferencer): IO[CredentialSchemaParsingError, Json] = { + def resolveJWTSchema(uri: URI, uriResolver: UriResolver): IO[CredentialSchemaParsingError, Json] = { for { - content <- uriDereferencer.dereference(uri).orDieAsUnmanagedFailure + content <- uriResolver.resolve(uri.toString).orDieAsUnmanagedFailure json <- ZIO .fromEither(content.fromJson[Json]) .mapError(error => CredentialSchemaParsingError(error)) @@ -127,11 +127,11 @@ object CredentialSchema { def validSchemaValidator( schemaId: String, - uriDereferencer: URIDereferencer + uriResolver: UriResolver ): IO[InvalidURI | CredentialSchemaParsingError, JsonSchemaValidator] = { for { uri <- ZIO.attempt(new URI(schemaId)).mapError(_ => InvalidURI(schemaId)) - json <- resolveJWTSchema(uri, uriDereferencer) + json <- resolveJWTSchema(uri, uriResolver) schemaValidator <- JsonSchemaValidatorImpl .from(json) .orElse( @@ -148,10 +148,10 @@ object CredentialSchema { def validateJWTCredentialSubject( schemaId: String, credentialSubject: String, - uriDereferencer: URIDereferencer + uriResolver: UriResolver ): IO[InvalidURI | CredentialSchemaParsingError | CredentialSchemaValidationError, Unit] = { for { - schemaValidator <- validSchemaValidator(schemaId, uriDereferencer) + schemaValidator <- validSchemaValidator(schemaId, uriResolver) _ <- schemaValidator.validate(credentialSubject).mapError(CredentialSchemaValidationError.apply) } yield () } @@ -159,11 +159,10 @@ object CredentialSchema { def validateAnonCredsClaims( schemaId: String, claims: String, - uriDereferencer: URIDereferencer + uriResolver: UriResolver ): IO[InvalidURI | CredentialSchemaParsingError | VCClaimsParsingError | VCClaimValidationError, Unit] = { for { - uri <- ZIO.attempt(new URI(schemaId)).mapError(_ => InvalidURI(schemaId)) - content <- uriDereferencer.dereference(uri).orDieAsUnmanagedFailure + content <- uriResolver.resolve(schemaId).orDieAsUnmanagedFailure validAttrNames <- AnoncredSchemaSerDesV1.schemaSerDes .deserialize(content) diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepository.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepository.scala index 4732fc7ddd..6a86509592 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepository.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepository.scala @@ -22,7 +22,7 @@ trait CredentialStatusListRepository { def createNewForTheWallet( jwtIssuer: Issuer, - statusListRegistryUrl: String + statusListRegistryServiceName: String ): URIO[WalletAccessContext, CredentialStatusList] def allocateSpaceForCredential( diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepositoryInMemory.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepositoryInMemory.scala index 7c1881ba69..a3efd1fec0 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepositoryInMemory.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/repository/CredentialStatusListRepositoryInMemory.scala @@ -1,5 +1,6 @@ package org.hyperledger.identus.pollux.core.repository +import io.lemonlabs.uri.Url import org.hyperledger.identus.castor.core.model.did.{CanonicalPrismDID, PrismDID} import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.vc.jwt.{revocation, Issuer, StatusPurpose} @@ -84,7 +85,7 @@ class CredentialStatusListRepositoryInMemory( def createNewForTheWallet( jwtIssuer: Issuer, - statusListRegistryUrl: String + statusListRegistryServiceName: String ): URIO[WalletAccessContext, CredentialStatusList] = { val id = UUID.randomUUID() @@ -99,9 +100,13 @@ class CredentialStatusListRepositoryInMemory( case DecodingError(message) => new Throwable(message) case IndexOutOfBounds(message) => new Throwable(message) } + resourcePath = + s"credential-status/$id" emptyJwtCredential <- VCStatusList2021 .build( - vcId = s"$statusListRegistryUrl/credential-status/$id", + vcId = Url + .parse(s"${jwtIssuer.did}?resourceService=$statusListRegistryServiceName&resourcePath=$resourcePath") + .toString, revocationData = bitString, jwtIssuer = jwtIssuer ) diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialDefinitionServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialDefinitionServiceImpl.scala index 9dcca15cee..730cd4d36b 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialDefinitionServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/CredentialDefinitionServiceImpl.scala @@ -12,8 +12,7 @@ import org.hyperledger.identus.pollux.core.model.error.{ } import org.hyperledger.identus.pollux.core.model.error.CredentialSchemaError.{ CredentialSchemaParsingError, - CredentialSchemaValidationError, - InvalidURI + CredentialSchemaValidationError } import org.hyperledger.identus.pollux.core.model.schema.`type`.anoncred.AnoncredSchemaSerDesV1 import org.hyperledger.identus.pollux.core.model.schema.validator.JsonSchemaError @@ -27,22 +26,21 @@ import org.hyperledger.identus.pollux.core.service.serdes.{ ProofKeyCredentialDefinitionSchemaSerDesV1, PublicCredentialDefinitionSerDesV1 } +import org.hyperledger.identus.shared.http.UriResolver import zio.* -import java.net.URI import java.util.UUID import scala.util.Try class CredentialDefinitionServiceImpl( genericSecretStorage: GenericSecretStorage, credentialDefinitionRepository: CredentialDefinitionRepository, - uriDereferencer: URIDereferencer + uriResolver: UriResolver ) extends CredentialDefinitionService { override def create(in: CredentialDefinition.Input): Result[CredentialDefinition] = { for { - uri <- ZIO.attempt(new URI(in.schemaId)).mapError(error => InvalidURI(in.schemaId)).orDieAsUnmanagedFailure - content <- uriDereferencer.dereference(uri).orDieAsUnmanagedFailure + content <- uriResolver.resolve(in.schemaId).orDieAsUnmanagedFailure anoncredSchema <- AnoncredSchemaSerDesV1.schemaSerDes .deserialize(content) .mapError(error => CredentialSchemaParsingError(error.error)) @@ -124,7 +122,7 @@ class CredentialDefinitionServiceImpl( object CredentialDefinitionServiceImpl { val layer: URLayer[ - GenericSecretStorage & CredentialDefinitionRepository & URIDereferencer, + GenericSecretStorage & CredentialDefinitionRepository & UriResolver, CredentialDefinitionService ] = ZLayer.fromFunction(CredentialDefinitionServiceImpl(_, _, _)) 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 047748a844..57e75de904 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 @@ -119,7 +119,7 @@ trait CredentialService { def generateJWTCredential( recordId: DidCommID, - statusListRegistryUrl: String, + statusListRegistryServiceName: String, ): ZIO[WalletAccessContext, RecordNotFound | CredentialRequestValidationFailed, IssueCredentialRecord] def generateSDJWTCredential( 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 c0af18ac02..9674cee028 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 @@ -2,6 +2,7 @@ package org.hyperledger.identus.pollux.core.service import io.circe.syntax.* import io.circe.Json +import io.lemonlabs.uri.Url import org.hyperledger.identus.agent.walletapi.model.{ManagedDIDState, PublicationState} import org.hyperledger.identus.agent.walletapi.service.ManagedDIDService import org.hyperledger.identus.agent.walletapi.storage.GenericSecretStorage @@ -23,21 +24,20 @@ import org.hyperledger.identus.pollux.core.repository.{CredentialRepository, Cre import org.hyperledger.identus.pollux.sdjwt.* import org.hyperledger.identus.pollux.vc.jwt.{Issuer as JwtIssuer, *} import org.hyperledger.identus.shared.crypto.{Ed25519KeyPair, Ed25519PublicKey, Secp256k1KeyPair} -import org.hyperledger.identus.shared.http.{DataUrlResolver, GenericUriResolver} +import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.models.* import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect import zio.* import zio.json.* import zio.prelude.ZValidation -import java.net.URI import java.time.{Instant, ZoneId} import java.util.UUID import scala.language.implicitConversions object CredentialServiceImpl { val layer: URLayer[ - CredentialRepository & CredentialStatusListRepository & DidResolver & URIDereferencer & GenericSecretStorage & + CredentialRepository & CredentialStatusListRepository & DidResolver & UriResolver & GenericSecretStorage & CredentialDefinitionService & LinkSecretService & DIDService & ManagedDIDService, CredentialService ] = { @@ -46,7 +46,7 @@ object CredentialServiceImpl { credentialRepo <- ZIO.service[CredentialRepository] credentialStatusListRepo <- ZIO.service[CredentialStatusListRepository] didResolver <- ZIO.service[DidResolver] - uriDereferencer <- ZIO.service[URIDereferencer] + uriResolver <- ZIO.service[UriResolver] genericSecretStorage <- ZIO.service[GenericSecretStorage] credDefenitionService <- ZIO.service[CredentialDefinitionService] linkSecretService <- ZIO.service[LinkSecretService] @@ -57,7 +57,7 @@ object CredentialServiceImpl { credentialRepo, credentialStatusListRepo, didResolver, - uriDereferencer, + uriResolver, genericSecretStorage, credDefenitionService, linkSecretService, @@ -77,7 +77,7 @@ class CredentialServiceImpl( credentialRepository: CredentialRepository, credentialStatusListRepository: CredentialStatusListRepository, didResolver: DidResolver, - uriDereferencer: URIDereferencer, + uriResolver: UriResolver, genericSecretStorage: GenericSecretStorage, credentialDefinitionService: CredentialDefinitionService, linkSecretService: LinkSecretService, @@ -242,7 +242,11 @@ class CredentialServiceImpl( for { credentialDefinition <- getCredentialDefinition(credentialDefinitionGUID) _ <- CredentialSchema - .validateAnonCredsClaims(credentialDefinition.schemaId, claims.noSpaces, uriDereferencer) + .validateAnonCredsClaims( + credentialDefinition.schemaId, + claims.noSpaces, + uriResolver, + ) .orDieAsUnmanagedFailure attributes <- CredentialService.convertJsonClaimsToAttributes(claims) offer <- createAnonCredsDidCommOfferCredential( @@ -392,7 +396,7 @@ class CredentialServiceImpl( ): UIO[Unit] = maybeSchemaId match case Some(schemaId) => CredentialSchema - .validateJWTCredentialSubject(schemaId, claims.noSpaces, uriDereferencer) + .validateJWTCredentialSubject(schemaId, claims.noSpaces, uriResolver) .orDieAsUnmanagedFailure case None => ZIO.unit @@ -662,8 +666,8 @@ class CredentialServiceImpl( ) .orDieWith(_ => RuntimeException(s"No AnonCreds attachment found in the offer")) credentialOffer = anoncreds.AnoncredCredentialOffer(attachmentData) - credDefContent <- uriDereferencer - .dereference(new URI(credentialOffer.getCredDefId)) + credDefContent <- uriResolver + .resolve(credentialOffer.getCredDefId) .orDieAsUnmanagedFailure credentialDefinition = anoncreds.AnoncredCredentialDefinition(credDefContent) linkSecret <- linkSecretService.fetchOrCreate() @@ -786,8 +790,8 @@ class CredentialServiceImpl( ): URIO[WalletAccessContext, anoncreds.AnoncredCredential] = { for { credential <- ZIO.succeed(anoncreds.AnoncredCredential(new String(credentialBytes))) - credDefContent <- uriDereferencer - .dereference(new URI(credential.getCredDefId)) + credDefContent <- uriResolver + .resolve(credential.getCredDefId) .orDieAsUnmanagedFailure credentialDefinition = anoncreds.AnoncredCredentialDefinition(credDefContent) metadata <- ZIO @@ -1039,7 +1043,7 @@ class CredentialServiceImpl( override def generateJWTCredential( recordId: DidCommID, - statusListRegistryUrl: String, + statusListRegistryServiceName: String, ): ZIO[WalletAccessContext, RecordNotFound | CredentialRequestValidationFailed, IssueCredentialRecord] = { for { record <- getRecordWithState(recordId, ProtocolState.CredentialPending) @@ -1067,7 +1071,7 @@ class CredentialServiceImpl( // Custom for JWT issuanceDate = Instant.now() - credentialStatus <- allocateNewCredentialInStatusListForWallet(record, statusListRegistryUrl, jwtIssuer) + credentialStatus <- allocateNewCredentialInStatusListForWallet(record, statusListRegistryServiceName, jwtIssuer) // TODO: get schema when schema registry is available if schema ID is provided w3Credential = W3cCredentialPayload( `@context` = Set( @@ -1196,31 +1200,38 @@ class CredentialServiceImpl( private def allocateNewCredentialInStatusListForWallet( record: IssueCredentialRecord, - statusListRegistryUrl: String, + statusListRegistryServiceName: String, jwtIssuer: JwtIssuer ): URIO[WalletAccessContext, CredentialStatus] = { val effect = for { lastStatusList <- credentialStatusListRepository.getLatestOfTheWallet currentStatusList <- lastStatusList - .fold(credentialStatusListRepository.createNewForTheWallet(jwtIssuer, statusListRegistryUrl))( + .fold(credentialStatusListRepository.createNewForTheWallet(jwtIssuer, statusListRegistryServiceName))( ZIO.succeed(_) ) size = currentStatusList.size lastUsedIndex = currentStatusList.lastUsedIndex statusListToBeUsed <- if lastUsedIndex < size then ZIO.succeed(currentStatusList) - else credentialStatusListRepository.createNewForTheWallet(jwtIssuer, statusListRegistryUrl) + else credentialStatusListRepository.createNewForTheWallet(jwtIssuer, statusListRegistryServiceName) _ <- credentialStatusListRepository.allocateSpaceForCredential( issueCredentialRecordId = record.id, credentialStatusListId = statusListToBeUsed.id, statusListIndex = statusListToBeUsed.lastUsedIndex + 1 ) + resourcePath = + s"credential-status/${statusListToBeUsed.id}" + segment = statusListToBeUsed.lastUsedIndex + 1 } yield CredentialStatus( - id = s"$statusListRegistryUrl/credential-status/${statusListToBeUsed.id}#${statusListToBeUsed.lastUsedIndex + 1}", + id = Url + .parse(s"${jwtIssuer.did}?resourceService=$statusListRegistryServiceName&resourcePath=$resourcePath#$segment") + .toString, `type` = "StatusList2021Entry", statusPurpose = StatusPurpose.Revocation, statusListIndex = lastUsedIndex + 1, - statusListCredential = s"$statusListRegistryUrl/credential-status/${statusListToBeUsed.id}" + statusListCredential = Url + .parse(s"${jwtIssuer.did}?resourceService=$statusListRegistryServiceName&resourcePath=$resourcePath") + .toString ) issueCredentialSem.withPermit(effect) } @@ -1358,12 +1369,6 @@ class CredentialServiceImpl( ZIO.fail(CredentialRequestValidationFailed("domain/challenge proof validation failed")) clock = java.time.Clock.system(ZoneId.systemDefault) - - genericUriResolver = GenericUriResolver( - Map( - "data" -> DataUrlResolver(), - ) - ) verificationResult <- JwtPresentation .verify( jwt, @@ -1373,7 +1378,7 @@ class CredentialServiceImpl( verifyDates = false, leeway = Duration.Zero ) - )(didResolver, genericUriResolver)(clock) + )(didResolver, uriResolver)(clock) .mapError(errors => CredentialRequestValidationFailed(errors*)) result <- verificationResult match 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 ab1ecf2e8c..7ca6b084a6 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 @@ -149,9 +149,9 @@ class CredentialServiceNotifier( override def generateJWTCredential( recordId: DidCommID, - statusListRegistryUrl: String + statusListRegistryServiceName: String ): ZIO[WalletAccessContext, RecordNotFound | CredentialRequestValidationFailed, IssueCredentialRecord] = - notifyOnSuccess(svc.generateJWTCredential(recordId, statusListRegistryUrl)) + notifyOnSuccess(svc.generateJWTCredential(recordId, statusListRegistryServiceName)) override def generateSDJWTCredential( recordId: DidCommID, diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/GenericUriResolverImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/GenericUriResolverImpl.scala new file mode 100644 index 0000000000..1d17927a6a --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/GenericUriResolverImpl.scala @@ -0,0 +1,29 @@ +package org.hyperledger.identus.pollux.core.service + +import org.hyperledger.identus.pollux.core.service.uriResolvers.* +import org.hyperledger.identus.pollux.vc.jwt.DidResolver +import org.hyperledger.identus.shared.http.{GenericUriResolver, GenericUriResolverError, UriResolver} +import zio.* +import zio.http.* + +class GenericUriResolverImpl(client: Client, didResolver: DidResolver) extends UriResolver { + private val httpUrlResolver = HttpUrlResolver(client) + private val genericUriResolver = new GenericUriResolver( + Map( + "http" -> httpUrlResolver, + "https" -> httpUrlResolver, + "data" -> DataUrlResolver(), + "resource" -> ResourceUrlResolver(Map.empty), + "did" -> DidUrlResolver(httpUrlResolver, didResolver) + ) + ) + override def resolve(uri: String): IO[GenericUriResolverError, String] = { + + genericUriResolver.resolve(uri) + } + +} + +object GenericUriResolverImpl { + val layer: URLayer[Client & DidResolver, UriResolver] = ZLayer.fromFunction(GenericUriResolverImpl(_, _)) +} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/HttpURIDereferencerImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/HttpURIDereferencerImpl.scala deleted file mode 100644 index b38aaaf8ba..0000000000 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/HttpURIDereferencerImpl.scala +++ /dev/null @@ -1,41 +0,0 @@ -package org.hyperledger.identus.pollux.core.service - -import org.hyperledger.identus.pollux.core.service.URIDereferencerError.* -import zio.* -import zio.http.* - -import java.net.URI -import java.nio.charset.StandardCharsets - -class HttpURIDereferencerImpl(client: Client) extends URIDereferencer { - - override def dereference(uri: URI): IO[URIDereferencerError, String] = { - val program = for { - url <- ZIO.fromOption(URL.fromURI(uri)).mapError(_ => InvalidURI(uri)) - response <- client - .request(Request(url = url)) - .mapError(t => ConnectionError(t.getMessage)) - body <- response.status match { - case Status.Ok => - response.body.asString.mapError(t => ResponseProcessingError(t.getMessage)) - case Status.NotFound => - ZIO.fail(ResourceNotFound(uri)) - case status if status.isError => - response.body.asStream - .take(1024) // Only take the first 1024 bytes from the response body (if any). - .runCollect - .map(c => new String(c.toArray, StandardCharsets.UTF_8)) - .orDie - .flatMap(errorMessage => ZIO.fail(UnexpectedUpstreamResponseReceived(status.code, Some(errorMessage)))) - case status => - ZIO.fail(UnexpectedUpstreamResponseReceived(status.code)) - } - } yield body - program.provideSomeLayer(zio.Scope.default) - } - -} - -object HttpURIDereferencerImpl { - val layer: URLayer[Client, URIDereferencer] = ZLayer.fromFunction(HttpURIDereferencerImpl(_)) -} 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 9fe6e171fa..00949f1177 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 @@ -12,6 +12,7 @@ import org.hyperledger.identus.pollux.core.service.OID4VCIIssuerMetadataServiceE UnsupportedCredentialFormat } import org.hyperledger.identus.shared.db.Errors.UnexpectedAffectedRow +import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.models.{Failure, StatusCode, WalletAccessContext} import zio.* @@ -81,7 +82,7 @@ trait OID4VCIIssuerMetadataService { ): ZIO[WalletAccessContext, CredentialConfigurationNotFound, Unit] } -class OID4VCIIssuerMetadataServiceImpl(repository: OID4VCIIssuerMetadataRepository, uriDereferencer: URIDereferencer) +class OID4VCIIssuerMetadataServiceImpl(repository: OID4VCIIssuerMetadataRepository, uriResolver: UriResolver) extends OID4VCIIssuerMetadataService { override def createCredentialIssuer(issuer: CredentialIssuer): URIO[WalletAccessContext, CredentialIssuer] = @@ -135,7 +136,7 @@ class OID4VCIIssuerMetadataServiceImpl(repository: OID4VCIIssuerMetadataReposito } schemaUri <- ZIO.attempt(new URI(schemaId)).mapError(t => InvalidSchemaId(schemaId, t.getMessage)) _ <- CredentialSchema - .validSchemaValidator(schemaUri.toString(), uriDereferencer) + .validSchemaValidator(schemaUri.toString(), uriResolver) .catchAll { case e: InvalidURI => ZIO.fail(InvalidSchemaId(schemaId, e.userFacingMessage)) case e: CredentialSchemaParsingError => ZIO.fail(InvalidSchemaId(schemaId, e.cause)) @@ -179,7 +180,7 @@ class OID4VCIIssuerMetadataServiceImpl(repository: OID4VCIIssuerMetadataReposito } object OID4VCIIssuerMetadataServiceImpl { - def layer: URLayer[OID4VCIIssuerMetadataRepository & URIDereferencer, OID4VCIIssuerMetadataService] = { + def layer: URLayer[OID4VCIIssuerMetadataRepository & UriResolver, OID4VCIIssuerMetadataService] = { ZLayer.fromFunction(OID4VCIIssuerMetadataServiceImpl(_, _)) } } diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala index acdc2adc06..ff245f5aea 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceImpl.scala @@ -18,12 +18,12 @@ import org.hyperledger.identus.pollux.core.repository.{CredentialRepository, Pre import org.hyperledger.identus.pollux.core.service.serdes.* import org.hyperledger.identus.pollux.sdjwt.{CredentialCompact, HolderPrivateKey, PresentationCompact, SDJWT} import org.hyperledger.identus.pollux.vc.jwt.* +import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.models.* import org.hyperledger.identus.shared.utils.aspects.CustomMetricsAspect import zio.* import zio.json.* -import java.net.URI import java.time.Instant import java.util.{Base64 as JBase64, UUID} import java.util as ju @@ -31,7 +31,7 @@ import scala.util.chaining.* import scala.util.Try private class PresentationServiceImpl( - uriDereferencer: URIDereferencer, + uriResolver: UriResolver, linkSecretService: LinkSecretService, presentationRepository: PresentationRepository, credentialRepository: CredentialRepository, @@ -714,8 +714,7 @@ private class PresentationServiceImpl( private def resolveSchema(schemaUri: String): IO[PresentationError, (String, AnoncredSchemaDef)] = { for { - uri <- ZIO.attempt(new URI(schemaUri)).mapError(e => InvalidSchemaURIError(schemaUri, e)) - content <- uriDereferencer.dereference(uri).mapError(e => SchemaURIDereferencingError(e)) + content <- uriResolver.resolve(schemaUri).mapError(e => SchemaURIDereferencingError(e)) anoncredSchema <- AnoncredSchemaSerDesV1.schemaSerDes .deserialize(content) @@ -734,10 +733,9 @@ private class PresentationServiceImpl( credentialDefinitionUri: String ): IO[PresentationError, (String, AnoncredCredentialDefinition)] = { for { - uri <- ZIO - .attempt(new URI(credentialDefinitionUri)) - .mapError(e => InvalidCredentialDefinitionURIError(credentialDefinitionUri, e)) - content <- uriDereferencer.dereference(uri).mapError(e => CredentialDefinitionURIDereferencingError(e)) + content <- uriResolver + .resolve(credentialDefinitionUri) + .mapError(e => CredentialDefinitionURIDereferencingError(e)) _ <- PublicCredentialDefinitionSerDesV1.schemaSerDes .validate(content) @@ -1196,7 +1194,7 @@ private class PresentationServiceImpl( object PresentationServiceImpl { val layer: URLayer[ - URIDereferencer & LinkSecretService & PresentationRepository & CredentialRepository, + UriResolver & LinkSecretService & PresentationRepository & CredentialRepository, PresentationService ] = ZLayer.fromFunction(PresentationServiceImpl(_, _, _, _)) diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/ResourceURIDereferencerImpl.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/ResourceURIDereferencerImpl.scala deleted file mode 100644 index 7b79ed8f2c..0000000000 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/ResourceURIDereferencerImpl.scala +++ /dev/null @@ -1,37 +0,0 @@ -package org.hyperledger.identus.pollux.core.service - -import org.hyperledger.identus.pollux.core.service.URIDereferencerError.ResourceNotFound -import zio.* - -import java.net.URI - -class ResourceURIDereferencerImpl(extraResources: Map[String, String]) extends URIDereferencer { - - override def dereference(uri: URI): IO[URIDereferencerError, String] = { - for { - scheme <- ZIO.succeed(uri.getScheme) - body <- scheme match - case "resource" => - val inputStream = this.getClass.getResourceAsStream(uri.getPath) - if (inputStream != null) - val content = scala.io.Source.fromInputStream(inputStream).mkString - inputStream.close() - ZIO.succeed(content) - else ZIO.fail(ResourceNotFound(uri)) - case _ => - extraResources - .get(uri.toString) - .map(ZIO.succeed(_)) - .getOrElse(ZIO.fail(ResourceNotFound(uri))) - } yield body - } - -} - -object ResourceURIDereferencerImpl { - def layer: ULayer[ResourceURIDereferencerImpl] = - ZLayer.succeed(new ResourceURIDereferencerImpl(Map.empty)) - - def layerWithExtraResources: URLayer[Map[String, String], ResourceURIDereferencerImpl] = - ZLayer.fromFunction(ResourceURIDereferencerImpl(_)) -} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/URIDereferencer.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/URIDereferencer.scala deleted file mode 100644 index fa80836744..0000000000 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/URIDereferencer.scala +++ /dev/null @@ -1,49 +0,0 @@ -package org.hyperledger.identus.pollux.core.service - -import org.hyperledger.identus.shared.models.{Failure, StatusCode} -import zio.IO - -import java.net.URI - -trait URIDereferencer { - def dereference(uri: URI): IO[URIDereferencerError, String] -} - -sealed trait URIDereferencerError( - val statusCode: StatusCode, - val userFacingMessage: String -) extends Failure { - override val namespace: String = "URIDereferencer" -} - -object URIDereferencerError { - final case class InvalidURI(uri: URI) - extends URIDereferencerError( - StatusCode.UnprocessableContent, - s"The URI to dereference is invalid: uri=[$uri]" - ) - - final case class ConnectionError(cause: String) - extends URIDereferencerError( - StatusCode.BadGateway, - s"An error occurred while connecting to the URI's underlying server: cause=[$cause]" - ) - - final case class ResourceNotFound(uri: URI) - extends URIDereferencerError( - StatusCode.NotFound, - s"The resource was not found on the URI's underlying server: uri=[$uri]" - ) - - final case class ResponseProcessingError(cause: String) - extends URIDereferencerError( - StatusCode.BadGateway, - s"An error occurred while processing the URI's underlying server response: cause=[$cause]" - ) - - final case class UnexpectedUpstreamResponseReceived(status: Int, content: Option[String] = None) - extends URIDereferencerError( - StatusCode.BadGateway, - s"An unexpected response was received from the URI's underlying server: status=[$status], content=[${content.getOrElse("n/a")}]" - ) -} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DataUrlResolver.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DataUrlResolver.scala new file mode 100644 index 0000000000..176b3a0f24 --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DataUrlResolver.scala @@ -0,0 +1,18 @@ +package org.hyperledger.identus.pollux.core.service.uriResolvers + +import io.lemonlabs.uri.DataUrl +import org.hyperledger.identus.shared.http.{GenericUriResolverError, InvalidUri, UriResolver} +import zio.* + +class DataUrlResolver extends UriResolver { + override def resolve(dataUrl: String): IO[GenericUriResolverError, String] = { + + DataUrl.parseOption(dataUrl).fold(ZIO.fail(InvalidUri(dataUrl))) { url => + ZIO.succeed(String(url.data, url.mediaType.charset)) + } + } +} + +object DataUrlResolver { + val layer: ULayer[DataUrlResolver] = ZLayer.succeed(new DataUrlResolver) +} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DidUrlResolver.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DidUrlResolver.scala new file mode 100644 index 0000000000..7c9e15b73a --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/DidUrlResolver.scala @@ -0,0 +1,17 @@ +package org.hyperledger.identus.pollux.core.service.uriResolvers + +import org.hyperledger.identus.pollux.vc.jwt.DidResolver +import org.hyperledger.identus.shared.http.{GenericUriResolverError, UriResolver} +import zio.* + +class DidUrlResolver(httpUrlResolver: HttpUrlResolver, didResolver: DidResolver) extends UriResolver { + def resolve(uri: String): IO[GenericUriResolverError, String] = { + ??? // TODO: implement did URL resolver + } + +} + +object DidUrlResolver { + val layer: URLayer[HttpUrlResolver & DidResolver, DidUrlResolver] = + ZLayer.fromFunction(DidUrlResolver(_, _)) +} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/HttpUrlResolver.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/HttpUrlResolver.scala new file mode 100644 index 0000000000..535311b648 --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/HttpUrlResolver.scala @@ -0,0 +1,75 @@ +package org.hyperledger.identus.pollux.core.service.uriResolvers + +import org.hyperledger.identus.shared.http.{GenericUriResolverError, UriResolver} +import org.hyperledger.identus.shared.models.StatusCode +import zio.* +import zio.http.* + +import java.nio.charset.StandardCharsets + +class HttpUrlResolver(client: Client) extends UriResolver { + import HttpUriResolver.* + override def resolve(uri: String): IO[GenericUriResolverError, String] = { + val program = for { + url <- ZIO.fromEither(URL.decode(uri)).mapError(_ => InvalidURI(uri)) + response <- client + .request(Request(url = url)) + .mapError(t => ConnectionError(t.getMessage)) + body <- response.status match { + case Status.Ok => + response.body.asString.mapError(t => ResponseProcessingError(t.getMessage)) + case Status.NotFound => + ZIO.fail(ResourceNotFound(uri)) + case status if status.isError => + response.body.asStream + .take(1024) // Only take the first 1024 bytes from the response body (if any). + .runCollect + .map(c => new String(c.toArray, StandardCharsets.UTF_8)) + .orDie + .flatMap(errorMessage => ZIO.fail(UnexpectedUpstreamResponseReceived(status.code, Some(errorMessage)))) + case status => + ZIO.fail(UnexpectedUpstreamResponseReceived(status.code)) + } + } yield body + program.provideSomeLayer(zio.Scope.default) + } + +} + +object HttpUriResolver { + val layer: URLayer[Client, HttpUrlResolver] = ZLayer.fromFunction(HttpUrlResolver(_)) + + class HttpUriResolverError(statusCode: StatusCode, userFacingMessage: String) + extends GenericUriResolverError(statusCode, userFacingMessage) + + final case class InvalidURI(uri: String) + extends HttpUriResolverError( + StatusCode.UnprocessableContent, + s"The URI to resolve is invalid: uri=[$uri]" + ) + + final case class ConnectionError(cause: String) + extends HttpUriResolverError( + StatusCode.BadGateway, + s"An error occurred while connecting to the URI's underlying server: cause=[$cause]" + ) + + final case class ResourceNotFound(uri: String) + extends HttpUriResolverError( + StatusCode.NotFound, + s"The resource was not found on the URI's underlying server: uri=[$uri]" + ) + + final case class ResponseProcessingError(cause: String) + extends HttpUriResolverError( + StatusCode.BadGateway, + s"An error occurred while processing the URI's underlying server response: cause=[$cause]" + ) + + final case class UnexpectedUpstreamResponseReceived(status: Int, content: Option[String] = None) + extends HttpUriResolverError( + StatusCode.BadGateway, + s"An unexpected response was received from the URI's underlying server: status=[$status], content=[${content.getOrElse("n/a")}]" + ) + +} diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/ResourceUrlResolver.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/ResourceUrlResolver.scala new file mode 100644 index 0000000000..0e95320ed8 --- /dev/null +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/service/uriResolvers/ResourceUrlResolver.scala @@ -0,0 +1,57 @@ +package org.hyperledger.identus.pollux.core.service.uriResolvers + +import org.hyperledger.identus.shared.http.{GenericUriResolverError, InvalidUri, UriResolver} +import org.hyperledger.identus.shared.models.StatusCode +import zio.* + +import java.net.URI +import scala.util.Try + +class ResourceUrlResolver(extraResources: Map[String, String]) extends UriResolver { + import ResourceUrlResolver.* + + def resolve(uri: String): IO[GenericUriResolverError, String] = { + for { + javaUri <- ZIO.fromTry(Try(URI(uri))).mapError(_ => InvalidUri(uri)) + scheme <- ZIO.succeed(javaUri.getScheme) + body <- scheme match + case "resource" => + val inputStream = this.getClass.getResourceAsStream(javaUri.getPath) + if (inputStream != null) + val content = scala.io.Source.fromInputStream(inputStream).mkString + inputStream.close() + ZIO.succeed(content) + else ZIO.fail(ResourceNotFound(uri)) + case _ => + extraResources + .get(uri) + .map(ZIO.succeed(_)) + .getOrElse(ZIO.fail(ResourceNotFound(uri))) + } yield body + + } + +} + +class ResourceUrlResolverError(statusCode: StatusCode, userFacingMessage: String) + extends GenericUriResolverError(statusCode, userFacingMessage) + +object ResourceUrlResolver { + def layer: ULayer[ResourceUrlResolver] = + ZLayer.succeed(new ResourceUrlResolver(Map.empty)) + + def layerWithExtraResources: URLayer[Map[String, String], ResourceUrlResolver] = + ZLayer.fromFunction(ResourceUrlResolver(_)) + + final case class InvalidURI(uri: String) + extends ResourceUrlResolverError( + StatusCode.UnprocessableContent, + s"The URI to resolve is invalid: uri=[$uri]" + ) + + final case class ResourceNotFound(uri: String) + extends ResourceUrlResolverError( + StatusCode.NotFound, + s"The resource was not found on the URI's underlying server: uri=[$uri]" + ) +} 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 4f7f0f48a3..95734b2c67 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,14 +1,13 @@ package org.hyperledger.identus.pollux.core.service.verification import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema -import org.hyperledger.identus.pollux.core.service.URIDereferencer import org.hyperledger.identus.pollux.vc.jwt.{DidResolver, JWT, JWTVerification, JwtCredential} +import org.hyperledger.identus.shared.http.UriResolver import zio.* import java.time.OffsetDateTime -class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDereferencer) - extends VcVerificationService { +class VcVerificationServiceImpl(didResolver: DidResolver, uriResolver: UriResolver) extends VcVerificationService { override def verify( vcVerificationRequests: List[VcVerificationRequest] ): IO[VcVerificationServiceError, List[VcVerificationResult]] = { @@ -56,7 +55,7 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe result <- CredentialSchema .validSchemaValidator( credentialSchema.id, - uriDereferencer + uriResolver ) .mapError(error => VcVerificationServiceError.UnexpectedError(s"Schema Validator Failed: $error")) } yield result @@ -95,7 +94,7 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe .validateJWTCredentialSubject( credentialSchema.id, decodedJwt.credentialSubject.noSpaces, - uriDereferencer + uriResolver ) .mapError(error => VcVerificationServiceError.UnexpectedError(s"JWT Credential Subject Validation Failed: $error") @@ -255,6 +254,6 @@ class VcVerificationServiceImpl(didResolver: DidResolver, uriDereferencer: URIDe } object VcVerificationServiceImpl { - val layer: URLayer[DidResolver & URIDereferencer, VcVerificationService] = + val layer: URLayer[DidResolver & UriResolver, VcVerificationService] = ZLayer.fromFunction(VcVerificationServiceImpl(_, _)) } diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialDefinitionServiceSpecHelper.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialDefinitionServiceSpecHelper.scala index 35353652ab..88ada5aa74 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialDefinitionServiceSpecHelper.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialDefinitionServiceSpecHelper.scala @@ -4,6 +4,7 @@ import org.hyperledger.identus.agent.walletapi.memory.GenericSecretStorageInMemo import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.model.schema.CredentialDefinition import org.hyperledger.identus.pollux.core.repository.CredentialDefinitionRepositoryInMemory +import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import org.hyperledger.identus.shared.models.WalletId.* import zio.* @@ -15,7 +16,7 @@ trait CredentialDefinitionServiceSpecHelper { protected val defaultWalletLayer = ZLayer.succeed(WalletAccessContext(WalletId.default)) protected val credentialDefinitionServiceLayer = - GenericSecretStorageInMemory.layer ++ CredentialDefinitionRepositoryInMemory.layer ++ ResourceURIDereferencerImpl.layer >>> + GenericSecretStorageInMemory.layer ++ CredentialDefinitionRepositoryInMemory.layer ++ ResourceUrlResolver.layer >>> CredentialDefinitionServiceImpl.layer ++ defaultWalletLayer val defaultDefinition = 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 4640594451..ab33da4bc2 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 @@ -19,6 +19,7 @@ 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.IssueCredentialRecord.{ProtocolState, Role} +import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver import org.hyperledger.identus.shared.models.{KeyId, UnmanagedFailureException, WalletAccessContext, WalletId} import zio.* import zio.mock.MockSpecDefault @@ -37,7 +38,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS ).provideSomeLayer( MockDIDService.empty ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> credentialServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ) @@ -526,7 +527,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS // Issuer generates credential credentialGenerateRecord <- issuerSvc.generateJWTCredential( issuerRecordId, - "https://test-status-list.registry" + "status-list-registry" ) // Issuer sends credential _ <- issuerSvc.markCredentialSent(issuerRecordId) @@ -582,7 +583,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS .service[CredentialService] .provideSomeLayer( holderCredDefResolverLayer >>> - ResourceURIDereferencerImpl.layerWithExtraResources >>> + ResourceUrlResolver.layerWithExtraResources >>> credentialServiceLayer ) offerCredential <- ZIO.fromEither(OfferCredential.readFromMessage(msg)) diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifierSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifierSpec.scala index ff37a66709..54eb8d8c14 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifierSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/CredentialServiceNotifierSpec.scala @@ -99,7 +99,7 @@ object CredentialServiceNotifierSpec extends MockSpecDefault with CredentialServ _ <- svc.markOfferSent(issuerRecordId) _ <- svc.receiveCredentialRequest(requestCredential()) _ <- svc.acceptCredentialRequest(issuerRecordId) - _ <- svc.generateJWTCredential(issuerRecordId, "https://test-status-list.registry") + _ <- svc.generateJWTCredential(issuerRecordId, "status-list-registry") _ <- svc.markCredentialSent(issuerRecordId) consumer <- ens.consumer[IssueCredentialRecord]("Issue") events <- consumer.poll(50) 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 5b8df2ac05..71ba2e8877 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 @@ -16,6 +16,7 @@ import org.hyperledger.identus.pollux.core.repository.{ CredentialStatusListRepositoryInMemory } import org.hyperledger.identus.pollux.vc.jwt.* +import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import zio.* @@ -31,8 +32,8 @@ trait CredentialServiceSpecHelper { CredentialDefinitionRepositoryInMemory.layer >>> CredentialDefinitionServiceImpl.layer protected val credentialServiceLayer - : URLayer[DIDService & ManagedDIDService & URIDereferencer, CredentialService & CredentialDefinitionService] = - ZLayer.makeSome[DIDService & ManagedDIDService & URIDereferencer, CredentialService & CredentialDefinitionService]( + : URLayer[DIDService & ManagedDIDService & UriResolver, CredentialService & CredentialDefinitionService] = + ZLayer.makeSome[DIDService & ManagedDIDService & UriResolver, CredentialService & CredentialDefinitionService]( CredentialRepositoryInMemory.layer, CredentialStatusListRepositoryInMemory.layer, ZLayer.fromFunction(PrismDidResolver(_)), 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 29533eb4d1..7f1b448068 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 @@ -207,9 +207,9 @@ object MockCredentialService extends Mock[CredentialService] { override def generateJWTCredential( recordId: DidCommID, - statusListRegistryUrl: String, + statusListRegistryServiceName: String, ): ZIO[WalletAccessContext, RecordNotFound | CredentialRequestValidationFailed, IssueCredentialRecord] = - proxy(GenerateJWTCredential, recordId, statusListRegistryUrl) + proxy(GenerateJWTCredential, recordId, statusListRegistryServiceName) override def generateSDJWTCredential( recordId: DidCommID, diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala index 0c356aeba6..345c3bfd52 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/PresentationServiceSpecHelper.scala @@ -9,8 +9,10 @@ import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.model.error.PresentationError import org.hyperledger.identus.pollux.core.repository.* import org.hyperledger.identus.pollux.core.service.serdes.* +import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.shared.crypto.KmpSecp256k1KeyOps +import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import zio.* @@ -25,18 +27,18 @@ trait PresentationServiceSpecHelper { AgentPeerService.makeLayer(PeerDID.makePeerDid(serviceEndpoint = Some("http://localhost:9099"))) val genericSecretStorageLayer = GenericSecretStorageInMemory.layer - val uriDereferencerLayer = ResourceURIDereferencerImpl.layer + val uriResolverLayer = ResourceUrlResolver.layer val credentialDefLayer = - CredentialDefinitionRepositoryInMemory.layer ++ uriDereferencerLayer >>> CredentialDefinitionServiceImpl.layer + CredentialDefinitionRepositoryInMemory.layer ++ uriResolverLayer >>> CredentialDefinitionServiceImpl.layer val linkSecretLayer = genericSecretStorageLayer >+> LinkSecretServiceImpl.layer val presentationServiceLayer = ZLayer.make[ - PresentationService & CredentialDefinitionService & URIDereferencer & LinkSecretService & PresentationRepository & + PresentationService & CredentialDefinitionService & UriResolver & LinkSecretService & PresentationRepository & CredentialRepository ]( PresentationServiceImpl.layer, credentialDefLayer, - uriDereferencerLayer, + uriResolverLayer, linkSecretLayer, PresentationRepositoryInMemory.layer, CredentialRepositoryInMemory.layer diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala index a182ed5824..0972bf1782 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceImplSpec.scala @@ -4,7 +4,7 @@ import io.circe.* import io.circe.syntax.* import org.hyperledger.identus.agent.walletapi.service.MockManagedDIDService import org.hyperledger.identus.castor.core.service.MockDIDService -import org.hyperledger.identus.pollux.core.service.ResourceURIDereferencerImpl +import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.pollux.vc.jwt.CredentialPayload.Implicits.* import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} @@ -77,7 +77,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS }.provideSomeLayer( MockDIDService.empty ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ), @@ -141,7 +141,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS }.provideSomeLayer( MockDIDService.empty ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ), @@ -204,7 +204,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS }.provideSomeLayer( issuerDidServiceExpectations.toLayer ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ), @@ -271,7 +271,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS }.provideSomeLayer( MockDIDService.empty ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ), @@ -339,7 +339,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS }.provideSomeLayer( MockDIDService.empty ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ), @@ -403,7 +403,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS }.provideSomeLayer( MockDIDService.empty ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ), @@ -467,7 +467,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS }.provideSomeLayer( MockDIDService.empty ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ), @@ -531,7 +531,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS }.provideSomeLayer( MockDIDService.empty ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ), @@ -595,7 +595,7 @@ object VcVerificationServiceImplSpec extends ZIOSpecDefault with VcVerificationS }.provideSomeLayer( MockDIDService.empty ++ MockManagedDIDService.empty ++ - ResourceURIDereferencerImpl.layer >+> + ResourceUrlResolver.layer >+> someVcVerificationServiceLayer ++ ZLayer.succeed(WalletAccessContext(WalletId.random)) ) diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceSpecHelper.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceSpecHelper.scala index ed178729fd..6a0ef07e58 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceSpecHelper.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/verification/VcVerificationServiceSpecHelper.scala @@ -3,8 +3,9 @@ package org.hyperledger.identus.pollux.core.service.verification import org.hyperledger.identus.agent.walletapi.service.{ManagedDIDService, MockManagedDIDService} import org.hyperledger.identus.castor.core.model.did.VerificationRelationship import org.hyperledger.identus.castor.core.service.{DIDService, MockDIDService} -import org.hyperledger.identus.pollux.core.service.{ResourceURIDereferencerImpl, URIDereferencer} +import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver import org.hyperledger.identus.pollux.vc.jwt.* +import org.hyperledger.identus.shared.http.UriResolver import org.hyperledger.identus.shared.models.{WalletAccessContext, WalletId} import org.hyperledger.identus.shared.models.WalletId.* import zio.* @@ -37,12 +38,12 @@ trait VcVerificationServiceSpecHelper { MockManagedDIDService.empty >>> ZLayer.fromFunction(PrismDidResolver(_)) protected val vcVerificationServiceLayer: ZLayer[Any, Nothing, VcVerificationService & WalletAccessContext] = - emptyDidResolverLayer ++ ResourceURIDereferencerImpl.layer >>> + emptyDidResolverLayer ++ ResourceUrlResolver.layer >>> VcVerificationServiceImpl.layer ++ defaultWalletLayer protected val someVcVerificationServiceLayer - : URLayer[DIDService & ManagedDIDService & URIDereferencer, VcVerificationService] = - ZLayer.makeSome[DIDService & ManagedDIDService & URIDereferencer, VcVerificationService]( + : URLayer[DIDService & ManagedDIDService & UriResolver, VcVerificationService] = + ZLayer.makeSome[DIDService & ManagedDIDService & UriResolver, VcVerificationService]( ZLayer.fromFunction(PrismDidResolver(_)), VcVerificationServiceImpl.layer ) diff --git a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcCredentialStatusListRepository.scala b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcCredentialStatusListRepository.scala index 6102088e9e..8968d31247 100644 --- a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcCredentialStatusListRepository.scala +++ b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/repository/JdbcCredentialStatusListRepository.scala @@ -4,6 +4,7 @@ import doobie.* import doobie.implicits.* import doobie.postgres.* import doobie.postgres.implicits.* +import io.lemonlabs.uri.Url import org.hyperledger.identus.castor.core.model.did.* import org.hyperledger.identus.pollux.core.model.* import org.hyperledger.identus.pollux.core.repository.CredentialStatusListRepository @@ -75,7 +76,7 @@ class JdbcCredentialStatusListRepository(xa: Transactor[ContextAwareTask], xb: T def createNewForTheWallet( jwtIssuer: Issuer, - statusListRegistryUrl: String + statusListRegistryServiceName: String ): URIO[WalletAccessContext, CredentialStatusList] = { val id = UUID.randomUUID() @@ -89,9 +90,13 @@ class JdbcCredentialStatusListRepository(xa: Transactor[ContextAwareTask], xb: T case DecodingError(message) => new Throwable(message) case IndexOutOfBounds(message) => new Throwable(message) } + resourcePath = + s"credential-status/$id" emptyStatusListCredential <- VCStatusList2021 .build( - vcId = s"$statusListRegistryUrl/credential-status/$id", + vcId = Url + .parse(s"${jwtIssuer.did}?resourceService=$statusListRegistryServiceName&resourcePath=$resourcePath") + .toString, revocationData = bitString, jwtIssuer = jwtIssuer ) diff --git a/pollux/sql-doobie/src/test/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataServiceSpec.scala b/pollux/sql-doobie/src/test/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataServiceSpec.scala index bdae719bd9..346ef2b580 100644 --- a/pollux/sql-doobie/src/test/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataServiceSpec.scala +++ b/pollux/sql-doobie/src/test/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataServiceSpec.scala @@ -1,5 +1,6 @@ package org.hyperledger.identus.pollux.core.service +import org.hyperledger.identus.pollux.core.service.uriResolvers.ResourceUrlResolver import org.hyperledger.identus.pollux.sql.repository.JdbcOID4VCIIssuerMetadataRepository import org.hyperledger.identus.sharedtest.containers.PostgresTestContainerSupport import org.hyperledger.identus.test.container.MigrationAspects @@ -16,7 +17,7 @@ object OID4VCIIssuerMetadataServiceSpec extends ZIOSpecDefault, PostgresTestCont private val testEnvironmentLayer = ZLayer.make[OID4VCIIssuerMetadataService]( OID4VCIIssuerMetadataServiceImpl.layer, JdbcOID4VCIIssuerMetadataRepository.layer, - ResourceURIDereferencerImpl.layer, + ResourceUrlResolver.layer, contextAwareTransactorLayer, systemTransactorLayer ) diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/http/GenericUriResolver.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/http/GenericUriResolver.scala index f8b5741f3a..72d6d613bc 100644 --- a/shared/core/src/main/scala/org/hyperledger/identus/shared/http/GenericUriResolver.scala +++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/http/GenericUriResolver.scala @@ -1,8 +1,11 @@ package org.hyperledger.identus.shared.http -import io.lemonlabs.uri.{DataUrl, Uri, Url, Urn} +import io.lemonlabs.uri.{Uri, Url, Urn} +import org.hyperledger.identus.shared.models.{Failure, StatusCode} import zio.* +import scala.util + trait UriResolver { def resolve(uri: String): IO[GenericUriResolverError, String] @@ -12,41 +15,34 @@ trait UriResolver { class GenericUriResolver(resolvers: Map[String, UriResolver]) extends UriResolver { override def resolve(uri: String): IO[GenericUriResolverError, String] = { - val parsedUri = Uri.parse(uri) - parsedUri match + val parsedUri = Uri.parseTry(uri) + + ZIO.fromTry(parsedUri).mapError(_ => InvalidUri(uri)).flatMap { case url: Url => url.schemeOption.fold(ZIO.fail(InvalidUri(uri)))(schema => - resolvers.get(schema).fold(ZIO.fail(UnsupportedUriSchema(schema)))(resolver => resolver.resolve(uri)) + resolvers.get(schema).fold(ZIO.fail(UnsupportedUriSchema(schema))) { resolver => + resolver.resolve(uri) + } ) case Urn(path) => ZIO.fail(InvalidUri(uri)) // Must be a URL - } - -} - -class DataUrlResolver extends UriResolver { - override def resolve(dataUrl: String): IO[GenericUriResolverError, String] = { - - DataUrl.parseOption(dataUrl).fold(ZIO.fail(InvalidUri(dataUrl))) { url => - ZIO.succeed(String(url.data, url.mediaType.charset)) } } } -sealed trait GenericUriResolverError { +trait GenericUriResolverError(val statusCode: StatusCode, val userFacingMessage: String) extends Failure { + override val namespace: String = "UriResolver" def toThrowable: Throwable = { this match case InvalidUri(uri) => new RuntimeException(s"Invalid URI: $uri") case UnsupportedUriSchema(schema) => new RuntimeException(s"Unsupported URI schema: $schema") - case SchemaSpecificResolutionError(schema, error) => - new RuntimeException(s"Error resolving ${schema} URL: ${error.getMessage}") } } -case class InvalidUri(uri: String) extends GenericUriResolverError - -case class UnsupportedUriSchema(schema: String) extends GenericUriResolverError +case class InvalidUri(uri: String) + extends GenericUriResolverError(StatusCode.UnprocessableContent, s"The URI to dereference is invalid: uri=[$uri]") -case class SchemaSpecificResolutionError(schema: String, error: Throwable) extends GenericUriResolverError +case class UnsupportedUriSchema(schema: String) + extends GenericUriResolverError(StatusCode.UnprocessableContent, s"Unsupported URI schema: $schema")