Skip to content

Commit

Permalink
Parse a vci request into structured object
Browse files Browse the repository at this point in the history
  • Loading branch information
QZHelen committed Nov 21, 2024
1 parent c527c39 commit 960053a
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/src/main/java/com/credman/cmwallet/CmWalletApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import com.credman.cmwallet.data.model.CredentialItem
import com.credman.cmwallet.data.repository.CredentialRepository
import com.credman.cmwallet.data.room.Credential
import com.credman.cmwallet.data.room.CredentialDatabase
import com.credman.cmwallet.openid4vci.MdocCredConfigsSupportedItem
import com.credman.cmwallet.openid4vci.OpenId4VCI
import com.google.android.gms.identitycredentials.IdentityCredentialClient
import com.google.android.gms.identitycredentials.IdentityCredentialManager
import com.google.android.gms.identitycredentials.RegisterCreationOptionsRequest
Expand Down
39 changes: 39 additions & 0 deletions app/src/main/java/com/credman/cmwallet/openid4vci/OpenId4VCI.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.credman.cmwallet.openid4vci

import org.json.JSONObject

class OpenId4VCI(val request: String) {
val requestJson: JSONObject = JSONObject(request)

val credentialIssuer: String
val credentialConfigurationIds: List<String>
val credentialConfigurationsSupportedMap: Map<String, CredConfigsSupportedItem>

init {
require(requestJson.has(CREDENTIAL_ISSUER)) { "Issuance request must contain $CREDENTIAL_ISSUER" }
require(requestJson.has(CREDENTIAL_CONFIGURATION_IDS)) { "Issuance request must contain $CREDENTIAL_CONFIGURATION_IDS" }
// This should be required for the DC API browser profile
require(requestJson.has(ISSUER_METADATA)) { "Issuance request must contain $ISSUER_METADATA" }

credentialIssuer = requestJson.getString(CREDENTIAL_ISSUER)
credentialConfigurationIds = requestJson.getJSONArray(CREDENTIAL_CONFIGURATION_IDS).let {
val ids = mutableListOf<String>()
for (i in 0..<it.length()) {
ids.add(it.getString(i))
}
ids
}

val issuerMetadataJson = requestJson.getJSONObject(ISSUER_METADATA)
require(issuerMetadataJson.has(CREDENTIAL_CONFIGURATION_SUPPORTED)) { "Issuance request must contain $CREDENTIAL_CONFIGURATION_SUPPORTED" }
val credConfigSupportedJson = issuerMetadataJson.getJSONObject(CREDENTIAL_CONFIGURATION_SUPPORTED)
val itr = credConfigSupportedJson.keys()
val tmpMap = mutableMapOf<String, CredConfigsSupportedItem>()
while (itr.hasNext()) {
val configId = itr.next()
val item = credConfigSupportedJson.getJSONObject(configId)
tmpMap[configId] = CredConfigsSupportedItem.createFrom(credConfigSupportedJson.getJSONObject(configId))
}
credentialConfigurationsSupportedMap = tmpMap
}
}
190 changes: 190 additions & 0 deletions app/src/main/java/com/credman/cmwallet/openid4vci/RequestDataModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package com.credman.cmwallet.openid4vci

import org.json.JSONObject

data class CredConfigsSupportedDisplay(
val name: String,
val locale: String? = null,
val logo: CredConfigsSupportedDisplayLogo? = null,
val description: String? = null,
val backgroundColor: String? = null,
// `https:` or `data:` scheme
val backgroundImage: String? = null,
val textColor: String,
) {
constructor(json: JSONObject) : this(
name = json.getString(NAME),
locale = json.optString(LOCALE),
logo = json.optJSONObject(LOGO)?.let {
CredConfigsSupportedDisplayLogo(uri = it.getString(URI), altText = it.optString(ALT_TEXT))
},
description = json.optString(DESCRIPTION),
backgroundColor = json.optString(BACKGROUND_COLOR),
backgroundImage = json.optJSONObject(BACKGROUND_IMAGE)?.getString(URI),
textColor = json.optString(TEXT_COLOR),
)
}

private fun JSONObject.getDisplays(): List<CredConfigsSupportedDisplay>? =
this.optJSONArray(DISPLAY)?.let {
val out = mutableListOf<CredConfigsSupportedDisplay>()
for (i in 0..< it.length()) {
out.add(CredConfigsSupportedDisplay(it.getJSONObject(i)))
}
out
}

data class CredConfigsSupportedDisplayLogo(
val uri: String,
val altText: String?
)

sealed class CredConfigsSupportedItem(
val format: String,
val cryptographicBindingMethodsSupported: List<String>? = null,
val credentialSigningAlgValuesSupported: List<String>? = null,
val display: List<CredConfigsSupportedDisplay>? = null,
) {
companion object {
fun createFrom(json: JSONObject): CredConfigsSupportedItem {
return if (json.has(DOCTYPE)) {
MdocCredConfigsSupportedItem(json)
} else {
UnknownCredConfigsSupportedItem(json)
}
}
}
}

