Skip to content

Commit df3c5f2

Browse files
Merge pull request #209 from wistefan/optional-args-tir-policy
Support policies with optional arguments
2 parents 1915a97 + eaca8d9 commit df3c5f2

File tree

6 files changed

+161
-25
lines changed

6 files changed

+161
-25
lines changed

build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ dependencies {
110110
testImplementation("io.kotest:kotest-runner-junit5:5.5.4")
111111
testImplementation("io.kotest:kotest-assertions-core:5.5.4")
112112
testImplementation("io.kotest:kotest-assertions-json:5.5.4")
113+
113114
}
114115

115116
tasks.withType<Test> {

src/main/kotlin/id/walt/auditor/PolicyRegistry.kt

+30-8
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,48 @@ package id.walt.auditor
22

33
import com.beust.klaxon.JsonObject
44
import com.beust.klaxon.Klaxon
5+
import com.beust.klaxon.KlaxonException
56
import id.walt.auditor.dynamic.DynamicPolicy
67
import id.walt.auditor.dynamic.DynamicPolicyArg
78
import id.walt.common.deepMerge
89
import id.walt.common.resolveContent
910
import id.walt.model.dif.PresentationDefinition
1011
import id.walt.services.context.ContextManager
1112
import id.walt.services.hkvstore.HKVKey
13+
import mu.KotlinLogging
1214
import java.io.StringReader
15+
import java.lang.reflect.InvocationTargetException
1316
import kotlin.reflect.KClass
1417
import kotlin.reflect.full.createInstance
1518
import kotlin.reflect.full.primaryConstructor
1619

20+
private val log = KotlinLogging.logger {}
21+
1722
open class PolicyFactory<P : VerificationPolicy, A : Any>(
1823
val policyType: KClass<P>,
1924
val argType: KClass<A>?,
2025
val name: String,
21-
val description: String? = null
26+
val description: String? = null,
27+
val optionalArgument: Boolean = false
2228
) {
2329
open fun create(argument: Any? = null): P {
24-
return argType?.let {
25-
policyType.primaryConstructor!!.call(argument)
30+
try {
31+
return argType?.let {
32+
if(optionalArgument) {
33+
argument?.let {
34+
return policyType.primaryConstructor!!.call(it)
35+
}
36+
} else {
37+
return policyType.primaryConstructor!!.call(argument)
38+
}
39+
} ?: policyType.createInstance()
40+
} catch (e: KlaxonException) {
41+
throw IllegalArgumentException("Provided argument was of wrong type.", e)
42+
} catch (e: InvocationTargetException) {
43+
throw IllegalArgumentException("No argument was provided.", e)
2644
}
27-
?: policyType.createInstance()
2845
}
2946

30-
3147
val requiredArgumentType = when (argType) {
3248
null -> "None"
3349
else -> argType.simpleName!!
@@ -75,8 +91,9 @@ object PolicyRegistry {
7591
fun <P : ParameterizedVerificationPolicy<A>, A : Any> register(
7692
policy: KClass<P>,
7793
argType: KClass<A>,
78-
description: String? = null
79-
) = policies.put(policy.simpleName!!, PolicyFactory(policy, argType, policy.simpleName!!, description))
94+
description: String? = null,
95+
optionalArgument: Boolean = false
96+
) = policies.put(policy.simpleName!!, PolicyFactory(policy, argType, policy.simpleName!!, description, optionalArgument))
8097

8198
fun <P : SimpleVerificationPolicy> register(policy: KClass<P>, description: String? = null) =
8299
policies.put(policy.simpleName!!, PolicyFactory<P, Unit>(policy, null, policy.simpleName!!, description))
@@ -173,7 +190,12 @@ object PolicyRegistry {
173190
//register(JsonSchemaPolicy::class, "Verify by JSON schema")
174191
register(TrustedSchemaRegistryPolicy::class, "Verify by EBSI Trusted Schema Registry")
175192
register(TrustedIssuerDidPolicy::class, "Verify by trusted issuer did")
176-
register(TrustedIssuerRegistryPolicy::class, "Verify by trusted EBSI Trusted Issuer Registry record")
193+
register(
194+
TrustedIssuerRegistryPolicy::class,
195+
TrustedIssuerRegistryPolicyArg::class,
196+
"Verify by an EBSI Trusted Issuers Registry compliant api.",
197+
true
198+
)
177199
register(TrustedSubjectDidPolicy::class, "Verify by trusted subject did")
178200
register(IssuedDateBeforePolicy::class, "Verify by issuance date")
179201
register(ValidFromBeforePolicy::class, "Verify by valid from")

src/main/kotlin/id/walt/auditor/VerificationPolicy.kt

+30-7
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ abstract class SimpleVerificationPolicy : VerificationPolicy()
5757

5858
abstract class ParameterizedVerificationPolicy<T>(val argument: T) : VerificationPolicy()
5959

60+
abstract class OptionalParameterizedVerificationPolicy<T>(val argument: T?) : VerificationPolicy()
61+
6062
class SignaturePolicy : SimpleVerificationPolicy() {
6163
override val description: String = "Verify by signature"
6264
override fun doVerify(vc: VerifiableCredential) = runCatching {
@@ -102,8 +104,21 @@ class TrustedIssuerDidPolicy : SimpleVerificationPolicy() {
102104
}
103105
}
104106

105-
class TrustedIssuerRegistryPolicy : SimpleVerificationPolicy() {
106-
override val description: String = "Verify by trusted EBSI Trusted Issuer Registry record"
107+
108+
data class TrustedIssuerRegistryPolicyArg(val registryAddress: String)
109+
110+
class TrustedIssuerRegistryPolicy(registryArg: TrustedIssuerRegistryPolicyArg) :
111+
ParameterizedVerificationPolicy<TrustedIssuerRegistryPolicyArg>(registryArg) {
112+
113+
constructor(registryAddress: String) : this(
114+
TrustedIssuerRegistryPolicyArg(registryAddress)
115+
)
116+
117+
constructor() : this(
118+
TrustedIssuerRegistryPolicyArg("https://api-pilot.ebsi.eu/trusted-issuers-registry/v2/issuers/")
119+
)
120+
121+
override val description: String = "Verify by an EBSI Trusted Issuers Registry compliant api."
107122
override fun doVerify(vc: VerifiableCredential): Boolean {
108123

109124
// VPs are not considered
@@ -119,17 +134,23 @@ class TrustedIssuerRegistryPolicy : SimpleVerificationPolicy() {
119134
log.debug { "Resolved DID ${resolvedIssuerDid.id} does not match the issuer DID $issuerDid" }
120135
return false
121136
}
137+
var tirRecord: TrustedIssuer
122138

123-
val tirRecord = runCatching {
124-
TrustedIssuerClient.getIssuer(issuerDid)
125-
}.getOrElse { throw Exception("Could not resolve issuer TIR record of $issuerDid", it) }
126139

127-
return isValidTrustedIssuerRecord(tirRecord)
140+
runCatching {
141+
tirRecord = TrustedIssuerClient.getIssuer(issuerDid, argument.registryAddress)
142+
return isValidTrustedIssuerRecord(tirRecord)
143+
}.getOrElse {
144+
log.debug { it }
145+
log.warn { "Could not resolve issuer TIR record of $issuerDid" }
146+
return false
147+
}
128148
}
129149

130150
private fun isValidTrustedIssuerRecord(tirRecord: TrustedIssuer): Boolean {
131151
for (attribute in tirRecord.attributes) {
132152
val attributeInfo = AttributeInfo.from(attribute.body)
153+
log.warn { attributeInfo }
133154
if (TIR_TYPE_ATTRIBUTE == attributeInfo?.type && TIR_NAME_ISSUER == attributeInfo.name) {
134155
return true
135156
}
@@ -140,6 +161,7 @@ class TrustedIssuerRegistryPolicy : SimpleVerificationPolicy() {
140161
override var applyToVP: Boolean = false
141162
}
142163

164+
143165
class TrustedSubjectDidPolicy : SimpleVerificationPolicy() {
144166
override val description: String = "Verify by trusted subject did"
145167
override fun doVerify(vc: VerifiableCredential): Boolean {
@@ -220,7 +242,8 @@ class CredentialStatusPolicy : SimpleVerificationPolicy() {
220242

221243
data class ChallengePolicyArg(val challenges: Set<String>, val applyToVC: Boolean = true, val applyToVP: Boolean = true)
222244

223-
class ChallengePolicy(challengeArg: ChallengePolicyArg) : ParameterizedVerificationPolicy<ChallengePolicyArg>(challengeArg) {
245+
class ChallengePolicy(challengeArg: ChallengePolicyArg) :
246+
ParameterizedVerificationPolicy<ChallengePolicyArg>(challengeArg) {
224247
constructor(challenge: String, applyToVC: Boolean = true, applyToVP: Boolean = true) : this(
225248
ChallengePolicyArg(
226249
setOf(

src/main/kotlin/id/walt/services/ecosystems/essif/TrustedIssuerClient.kt

+10-2
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,25 @@ object TrustedIssuerClient {
112112
return@runBlocking trustedIssuer
113113
}
114114

115-
fun getIssuer(did: String): TrustedIssuer = runBlocking {
115+
116+
fun getIssuer(did: String, registryAddress: String): TrustedIssuer = runBlocking {
116117
log.debug { "Getting trusted issuer with DID $did" }
117118

119+
val registryUrl = registryAddress + did
120+
118121
val trustedIssuer: String =
119-
WaltIdServices.http.get("https://api-pilot.ebsi.eu/trusted-issuers-registry/v2/issuers/$did").bodyAsText()
122+
WaltIdServices.http.get(registryUrl).bodyAsText()
120123

121124
log.debug { trustedIssuer }
122125

123126
return@runBlocking Klaxon().parse<TrustedIssuer>(trustedIssuer)!!
124127
}
125128

129+
fun getIssuer(did: String): TrustedIssuer = runBlocking {
130+
log.debug { "Getting trusted issuer with DID $did" }
131+
return@runBlocking getIssuer(did, "https://api-pilot.ebsi.eu/trusted-issuers-registry/v2/issuers/")
132+
}
133+
126134
///////////////////////////////////////////////////////////////////////////////////////////////
127135
//TODO: the methods below are stubbed - to be considered
128136

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package id.walt.auditor
2+
3+
import io.kotest.assertions.throwables.shouldThrow
4+
import io.kotest.core.spec.style.AnnotationSpec
5+
import io.kotest.matchers.should
6+
import io.kotest.matchers.shouldBe
7+
import io.kotest.matchers.types.beInstanceOf
8+
import org.junit.jupiter.api.Assertions.assertAll
9+
10+
class PolicyFactoryTest : AnnotationSpec() {
11+
12+
private val testArgument = TrustedIssuerRegistryPolicyArg("testArg")
13+
private val wrongArg = AnotherArg("Else")
14+
private val defaultARg = TrustedIssuerRegistryPolicyArg("https://api-pilot.ebsi.eu/trusted-issuers-registry/v2/issuers/")
15+
16+
@Test
17+
fun createTrustedIssuerRegistryPolicyWithoutOptionalArg() {
18+
val factoryToTest =
19+
PolicyFactory(TrustedIssuerRegistryPolicy::class, TrustedIssuerRegistryPolicyArg::class, "testPolicy", null, false)
20+
21+
assertAll(
22+
{ factoryToTest.create(testArgument).argument shouldBe testArgument },
23+
{ shouldThrow<IllegalArgumentException> { factoryToTest.create() } },
24+
{ shouldThrow<IllegalArgumentException> { factoryToTest.create(wrongArg) } }
25+
)
26+
}
27+
28+
@Test
29+
fun createTrustedIssuerRegistryPolicyWithOptionalArg() {
30+
val factoryToTest =
31+
PolicyFactory(TrustedIssuerRegistryPolicy::class, TrustedIssuerRegistryPolicyArg::class, "testPolicy", null, true)
32+
assertAll(
33+
{ factoryToTest.create(testArgument).argument shouldBe testArgument },
34+
{ factoryToTest.create().argument shouldBe defaultARg },
35+
{ shouldThrow<IllegalArgumentException> { factoryToTest.create(wrongArg) } }
36+
)
37+
}
38+
39+
@Test
40+
fun createSimplePolicy() {
41+
val factoryToTest =
42+
PolicyFactory<SignaturePolicy, Unit>(SignaturePolicy::class, null, "testPolicy", null)
43+
44+
assertAll(
45+
{ factoryToTest.create() should beInstanceOf<SignaturePolicy>() },
46+
{ factoryToTest.create(testArgument) shouldBe beInstanceOf<SignaturePolicy>() }
47+
)
48+
}
49+
}
50+
51+
52+
data class AnotherArg(val somethingElse: String)

src/test/kotlin/id/walt/auditor/TrustedIssuerRegistryPolicyTest.kt

+38-8
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,19 @@ import id.walt.signatory.Signatory
1515
import io.kotest.core.spec.style.AnnotationSpec
1616
import io.kotest.core.test.TestCase
1717
import io.kotest.matchers.shouldBe
18+
import io.mockk.CapturingSlot
1819
import io.mockk.every
1920
import io.mockk.mockkObject
21+
import org.junit.jupiter.api.assertAll
2022

2123
class TrustedIssuerRegistryPolicyTest : AnnotationSpec() {
2224

23-
private val testedPolicy = TrustedIssuerRegistryPolicy()
25+
private val defaultRegistry = "https://api-pilot.ebsi.eu/trusted-issuers-registry/v2/issuers/"
26+
private val otherRegistry = "http://my-other-registry.org/v3/issuers/"
27+
28+
private val simplePolicy = TrustedIssuerRegistryPolicy();
29+
private val parameterizedPolicy = TrustedIssuerRegistryPolicy(otherRegistry);
30+
2431
private val mockedHash = "mockHash"
2532
private val validAttrInfoJson =
2633
"{\"@context\":\"https://ebsi.eu\",\"type\":\"attribute\",\"name\":\"issuer\",\"data\":\"5d50b3fa18dde32b384d8c6d096869de\"}"
@@ -56,17 +63,29 @@ class TrustedIssuerRegistryPolicyTest : AnnotationSpec() {
5663
@Test
5764
fun whenTrustedIssuerRegistryContainsValidAttributeThenReturnTrue() {
5865
val attributeList = listOf(Attribute(mockedHash, encBase64Str(validAttrInfoJson)))
59-
mockTrustedIssuerWithAttributes(attributeList)
66+
val capture = mockTrustedIssuerWithAttributes(attributeList)
6067

61-
testedPolicy.verify(verifiableCredential) shouldBe true
68+
assertAll(
69+
{ simplePolicy.verify(verifiableCredential) shouldBe true },
70+
{ capture.captured shouldBe defaultRegistry }
71+
)
72+
73+
assertAll(
74+
{ parameterizedPolicy.verify(verifiableCredential) shouldBe true },
75+
{ capture.captured shouldBe otherRegistry }
76+
)
6277
}
6378

79+
6480
@Test
6581
fun whenTrustedIssuerRegistryContainsInvalidBase64AttributeThenReturnFalse() {
6682
val attributeList = listOf(Attribute(mockedHash, "invalidBase64EncodedString"))
6783
mockTrustedIssuerWithAttributes(attributeList)
6884

69-
testedPolicy.verify(verifiableCredential) shouldBe false
85+
assertAll(
86+
{ simplePolicy.verify(verifiableCredential) shouldBe false },
87+
{ parameterizedPolicy.verify(verifiableCredential) shouldBe false }
88+
)
7089
}
7190

7291
@Test
@@ -75,14 +94,25 @@ class TrustedIssuerRegistryPolicyTest : AnnotationSpec() {
7594
val attr2 = Attribute(mockedHash, encBase64Str("invalidAttr"))
7695
val attr3 = Attribute(mockedHash, encBase64Str(validAttrInfoJson))
7796
val attributeList = listOf(attr1, attr2, attr3)
78-
mockTrustedIssuerWithAttributes(attributeList)
97+
val capture = mockTrustedIssuerWithAttributes(attributeList)
98+
99+
assertAll(
100+
{ simplePolicy.verify(verifiableCredential) shouldBe true },
101+
{ capture.captured shouldBe defaultRegistry }
102+
)
103+
104+
assertAll(
105+
{ parameterizedPolicy.verify(verifiableCredential) shouldBe true },
106+
{ capture.captured shouldBe otherRegistry }
107+
)
79108

80-
testedPolicy.verify(verifiableCredential) shouldBe true
81109
}
82110

83-
private fun mockTrustedIssuerWithAttributes(attributeList: List<Attribute>) {
111+
private fun mockTrustedIssuerWithAttributes(attributeList: List<Attribute>): CapturingSlot<String> {
84112
val tirRecord = TrustedIssuer(did, attributeList)
113+
val slot = CapturingSlot<String>()
85114
mockkObject(TrustedIssuerClient)
86-
every { TrustedIssuerClient.getIssuer(any()) } returns tirRecord
115+
every { TrustedIssuerClient.getIssuer(any(), capture(slot)) } returns tirRecord
116+
return slot
87117
}
88118
}

0 commit comments

Comments
 (0)