Skip to content

Commit

Permalink
refactor CredentialConfigurationFilter and use the Config.ProofType f…
Browse files Browse the repository at this point in the history
…or filtering based on proof types; move DefaultOffer class from Offer.Kt to separate file
  • Loading branch information
vkanellopoulos committed Jun 6, 2024
1 parent 820607b commit 18ab212
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,54 @@ import eu.europa.ec.eudi.openid4vci.MsoMdocCredential
import kotlin.reflect.KClass

internal fun interface CredentialConfigurationFilter {
operator fun invoke(conf: CredentialConfiguration): Boolean

/**
*
*/
class DocTypeFilterFactory(private val docType: String) : CredentialConfigurationFilter {
override fun invoke(conf: CredentialConfiguration): Boolean {
return conf is MsoMdocCredential && conf.docType == docType
}
}

class FormatFilterFactory<in F : CredentialConfiguration>(private val format: KClass<F>) :
CredentialConfigurationFilter {
override fun invoke(conf: CredentialConfiguration): Boolean {
return format.isInstance(conf)
}
}
operator fun invoke(conf: CredentialConfiguration): Boolean = filter(conf)
fun filter(conf: CredentialConfiguration): Boolean

/**
* Default [CredentialConfigurationFilter] implementations for credential format and proof type.
* @property MsoMdocFormatFilter filter for credential format
* @property ProofTypeFilter filter for proof type
*/
companion object {
@JvmSynthetic
internal fun FormatFilterFactory(format: KClass<out CredentialConfiguration>) =
CredentialConfigurationFilter { conf ->
format.isInstance(conf)
}

@JvmSynthetic
internal val MsoMdocFormatFilter: CredentialConfigurationFilter =
FormatFilterFactory(MsoMdocCredential::class)

@JvmSynthetic
internal val ProofTypeFilter: CredentialConfigurationFilter = CredentialConfigurationFilter { conf ->
try {
SupportedProofType.selectProofType(conf)
true
} catch (e: Throwable) {
false
internal fun DocTypeFilter(docType: String): CredentialConfigurationFilter =
Compose(MsoMdocFormatFilter, CredentialConfigurationFilter { conf -> conf.docType == docType })

@JvmSynthetic
internal fun ProofTypeFilter(vararg supportedProofTypes: OpenId4VciManager.Config.ProofType): CredentialConfigurationFilter =
ProofTypeFilter(supportedProofTypes.toList())

@JvmSynthetic
internal fun ProofTypeFilter(supportedProofTypes: List<OpenId4VciManager.Config.ProofType>): CredentialConfigurationFilter =
CredentialConfigurationFilter { conf ->
try {
SupportedProofType.prioritize(supportedProofTypes).select(conf)
true
} catch (e: Throwable) {
false
}
}
}

/**
* Composed(
* MsoMdocFormatFilter,
* ProofTypeFilter(supportedProofTypes)
* DocTypeFilterFactory(docType)
* )(conf)
*/
@JvmSynthetic
internal fun Compose(vararg filters: CredentialConfigurationFilter): CredentialConfigurationFilter =
CredentialConfigurationFilter { conf -> filters.all { it(conf) } }

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2024 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package eu.europa.ec.eudi.wallet.issue.openid4vci

import eu.europa.ec.eudi.openid4vci.CredentialConfiguration
import eu.europa.ec.eudi.openid4vci.CredentialOffer
import eu.europa.ec.eudi.openid4vci.MsoMdocCredential

internal data class DefaultOffer(
@JvmSynthetic val credentialOffer: CredentialOffer,
@JvmSynthetic val credentialConfigurationFilter: CredentialConfigurationFilter = CredentialConfigurationFilter.MsoMdocFormatFilter
) : Offer {

private val issuerMetadata = credentialOffer.credentialIssuerMetadata

override val issuerName: String = issuerMetadata.credentialIssuerIdentifier.value.value.host
override val offeredDocuments: List<Offer.OfferedDocument> = issuerMetadata.credentialConfigurationsSupported
.filterKeys { it in credentialOffer.credentialConfigurationIdentifiers }
.filterValues { credentialConfigurationFilter(it) }
.map { (id, conf) -> Offer.OfferedDocument(conf.name, conf.docType, id, conf) }
}

internal val CredentialConfiguration.name: String
@JvmSynthetic get() = this.display.takeUnless { it.isEmpty() }?.get(0)?.name ?: docType

internal val CredentialConfiguration.docType: String
@JvmSynthetic get() = when (this) {
is MsoMdocCredential -> docType
else -> "unknown"
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ import eu.europa.ec.eudi.wallet.document.DocumentId
import eu.europa.ec.eudi.wallet.document.DocumentManager
import eu.europa.ec.eudi.wallet.document.IssuanceRequest
import eu.europa.ec.eudi.wallet.internal.mainExecutor
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.Companion.Compose
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.Companion.DocTypeFilter
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.Companion.MsoMdocFormatFilter
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.Companion.ProofTypeFilter
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.DocTypeFilterFactory
import eu.europa.ec.eudi.wallet.issue.openid4vci.IssueEvent.Companion.failure
import eu.europa.ec.eudi.wallet.issue.openid4vci.ProofSigner.UserAuthStatus
import kotlinx.coroutines.*
Expand Down Expand Up @@ -59,13 +60,13 @@ internal class DefaultOpenId4VciManager(
.use { client ->
Issuer.metaData(client, credentialIssuerId)
}
val credentialConfigurationFilter = Compose(
DocTypeFilter(docType),
ProofTypeFilter(config.proofTypes)
)
val credentialConfigurationId =
credentialIssuerMetadata.credentialConfigurationsSupported.filter { (_, conf) ->
listOf(
MsoMdocFormatFilter,
DocTypeFilterFactory(docType),
ProofTypeFilter
).all { filter -> filter(conf) }
credentialIssuerMetadata.credentialConfigurationsSupported.filterValues { conf ->
credentialConfigurationFilter(conf)
}.keys.firstOrNull() ?: throw IllegalStateException("No suitable configuration found")

val credentialOffer = CredentialOffer(
Expand All @@ -75,7 +76,7 @@ internal class DefaultOpenId4VciManager(
credentialConfigurationIdentifiers = listOf(credentialConfigurationId)
)

val offer = DefaultOffer(credentialOffer)
val offer = DefaultOffer(credentialOffer, credentialConfigurationFilter)
doIssueDocumentByOffer(offer, config, listener)

} catch (e: Throwable) {
Expand Down Expand Up @@ -114,7 +115,14 @@ internal class DefaultOpenId4VciManager(
try {
val offer = offerUriCache[offerUri]
?: CredentialOfferRequestResolver().resolve(offerUri).getOrThrow()
.let { DefaultOffer(it) }
.let {
DefaultOffer(
it, Compose(
MsoMdocFormatFilter,
ProofTypeFilter(config.proofTypes)
)
)
}
doIssueDocumentByOffer(offer, config, listener)
} catch (e: Throwable) {
listener(failure(e))
Expand All @@ -132,7 +140,8 @@ internal class DefaultOpenId4VciManager(
launch(onResolvedOffer.wrap(executor)) { coroutineScope, callback ->
try {
val credentialOffer = CredentialOfferRequestResolver().resolve(offerUri).getOrThrow()
val offer = DefaultOffer(credentialOffer)
val offer =
DefaultOffer(credentialOffer, Compose(MsoMdocFormatFilter, ProofTypeFilter(config.proofTypes)))
offerUriCache[offerUri] = offer
callback(OfferResult.Success(offer))
coroutineScope.cancel("resolveDocumentOffer succeeded")
Expand Down Expand Up @@ -220,7 +229,11 @@ internal class DefaultOpenId4VciManager(
addedDocuments: MutableSet<DocumentId>,
onEvent: OpenId4VciManager.OnResult<IssueEvent>
) {
val payload = IssuanceRequestPayload.ConfigurationBased(credentialConfigurationIdentifier, null)
val claimSet = when (credentialConfiguration) {
is MsoMdocCredential -> credentialConfiguration.claims.toClaimSet()
else -> null
}
val payload = IssuanceRequestPayload.ConfigurationBased(credentialConfigurationIdentifier, claimSet)
when (authRequest) {
is AuthorizedRequest.NoProofRequired -> doRequestSingleNoProof(
authRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@file:JvmMultifileClass

package eu.europa.ec.eudi.wallet.issue.openid4vci

import eu.europa.ec.eudi.openid4vci.CredentialConfiguration
import eu.europa.ec.eudi.openid4vci.CredentialConfigurationIdentifier
import eu.europa.ec.eudi.openid4vci.CredentialOffer
import eu.europa.ec.eudi.openid4vci.MsoMdocCredential
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.Companion.MsoMdocFormatFilter
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.Companion.ProofTypeFilter

/**
* An offer of credentials to be issued.
Expand Down Expand Up @@ -53,33 +46,3 @@ interface Offer {
}
}

internal data class DefaultOffer(
@JvmSynthetic val credentialOffer: CredentialOffer,
@JvmSynthetic val filterConfigurations: List<CredentialConfigurationFilter> = listOf(
MsoMdocFormatFilter,
ProofTypeFilter
)
) : Offer {

override val issuerName: String
get() = credentialOffer.credentialIssuerMetadata.credentialIssuerIdentifier.value.value.host
override val offeredDocuments: List<Offer.OfferedDocument>
get() = credentialOffer.credentialIssuerMetadata.credentialConfigurationsSupported.filter { (id, _) ->
id in credentialOffer.credentialConfigurationIdentifiers
}.filterValues { conf ->
filterConfigurations.all { filter -> filter(conf) }
}.map { (id, conf) ->
Offer.OfferedDocument(conf.name, conf.docType, id, conf)
}
}

internal val CredentialConfiguration.name: String
@JvmSynthetic get() = this.display.takeUnless { it.isEmpty() }?.get(0)?.name ?: docType

internal val CredentialConfiguration.docType: String
@JvmSynthetic get() = when (this) {
is MsoMdocCredential -> docType
else -> "unknown"
}


Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ internal abstract class ProofSigner {
Result.success(
SupportedProofType
.apply { supportedProofTypesPrioritized?.let { prioritize(it) } }
.selectProofType(credentialConfiguration)
.select(credentialConfiguration)
.createProofSigner(issuanceRequest)
)
} catch (e: Throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ internal sealed interface SupportedProofType {
}

@JvmStatic
fun selectProofType(credentialConfiguration: CredentialConfiguration): SelectedProofType {
fun select(credentialConfiguration: CredentialConfiguration): SelectedProofType {
val issuerProofTypesSupportedMap =
credentialConfiguration.proofTypesSupported.values.associateBy { it.type() }
for (supportedProofType in supportedProofTypes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import eu.europa.ec.eudi.openid4vci.MsoMdocCredential
import eu.europa.ec.eudi.openid4vci.ProofTypeMeta
import eu.europa.ec.eudi.openid4vci.ProofTypesSupported
import eu.europa.ec.eudi.openid4vci.SdJwtVcCredential
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.Companion.DocTypeFilter
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.Companion.MsoMdocFormatFilter
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.Companion.ProofTypeFilter
import eu.europa.ec.eudi.wallet.issue.openid4vci.CredentialConfigurationFilter.DocTypeFilterFactory
import eu.europa.ec.eudi.wallet.issue.openid4vci.OpenId4VciManager.Config.ProofType
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertFalse
Expand Down Expand Up @@ -54,7 +55,7 @@ class CredentialConfigurationFilterTest {
)
)

assertTrue(ProofTypeFilter(credentialConfiguration))
assertTrue(ProofTypeFilter(ProofType.JWT)(credentialConfiguration))
}

@Test
Expand All @@ -66,7 +67,7 @@ class CredentialConfigurationFilterTest {
)
)

assertFalse(ProofTypeFilter(credentialConfiguration))
assertFalse(ProofTypeFilter(ProofType.CWT)(credentialConfiguration))
}

@Test
Expand All @@ -78,15 +79,15 @@ class CredentialConfigurationFilterTest {
)
)

assertFalse(ProofTypeFilter(credentialConfiguration))
assertFalse(ProofTypeFilter(ProofType.JWT)(credentialConfiguration))
}

@Test
fun `ProofTypeFilter returns false for empty proof types`() {
val credentialConfiguration = mockk<MsoMdocCredential>(relaxed = true)
every { credentialConfiguration.proofTypesSupported } returns ProofTypesSupported.Empty

assertFalse(ProofTypeFilter(credentialConfiguration))
assertFalse(ProofTypeFilter(ProofType.JWT)(credentialConfiguration))
}

@Test
Expand All @@ -95,7 +96,7 @@ class CredentialConfigurationFilterTest {
val credentialConfiguration = mockk<MsoMdocCredential>(relaxed = true)
every { credentialConfiguration.docType } returns docType

assertTrue(DocTypeFilterFactory(docType).invoke(credentialConfiguration))
assertTrue(DocTypeFilter(docType).invoke(credentialConfiguration))
}

@Test
Expand All @@ -104,6 +105,6 @@ class CredentialConfigurationFilterTest {
val credentialConfiguration = mockk<MsoMdocCredential>(relaxed = true)
every { credentialConfiguration.docType } returns "differentDocType"

assertFalse(DocTypeFilterFactory(docType).invoke(credentialConfiguration))
assertFalse(DocTypeFilter(docType).invoke(credentialConfiguration))
}
}
Loading

0 comments on commit 18ab212

Please sign in to comment.