Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow policies to be implemented with Java code #645

Merged
merged 1 commit into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
import java.io.File
import kotlin.test.*

Expand Down Expand Up @@ -47,7 +48,7 @@ class ExpirationDatePolicyTest {
@Test
fun `should NOT fail with a valid signed VC and data = jws-payload('vc')`() {

val data = notExpiredJws.decodeJws().payload["vc"]!!
val data = notExpiredJws.decodeJws().payload["vc"]!!.jsonObject

val policy = ExpirationDatePolicy()
val result = runBlocking { policy.verify(data = data, context = emptyMap()) }
Expand All @@ -58,7 +59,7 @@ class ExpirationDatePolicyTest {
@Test
fun `should NOT fail with a valid NOT signed VC and data = jwt`() {

val data = Json.parseToJsonElement(notExpiredJwt)
val data = Json.parseToJsonElement(notExpiredJwt).jsonObject

val policy = ExpirationDatePolicy()
val result = runBlocking { policy.verify(data = data, context = emptyMap()) }
Expand All @@ -69,7 +70,7 @@ class ExpirationDatePolicyTest {
@Test
fun `should fail ExpirationDatePolicy + jws-payload('vc')`() {

val data = expiredJws.decodeJws().payload["vc"]!!
val data = expiredJws.decodeJws().payload["vc"]!!.jsonObject

val policy = ExpirationDatePolicy()
val result = runBlocking { policy.verify(data = data, context = emptyMap()) }
Expand All @@ -80,7 +81,7 @@ class ExpirationDatePolicyTest {
@Test
fun `should fail ExpirationDatePolicy + jwt`() {

val data = Json.parseToJsonElement(expiredJwt)
val data = Json.parseToJsonElement(expiredJwt).jsonObject

val policy = ExpirationDatePolicy()
val result = runBlocking { policy.verify(data = data, context = emptyMap()) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package id.walt.credentials.verification

import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import love.forte.plugin.suspendtrans.annotation.JsPromise
import love.forte.plugin.suspendtrans.annotation.JvmAsync
Expand All @@ -20,3 +21,14 @@ abstract class CredentialDataValidatorPolicy(
abstract suspend fun verify(data: JsonObject, args: Any? = null, context: Map<String, Any>): Result<Any>

}

abstract class JavaCredentialDataValidatorPolicy(
override val name: String,
override val description: String? = null
): CredentialDataValidatorPolicy(name, description) {
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
return runCatching { javaVerify(data, args, context) }
}

abstract fun javaVerify(data: JsonObject, args: Any? = null, context: Map<String, Any>): Any
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package id.walt.credentials.verification

import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import love.forte.plugin.suspendtrans.annotation.JsPromise
import love.forte.plugin.suspendtrans.annotation.JvmAsync
import love.forte.plugin.suspendtrans.annotation.JvmBlocking
Expand All @@ -17,6 +17,16 @@ abstract class CredentialWrapperValidatorPolicy(
@JvmAsync
@JsPromise
@JsExport.Ignore
abstract suspend fun verify(data: JsonElement, args: Any? = null, context: Map<String, Any>): Result<Any>
abstract suspend fun verify(data: JsonObject, args: Any? = null, context: Map<String, Any>): Result<Any>
}

abstract class JavaCredentialWrapperValidatorPolicy(
override val name: String,
override val description: String? = null
): CredentialWrapperValidatorPolicy(name, description) {
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
return runCatching { javaVerify(data, args, context) }
}

abstract fun javaVerify(data: JsonObject, args: Any?, context: Map<String, Any>): Any
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package id.walt.credentials.verification

import kotlinx.serialization.json.JsonElement
import love.forte.plugin.suspendtrans.annotation.JsPromise
import love.forte.plugin.suspendtrans.annotation.JvmAsync
import love.forte.plugin.suspendtrans.annotation.JvmBlocking
Expand All @@ -17,3 +18,14 @@ abstract class JwtVerificationPolicy(override val name: String, override val des
abstract suspend fun verify(credential: String, args: Any? = null, context: Map<String, Any>): Result<Any>

}

abstract class JavaJwtVerificationPolicy(
override val name: String,
override val description: String? = null
): JwtVerificationPolicy(name, description) {
override suspend fun verify(credential: String, args: Any?, context: Map<String, Any>): Result<Any> {
return runCatching { javaVerify(credential, args, context) }
}

abstract fun javaVerify(credential: String, args: Any? = null, context: Map<String, Any>): Any
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ class AllowedIssuerPolicy : CredentialWrapperValidatorPolicy(
@JvmAsync
@JsPromise
@JsExport.Ignore
override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
val allowedIssuers = when (args) {
is JsonPrimitive -> listOf(args.content)
is JsonArray -> args.map { it.jsonPrimitive.content }
else -> throw IllegalArgumentException("Invalid argument, please provide a single allowed issuer, or an list of allowed issuers.")
}

val issuer =
data.jsonObject[JwsOption.ISSUER]?.jsonPrimitive?.content
?: data.jsonObject["issuer"]?.jsonPrimitive?.content
data[JwsOption.ISSUER]?.jsonPrimitive?.content
?: data["issuer"]?.jsonPrimitive?.content
?: throw IllegalArgumentException("No issuer found in credential: $data")

return when (issuer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ExpirationDatePolicy : CredentialWrapperValidatorPolicy(
@JvmAsync
@JsPromise
@JsExport.Ignore
override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
val (key, exp) = getExpirationKeyValuePair(data) ?: return buildPolicyUnavailableResult()

val now = Clock.System.now()
Expand All @@ -34,18 +34,18 @@ class ExpirationDatePolicy : CredentialWrapperValidatorPolicy(
}
}

private fun getExpirationKeyValuePair(data: JsonElement): Pair<String, Instant>? =
checkVc(data.jsonObject["vc"]) ?: checkVc(data) ?: checkJwt(data)
private fun getExpirationKeyValuePair(data: JsonObject): Pair<String, Instant>? =
checkVc(data["vc"]?.jsonObject) ?: checkVc(data) ?: checkJwt(data)

private fun checkJwt(data: JsonElement?) =
private fun checkJwt(data: JsonObject?) =
data?.jsonObject?.get(JwtClaims.NotAfter.getValue())?.jsonPrimitive?.longOrNull?.let {
Pair("jwt:${JwtClaims.NotAfter.getValue()}", Instant.fromEpochSeconds(it))
}

private fun checkVc(data: JsonElement?) =
data?.jsonObject?.get(VcClaims.V2.NotAfter.getValue())?.jsonPrimitive?.let {
private fun checkVc(data: JsonObject?) =
data?.get(VcClaims.V2.NotAfter.getValue())?.jsonPrimitive?.let {
Pair(VcClaims.V2.NotAfter.getValue(), Instant.parse(it.content))
} ?: data?.jsonObject?.get(VcClaims.V1.NotAfter.getValue())?.jsonPrimitive?.let {
} ?: data?.get(VcClaims.V1.NotAfter.getValue())?.jsonPrimitive?.let {
Pair(VcClaims.V1.NotAfter.getValue(), Instant.parse(it.content))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ class NotBeforeDatePolicy : CredentialWrapperValidatorPolicy(
@JvmAsync
@JsPromise
@JsExport.Ignore
override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
var successfulKey = ""
fun getEpochTimestamp(key: String) =
data.jsonObject[key]?.jsonPrimitive?.longOrNull?.let { Instant.fromEpochSeconds(it) }.also {
data[key]?.jsonPrimitive?.longOrNull?.let { Instant.fromEpochSeconds(it) }.also {
successfulKey = key
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import kotlinx.serialization.json.*
abstract class RevocationPolicyMp : CredentialWrapperValidatorPolicy(
"revoked_status_list", "Verifies Credential Status"
) {
abstract override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any>
abstract override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any>
}

expect class RevocationPolicy(): RevocationPolicyMp {
override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any>
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any>
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@ class WebhookPolicy : CredentialWrapperValidatorPolicy(
"Sends the credential data to an webhook URL as HTTP POST, and returns the verified status based on the webhooks set status code (success = 200 - 299)."
) {

val http = HttpClient {
install(ContentNegotiation) {
json()
companion object {
private val http = HttpClient {
install(ContentNegotiation) {
json()
}
}
}

@JvmBlocking
@JvmAsync
@JsPromise
@JsExport.Ignore
override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
val url = (args as JsonPrimitive).content

val response = http.post(url) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import id.walt.credentials.schemes.JwsSignatureScheme.JwsOption
import id.walt.credentials.verification.CredentialWrapperValidatorPolicy
import id.walt.credentials.verification.HolderBindingException
import id.walt.crypto.utils.JwsUtils.decodeJws
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.*
import love.forte.plugin.suspendtrans.annotation.JsPromise
import love.forte.plugin.suspendtrans.annotation.JvmAsync
import love.forte.plugin.suspendtrans.annotation.JvmBlocking
Expand All @@ -24,10 +21,10 @@ class HolderBindingPolicy : CredentialWrapperValidatorPolicy(
@JvmAsync
@JsPromise
@JsExport.Ignore
override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
val presenterDid = data.jsonObject[JwsOption.ISSUER]!!.jsonPrimitive.content
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
val presenterDid = data[JwsOption.ISSUER]!!.jsonPrimitive.content

val vp = data.jsonObject["vp"]?.jsonObject ?: throw IllegalArgumentException("No \"vp\" field in VP!")
val vp = data["vp"]?.jsonObject ?: throw IllegalArgumentException("No \"vp\" field in VP!")

val credentials =
vp["verifiableCredential"]?.jsonArray ?: throw IllegalArgumentException("No \"verifiableCredential\" field in \"vp\"!")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,10 @@ class MaximumCredentialsPolicy : CredentialWrapperValidatorPolicy(
@JvmAsync
@JsPromise
@JsExport.Ignore
override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
val n = (args as JsonPrimitive).int

when (data) {
is JsonObject -> data["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray
is JsonArray -> data
else -> throw IllegalArgumentException("Can")
}

val presentedCount = data.jsonObject["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray?.count()
val presentedCount = data["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray?.count()
?: return Result.success(JsonObject(mapOf("policy_available" to JsonPrimitive(false))))

val success = presentedCount <= n
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class MinimumCredentialsPolicy : CredentialWrapperValidatorPolicy(
@JvmAsync
@JsPromise
@JsExport.Ignore
override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
val n = (args as JsonPrimitive).int
val presentedCount = data.jsonObject["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray?.count()
val presentedCount = data["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray?.count()
?: return Result.success(JsonObject(mapOf("policy_available" to JsonPrimitive(false))))

val success = presentedCount >= n
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package id.walt.credentials.verification.policies

import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject

//@JsPromise
//@JsExport.Ignore
actual class RevocationPolicy: RevocationPolicyMp() {
actual override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
actual override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import java.util.zip.GZIPInputStream
actual class RevocationPolicy : RevocationPolicyMp() {
@JvmBlocking
@JvmAsync
actual override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
actual override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {

var successfulKey = ""

fun setKey(key: String) {
successfulKey = key
}

val credentialStatus = data.jsonObject["vc"]?.jsonObject?.get("credentialStatus")
val credentialStatus = data["vc"]?.jsonObject?.get("credentialStatus")
?: return Result.success(
JsonObject(mapOf("policy_available" to JsonPrimitive(false)))
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ class PresentationDefinitionPolicy : CredentialWrapperValidatorPolicy(
"Verifies that with an Verifiable Presentation at minimum the list of credentials `request_credentials` has been presented."
) {

override suspend fun verify(data: JsonElement, args: Any?, context: Map<String, Any>): Result<Any> {
override suspend fun verify(data: JsonObject, args: Any?, context: Map<String, Any>): Result<Any> {
val presentationDefinition = context["presentationDefinition"] as? PresentationDefinition
?: throw IllegalArgumentException("No presentationDefinition in context!")

val requestedTypes = presentationDefinition.primitiveVerificationGetTypeList()

val presentedTypes =
data.jsonObject["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray?.mapNotNull {
data["vp"]!!.jsonObject["verifiableCredential"]?.jsonArray?.mapNotNull {
it.jsonPrimitive.contentOrNull?.decodeJws()?.payload
?.jsonObject?.get("vc")?.jsonObject?.get("type")?.jsonArray?.last()?.jsonPrimitive?.contentOrNull
} ?: emptyList()
Expand Down
Loading