class UnknownCredConfigsSupportedItem(
format: String,
cryptographicBindingMethodsSupported: List<String>? = null,
credentialSigningAlgValuesSupported: List<String>? = null,
display: List<CredConfigsSupportedDisplay>? = null,
) : CredConfigsSupportedItem(
format,
cryptographicBindingMethodsSupported,
credentialSigningAlgValuesSupported,
display
) {
constructor(json: JSONObject): this(
format = json.getString(FORMAT),
cryptographicBindingMethodsSupported = json.getCryptographicBindingMethodsSupported(),
credentialSigningAlgValuesSupported = json.getCredentialSigningAlgValuesSupported(),
display = json.getDisplays(),
)
}

class MdocCredConfigsSupportedItem(
format: String,
cryptographicBindingMethodsSupported: List<String>? = null,
credentialSigningAlgValuesSupported: List<String>? = null,
display: List<CredConfigsSupportedDisplay>? = null,
val doctype: String,
// Namespace as key e.g. "org.iso.18013.5.1"
val claims: Map<String, NamespacedClaims>?,
) : CredConfigsSupportedItem(
format,
cryptographicBindingMethodsSupported,
credentialSigningAlgValuesSupported,
display
) {
constructor(json: JSONObject): this(
format = json.getString(FORMAT),
cryptographicBindingMethodsSupported = json.getCryptographicBindingMethodsSupported(),
credentialSigningAlgValuesSupported = json.getCredentialSigningAlgValuesSupported(),
display = json.getDisplays(),
doctype = json.getString(DOCTYPE),
claims = json.optJSONObject(CLAIMS)?.let {
val keys = it.keys()
val out = mutableMapOf<String, NamespacedClaims>()
while(keys.hasNext()) {
val key = keys.next()
out[key] = NamespacedClaims(it.getJSONObject(key))
}
out
}
)
}

data class NamespacedClaims(
val values: Map<String, Claim>
) {
constructor(json: JSONObject) : this(
values = json.let {
val keys = it.keys()
val out = mutableMapOf<String, Claim>()
while(keys.hasNext()) {
val key = keys.next()
out[key] = Claim(it.getJSONObject(key))
}
out
}
)
}

data class Claim(
val display: List<ClaimDisplay>? = null,
val mandatory: Boolean = false,
val valueType: String? = null,
) {
constructor(json: JSONObject) : this(
display = json.optJSONArray(DISPLAY)?.let {
val out = mutableListOf<ClaimDisplay>()
for (i in 0..< it.length()) {
out.add(ClaimDisplay(it.getJSONObject(i)))
}
out
},
mandatory = json.optBoolean(MANDATORY, false),
valueType = json.optString(VALUE_TYPE),
)
}

data class ClaimDisplay(
val name: String?,
val locale: String?,
) {
constructor(json: JSONObject) : this(json.optString(NAME), json.optString(LOCALE))
}

private fun JSONObject.getCryptographicBindingMethodsSupported(): List<String>? =
this.optJSONArray(CRYPTOGRAPHIC_BINDING_METHODS_SUPPORTED)?.let {
val out = mutableListOf<String>()
for (i in 0..< it.length()) {
out.add(it.getString(i))
}
out
}

private fun JSONObject.getCredentialSigningAlgValuesSupported(): List<String>? =
this.optJSONArray(CREDENTIAL_SIGNING_ALG_VALUES_SUPPORTED)?.let {
val out = mutableListOf<String>()
for (i in 0..< it.length()) {
out.add(it.getString(i))
}
out
}


internal const val CREDENTIAL_ISSUER = "credential_issuer"
internal const val CREDENTIAL_CONFIGURATION_IDS = "credential_configuration_ids"
internal const val ISSUER_METADATA = "issuer_metadata"
internal const val CREDENTIAL_CONFIGURATION_SUPPORTED = "credential_configurations_supported"
internal const val CRYPTOGRAPHIC_BINDING_METHODS_SUPPORTED = "cryptographic_binding_methods_supported"
internal const val CREDENTIAL_SIGNING_ALG_VALUES_SUPPORTED = "credential_signing_alg_values_supported"
internal const val FORMAT = "format"
internal const val DOCTYPE = "doctype"
internal const val DISPLAY = "display"
internal const val NAME = "name"
internal const val URI = "uri"
internal const val LOCALE = "locale"
internal const val MANDATORY = "mandatory"
internal const val LOGO = "logo"
internal const val DESCRIPTION = "description"
internal const val CLAIMS = "claims"
internal const val BACKGROUND_IMAGE = "background_image"
internal const val BACKGROUND_COLOR = "background_color"
internal const val TEXT_COLOR = "text_color"
internal const val VALUE_TYPE = "value_type"
internal const val ALT_TEXT = "alt_text"

0 comments on commit 960053a

Please sign in to comment.