Skip to content

Commit

Permalink
fix: oid4vci endpoints error statuses and negative input validation (#…
Browse files Browse the repository at this point in the history
…1384)

Signed-off-by: Pat Losoponkul <[email protected]>
  • Loading branch information
patlo-iog authored Sep 30, 2024
1 parent 658e094 commit 65cc9a7
Show file tree
Hide file tree
Showing 14 changed files with 69 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
@@ -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}

Expand Down Expand Up @@ -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}]"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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]]
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 65cc9a7

Please sign in to comment.