diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index a563245..040567e 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,14 +4,6 @@
-
-
-
-
-
-
-
-
diff --git a/.idea/other.xml b/.idea/other.xml
deleted file mode 100644
index 94c96f6..0000000
--- a/.idea/other.xml
+++ /dev/null
@@ -1,318 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/MobileSdk/build.gradle.kts b/MobileSdk/build.gradle.kts
index 0459155..a8ae907 100644
--- a/MobileSdk/build.gradle.kts
+++ b/MobileSdk/build.gradle.kts
@@ -118,7 +118,7 @@ android {
}
dependencies {
- api("com.spruceid.mobile.sdk.rs:mobilesdkrs:0.0.32")
+ api("com.spruceid.mobile.sdk.rs:mobilesdkrs:0.0.33")
//noinspection GradleCompatible
implementation("com.android.support:appcompat-v7:28.0.0")
/* Begin UI dependencies */
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/BaseCredential.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/BaseCredential.kt
deleted file mode 100644
index 79e8657..0000000
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/BaseCredential.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.spruceid.mobile.sdk
-
-open class BaseCredential {
- private var id: String?
-
- constructor() {
- this.id = null
- }
-
- constructor(id: String) {
- this.id = id
- }
-
- fun getId(): String? {
- return this.id
- }
-
- fun setId(id: String) {
- this.id = id
- }
-
- override fun toString(): String {
- return "Credential($id)"
- }
-
- open fun get(keys: List): Map {
- return if (keys.contains("id")) {
- mapOf("id" to this.id!!)
- } else {
- emptyMap()
- }
- }
-}
\ No newline at end of file
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/Credential.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/Credential.kt
new file mode 100644
index 0000000..993b4f5
--- /dev/null
+++ b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/Credential.kt
@@ -0,0 +1,100 @@
+package com.spruceid.mobile.sdk
+
+import com.spruceid.mobile.sdk.rs.JsonVc
+import com.spruceid.mobile.sdk.rs.JwtVc
+import com.spruceid.mobile.sdk.rs.Mdoc
+import org.json.JSONObject
+
+/**
+ * Access all of the elements in the mdoc, ignoring namespaces and missing elements that cannot be encoded as JSON.
+ */
+fun Mdoc.jsonEncodedDetailsAll(): JSONObject = this.jsonEncodedDetailsInternal(null)
+
+/**
+ * Access the specified elements in the mdoc, ignoring namespaces and missing elements that cannot be encoded as JSON.
+ */
+fun Mdoc.jsonEncodedDetailsFiltered(elementIdentifiers: List): JSONObject = this.jsonEncodedDetailsInternal(elementIdentifiers)
+
+
+private fun Mdoc.jsonEncodedDetailsInternal(elementIdentifiers: List?): JSONObject =
+ JSONObject(
+ // Ignore the namespaces.
+ this.details().values.flatMap { elements ->
+ elements.map { element ->
+ val id = element.identifier
+ val jsonString = element.value
+
+ // If a filter is provided, filter out non-specified ids.
+ if (elementIdentifiers != null) {
+ if (!elementIdentifiers.contains(id)) {
+ return@map null
+ }
+ }
+
+ if (jsonString != null) {
+ val json: JSONObject
+ try {
+ json = JSONObject(jsonString)
+ } catch (e: Error) {
+ print("failed to decode '$id' as JSON: $e")
+ return@map null
+ }
+ return@map Pair(id, json)
+ }
+
+ return@map null
+ }
+ }.filterNotNull().toMap()
+ )
+
+/**
+ * Access the W3C VCDM credential (not including the JWT envelope).
+ */
+fun JwtVc.credentialClaims(): JSONObject {
+ try {
+ return JSONObject(this.credentialAsJsonEncodedUtf8String())
+ } catch (e: Error) {
+ print("failed to decode VCDM data from UTF-8-encoded JSON")
+ return JSONObject()
+ }
+}
+
+/**
+ * Access the specified claims from the W3C VCDM credential (not including the JWT envelope).
+ */
+fun JwtVc.credentialClaimsFiltered(claimNames: List): JSONObject {
+ val old = this.credentialClaims()
+ val new = JSONObject()
+ for (name in claimNames) {
+ if (old.has(name)) {
+ new.put(name, old.get(name))
+ }
+ }
+ return new
+}
+
+/**
+ * Access the W3C VCDM credential.
+ */
+fun JsonVc.credentialClaims(): JSONObject {
+ try {
+ return JSONObject(this.credentialAsJsonEncodedUtf8String())
+ } catch (e: Error) {
+ print("failed to decode VCDM data from UTF-8-encoded JSON")
+ return JSONObject()
+ }
+}
+
+/**
+ * Access the specified claims from the W3C VCDM credential.
+ */
+fun JsonVc.credentialClaimsFiltered(claimNames: List): JSONObject {
+ val old = this.credentialClaims()
+ val new = JSONObject()
+ for (name in claimNames) {
+ if (old.has(name)) {
+ new.put(name, old.get(name))
+ }
+ }
+ return new
+}
\ No newline at end of file
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt
index a1c02e7..acb52e2 100644
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt
+++ b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialPack.kt
@@ -1,108 +1,98 @@
package com.spruceid.mobile.sdk
-import java.security.KeyFactory
-import java.security.KeyStore
-import java.security.cert.Certificate
-import java.security.cert.CertificateFactory
-import java.security.spec.PKCS8EncodedKeySpec
-import java.util.Base64
+import com.spruceid.mobile.sdk.rs.JsonVc
+import com.spruceid.mobile.sdk.rs.JwtVc
+import com.spruceid.mobile.sdk.rs.Mdoc
+import com.spruceid.mobile.sdk.rs.ParsedCredential
+import org.json.JSONObject
/**
* Collection of BaseCredentials with methods to interact with all instances
*/
class CredentialPack {
- private val credentials: MutableList
+ private val credentials: MutableList
constructor() {
credentials = mutableListOf()
}
- constructor(credentialsArray: MutableList) {
+ constructor(credentialsArray: MutableList) {
this.credentials = credentialsArray
}
- fun addW3CVC(credentialString: String): List {
- val vc = W3CVC(credentialString = credentialString)
- credentials.add(vc)
+ /**
+ * Add a JwtVc to the CredentialPack.
+ */
+ fun addJwtVc(jwtVc: JwtVc): List {
+ credentials.add(ParsedCredential.newJwtVcJson(jwtVc))
return credentials
}
- fun addMDoc(
- id: String,
- mdocBase64: String,
- keyPEM: String,
- keyBase64: String
- ): List {
- try {
- val decodedKey = Base64.getDecoder().decode(
- keyBase64
- )
-
- val privateKey = KeyFactory.getInstance(
- "EC"
- ).generatePrivate(
- PKCS8EncodedKeySpec(
- decodedKey
- )
- )
-
- val cert: Array = arrayOf(
- CertificateFactory.getInstance(
- "X.509"
- ).generateCertificate(
- keyPEM.byteInputStream()
- )
- )
-
- val ks: KeyStore = KeyStore.getInstance(
- "AndroidKeyStore"
- )
-
- ks.load(
- null
- )
-
- ks.setKeyEntry(
- "someAlias",
- privateKey,
- null,
- cert
- )
-
- credentials.add(
- MDoc(
- id,
- Base64.getDecoder().decode(mdocBase64),
- "someAlias"
- )
- )
- } catch (e: Throwable) {
- print(
- e
- )
- throw e
- }
+ /**
+ * Add a JsonVc to the CredentialPack.
+ */
+ fun addJsonVc(jsonVc: JsonVc): List {
+ credentials.add(ParsedCredential.newLdpVc(jsonVc))
return credentials
}
- fun get(keys: List): Map> {
- val values = emptyMap>().toMutableMap()
-
- for (credential in credentials) {
- values[credential.getId()!!] = credential.get(keys)
- }
- return values
- }
-
- fun getCredentialsByIds(credentialsIds: List): List {
- return credentials.filter { credential -> credentialsIds.contains(credential.getId()) }
- }
-
- fun getCredentials(): List {
+ /**
+ * Add an Mdoc to the CredentialPack.
+ */
+ fun addMdoc(mdoc: Mdoc): List {
+ credentials.add(ParsedCredential.newMsoMdoc(mdoc))
return credentials
}
- fun getCredentialById(credentialId: String): BaseCredential? {
- return credentials.find { credential -> credential.getId().equals(credentialId) }
- }
+ /**
+ * Find claims from all credentials in this CredentialPack.
+ */
+ fun findCredentialClaims(claimNames: List): Map =
+ this.list()
+ .map { credential ->
+ var claims: JSONObject
+ val mdoc = credential.asMsoMdoc()
+ val jwtVc = credential.asJwtVc()
+ val jsonVc = credential.asJsonVc()
+
+ if (mdoc != null) {
+ claims = mdoc.jsonEncodedDetailsFiltered(claimNames)
+ } else if (jwtVc != null) {
+ claims = jwtVc.credentialClaimsFiltered(claimNames)
+ } else if (jsonVc != null) {
+ claims = jsonVc.credentialClaimsFiltered(claimNames)
+ } else {
+ var type: String
+ try {
+ type = credential.intoGenericForm().type
+ } catch (e: Error) {
+ type = "unknown"
+ }
+ print("unsupported credential type: $type")
+ claims = JSONObject()
+ }
+
+ return@map Pair(credential.id(), claims)
+ }
+ .toMap()
+
+
+ /**
+ * Get credentials by id.
+ */
+ fun getCredentialsByIds(credentialsIds: List): List =
+ this.list().filter { credential -> credentialsIds.contains(credential.id()) }
+
+
+ /**
+ * Get a credential by id.
+ */
+ fun getCredentialById(credentialId: String): ParsedCredential? =
+ this.list().find { credential -> credential.id() == credentialId }
+
+
+ /**
+ * List all of the credentials in the CredentialPack.
+ */
+ fun list(): List = this.credentials
}
\ No newline at end of file
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialsViewModel.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialsViewModel.kt
index 8bc1840..8f2ed2a 100644
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialsViewModel.kt
+++ b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/CredentialsViewModel.kt
@@ -5,6 +5,7 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import com.spruceid.mobile.sdk.rs.ItemsRequest
import com.spruceid.mobile.sdk.rs.MdlPresentationSession
+import com.spruceid.mobile.sdk.rs.ParsedCredential
import com.spruceid.mobile.sdk.rs.initializeMdlPresentationFromBytes
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -14,7 +15,7 @@ import java.util.UUID
class CredentialsViewModel : ViewModel() {
- private val _credentials = MutableStateFlow>(arrayListOf())
+ private val _credentials = MutableStateFlow>(arrayListOf())
val credentials = _credentials.asStateFlow()
private val _currState = MutableStateFlow(PresentmentState.UNINITIALIZED)
@@ -37,7 +38,7 @@ class CredentialsViewModel : ViewModel() {
private val _transport = MutableStateFlow(null)
- fun storeCredential(credential: BaseCredential) {
+ fun storeCredential(credential: ParsedCredential) {
_credentials.value.add(credential)
}
diff --git a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt
index e2f4fc9..7457b76 100644
--- a/MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt
+++ b/MobileSdk/src/main/java/com/spruceid/mobile/sdk/ui/BaseCard.kt
@@ -9,6 +9,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.spruceid.mobile.sdk.CredentialPack
+import org.json.JSONObject
/**
* Data class with the specification to display the credential pack in a list view
@@ -23,13 +24,13 @@ import com.spruceid.mobile.sdk.CredentialPack
*/
data class CardRenderingListView(
val titleKeys: List,
- val titleFormatter: @Composable ((values: Map>) -> Unit)? = null,
+ val titleFormatter: @Composable ((values: Map) -> Unit)? = null,
val descriptionKeys: List? = null,
- val descriptionFormatter: @Composable ((values: Map>) -> Unit)? = null,
+ val descriptionFormatter: @Composable ((values: Map) -> Unit)? = null,
val leadingIconKeys: List? = null,
- val leadingIconFormatter: @Composable ((values: Map>) -> Unit)? = null,
+ val leadingIconFormatter: @Composable ((values: Map) -> Unit)? = null,
val trailingActionKeys: List? = null,
- val trailingActionButton: @Composable ((values: Map>) -> Unit)? = null
+ val trailingActionButton: @Composable ((values: Map) -> Unit)? = null
)
/**
@@ -39,7 +40,7 @@ data class CardRenderingListView(
*/
data class CardRenderingDetailsField(
val keys: List,
- val formatter: @Composable ((values: Map>) -> Unit)? = null
+ val formatter: @Composable ((values: Map) -> Unit)? = null
)
/**
@@ -99,8 +100,8 @@ fun CardListView(
credentialPack: CredentialPack,
rendering: CardRenderingListView
) {
- val titleValues = credentialPack.get(rendering.titleKeys)
- val descriptionValues = credentialPack.get(rendering.descriptionKeys ?: emptyList())
+ val titleValues = credentialPack.findCredentialClaims(rendering.titleKeys)
+ val descriptionValues = credentialPack.findCredentialClaims(rendering.descriptionKeys ?: emptyList())
Row(
Modifier.height(intrinsicSize = IntrinsicSize.Max)
@@ -108,7 +109,7 @@ fun CardListView(
// Leading icon
if(rendering.leadingIconFormatter != null) {
rendering.leadingIconFormatter.invoke(
- credentialPack.get(rendering.leadingIconKeys ?: emptyList())
+ credentialPack.findCredentialClaims(rendering.leadingIconKeys ?: emptyList())
)
}
@@ -118,8 +119,11 @@ fun CardListView(
rendering.titleFormatter.invoke(titleValues)
} else {
Text(text = titleValues.values
- .fold(emptyList()) { acc, next -> acc + next.values
- .joinToString(" ") { value -> value.toString() }
+ .fold(emptyList()) { acc, next -> acc +
+ next.keys()
+ .asSequence()
+ .map { key -> next.get(key)}
+ .joinToString(" ") { value -> value.toString() }
}.joinToString("").trim())
}
@@ -128,8 +132,11 @@ fun CardListView(
rendering.descriptionFormatter.invoke(descriptionValues)
} else {
Text(text = descriptionValues.values
- .fold(emptyList()) { acc, next -> acc + next.values
- .joinToString(" ") { value -> value.toString() }
+ .fold(emptyList()) { acc, next -> acc +
+ next.keys()
+ .asSequence()
+ .map { key -> next.get(key)}
+ .joinToString(" ") { value -> value.toString() }
}.joinToString("").trim())
}
}
@@ -139,7 +146,7 @@ fun CardListView(
// Trailing action button
if(rendering.trailingActionButton != null) {
rendering.trailingActionButton.invoke(
- credentialPack.get(rendering.trailingActionKeys ?: emptyList())
+ credentialPack.findCredentialClaims(rendering.trailingActionKeys ?: emptyList())
)
}
}
@@ -157,14 +164,17 @@ fun CardDetailsView(
) {
Column {
rendering.fields.forEach {
- val values = credentialPack.get(it.keys)
+ val values = credentialPack.findCredentialClaims(it.keys)
if(it.formatter != null) {
it.formatter.invoke(values)
} else {
Text(text = values.values
- .fold(emptyList()) { acc, next -> acc + next.values
- .joinToString(" ") { value -> value.toString() }
+ .fold(emptyList()) { acc, next -> acc +
+ next.keys()
+ .asSequence()
+ .map { key -> next.get(key)}
+ .joinToString(" ") { value -> value.toString() }
}.joinToString("").trim())
}
}