From 9771f9f74c240a9e2a401d24ce1d5a9f56a71068 Mon Sep 17 00:00:00 2001 From: Babis Routis <127745316+babisRoutis@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:50:14 +0200 Subject: [PATCH] Removed deprecated context receivers (#242) --- build.gradle.kts | 2 - .../pidissuer/adapter/input/web/IssuerApi.kt | 24 +- .../pidissuer/adapter/input/web/IssuerUi.kt | 4 +- .../pidissuer/adapter/input/web/WalletApi.kt | 38 +- .../adapter/out/jose/JWKExtensions.kt | 22 +- .../adapter/out/jose/ValidateJwtProof.kt | 20 +- .../adapter/out/jose/ValidateProofs.kt | 45 +- ...DefaultEncodeMobileDrivingLicenceInCbor.kt | 11 +- .../mdl/EncodeMobileDrivingLicenceInCbor.kt | 5 +- .../out/mdl/GetMobileDrivingLicenceData.kt | 6 +- .../mdl/GetMobileDrivingLicenceDataMock.kt | 10 +- .../out/mdl/IssueMobileDrivingLicence.kt | 20 +- .../adapter/out/pid/EncodePidInSdJwtVc.kt | 8 +- .../pidissuer/adapter/out/pid/GetPidData.kt | 9 +- .../adapter/out/pid/IssueMsoMdocPid.kt | 79 ++-- .../adapter/out/pid/IssueSdJwtVcPid.kt | 82 ++-- .../pidissuer/domain/CredentialRequest.kt | 10 +- .../eudi/pidissuer/domain/MsoMdocProfile.kt | 11 +- .../eudi/pidissuer/domain/SdJwtVcProfile.kt | 9 +- .../port/input/CreateCredentialsOffer.kt | 29 +- .../port/input/GetDeferredCredential.kt | 54 ++- .../pidissuer/port/input/IssueCredential.kt | 445 ++++++++++-------- .../port/out/IssueSpecificCredential.kt | 20 +- .../adapter/out/jose/ValidateJwtProofTest.kt | 55 +-- .../adapter/out/jose/ValidateProofTest.kt | 5 +- .../GetMobileDrivingLicenceDataMockTest.kt | 14 +- 26 files changed, 548 insertions(+), 489 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 58b7f653..9eaf7cad 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -103,8 +103,6 @@ kotlin { compilerOptions { apiVersion = KotlinVersion.KOTLIN_2_0 - - freeCompilerArgs.add("-Xcontext-receivers") freeCompilerArgs.add("-Xjsr305=strict") } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/IssuerApi.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/IssuerApi.kt index 7c790163..9c8e57c5 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/IssuerApi.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/IssuerApi.kt @@ -15,8 +15,6 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.input.web -import arrow.core.getOrElse -import arrow.core.raise.either import eu.europa.ec.eudi.pidissuer.domain.CredentialConfigurationId import eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOffer import eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOfferError @@ -46,18 +44,16 @@ class IssuerApi( .map(::CredentialConfigurationId) .toSet() - return either { - val credentialsOffer = createCredentialsOffer(credentialIds) - log.info("Successfully generated Credentials Offer. URI: '{}'", credentialsOffer) - - ServerResponse.ok() - .json() - .bodyValueAndAwait(CreateCredentialsOfferResponseTO.success(credentialsOffer)) - }.getOrElse { error -> - ServerResponse.badRequest() - .json() - .bodyValueAndAwait(CreateCredentialsOfferResponseTO.error(error)) - } + return createCredentialsOffer(credentialIds).fold( + ifRight = { credentialsOffer -> + ServerResponse.ok().json() + .bodyValueAndAwait(CreateCredentialsOfferResponseTO.success(credentialsOffer)) + .also { log.info("Successfully generated Credentials Offer. URI: '{}'", credentialsOffer) } + }, + ifLeft = { error -> + ServerResponse.badRequest().json().bodyValueAndAwait(CreateCredentialsOfferResponseTO.error(error)) + }, + ) } companion object { diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/IssuerUi.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/IssuerUi.kt index f33c770d..af4c55d0 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/IssuerUi.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/IssuerUi.kt @@ -16,7 +16,6 @@ package eu.europa.ec.eudi.pidissuer.adapter.input.web import arrow.core.getOrElse -import arrow.core.raise.either import eu.europa.ec.eudi.pidissuer.domain.CredentialConfigurationId import eu.europa.ec.eudi.pidissuer.domain.CredentialIssuerMetaData import eu.europa.ec.eudi.pidissuer.port.input.CreateCredentialsOffer @@ -83,8 +82,7 @@ class IssuerUi( .toSet() val credentialsOfferUri = formData["credentialsOfferUri"]?.firstOrNull { it.isNotBlank() } - return either { - val credentialsOffer = createCredentialsOffer(credentialIds, credentialsOfferUri) + return createCredentialsOffer(credentialIds, credentialsOfferUri).map { credentialsOffer -> log.info("Successfully generated Credentials Offer. URI: '{}'", credentialsOffer) val qrCode = diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/WalletApi.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/WalletApi.kt index adfe148b..900c7579 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/WalletApi.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/WalletApi.kt @@ -16,8 +16,6 @@ package eu.europa.ec.eudi.pidissuer.adapter.input.web import arrow.core.NonEmptySet -import arrow.core.getOrElse -import arrow.core.raise.either import arrow.core.raise.ensure import arrow.core.raise.ensureNotNull import arrow.core.raise.result @@ -87,23 +85,25 @@ class WalletApi( private suspend fun handleGetDeferredCredential(req: ServerRequest): ServerResponse = coroutineScope { val requestTO = req.awaitBody() - either { - when (val deferredResponse = getDeferredCredential(requestTO)) { - is DeferredCredentialSuccessResponse.EncryptedJwtIssued -> - ServerResponse - .ok() - .contentType(APPLICATION_JWT) - .bodyValueAndAwait(deferredResponse.jwt) - - is DeferredCredentialSuccessResponse.PlainTO -> - ServerResponse - .ok() - .json() - .bodyValueAndAwait(deferredResponse) - } - }.getOrElse { error -> - ServerResponse.badRequest().json().bodyValueAndAwait(error) - } + getDeferredCredential(requestTO) + .fold( + ifRight = { deferredResponse -> + when (deferredResponse) { + is DeferredCredentialSuccessResponse.EncryptedJwtIssued -> + ServerResponse + .ok() + .contentType(APPLICATION_JWT) + .bodyValueAndAwait(deferredResponse.jwt) + + is DeferredCredentialSuccessResponse.PlainTO -> + ServerResponse + .ok() + .json() + .bodyValueAndAwait(deferredResponse) + } + }, + ifLeft = { error -> ServerResponse.badRequest().json().bodyValueAndAwait(error) }, + ) } private suspend fun handleHelloHolder(req: ServerRequest): ServerResponse = coroutineScope { diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/JWKExtensions.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/JWKExtensions.kt index c0daedea..87a0f397 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/JWKExtensions.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/JWKExtensions.kt @@ -19,12 +19,16 @@ import arrow.core.raise.Raise import com.nimbusds.jose.jwk.ECKey import com.nimbusds.jose.jwk.JWK -/** - * Converts this [JWK] to an [ECKey] or raises [onFailure]. - */ -context(Raise) -internal fun JWK.toECKeyOrFail(onFailure: () -> E): ECKey = - when (this) { - is ECKey -> this - else -> raise(onFailure()) - } +internal interface JWKExtensions : Raise { + /** + * Converts this [JWK] to an [ECKey] or raises [onFailure]. + */ + fun JWK.toECKeyOrFail(onFailure: () -> Error): ECKey = + when (this) { + is ECKey -> this + else -> raise(onFailure()) + } +} + +internal fun Raise.jwkExtensions(): JWKExtensions = + object : JWKExtensions, Raise by this {} diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProof.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProof.kt index 277024c9..78d6c951 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProof.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProof.kt @@ -15,10 +15,10 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.jose +import arrow.core.Either import arrow.core.NonEmptySet -import arrow.core.raise.Raise +import arrow.core.raise.either import arrow.core.raise.ensureNotNull -import arrow.core.raise.result import com.nimbusds.jose.JOSEObjectType import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.JWSHeader @@ -49,33 +49,33 @@ import kotlin.time.DurationUnit internal class ValidateJwtProof( private val credentialIssuerId: CredentialIssuerId, ) { - context(Raise) operator fun invoke( unvalidatedProof: UnvalidatedProof.Jwt, credentialConfiguration: CredentialConfiguration, - ): Pair { + ): Either> = either { val proofType = credentialConfiguration.proofTypesSupported[ProofTypeEnum.JWT] ensureNotNull(proofType) { IssueCredentialError.InvalidProof("credential configuration '${credentialConfiguration.id.value}' doesn't support 'jwt' proofs") } check(proofType is ProofType.Jwt) - - return credentialKeyAndNonce(unvalidatedProof, proofType) + credentialKeyAndNonce(unvalidatedProof, proofType).bind() } - context(Raise) private fun credentialKeyAndNonce( unvalidatedProof: UnvalidatedProof.Jwt, proofType: ProofType.Jwt, - ): Pair = result { + ): Either> = Either.catch { val signedJwt = SignedJWT.parse(unvalidatedProof.jwt) - val (algorithm, credentialKey) = algorithmAndCredentialKey(signedJwt.header, proofType.signingAlgorithmsSupported) + val (algorithm, credentialKey) = algorithmAndCredentialKey( + signedJwt.header, + proofType.signingAlgorithmsSupported, + ) val keySelector = keySelector(credentialKey, algorithm) val processor = processor(credentialIssuerId, keySelector) val claimSet = processor.process(signedJwt, null) credentialKey to claimSet.getStringClaim("nonce") - }.getOrElse { raise(IssueCredentialError.InvalidProof("Invalid proof JWT", it)) } + }.mapLeft { IssueCredentialError.InvalidProof("Invalid proof JWT", it) } } private fun algorithmAndCredentialKey( diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateProofs.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateProofs.kt index 8a887167..bc2cd496 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateProofs.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateProofs.kt @@ -15,8 +15,9 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.jose +import arrow.core.Either import arrow.core.NonEmptyList -import arrow.core.raise.Raise +import arrow.core.raise.either import arrow.core.raise.ensure import arrow.core.toNonEmptyListOrNull import com.nimbusds.jose.jwk.JWK @@ -24,7 +25,7 @@ import eu.europa.ec.eudi.pidissuer.domain.CredentialConfiguration import eu.europa.ec.eudi.pidissuer.domain.UnvalidatedProof import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError.InvalidProof import eu.europa.ec.eudi.pidissuer.port.out.credential.VerifyCNonce -import kotlinx.coroutines.* +import kotlinx.coroutines.coroutineScope import java.time.Instant /** @@ -36,34 +37,32 @@ internal class ValidateProofs( private val extractJwkFromCredentialKey: ExtractJwkFromCredentialKey, ) { - context(Raise) suspend operator fun invoke( unvalidatedProofs: NonEmptyList, credentialConfiguration: CredentialConfiguration, at: Instant, - ): NonEmptyList = coroutineScope { - val credentialKeysAndCNonces = unvalidatedProofs.map { - when (it) { - is UnvalidatedProof.Jwt -> async { - withContext(Dispatchers.Default) { - validateJwtProof(it, credentialConfiguration) - } + ): Either> = coroutineScope { + either { + val credentialKeysAndCNonces = unvalidatedProofs.map { + when (it) { + is UnvalidatedProof.Jwt -> + validateJwtProof(it, credentialConfiguration).bind() + is UnvalidatedProof.LdpVp -> raise(InvalidProof("Supporting only JWT proof")) } - is UnvalidatedProof.LdpVp -> raise(InvalidProof("Supporting only JWT proof")) } - }.awaitAll() - val cnonces = credentialKeysAndCNonces.map { it.second }.toNonEmptyListOrNull() - checkNotNull(cnonces) - ensure(verifyCNonce(cnonces, at)) { - InvalidProof("CNonce is not valid") - } - - val jwks = credentialKeysAndCNonces.map { - extractJwkFromCredentialKey(it.first).getOrElse { error -> - raise(InvalidProof("Unable to extract JWK from CredentialKey", error)) + val cnonces = credentialKeysAndCNonces.map { it.second }.toNonEmptyListOrNull() + checkNotNull(cnonces) + ensure(verifyCNonce(cnonces, at)) { + InvalidProof("CNonce is not valid") } - }.toNonEmptyListOrNull() - checkNotNull(jwks) + + val jwks = credentialKeysAndCNonces.map { + extractJwkFromCredentialKey(it.first).getOrElse { error -> + raise(InvalidProof("Unable to extract JWK from CredentialKey", error)) + } + }.toNonEmptyListOrNull() + checkNotNull(jwks) + } } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/DefaultEncodeMobileDrivingLicenceInCbor.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/DefaultEncodeMobileDrivingLicenceInCbor.kt index ed50a657..7b275bdf 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/DefaultEncodeMobileDrivingLicenceInCbor.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/DefaultEncodeMobileDrivingLicenceInCbor.kt @@ -15,7 +15,7 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.mdl -import arrow.core.raise.Raise +import arrow.core.Either import com.nimbusds.jose.jwk.ECKey import eu.europa.ec.eudi.pidissuer.adapter.out.IssuerSigningKey import eu.europa.ec.eudi.pidissuer.adapter.out.mdl.DrivingPrivilege.Restriction.GenericRestriction @@ -45,13 +45,8 @@ class DefaultEncodeMobileDrivingLicenceInCbor( addItemsToSign(licence) } - context(Raise) - override suspend fun invoke(licence: MobileDrivingLicence, holderKey: ECKey): String = - try { - signer.sign(licence, holderKey) - } catch (t: Throwable) { - raise(Unexpected("Failed to encode mDL", t)) - } + override suspend fun invoke(licence: MobileDrivingLicence, holderKey: ECKey): Either = + Either.catch { signer.sign(licence, holderKey) }.mapLeft { Unexpected("Failed to encode mDL", it) } } private fun MDocBuilder.addItemsToSign(licence: MobileDrivingLicence) { diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/EncodeMobileDrivingLicenceInCbor.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/EncodeMobileDrivingLicenceInCbor.kt index 843f2b71..1115d120 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/EncodeMobileDrivingLicenceInCbor.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/EncodeMobileDrivingLicenceInCbor.kt @@ -15,7 +15,7 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.mdl -import arrow.core.raise.Raise +import arrow.core.Either import com.nimbusds.jose.jwk.ECKey import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError @@ -24,6 +24,5 @@ import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError */ fun interface EncodeMobileDrivingLicenceInCbor { - context(Raise) - suspend operator fun invoke(licence: MobileDrivingLicence, holderKey: ECKey): String + suspend operator fun invoke(licence: MobileDrivingLicence, holderKey: ECKey): Either } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceData.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceData.kt index 9b9c4e79..0a5e22b7 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceData.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceData.kt @@ -15,7 +15,7 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.mdl -import arrow.core.raise.Raise +import arrow.core.Either import eu.europa.ec.eudi.pidissuer.port.input.AuthorizationContext import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError @@ -28,6 +28,6 @@ fun interface GetMobileDrivingLicenceData { * Gets the data of the Mobile Driving Licence of an authorized user. In case the authorized user * has no Driving Licence, null is returned. */ - context(Raise) - suspend operator fun invoke(context: AuthorizationContext): MobileDrivingLicence? + + suspend operator fun invoke(context: AuthorizationContext): Either } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceDataMock.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceDataMock.kt index 033d96bf..d06cc7be 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceDataMock.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceDataMock.kt @@ -15,9 +15,11 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.mdl +import arrow.core.Either import arrow.core.nonEmptySetOf import arrow.core.raise.Raise import arrow.core.raise.catch +import arrow.core.raise.either import arrow.core.raise.ensureNotNull import eu.europa.ec.eudi.pidissuer.adapter.out.mdl.DrivingPrivilege.Restriction.GenericRestriction import eu.europa.ec.eudi.pidissuer.adapter.out.mdl.DrivingPrivilege.Restriction.ParameterizedRestriction @@ -34,8 +36,7 @@ import java.time.Month */ class GetMobileDrivingLicenceDataMock : GetMobileDrivingLicenceData { - context(Raise) - override suspend fun invoke(context: AuthorizationContext): MobileDrivingLicence { + override suspend fun invoke(context: AuthorizationContext): Either = either { log.info("Getting mock data for Mobile Driving Licence") val driver = Driver( @@ -80,7 +81,7 @@ class GetMobileDrivingLicenceDataMock : GetMobileDrivingLicenceData { ), ) - return MobileDrivingLicence( + MobileDrivingLicence( driver, IssueAndExpiry(LocalDate.of(2000, Month.JANUARY, 1), LocalDate.of(2040, Month.DECEMBER, 31)), issuer, @@ -94,8 +95,7 @@ class GetMobileDrivingLicenceDataMock : GetMobileDrivingLicenceData { private val log = LoggerFactory.getLogger(GetMobileDrivingLicenceDataMock::class.java) - context(Raise) - private suspend fun loadResource(path: String): ByteArray = + private suspend fun Raise.loadResource(path: String): ByteArray = withContext(Dispatchers.IO) { val portrait = ensureNotNull(Companion::class.java.getResourceAsStream(path)) { diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/IssueMobileDrivingLicence.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/IssueMobileDrivingLicence.kt index 2e818446..b28bb489 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/IssueMobileDrivingLicence.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/IssueMobileDrivingLicence.kt @@ -15,14 +15,15 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.mdl +import arrow.core.Either import arrow.core.nonEmptySetOf -import arrow.core.raise.Raise +import arrow.core.raise.either import arrow.core.raise.ensureNotNull import arrow.core.toNonEmptyListOrNull import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.jwk.JWK import eu.europa.ec.eudi.pidissuer.adapter.out.jose.ValidateProofs -import eu.europa.ec.eudi.pidissuer.adapter.out.jose.toECKeyOrFail +import eu.europa.ec.eudi.pidissuer.adapter.out.jose.jwkExtensions import eu.europa.ec.eudi.pidissuer.domain.* import eu.europa.ec.eudi.pidissuer.port.input.AuthorizationContext import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError @@ -30,7 +31,6 @@ import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError.InvalidProof import eu.europa.ec.eudi.pidissuer.port.out.IssueSpecificCredential import eu.europa.ec.eudi.pidissuer.port.out.persistence.GenerateNotificationId import eu.europa.ec.eudi.pidissuer.port.out.persistence.StoreIssuedCredentials -import kotlinx.coroutines.* import kotlinx.serialization.json.JsonPrimitive import org.slf4j.LoggerFactory import java.time.Clock @@ -310,16 +310,18 @@ internal class IssueMobileDrivingLicence( override val publicKey: JWK? get() = null - context(Raise) override suspend fun invoke( authorizationContext: AuthorizationContext, request: CredentialRequest, credentialIdentifier: CredentialIdentifier?, - ): CredentialResponse = coroutineScope { + ): Either = either { log.info("Issuing mDL") - val holderKeys = validateProofs(request.unvalidatedProofs, supportedCredential, clock.instant()) - .map { it.toECKeyOrFail { InvalidProof("Only EC Key is supported") } } - val licence = ensureNotNull(getMobileDrivingLicenceData(authorizationContext)) { + val holderKeys = with(jwkExtensions()) { + validateProofs(request.unvalidatedProofs, supportedCredential, clock.instant()).bind() + .map { jwk -> jwk.toECKeyOrFail { InvalidProof("Only EC Key is supported") } } + } + val licence = getMobileDrivingLicenceData(authorizationContext).bind() + ensureNotNull(licence) { IssueCredentialError.Unexpected("Unable to fetch mDL data") } @@ -328,7 +330,7 @@ internal class IssueMobileDrivingLicence( else null val issuedCredentials = holderKeys.map { holderKey -> - val cbor = encodeMobileDrivingLicenceInCbor(licence, holderKey) + val cbor = encodeMobileDrivingLicenceInCbor(licence, holderKey).bind() cbor to holderKey.toPublicJWK() }.toNonEmptyListOrNull() ensureNotNull(issuedCredentials) { diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt index 8565ffbc..34059d80 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt @@ -15,7 +15,8 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.pid -import arrow.core.raise.Raise +import arrow.core.Either +import arrow.core.raise.either import com.nimbusds.jose.JOSEObjectType import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.crypto.ECDSASigner @@ -87,12 +88,11 @@ class EncodePidInSdJwtVc( } } - context(Raise) fun invoke( pid: Pid, pidMetaData: PidMetaData, holderKey: JWK, - ): String { + ): Either = either { val at = clock.instant().atZone(clock.zone) val sdJwtSpec = selectivelyDisclosed( pid = pid, @@ -111,7 +111,7 @@ class EncodePidInSdJwtVc( log.info(with(Printer) { issuedSdJwt.prettyPrint() }) } - return issuedSdJwt.serialize() + issuedSdJwt.serialize() } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidData.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidData.kt index b01ea767..7b0f0aeb 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidData.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/GetPidData.kt @@ -15,7 +15,8 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.pid -import arrow.core.raise.Raise +import arrow.core.Either +import arrow.core.raise.either import arrow.core.raise.ensureNotNull import eu.europa.ec.eudi.pidissuer.port.input.AuthorizationContext import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError @@ -24,9 +25,9 @@ import eu.europa.ec.eudi.pidissuer.port.input.Username fun interface GetPidData { suspend operator fun invoke(username: Username): Pair? - context (Raise) - suspend operator fun invoke(authorizationContext: AuthorizationContext): Pair { + suspend operator fun invoke(authorizationContext: AuthorizationContext): + Either> = either { val data = invoke(authorizationContext.username) - return ensureNotNull(data) { IssueCredentialError.Unexpected("Cannot obtain data") } + ensureNotNull(data) { IssueCredentialError.Unexpected("Cannot obtain data") } } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt index cace42c8..7be92304 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt @@ -15,14 +15,15 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.pid +import arrow.core.Either import arrow.core.nonEmptySetOf -import arrow.core.raise.Raise +import arrow.core.raise.either import arrow.core.raise.ensureNotNull import arrow.core.toNonEmptyListOrNull import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.jwk.JWK import eu.europa.ec.eudi.pidissuer.adapter.out.jose.ValidateProofs -import eu.europa.ec.eudi.pidissuer.adapter.out.jose.toECKeyOrFail +import eu.europa.ec.eudi.pidissuer.adapter.out.jose.jwkExtensions import eu.europa.ec.eudi.pidissuer.domain.* import eu.europa.ec.eudi.pidissuer.port.input.AuthorizationContext import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError @@ -273,49 +274,53 @@ internal class IssueMsoMdocPid( get() = PidMsoMdocV1 override val publicKey: JWK? = null - context(Raise) override suspend fun invoke( authorizationContext: AuthorizationContext, request: CredentialRequest, credentialIdentifier: CredentialIdentifier?, - ): CredentialResponse = coroutineScope { - log.info("Handling issuance request ...") - val holderPubKeys = validateProofs(request.unvalidatedProofs, supportedCredential, clock.instant()) - .map { it.toECKeyOrFail { InvalidProof("Only EC Key is supported") } } + ): Either = coroutineScope { + either { + log.info("Handling issuance request ...") + val holderPubKeys = with(jwkExtensions()) { + validateProofs(request.unvalidatedProofs, supportedCredential, clock.instant()) + .bind() + .map { jwk -> jwk.toECKeyOrFail { InvalidProof("Only EC Key is supported") } } + } - val pidData = async { getPidData(authorizationContext) } - val notificationId = - if (notificationsEnabled) generateNotificationId() - else null + val pidData = async { getPidData(authorizationContext) } + val notificationId = + if (notificationsEnabled) generateNotificationId() + else null - val (pid, pidMetaData) = pidData.await() - val issuedCredentials = holderPubKeys.map { holderKey -> - val cbor = encodePidInCbor(pid, pidMetaData, holderKey).also { - log.info("Issued $it") + val (pid, pidMetaData) = pidData.await().bind() + val issuedCredentials = holderPubKeys.map { holderKey -> + val cbor = encodePidInCbor(pid, pidMetaData, holderKey).also { + log.info("Issued $it") + } + cbor to holderKey.toPublicJWK() + }.toNonEmptyListOrNull() + ensureNotNull(issuedCredentials) { + IssueCredentialError.Unexpected("Unable to issue PID") } - cbor to holderKey.toPublicJWK() - }.toNonEmptyListOrNull() - ensureNotNull(issuedCredentials) { - IssueCredentialError.Unexpected("Unable to issue PID") - } - storeIssuedCredentials( - IssuedCredentials( - format = MSO_MDOC_FORMAT, - type = supportedCredential.docType, - holder = with(pid) { - "${familyName.value} ${givenName.value}" - }, - holderPublicKeys = issuedCredentials.map { it.second }, - issuedAt = clock.instant(), - notificationId = notificationId, - ), - ) + storeIssuedCredentials( + IssuedCredentials( + format = MSO_MDOC_FORMAT, + type = supportedCredential.docType, + holder = with(pid) { + "${familyName.value} ${givenName.value}" + }, + holderPublicKeys = issuedCredentials.map { it.second }, + issuedAt = clock.instant(), + notificationId = notificationId, + ), + ) - CredentialResponse.Issued(issuedCredentials.map { JsonPrimitive(it.first) }, notificationId) - .also { - log.info("Successfully issued PIDs") - log.debug("Issued PIDs data {}", it) - } + CredentialResponse.Issued(issuedCredentials.map { JsonPrimitive(it.first) }, notificationId) + .also { + log.info("Successfully issued PIDs") + log.debug("Issued PIDs data {}", it) + } + } } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt index 6bc9ca9e..cbd01cad 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt @@ -15,8 +15,9 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.pid +import arrow.core.Either import arrow.core.nonEmptySetOf -import arrow.core.raise.Raise +import arrow.core.raise.either import arrow.core.raise.ensureNotNull import arrow.core.toNonEmptyListOrNull import com.nimbusds.jose.JWSAlgorithm @@ -96,7 +97,16 @@ fun pidSdJwtVcV1(signingAlgorithm: JWSAlgorithm): SdJwtVcCredentialConfiguration cryptographicBindingMethodsSupported = nonEmptySetOf(CryptographicBindingMethod.Jwk), credentialSigningAlgorithmsSupported = nonEmptySetOf(signingAlgorithm), scope = PidSdJwtVcScope, - proofTypesSupported = ProofTypesSupported(nonEmptySetOf(ProofType.Jwt(nonEmptySetOf(JWSAlgorithm.RS256, JWSAlgorithm.ES256)))), + proofTypesSupported = ProofTypesSupported( + nonEmptySetOf( + ProofType.Jwt( + nonEmptySetOf( + JWSAlgorithm.RS256, + JWSAlgorithm.ES256, + ), + ), + ), + ), ) typealias TimeDependant = (ZonedDateTime) -> F @@ -134,45 +144,45 @@ internal class IssueSdJwtVcPid( supportedCredential.type, ) - context(Raise) override suspend fun invoke( authorizationContext: AuthorizationContext, request: CredentialRequest, credentialIdentifier: CredentialIdentifier?, - ): CredentialResponse = coroutineScope { + ): Either = coroutineScope { log.info("Handling issuance request ...") - val holderPubKeys = validateProofs(request.unvalidatedProofs, supportedCredential, clock.instant()) - - val pidData = async { getPidData(authorizationContext) } - val (pid, pidMetaData) = pidData.await() - val notificationId = - if (notificationsEnabled) generateNotificationId() - else null - val issuedCredentials = holderPubKeys.map { holderPubKey -> - val sdJwt = encodePidInSdJwt.invoke(pid, pidMetaData, holderPubKey) - sdJwt to holderPubKey.toPublicJWK() - }.toNonEmptyListOrNull() - ensureNotNull(issuedCredentials) { - IssueCredentialError.Unexpected("Unable to issue PID") - } - - storeIssuedCredentials( - IssuedCredentials( - format = SD_JWT_VC_FORMAT, - type = supportedCredential.type.value, - holder = with(pid) { - "${familyName.value} ${givenName.value}" - }, - holderPublicKeys = issuedCredentials.map { it.second }, - issuedAt = clock.instant(), - notificationId = notificationId, - ), - ) - - CredentialResponse.Issued(issuedCredentials.map { JsonPrimitive(it.first) }, notificationId) - .also { - log.info("Successfully issued PIDs") - log.debug("Issued PIDs data {}", it) + either { + val holderPubKeys = validateProofs(request.unvalidatedProofs, supportedCredential, clock.instant()).bind() + val pidData = async { getPidData(authorizationContext) } + val (pid, pidMetaData) = pidData.await().bind() + val notificationId = + if (notificationsEnabled) generateNotificationId() + else null + val issuedCredentials = holderPubKeys.map { holderPubKey -> + val sdJwt = encodePidInSdJwt.invoke(pid, pidMetaData, holderPubKey).bind() + sdJwt to holderPubKey.toPublicJWK() + }.toNonEmptyListOrNull() + ensureNotNull(issuedCredentials) { + IssueCredentialError.Unexpected("Unable to issue PID") } + + storeIssuedCredentials( + IssuedCredentials( + format = SD_JWT_VC_FORMAT, + type = supportedCredential.type.value, + holder = with(pid) { + "${familyName.value} ${givenName.value}" + }, + holderPublicKeys = issuedCredentials.map { it.second }, + issuedAt = clock.instant(), + notificationId = notificationId, + ), + ) + + CredentialResponse.Issued(issuedCredentials.map { JsonPrimitive(it.first) }, notificationId) + .also { + log.info("Successfully issued PIDs") + log.debug("Issued PIDs data {}", it) + } + } } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/CredentialRequest.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/CredentialRequest.kt index dc9c38f1..52bb4d03 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/CredentialRequest.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/CredentialRequest.kt @@ -153,16 +153,16 @@ sealed interface CredentialRequest { val credentialResponseEncryption: RequestedResponseEncryption } -context(Raise) -fun CredentialRequest.assertIsSupported(meta: CredentialConfiguration) = - when (this) { +fun Raise.assertIsSupported(credentialRequest: CredentialRequest, meta: CredentialConfiguration) { + when (credentialRequest) { is MsoMdocCredentialRequest -> { ensure(meta is MsoMdocCredentialConfiguration) { "Was expecting a ${MSO_MDOC_FORMAT.value}" } - validate(meta) + validate(credentialRequest, meta) } is SdJwtVcCredentialRequest -> { ensure(meta is SdJwtVcCredentialConfiguration) { "Was expecting a ${SD_JWT_VC_FORMAT.value}" } - validate(meta) + validate(credentialRequest, meta) } } +} diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/MsoMdocProfile.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/MsoMdocProfile.kt index 65a7d307..adbc24a2 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/MsoMdocProfile.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/MsoMdocProfile.kt @@ -61,14 +61,15 @@ data class MsoMdocCredentialRequest( override val format: Format = MSO_MDOC_FORMAT } -context(Raise) -internal fun MsoMdocCredentialRequest.validate(meta: MsoMdocCredentialConfiguration) { - ensure(docType == meta.docType) { "doctype is $docType but was expecting ${meta.docType}" } +internal fun Raise.validate(msoMdocCredentialRequest: MsoMdocCredentialRequest, meta: MsoMdocCredentialConfiguration) { + ensure(msoMdocCredentialRequest.docType == meta.docType) { + "doctype is ${msoMdocCredentialRequest.docType} but was expecting ${meta.docType}" + } if (meta.msoClaims.isEmpty()) { - ensure(claims.isEmpty()) { "Requested claims should be empty. " } + ensure(msoMdocCredentialRequest.claims.isEmpty()) { "Requested claims should be empty. " } } else { val expectedAttributeNames = meta.msoClaims.mapValues { kv -> kv.value.map { it.name } } - claims.forEach { (namespace, attributes) -> + msoMdocCredentialRequest.claims.forEach { (namespace, attributes) -> val expectedAttributeNamesForNamespace = expectedAttributeNames[namespace] ensureNotNull(expectedAttributeNamesForNamespace) { "Unexpected namespace $namespace" } attributes.forEach { attr -> diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/SdJwtVcProfile.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/SdJwtVcProfile.kt index 767db1d0..5443cd38 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/SdJwtVcProfile.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/SdJwtVcProfile.kt @@ -53,14 +53,13 @@ data class SdJwtVcCredentialRequest( override val format: Format = SD_JWT_VC_FORMAT } -context(Raise) -internal fun SdJwtVcCredentialRequest.validate(meta: SdJwtVcCredentialConfiguration) { - ensure(type == meta.type) { "doctype is $type but was expecting ${meta.type}" } +internal fun Raise.validate(sdJwtVcCredentialRequest: SdJwtVcCredentialRequest, meta: SdJwtVcCredentialConfiguration) { + ensure(sdJwtVcCredentialRequest.type == meta.type) { "doctype is ${sdJwtVcCredentialRequest.type} but was expecting ${meta.type}" } if (meta.claims.isEmpty()) { - ensure(claims.isEmpty()) { "Requested claims should be empty. " } + ensure(sdJwtVcCredentialRequest.claims.isEmpty()) { "Requested claims should be empty. " } } else { val expectedAttributeNames = meta.claims.map { it.name } - claims.forEach { name -> + sdJwtVcCredentialRequest.claims.forEach { name -> ensure(name in expectedAttributeNames) { "Unexpected attribute $name" } } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/CreateCredentialsOffer.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/CreateCredentialsOffer.kt index 702b3b1e..96b2d723 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/CreateCredentialsOffer.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/CreateCredentialsOffer.kt @@ -15,8 +15,10 @@ */ package eu.europa.ec.eudi.pidissuer.port.input +import arrow.core.Either import arrow.core.NonEmptySet import arrow.core.raise.Raise +import arrow.core.raise.either import arrow.core.raise.ensure import arrow.core.raise.ensureNotNull import arrow.core.toNonEmptySetOrNull @@ -119,17 +121,17 @@ class CreateCredentialsOffer( private val credentialsOfferUri: String, ) { - context(Raise) operator fun invoke( unvalidatedCredentialConfigurationIds: Set, customCredentialsOfferUri: String? = null, - ): URI { - val offer = with(metadata) { - val credentialConfigurationIds = validate(unvalidatedCredentialConfigurationIds) - authorizationCodeGrantOffer(credentialConfigurationIds) + ): Either = either { + val offer = run { + val credentialConfigurationIds = + validate(metadata, unvalidatedCredentialConfigurationIds) + authorizationCodeGrantOffer(metadata, credentialConfigurationIds) } - return runCatching { + runCatching { Uri.parse(customCredentialsOfferUri ?: credentialsOfferUri) .buildUpon() .appendQueryParameter("credential_offer", Json.encodeToString(offer)) @@ -139,11 +141,13 @@ class CreateCredentialsOffer( } } -context(Raise, CredentialIssuerMetaData) -private fun validate(unvalidatedIds: Set): NonEmptySet { +private fun Raise.validate( + metadata: CredentialIssuerMetaData, + unvalidatedIds: Set, +): NonEmptySet { val nonEmptyIds = unvalidatedIds.toNonEmptySetOrNull() ensureNotNull(nonEmptyIds) { MissingCredentialConfigurationIds } - val supportedIds = credentialConfigurationsSupported.map(CredentialConfiguration::id) + val supportedIds = metadata.credentialConfigurationsSupported.map(CredentialConfiguration::id) nonEmptyIds.forEach { id -> ensure(id in supportedIds) { InvalidCredentialConfigurationId(id) } } @@ -160,16 +164,15 @@ private fun validate(unvalidatedIds: Set): NonEmptySe * @param credentialConfigurationIds the Ids of the Credentials to include in the generated request * @return the resulting TO */ -context(CredentialIssuerMetaData) private fun authorizationCodeGrantOffer( + metadata: CredentialIssuerMetaData, credentialConfigurationIds: NonEmptySet, ): CredentialsOfferTO { val authorizationCode = AuthorizationCodeTO( - authorizationServer = authorizationServers.firstOrNull()?.externalForm, + authorizationServer = metadata.authorizationServers.firstOrNull()?.externalForm, ) - return CredentialsOfferTO( - id.externalForm, + metadata.id.externalForm, credentialConfigurationIds.map(CredentialConfigurationId::value).toSet(), GrantsTO(authorizationCode), ) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/GetDeferredCredential.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/GetDeferredCredential.kt index 0e06ea44..6d9f8d14 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/GetDeferredCredential.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/GetDeferredCredential.kt @@ -15,13 +15,14 @@ */ package eu.europa.ec.eudi.pidissuer.port.input +import arrow.core.Either import arrow.core.raise.Raise +import arrow.core.raise.either import eu.europa.ec.eudi.pidissuer.domain.RequestedResponseEncryption import eu.europa.ec.eudi.pidissuer.domain.TransactionId import eu.europa.ec.eudi.pidissuer.port.out.jose.EncryptDeferredResponse import eu.europa.ec.eudi.pidissuer.port.out.persistence.LoadDeferredCredentialByTransactionId import eu.europa.ec.eudi.pidissuer.port.out.persistence.LoadDeferredCredentialResult -import kotlinx.coroutines.coroutineScope import kotlinx.serialization.Required import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -116,33 +117,40 @@ class GetDeferredCredential( private val log = LoggerFactory.getLogger(GetDeferredCredential::class.java) - context (Raise) - suspend operator fun invoke(requestTO: DeferredCredentialRequestTO): DeferredCredentialSuccessResponse = coroutineScope { + suspend operator fun invoke( + requestTO: DeferredCredentialRequestTO, + ): Either = either { val transactionId = TransactionId(requestTO.transactionId) log.info("GetDeferredCredential for $transactionId ...") - loadDeferredCredentialByTransactionId(transactionId).toTo() + toTo(loadDeferredCredentialByTransactionId(transactionId)) } - context (Raise) - private fun LoadDeferredCredentialResult.toTo(): DeferredCredentialSuccessResponse = when (this) { - is LoadDeferredCredentialResult.IssuancePending -> raise(GetDeferredCredentialErrorTO.IssuancePending) - is LoadDeferredCredentialResult.InvalidTransactionId -> raise(GetDeferredCredentialErrorTO.InvalidTransactionId) - is LoadDeferredCredentialResult.Found -> { - val plain = when (credential.credentials.size) { - 1 -> DeferredCredentialSuccessResponse.PlainTO.single( - credential.credentials.head, - credential.notificationId?.value, - ) - else -> DeferredCredentialSuccessResponse.PlainTO.multiple( - JsonArray(credential.credentials), - credential.notificationId?.value, - ) - } + private fun Raise.toTo( + loadDeferredCredentialResult: LoadDeferredCredentialResult, + ): DeferredCredentialSuccessResponse = + when (loadDeferredCredentialResult) { + is LoadDeferredCredentialResult.IssuancePending -> raise(GetDeferredCredentialErrorTO.IssuancePending) + is LoadDeferredCredentialResult.InvalidTransactionId -> raise(GetDeferredCredentialErrorTO.InvalidTransactionId) + is LoadDeferredCredentialResult.Found -> { + val plain = when (loadDeferredCredentialResult.credential.credentials.size) { + 1 -> DeferredCredentialSuccessResponse.PlainTO.single( + loadDeferredCredentialResult.credential.credentials.head, + loadDeferredCredentialResult.credential.notificationId?.value, + ) + + else -> DeferredCredentialSuccessResponse.PlainTO.multiple( + JsonArray(loadDeferredCredentialResult.credential.credentials), + loadDeferredCredentialResult.credential.notificationId?.value, + ) + } - when (responseEncryption) { - RequestedResponseEncryption.NotRequired -> plain - is RequestedResponseEncryption.Required -> encryptCredentialResponse(plain, responseEncryption).getOrThrow() + when (loadDeferredCredentialResult.responseEncryption) { + RequestedResponseEncryption.NotRequired -> plain + is RequestedResponseEncryption.Required -> encryptCredentialResponse( + plain, + loadDeferredCredentialResult.responseEncryption, + ).getOrThrow() + } } } - } } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/IssueCredential.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/IssueCredential.kt index 03696c7b..57b00250 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/IssueCredential.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/input/IssueCredential.kt @@ -274,7 +274,6 @@ sealed interface IssueCredentialResponse { @SerialName("error") @Required val type: CredentialErrorTypeTo, @SerialName("error_description") val errorDescription: String? = null, @SerialName("c_nonce") val nonce: String? = null, - ) : IssueCredentialResponse } @@ -292,73 +291,23 @@ class IssueCredential( private val encryptCredentialResponse: EncryptCredentialResponse, ) { + private fun Raise.services(): Services = + Services(this, credentialIssuerMetadata, resolveCredentialRequestByCredentialIdentifier) + suspend operator fun invoke( authorizationContext: AuthorizationContext, credentialRequestTO: CredentialRequestTO, ): IssueCredentialResponse = coroutineScope { either { log.info("Handling issuance request for ${credentialRequestTO.format}..") - val unresolvedRequest = credentialRequestTO.toDomain( - credentialIssuerMetadata.credentialResponseEncryption, - credentialIssuerMetadata.batchCredentialIssuance, - ) - val (request, credentialIdentifier) = - when (unresolvedRequest) { - is UnresolvedCredentialRequest.ByFormat -> - unresolvedRequest.credentialRequest to null - - is UnresolvedCredentialRequest.ByCredentialIdentifier -> - resolve(unresolvedRequest) to unresolvedRequest.credentialIdentifier - } - val issued = issue(authorizationContext, request, credentialIdentifier) + val(request, issued) = + services().issueCredential(authorizationContext, credentialRequestTO) successResponse(request, issued) }.getOrElse { error -> errorResponse(error) } } - context(Raise) - private suspend fun resolve( - unresolvedRequest: UnresolvedCredentialRequest.ByCredentialIdentifier, - ): CredentialRequest = - either { - val resolvedRequest = resolveCredentialRequestByCredentialIdentifier( - unresolvedRequest.credentialIdentifier, - unresolvedRequest.unvalidatedProofs, - unresolvedRequest.credentialResponseEncryption, - ) - ensureNotNull(resolvedRequest) { InvalidCredentialIdentifier(unresolvedRequest.credentialIdentifier) } - resolvedRequest - }.bind() - - context(Raise) - private suspend fun issue( - authorizationContext: AuthorizationContext, - credentialRequest: CredentialRequest, - credentialIdentifier: CredentialIdentifier?, - ): CredentialResponse { - val issueSpecificCredential = specificIssuerFor(credentialRequest) - val expectedScope = checkNotNull(issueSpecificCredential.supportedCredential.scope) - ensure(authorizationContext.scopes.contains(expectedScope)) { WrongScope(expectedScope) } - return issueSpecificCredential(authorizationContext, credentialRequest, credentialIdentifier) - } - - context(Raise) - private fun specificIssuerFor(credentialRequest: CredentialRequest): IssueSpecificCredential { - val specificIssuer = credentialIssuerMetadata.specificCredentialIssuers - .find { issuer -> - either { credentialRequest.assertIsSupported(issuer.supportedCredential) }.isRight() - } - if (specificIssuer == null) { - val types = when (credentialRequest) { - is MsoMdocCredentialRequest -> listOf(credentialRequest.docType) - is SdJwtVcCredentialRequest -> listOf(credentialRequest.type).map { it.value } - } - raise(UnsupportedCredentialType(credentialRequest.format, types)) - } - return specificIssuer - } - private suspend fun successResponse( request: CredentialRequest, credential: CredentialResponse, @@ -379,6 +328,75 @@ class IssueCredential( return error.toTO(newCNonce) } } + +private class Services( + raise: Raise, + private val credentialIssuerMetadata: CredentialIssuerMetaData, + private val resolveCredentialRequestByCredentialIdentifier: ResolveCredentialRequestByCredentialIdentifier, +) : + Validations, + Raise by raise { + + suspend fun issueCredential( + authorizationContext: AuthorizationContext, + credentialRequestTO: CredentialRequestTO, + ): Pair = coroutineScope { + val unresolvedRequest = + credentialRequestTO.toDomain( + credentialIssuerMetadata.credentialResponseEncryption, + credentialIssuerMetadata.batchCredentialIssuance, + ) + val (request, credentialIdentifier) = + when (unresolvedRequest) { + is UnresolvedCredentialRequest.ByFormat -> + unresolvedRequest.credentialRequest to null + + is UnresolvedCredentialRequest.ByCredentialIdentifier -> + resolve(unresolvedRequest) to unresolvedRequest.credentialIdentifier + } + val issued = issue(authorizationContext, request, credentialIdentifier) + request to issued + } + + private suspend fun resolve( + unresolvedRequest: UnresolvedCredentialRequest.ByCredentialIdentifier, + ): CredentialRequest = + either { + val resolvedRequest = resolveCredentialRequestByCredentialIdentifier( + unresolvedRequest.credentialIdentifier, + unresolvedRequest.unvalidatedProofs, + unresolvedRequest.credentialResponseEncryption, + ) + ensureNotNull(resolvedRequest) { InvalidCredentialIdentifier(unresolvedRequest.credentialIdentifier) } + resolvedRequest + }.bind() + + private suspend fun issue( + authorizationContext: AuthorizationContext, + credentialRequest: CredentialRequest, + credentialIdentifier: CredentialIdentifier?, + ): CredentialResponse { + val issueSpecificCredential = specificIssuerFor(credentialRequest) + val expectedScope = checkNotNull(issueSpecificCredential.supportedCredential.scope) + ensure(authorizationContext.scopes.contains(expectedScope)) { WrongScope(expectedScope) } + return issueSpecificCredential(authorizationContext, credentialRequest, credentialIdentifier).bind() + } + + private fun specificIssuerFor(credentialRequest: CredentialRequest): IssueSpecificCredential { + val specificIssuer = credentialIssuerMetadata.specificCredentialIssuers + .find { issuer -> + either { assertIsSupported(credentialRequest, issuer.supportedCredential) }.isRight() + } + if (specificIssuer == null) { + val types = when (credentialRequest) { + is MsoMdocCredentialRequest -> listOf(credentialRequest.docType) + is SdJwtVcCredentialRequest -> listOf(credentialRequest.type).map { it.value } + } + raise(UnsupportedCredentialType(credentialRequest.format, types)) + } + return specificIssuer + } + } // // Mapping to domain // @@ -409,188 +427,213 @@ private val BatchCredentialIssuance.maxProofsSupported: Int is BatchCredentialIssuance.Supported -> batchSize } -/** - * Tries to convert a [CredentialRequestTO] to a [CredentialRequest]. - */ -context(Raise) -private fun CredentialRequestTO.toDomain( - supportedEncryption: CredentialResponseEncryption, - supportedBatchIssuance: BatchCredentialIssuance, -): UnresolvedCredentialRequest { - if (supportedBatchIssuance is BatchCredentialIssuance.NotSupported) { - ensure(proofs == null) { - InvalidProof("Credential Endpoint does not support Batch Issuance") +private interface Validations : Raise { + + /** + * Tries to convert a [CredentialRequestTO] to a [CredentialRequest]. + */ + fun CredentialRequestTO.toDomain( + supportedEncryption: CredentialResponseEncryption, + supportedBatchIssuance: BatchCredentialIssuance, + ): UnresolvedCredentialRequest { + if (supportedBatchIssuance is BatchCredentialIssuance.NotSupported) { + ensure(proofs == null) { + InvalidProof("Credential Endpoint does not support Batch Issuance") + } } - } - val proofs = - when { - proof != null && proofs == null -> nonEmptyListOf(proof.toDomain()) - proof == null && proofs != null -> { - val jwtProofs = proofs.jwtProofs?.map { UnvalidatedProof.Jwt(it) } - val ldpVpProofs = proofs.ldpVpProofs?.map { UnvalidatedProof.LdpVp(it) } - // Proof object contains exactly one parameter named as the proof type - ensure(jwtProofs == null || ldpVpProofs == null) { - InvalidProof("Only a single proof type is allowed") + val proofs = + when { + proof != null && proofs == null -> nonEmptyListOf(proof.toDomain()) + proof == null && proofs != null -> { + val jwtProofs = proofs.jwtProofs?.map { UnvalidatedProof.Jwt(it) } + val ldpVpProofs = proofs.ldpVpProofs?.map { UnvalidatedProof.LdpVp(it) } + // Proof object contains exactly one parameter named as the proof type + ensure(jwtProofs == null || ldpVpProofs == null) { + InvalidProof("Only a single proof type is allowed") + } + + val proofs = (jwtProofs.orEmpty() + ldpVpProofs.orEmpty()).toNonEmptyListOrNull() + ensureNotNull(proofs) { MissingProof } } - val proofs = (jwtProofs.orEmpty() + ldpVpProofs.orEmpty()).toNonEmptyListOrNull() - ensureNotNull(proofs) { MissingProof } - } + proof != null && proofs != null -> raise( + InvalidProof("Only one of `proof` or `proofs` is allowed"), + ) - proof != null && proofs != null -> raise(InvalidProof("Only one of `proof` or `proofs` is allowed")) - else -> raise(MissingProof) + else -> raise(MissingProof) + } + ensure(proofs.size <= supportedBatchIssuance.maxProofsSupported) { + InvalidProof("You can provide at most '${supportedBatchIssuance.maxProofsSupported}' proofs") } - ensure(proofs.size <= supportedBatchIssuance.maxProofsSupported) { - InvalidProof("You can provide at most '${supportedBatchIssuance.maxProofsSupported}' proofs") - } - val credentialResponseEncryption = - credentialResponseEncryption?.toDomain() ?: RequestedResponseEncryption.NotRequired - credentialResponseEncryption.ensureIsSupported(supportedEncryption) + val credentialResponseEncryption = + credentialResponseEncryption?.toDomain() + ?: RequestedResponseEncryption.NotRequired + credentialResponseEncryption.ensureIsSupported(supportedEncryption) + + fun credentialRequestByFormat(format: FormatTO): UnresolvedCredentialRequest.ByFormat = + when (format) { + FormatTO.MsoMdoc -> { + val docType = run { + ensure(!docType.isNullOrBlank()) { UnsupportedCredentialType(format = MSO_MDOC_FORMAT) } + docType + } + val claims = claims + ?.decodeAs>>() + ?.mapValues { (_, vs) -> vs.map { it.key } } + ?: emptyMap() + UnresolvedCredentialRequest.ByFormat( + MsoMdocCredentialRequest( + proofs, + credentialResponseEncryption, + docType, + claims, + ), + ) + } - fun credentialRequestByFormat(format: FormatTO): UnresolvedCredentialRequest.ByFormat = - when (format) { - FormatTO.MsoMdoc -> { - val docType = run { - ensure(!docType.isNullOrBlank()) { UnsupportedCredentialType(format = MSO_MDOC_FORMAT) } - docType + FormatTO.SdJwtVc -> { + val type = run { + ensure(!type.isNullOrBlank()) { UnsupportedCredentialType(format = SD_JWT_VC_FORMAT) } + type + } + val claims = claims + ?.decodeAs>() + ?.keys + ?: emptySet() + + UnresolvedCredentialRequest.ByFormat( + SdJwtVcCredentialRequest( + proofs, + credentialResponseEncryption, + SdJwtVcType(type), + claims, + ), + ) } - val claims = claims?.decodeAs>>() - ?.mapValues { (_, vs) -> vs.map { it.key } } - ?: emptyMap() - UnresolvedCredentialRequest.ByFormat( - MsoMdocCredentialRequest( - proofs, - credentialResponseEncryption, - docType, - claims, - ), - ) } - FormatTO.SdJwtVc -> { - val type = run { - ensure(!type.isNullOrBlank()) { UnsupportedCredentialType(format = SD_JWT_VC_FORMAT) } - type - } - val claims = claims?.decodeAs>()?.keys ?: emptySet() - - UnresolvedCredentialRequest.ByFormat( - SdJwtVcCredentialRequest( - proofs, - credentialResponseEncryption, - SdJwtVcType(type), - claims, - ), - ) + fun credentialRequestByCredentialIdentifier(credentialIdentifier: String): UnresolvedCredentialRequest.ByCredentialIdentifier { + ensure(docType == null) { + NoCredentialFormatSpecificParametersWhenCredentialIdentifierProvided("doctype") + } + ensure(claims == null) { + NoCredentialFormatSpecificParametersWhenCredentialIdentifierProvided("claims") + } + ensure(type == null) { + NoCredentialFormatSpecificParametersWhenCredentialIdentifierProvided("vct") } - } - fun credentialRequestByCredentialIdentifier(credentialIdentifier: String): UnresolvedCredentialRequest.ByCredentialIdentifier { - ensure(docType == null) { NoCredentialFormatSpecificParametersWhenCredentialIdentifierProvided("doctype") } - ensure(claims == null) { NoCredentialFormatSpecificParametersWhenCredentialIdentifierProvided("claims") } - ensure(type == null) { NoCredentialFormatSpecificParametersWhenCredentialIdentifierProvided("vct") } + return UnresolvedCredentialRequest.ByCredentialIdentifier( + CredentialIdentifier(credentialIdentifier), + proofs, + credentialResponseEncryption, + ) + } - return UnresolvedCredentialRequest.ByCredentialIdentifier( - CredentialIdentifier(credentialIdentifier), - proofs, - credentialResponseEncryption, + val formatOrCredentialIdentifier = + Ior.fromNullables( + format, + credentialIdentifier, + ) ?: raise(MissingBothFormatAndCredentialIdentifier) + return formatOrCredentialIdentifier.fold( + { format -> credentialRequestByFormat(format) }, + { credentialIdentifier -> credentialRequestByCredentialIdentifier(credentialIdentifier) }, + { _, _ -> raise(BothFormatAndCredentialIdentifierProvided) }, ) } - val formatOrCredentialIdentifier = - Ior.fromNullables(format, credentialIdentifier) ?: raise(MissingBothFormatAndCredentialIdentifier) - return formatOrCredentialIdentifier.fold( - { format -> credentialRequestByFormat(format) }, - { credentialIdentifier -> credentialRequestByCredentialIdentifier(credentialIdentifier) }, - { _, _ -> raise(BothFormatAndCredentialIdentifierProvided) }, - ) -} + private inline fun ClaimsTO.decodeAs(): T = + Either.catch { Json.decodeFromString(Json.encodeToString(this)) }.getOrElse { raise(InvalidClaims(it)) } -context(Raise) -private inline fun ClaimsTO.decodeAs(): T = - Either.catch { Json.decodeFromString(Json.encodeToString(this)) }.getOrElse { raise(InvalidClaims(it)) } - -/** - * Gets the [UnvalidatedProof] that corresponds to this [ProofTo]. - */ -context (Raise) -private fun ProofTo.toDomain(): UnvalidatedProof = when (type) { - ProofTypeTO.JWT -> { - ensure(!jwt.isNullOrEmpty()) { MissingProof } - UnvalidatedProof.Jwt(jwt) - } + /** + * Gets the [RequestedResponseEncryption] that corresponds to the provided values. + */ + fun CredentialResponseEncryptionTO.toDomain(): RequestedResponseEncryption.Required = + RequestedResponseEncryption.Required( + Json.encodeToString(key), + algorithm, + method, + ).getOrElse { raise(InvalidEncryptionParameters(it)) } - ProofTypeTO.LDP_VP -> { - ensureNotNull(ldpVp) { MissingProof } - UnvalidatedProof.LdpVp(ldpVp) - } -} + /** + * Verifies this [RequestedResponseEncryption] is supported by the provided [CredentialResponseEncryption], otherwise + * raises an [InvalidEncryptionParameters]. + */ + fun RequestedResponseEncryption.ensureIsSupported( + supported: CredentialResponseEncryption, + ) { + when (supported) { + is CredentialResponseEncryption.NotSupported -> { + ensure(this !is RequestedResponseEncryption.Required) { + // credential response encryption not supported by issuer but required by client + InvalidEncryptionParameters(IllegalArgumentException("credential response encryption is not supported")) + } + } -/** - * Verifies this [RequestedResponseEncryption] is supported by the provided [CredentialResponseEncryption], otherwise - * raises an [InvalidEncryptionParameters]. - */ -context(Raise) -private fun RequestedResponseEncryption.ensureIsSupported(supported: CredentialResponseEncryption) { - when (supported) { - is CredentialResponseEncryption.NotSupported -> { - ensure(this !is RequestedResponseEncryption.Required) { - // credential response encryption not supported by issuer but required by client - InvalidEncryptionParameters(IllegalArgumentException("credential response encryption is not supported")) + is CredentialResponseEncryption.Optional -> { + if (this is RequestedResponseEncryption.Required) { + // credential response encryption supported by issuer and required by client + // ensure provided parameters are supported + ensure(this.encryptionAlgorithm in supported.parameters.algorithmsSupported) { + InvalidEncryptionParameters( + IllegalArgumentException( + "jwe encryption algorithm '${this.encryptionAlgorithm.name}' is not supported", + ), + ) + } + ensure(this.encryptionMethod in supported.parameters.methodsSupported) { + InvalidEncryptionParameters( + IllegalArgumentException( + "jwe encryption method '${this.encryptionMethod.name}' is not supported", + ), + ) + } + } } - } - is CredentialResponseEncryption.Optional -> { - if (this is RequestedResponseEncryption.Required) { - // credential response encryption supported by issuer and required by client + is CredentialResponseEncryption.Required -> { + ensure(this is RequestedResponseEncryption.Required) { + // credential response encryption required by issuer but not required by client + InvalidEncryptionParameters(IllegalArgumentException("credential response encryption is required")) + } + // ensure provided parameters are supported - ensure(encryptionAlgorithm in supported.parameters.algorithmsSupported) { + ensure(this.encryptionAlgorithm in supported.parameters.algorithmsSupported) { InvalidEncryptionParameters( - IllegalArgumentException("jwe encryption algorithm '${encryptionAlgorithm.name}' is not supported"), + IllegalArgumentException( + "jwe encryption algorithm '${this.encryptionAlgorithm.name}' is not supported", + ), ) } - ensure(encryptionMethod in supported.parameters.methodsSupported) { + ensure(this.encryptionMethod in supported.parameters.methodsSupported) { InvalidEncryptionParameters( - IllegalArgumentException("jwe encryption method '${encryptionMethod.name}' is not supported"), + IllegalArgumentException( + "jwe encryption method '${this.encryptionMethod.name}' is not supported", + ), ) } } } + } - is CredentialResponseEncryption.Required -> { - ensure(this is RequestedResponseEncryption.Required) { - // credential response encryption required by issuer but not required by client - InvalidEncryptionParameters(IllegalArgumentException("credential response encryption is required")) - } + /** + * Gets the [UnvalidatedProof] that corresponds to this [ProofTo]. + */ + fun ProofTo.toDomain(): UnvalidatedProof = when (type) { + ProofTypeTO.JWT -> { + ensure(!jwt.isNullOrEmpty()) { MissingProof } + UnvalidatedProof.Jwt(jwt) + } - // ensure provided parameters are supported - ensure(encryptionAlgorithm in supported.parameters.algorithmsSupported) { - InvalidEncryptionParameters( - IllegalArgumentException("jwe encryption algorithm '${encryptionAlgorithm.name}' is not supported"), - ) - } - ensure(encryptionMethod in supported.parameters.methodsSupported) { - InvalidEncryptionParameters( - IllegalArgumentException("jwe encryption method '${encryptionMethod.name}' is not supported"), - ) - } + ProofTypeTO.LDP_VP -> { + ensureNotNull(ldpVp) { MissingProof } + UnvalidatedProof.LdpVp(ldpVp) } } } -/** - * Gets the [RequestedResponseEncryption] that corresponds to the provided values. - */ -context(Raise) -private fun CredentialResponseEncryptionTO.toDomain(): RequestedResponseEncryption.Required = - RequestedResponseEncryption.Required( - Json.encodeToString(key), - algorithm, - method, - ).getOrElse { raise(InvalidEncryptionParameters(it)) } - fun CredentialResponse.toTO(cNonce: String): IssueCredentialResponse.PlainTO = when (this) { is CredentialResponse.Issued -> { when (credentials.size) { diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/out/IssueSpecificCredential.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/out/IssueSpecificCredential.kt index 081d8474..5bbe67c2 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/out/IssueSpecificCredential.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/out/IssueSpecificCredential.kt @@ -15,9 +15,13 @@ */ package eu.europa.ec.eudi.pidissuer.port.out -import arrow.core.raise.Raise +import arrow.core.Either +import arrow.core.raise.either import com.nimbusds.jose.jwk.JWK -import eu.europa.ec.eudi.pidissuer.domain.* +import eu.europa.ec.eudi.pidissuer.domain.CredentialConfiguration +import eu.europa.ec.eudi.pidissuer.domain.CredentialIdentifier +import eu.europa.ec.eudi.pidissuer.domain.CredentialRequest +import eu.europa.ec.eudi.pidissuer.domain.CredentialResponse import eu.europa.ec.eudi.pidissuer.port.input.AuthorizationContext import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError import eu.europa.ec.eudi.pidissuer.port.out.persistence.GenerateTransactionId @@ -29,12 +33,11 @@ interface IssueSpecificCredential { val supportedCredential: CredentialConfiguration val publicKey: JWK? - context(Raise) suspend operator fun invoke( authorizationContext: AuthorizationContext, request: CredentialRequest, credentialIdentifier: CredentialIdentifier?, - ): CredentialResponse + ): Either } fun IssueSpecificCredential.asDeferred( @@ -51,18 +54,19 @@ private class DeferredIssuer( private val log = LoggerFactory.getLogger(DeferredIssuer::class.java) - context(Raise) override suspend fun invoke( authorizationContext: AuthorizationContext, request: CredentialRequest, credentialIdentifier: CredentialIdentifier?, - ): CredentialResponse { - val credentialResponse = issuer.invoke(authorizationContext, request, credentialIdentifier) + ): Either = either { + val credentialResponse = + issuer.invoke(authorizationContext, request, credentialIdentifier).bind() + require(credentialResponse is CredentialResponse.Issued) { "Actual issuer should return issued credentials" } val transactionId = generateTransactionId() storeDeferredCredential.invoke(transactionId, credentialResponse, request.credentialResponseEncryption) - return CredentialResponse.Deferred(transactionId).also { + CredentialResponse.Deferred(transactionId).also { log.info("Repackaged $credentialResponse as $it") } } diff --git a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProofTest.kt b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProofTest.kt index b244dfc1..9406ff65 100644 --- a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProofTest.kt +++ b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProofTest.kt @@ -17,7 +17,6 @@ package eu.europa.ec.eudi.pidissuer.adapter.out.jose import arrow.core.NonEmptyList import arrow.core.nonEmptySetOf -import arrow.core.raise.either import arrow.core.toNonEmptyListOrNull import arrow.core.toNonEmptySetOrNull import com.nimbusds.jose.JOSEObjectType @@ -63,12 +62,12 @@ internal class ValidateJwtProofTest { type(JOSEObjectType.JWT) jwk(key.toPublicJWK()) } - val result = either { + val result = validateJwtProof( UnvalidatedProof.Jwt(signedJwt.serialize()), credentialConfiguration, ) - } + assert(result.isLeft()) } @@ -77,12 +76,12 @@ internal class ValidateJwtProofTest { val key = loadKey() val signedJwt = generateSignedJwt(key, "nonce") - val result = either { + val result = validateJwtProof( UnvalidatedProof.Jwt(signedJwt.serialize()), credentialConfiguration, ) - } + assert(result.isLeft()) } @@ -95,12 +94,12 @@ internal class ValidateJwtProofTest { x509CertChain(chain.map { Base64.encode(it.encoded) }) } - val result = either { + val result = validateJwtProof( UnvalidatedProof.Jwt(signedJwt.serialize()), credentialConfiguration, ) - } + assertTrue { result.isLeft() } } @@ -113,12 +112,10 @@ internal class ValidateJwtProofTest { x509CertChain(chain.map { Base64.encode(it.encoded) }) } - either { - validateJwtProof( - UnvalidatedProof.Jwt(signedJwt.serialize()), - credentialConfiguration, - ) - }.fold( + validateJwtProof( + UnvalidatedProof.Jwt(signedJwt.serialize()), + credentialConfiguration, + ).fold( ifLeft = { fail("Unexpected $it", it.cause) }, ifRight = { credentialKey -> val x5c = assertIs>(credentialKey, "expected 'x5c' credential key") @@ -135,12 +132,10 @@ internal class ValidateJwtProofTest { jwk(key.toPublicJWK()) } - either { - validateJwtProof( - UnvalidatedProof.Jwt(signedJwt.serialize()), - credentialConfiguration, - ) - }.fold( + validateJwtProof( + UnvalidatedProof.Jwt(signedJwt.serialize()), + credentialConfiguration, + ).fold( ifLeft = { fail("Unexpected $it", it.cause) }, ifRight = { credentialKey -> val jwk = assertIs>(credentialKey, "expected 'jwk' credential key") @@ -157,12 +152,10 @@ internal class ValidateJwtProofTest { keyID("did:jwk:${Base64URL.encode(key.toPublicJWK().toJSONString())}#0") } - either { - validateJwtProof( - UnvalidatedProof.Jwt(signedJwt.serialize()), - credentialConfiguration, - ) - }.fold( + validateJwtProof( + UnvalidatedProof.Jwt(signedJwt.serialize()), + credentialConfiguration, + ).fold( ifLeft = { fail("Unexpected $it", it.cause) }, ifRight = { credentialKey -> val jwk = assertIs>(credentialKey, "expected 'jwk' credential key") @@ -180,12 +173,12 @@ internal class ValidateJwtProofTest { jwk(incorrectKey.toPublicJWK()) } - val result = either { + val result = validateJwtProof( UnvalidatedProof.Jwt(signedJwt.serialize()), credentialConfiguration, ) - } + assertTrue { result.isLeft() } } @@ -198,12 +191,12 @@ internal class ValidateJwtProofTest { x509CertChain(incorrectKey.toPublicJWK().x509CertChain) } - val result = either { + val result = validateJwtProof( UnvalidatedProof.Jwt(signedJwt.serialize()), credentialConfiguration, ) - } + assertTrue { result.isLeft() } } @@ -215,12 +208,12 @@ internal class ValidateJwtProofTest { generateSignedJwt(key, "nonce") { keyID("did:jwk:${Base64URL.encode(incorrectKey.toPublicJWK().toJSONString())}#0") } - val result = either { + val result = validateJwtProof( UnvalidatedProof.Jwt(signedJwt.serialize()), credentialConfiguration, ) - } + assertTrue { result.isLeft() } } diff --git a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateProofTest.kt b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateProofTest.kt index 76c9707a..f996cb0c 100644 --- a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateProofTest.kt +++ b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateProofTest.kt @@ -16,7 +16,6 @@ package eu.europa.ec.eudi.pidissuer.adapter.out.jose import arrow.core.nonEmptyListOf -import arrow.core.raise.either import eu.europa.ec.eudi.pidissuer.adapter.out.pid.PidMsoMdocV1 import eu.europa.ec.eudi.pidissuer.domain.CredentialIssuerId import eu.europa.ec.eudi.pidissuer.domain.UnvalidatedProof @@ -43,9 +42,9 @@ class ValidateProofTest { internal fun `fails with unsupported proof type`() = runTest { val proof = UnvalidatedProof.LdpVp("foo") - val result = either { + val result = validateProofs(nonEmptyListOf(proof), PidMsoMdocV1, clock.instant()) - } + assert(result.isLeft()) val error = assertIs(result.leftOrNull()) diff --git a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceDataMockTest.kt b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceDataMockTest.kt index b848a92f..2caa26c7 100644 --- a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceDataMockTest.kt +++ b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/mdl/GetMobileDrivingLicenceDataMockTest.kt @@ -17,7 +17,6 @@ package eu.europa.ec.eudi.pidissuer.adapter.out.mdl import arrow.core.getOrElse import arrow.core.nonEmptySetOf -import arrow.core.raise.either import com.nimbusds.oauth2.sdk.token.BearerAccessToken import eu.europa.ec.eudi.pidissuer.domain.Scope import eu.europa.ec.eudi.pidissuer.port.input.AuthorizationContext @@ -29,10 +28,13 @@ internal class GetMobileDrivingLicenceDataMockTest { @Test internal fun `get mDL success`() = runTest { val getMobileDrivingLicenceData = GetMobileDrivingLicenceDataMock() - either { - getMobileDrivingLicenceData( - AuthorizationContext("username", BearerAccessToken.parse("Bearer access-token"), nonEmptySetOf(Scope("test"))), - ) - }.getOrElse { throw RuntimeException(it.msg, it.cause) } + + getMobileDrivingLicenceData( + AuthorizationContext( + "username", + BearerAccessToken.parse("Bearer access-token"), + nonEmptySetOf(Scope("test")), + ), + ).getOrElse { throw RuntimeException(it.msg, it.cause) } } }