Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: oid4vci endpoints error statuses and negative input validation #1384

Merged
merged 5 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Loading