From 65cc9a712af722f5cb3dd36e78b088c20723097b Mon Sep 17 00:00:00 2001 From: patlo-iog Date: Mon, 30 Sep 2024 17:43:18 +0700 Subject: [PATCH] fix: oid4vci endpoints error statuses and negative input validation (#1384) Signed-off-by: Pat Losoponkul --- .../oid4vci/CredentialIssuerEndpoints.scala | 10 +++++++- .../CredentialIssuerController.scala | 10 ++++---- ...dDIDServiceWithEventNotificationImpl.scala | 3 +-- .../util/ManagedDIDTemplateValidator.scala | 7 ++++-- .../model/error/CredentialSchemaError.scala | 7 ++++++ .../core/model/schema/CredentialSchema.scala | 16 +++++++++---- .../core/service/CredentialServiceImpl.scala | 1 - .../core/service/GenericUriResolverImpl.scala | 3 +-- .../OID4VCIIssuerMetadataService.scala | 24 ++++++++++++++++--- .../service/uriResolvers/DidUrlResolver.scala | 3 +-- ...ID4VCIIssuerMetadataServiceSpecSuite.scala | 2 +- .../identus/pollux/sql/model/db/package.scala | 6 ++--- .../shared/http/GenericUriResolver.scala | 3 +-- .../identus/shared/models/Failure.scala | 1 + 14 files changed, 69 insertions(+), 27 deletions(-) diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerEndpoints.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerEndpoints.scala index b49c11068a..1920718961 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerEndpoints.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/CredentialIssuerEndpoints.scala @@ -1,6 +1,7 @@ package org.hyperledger.identus.oid4vci import org.hyperledger.identus.api.http.{EndpointOutputs, ErrorResponse, RequestContext} +import org.hyperledger.identus.api.http.EndpointOutputs.FailureVariant import org.hyperledger.identus.iam.authentication.apikey.ApiKeyCredentials import org.hyperledger.identus.iam.authentication.apikey.ApiKeyEndpointSecurityLogic.apiKeyHeader import org.hyperledger.identus.iam.authentication.oidc.JwtCredentials @@ -197,7 +198,14 @@ object CredentialIssuerEndpoints { statusCode(StatusCode.Created).description("Credential configuration created successfully") ) .out(jsonBody[CredentialConfiguration]) - .errorOut(EndpointOutputs.basicFailureAndNotFoundAndForbidden) + .errorOut( + EndpointOutputs.basicFailuresWith( + FailureVariant.notFound, + FailureVariant.unauthorized, + FailureVariant.forbidden, + FailureVariant.conflict + ) + ) .name("createCredentialConfiguration") .summary("Create a new credential configuration") .description( diff --git a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/controller/CredentialIssuerController.scala b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/controller/CredentialIssuerController.scala index 69300821ec..c60a371342 100644 --- a/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/controller/CredentialIssuerController.scala +++ b/cloud-agent/service/server/src/main/scala/org/hyperledger/identus/oid4vci/controller/CredentialIssuerController.scala @@ -125,10 +125,12 @@ case class CredentialIssuerControllerImpl( import CredentialIssuerController.Errors.* import OIDCCredentialIssuerService.Errors.* - private def parseURL(url: String): IO[ErrorResponse, URL] = + private def parseAbsoluteURL(url: String): IO[ErrorResponse, URL] = ZIO - .attempt(URI.create(url).toURL()) + .attempt(URI.create(url)) .mapError(ue => badRequest(detail = Some(s"Invalid URL: $url"))) + .filterOrFail(_.isAbsolute())(badRequest(detail = Some(s"Relative URL '$url' is not allowed"))) + .map(_.toURL()) private def baseCredentialIssuerUrl(issuerId: UUID): URL = URI(s"$agentBaseUrl/oid4vci/issuers/$issuerId").toURL() @@ -255,7 +257,7 @@ case class CredentialIssuerControllerImpl( request: CreateCredentialIssuerRequest ): ZIO[WalletAccessContext, ErrorResponse, CredentialIssuer] = for { - authServerUrl <- parseURL(request.authorizationServer.url) + authServerUrl <- parseAbsoluteURL(request.authorizationServer.url) id = request.id.getOrElse(UUID.randomUUID()) issuerToCreate = PolluxCredentialIssuer( id, @@ -287,7 +289,7 @@ case class CredentialIssuerControllerImpl( maybeAuthServerUrl <- ZIO .succeed(request.authorizationServer.flatMap(_.url)) .flatMap { - case Some(url) => parseURL(url).asSome + case Some(url) => parseAbsoluteURL(url).asSome case None => ZIO.none } issuer <- issuerMetadataService.updateCredentialIssuer( diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceWithEventNotificationImpl.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceWithEventNotificationImpl.scala index 6938ef3547..795e6d6199 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceWithEventNotificationImpl.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/service/ManagedDIDServiceWithEventNotificationImpl.scala @@ -3,8 +3,7 @@ package org.hyperledger.identus.agent.walletapi.service import org.hyperledger.identus.agent.walletapi.model.error.CommonWalletStorageError import org.hyperledger.identus.agent.walletapi.model.ManagedDIDDetail import org.hyperledger.identus.agent.walletapi.storage.{DIDNonSecretStorage, DIDSecretStorage, WalletSecretStorage} -import org.hyperledger.identus.castor.core.model.did.CanonicalPrismDID -import org.hyperledger.identus.castor.core.model.did.Service as DidDocumentService +import org.hyperledger.identus.castor.core.model.did.{CanonicalPrismDID, Service as DidDocumentService} import org.hyperledger.identus.castor.core.model.error.DIDOperationError import org.hyperledger.identus.castor.core.service.DIDService import org.hyperledger.identus.castor.core.util.DIDOperationValidator diff --git a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/ManagedDIDTemplateValidator.scala b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/ManagedDIDTemplateValidator.scala index 3e9385ae86..dad26a4b10 100644 --- a/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/ManagedDIDTemplateValidator.scala +++ b/cloud-agent/service/wallet-api/src/main/scala/org/hyperledger/identus/agent/walletapi/util/ManagedDIDTemplateValidator.scala @@ -2,8 +2,11 @@ package org.hyperledger.identus.agent.walletapi.util import org.hyperledger.identus.agent.walletapi.model.ManagedDIDTemplate import org.hyperledger.identus.agent.walletapi.service.ManagedDIDService -import org.hyperledger.identus.castor.core.model.did.{EllipticCurve, VerificationRelationship} -import org.hyperledger.identus.castor.core.model.did.Service as DidDocumentService +import org.hyperledger.identus.castor.core.model.did.{ + EllipticCurve, + Service as DidDocumentService, + VerificationRelationship +} object ManagedDIDTemplateValidator { diff --git a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialSchemaError.scala b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialSchemaError.scala index e1b1acd1f2..f2eddcf151 100644 --- a/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialSchemaError.scala +++ b/pollux/core/src/main/scala/org/hyperledger/identus/pollux/core/model/error/CredentialSchemaError.scala @@ -1,5 +1,6 @@ package org.hyperledger.identus.pollux.core.model.error +import org.hyperledger.identus.shared.http.GenericUriResolverError import org.hyperledger.identus.shared.json.JsonSchemaError import org.hyperledger.identus.shared.models.{Failure, StatusCode} @@ -46,4 +47,10 @@ object CredentialSchemaError { StatusCode.BadRequest, s"Unsupported credential schema type: ${`type`}" ) + + final case class SchemaDereferencingError(cause: GenericUriResolverError) + extends CredentialSchemaError( + StatusCode.InternalServerError, + s"The schema was not successfully dereferenced: cause=[${cause.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 d81c821369..092fb1043b 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 @@ -120,9 +120,14 @@ object CredentialSchema { given JsonEncoder[CredentialSchema] = DeriveJsonEncoder.gen[CredentialSchema] given JsonDecoder[CredentialSchema] = DeriveJsonDecoder.gen[CredentialSchema] - def resolveJWTSchema(uri: URI, uriResolver: UriResolver): IO[CredentialSchemaParsingError, Json] = { + def resolveJWTSchema( + uri: URI, + uriResolver: UriResolver + ): IO[CredentialSchemaParsingError | SchemaDereferencingError, Json] = { for { - content <- uriResolver.resolve(uri.toString).orDieAsUnmanagedFailure + content <- uriResolver + .resolve(uri.toString) + .mapError(SchemaDereferencingError(_)) json <- ZIO .fromEither(content.fromJson[Json]) .mapError(error => CredentialSchemaParsingError(error)) @@ -132,7 +137,7 @@ object CredentialSchema { def validSchemaValidator( schemaId: String, uriResolver: UriResolver - ): IO[InvalidURI | CredentialSchemaParsingError, JsonSchemaValidator] = { + ): IO[InvalidURI | CredentialSchemaParsingError | SchemaDereferencingError, JsonSchemaValidator] = { for { uri <- ZIO.attempt(new URI(schemaId)).mapError(_ => InvalidURI(schemaId)) json <- resolveJWTSchema(uri, uriResolver) @@ -153,7 +158,10 @@ object CredentialSchema { schemaId: String, credentialSubject: String, uriResolver: UriResolver - ): IO[InvalidURI | CredentialSchemaParsingError | CredentialSchemaValidationError, Unit] = { + ): IO[ + InvalidURI | CredentialSchemaParsingError | CredentialSchemaValidationError | SchemaDereferencingError, + Unit + ] = { for { schemaValidator <- validSchemaValidator(schemaId, uriResolver) _ <- schemaValidator.validate(credentialSubject).mapError(CredentialSchemaValidationError.apply) 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 a95ead74b0..4e059f8e05 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 @@ -4,7 +4,6 @@ import cats.implicits.* import io.circe.* import io.circe.parser.* import io.circe.syntax.* -import io.circe.Json 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 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 index 74d6fa0131..4a0113d0ad 100644 --- 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 @@ -2,8 +2,7 @@ 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 org.hyperledger.identus.shared.http.DataUrlResolver +import org.hyperledger.identus.shared.http.{DataUrlResolver, GenericUriResolver, GenericUriResolverError, UriResolver} import zio.* import zio.http.* 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 532000c850..e9be6a2cb5 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 @@ -1,12 +1,17 @@ package org.hyperledger.identus.pollux.core.service -import org.hyperledger.identus.pollux.core.model.error.CredentialSchemaError.{CredentialSchemaParsingError, InvalidURI} +import org.hyperledger.identus.pollux.core.model.error.CredentialSchemaError.{ + CredentialSchemaParsingError, + InvalidURI, + SchemaDereferencingError +} import org.hyperledger.identus.pollux.core.model.oid4vci.{CredentialConfiguration, CredentialIssuer} import org.hyperledger.identus.pollux.core.model.schema.CredentialSchema import org.hyperledger.identus.pollux.core.model.CredentialFormat import org.hyperledger.identus.pollux.core.repository.OID4VCIIssuerMetadataRepository import org.hyperledger.identus.pollux.core.service.OID4VCIIssuerMetadataServiceError.{ CredentialConfigurationNotFound, + DuplicateCredentialConfigId, InvalidSchemaId, IssuerIdNotFound, UnsupportedCredentialFormat @@ -45,6 +50,12 @@ object OID4VCIIssuerMetadataServiceError { s"Invalid schemaId $schemaId. $msg" ) + final case class DuplicateCredentialConfigId(id: String) + extends OID4VCIIssuerMetadataServiceError( + StatusCode.Conflict, + s"Duplicated credential configuration id: $id" + ) + final case class UnsupportedCredentialFormat(format: CredentialFormat) extends OID4VCIIssuerMetadataServiceError( StatusCode.BadRequest, @@ -68,7 +79,11 @@ trait OID4VCIIssuerMetadataService { format: CredentialFormat, configurationId: String, schemaId: String - ): ZIO[WalletAccessContext, InvalidSchemaId | UnsupportedCredentialFormat | IssuerIdNotFound, CredentialConfiguration] + ): ZIO[ + WalletAccessContext, + InvalidSchemaId | UnsupportedCredentialFormat | IssuerIdNotFound | DuplicateCredentialConfigId, + CredentialConfiguration + ] def getCredentialConfigurations( issuerId: UUID ): IO[IssuerIdNotFound, Seq[CredentialConfiguration]] @@ -130,11 +145,13 @@ class OID4VCIIssuerMetadataServiceImpl(repository: OID4VCIIssuerMetadataReposito schemaId: String ): ZIO[ WalletAccessContext, - InvalidSchemaId | UnsupportedCredentialFormat | IssuerIdNotFound, + InvalidSchemaId | UnsupportedCredentialFormat | IssuerIdNotFound | DuplicateCredentialConfigId, CredentialConfiguration ] = { for { _ <- getCredentialIssuer(issuerId) + _ <- getCredentialConfigurationById(issuerId, configurationId).flip + .mapError(_ => DuplicateCredentialConfigId(configurationId)) _ <- format match { case CredentialFormat.JWT => ZIO.unit case f => ZIO.fail(UnsupportedCredentialFormat(f)) @@ -144,6 +161,7 @@ class OID4VCIIssuerMetadataServiceImpl(repository: OID4VCIIssuerMetadataReposito .validSchemaValidator(schemaUri.toString(), uriResolver) .catchAll { case e: InvalidURI => ZIO.fail(InvalidSchemaId(schemaId, e.userFacingMessage)) + case e: SchemaDereferencingError => ZIO.fail(InvalidSchemaId(schemaId, e.userFacingMessage)) case e: CredentialSchemaParsingError => ZIO.fail(InvalidSchemaId(schemaId, e.cause)) } now <- ZIO.clockWith(_.instant) 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 index b252d35b1a..8e3f779ce5 100644 --- 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 @@ -5,8 +5,7 @@ import org.hyperledger.identus.pollux.vc.jwt import org.hyperledger.identus.pollux.vc.jwt.* import org.hyperledger.identus.shared.crypto.Sha256Hash import org.hyperledger.identus.shared.http.{GenericUriResolverError, UriResolver} -import org.hyperledger.identus.shared.models.PrismEnvelopeData -import org.hyperledger.identus.shared.models.StatusCode +import org.hyperledger.identus.shared.models.{PrismEnvelopeData, StatusCode} import zio.* import zio.json.* diff --git a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataServiceSpecSuite.scala b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataServiceSpecSuite.scala index 8d2d3614bb..082d107fd4 100644 --- a/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataServiceSpecSuite.scala +++ b/pollux/core/src/test/scala/org/hyperledger/identus/pollux/core/service/OID4VCIIssuerMetadataServiceSpecSuite.scala @@ -96,7 +96,7 @@ object OID4VCIIssuerMetadataServiceSpecSuite { exit1 <- createCredConfig("not a uri").exit exit2 <- createCredConfig("http://localhost/schema").exit } yield assert(exit1)(failsWithA[InvalidSchemaId]) && - assert(exit2)(dies(anything)) + assert(exit2)(failsWithA[InvalidSchemaId]) }, test("list credential configurations for non-existing issuer should fail") { for { diff --git a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/model/db/package.scala b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/model/db/package.scala index fd9392204d..4e4c2bf491 100644 --- a/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/model/db/package.scala +++ b/pollux/sql-doobie/src/main/scala/org/hyperledger/identus/pollux/sql/model/db/package.scala @@ -1,8 +1,8 @@ package org.hyperledger.identus.pollux.sql.model -import doobie._ -import doobie.postgres._ -import doobie.postgres.implicits._ +import doobie.* +import doobie.postgres.* +import doobie.postgres.implicits.* import io.getquill.doobie.DoobieContext import io.getquill.MappedEncoding import org.hyperledger.identus.pollux.core.model.ResourceResolutionMethod 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 25be250c52..7b628d66f8 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,7 @@ package org.hyperledger.identus.shared.http import io.lemonlabs.uri.{Uri, Url, Urn} -import org.hyperledger.identus.shared.models.{Failure, StatusCode} -import org.hyperledger.identus.shared.models.PrismEnvelopeData +import org.hyperledger.identus.shared.models.{Failure, PrismEnvelopeData, StatusCode} import org.hyperledger.identus.shared.utils.Base64Utils import zio.* import zio.json.* diff --git a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala index c2ea5dc53f..b41611d07e 100644 --- a/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala +++ b/shared/core/src/main/scala/org/hyperledger/identus/shared/models/Failure.scala @@ -38,6 +38,7 @@ object StatusCode { val Unauthorized: StatusCode = StatusCode(401) val Forbidden: StatusCode = StatusCode(403) val NotFound: StatusCode = StatusCode(404) + val Conflict: StatusCode = StatusCode(409) val UnprocessableContent: StatusCode = StatusCode(422) val InternalServerError: StatusCode = StatusCode(500)