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

Remove danubech dependency from credential package 1/2 #249

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package web5.sdk.credentials

import com.danubetech.verifiablecredentials.CredentialSubject
jiyoontbd marked this conversation as resolved.
Show resolved Hide resolved
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
Expand All @@ -13,6 +12,14 @@
import io.ktor.http.isSuccess
import io.ktor.serialization.jackson.jackson
import kotlinx.coroutines.runBlocking
import web5.sdk.credentials.model.CredentialSubject
import web5.sdk.credentials.model.DEFAULT_STATUS_LIST_2021_ENTRY_TYPE
import web5.sdk.credentials.model.DEFAULT_STATUS_LIST_2021_VC_TYPE
import web5.sdk.credentials.model.DEFAULT_STATUS_LIST_CONTEXT
import web5.sdk.credentials.model.DEFAULT_VC_CONTEXT
import web5.sdk.credentials.model.DEFAULT_VC_TYPE
import web5.sdk.credentials.model.StatusList2021Entry
import web5.sdk.credentials.model.VcDataModel
import web5.sdk.dids.DidResolvers
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
Expand All @@ -23,12 +30,6 @@
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream

/**
* Type alias representing the danubetech Status List 2021 Entry data model.
* This typealias simplifies the use of the [com.danubetech.verifiablecredentials.credentialstatus.StatusList2021Entry] class.
*/
public typealias StatusList2021Entry = com.danubetech.verifiablecredentials.credentialstatus.StatusList2021Entry

/**
* Status purpose of a status list credential or a credential with a credential status.
*/
Expand All @@ -47,6 +48,11 @@
*/
private const val STATUS_PURPOSE: String = "statusPurpose"

/**
* The JSON property key for a type.
*/
private const val TYPE: String = "type"

/**
* `StatusListCredential` represents a digitally verifiable status list credential according to the
* [W3C Verifiable Credentials Status List v2021](https://www.w3.org/TR/vc-status-list/).
Expand Down Expand Up @@ -104,19 +110,21 @@
throw IllegalArgumentException("issuer: $issuer not resolvable", e)
}

val claims = mapOf(STATUS_PURPOSE to statusPurpose.toString().lowercase(), ENCODED_LIST to bitString)
val credSubject = CredentialSubject.builder()
val claims = mapOf(TYPE to DEFAULT_STATUS_LIST_2021_ENTRY_TYPE,
STATUS_PURPOSE to statusPurpose.toString().lowercase(),
ENCODED_LIST to bitString)

val credSubject = CredentialSubject.Builder()
.id(URI.create(statusListCredentialId))
.type("StatusList2021")
.claims(claims)
.additionalClaims(claims)
.build()

val vcDataModel = VcDataModel.builder()
val vcDataModel = VcDataModel.Builder()
.id(URI.create(statusListCredentialId))
.context(mutableListOf(URI.create(DEFAULT_VC_CONTEXT), URI.create(DEFAULT_STATUS_LIST_CONTEXT)))
.type(mutableListOf(DEFAULT_VC_TYPE, DEFAULT_STATUS_LIST_2021_VC_TYPE))
.issuer(URI.create(issuer))
.issuanceDate(Date())
.context(URI.create("https://w3id.org/vc/status-list/2021/v1"))
.type("StatusList2021Credential")
jiyoontbd marked this conversation as resolved.
Show resolved Hide resolved
.credentialSubject(credSubject)
.build()

Expand All @@ -142,10 +150,11 @@
statusListCredential: VerifiableCredential
): Boolean {
val statusListEntryValue: StatusList2021Entry =
StatusList2021Entry.fromJsonObject(credentialToValidate.vcDataModel.credentialStatus.jsonObject)
StatusList2021Entry.fromJsonObject(credentialToValidate.vcDataModel.credentialStatus!!.toJson())

val statusListCredStatusPurpose: String? =
statusListCredential.vcDataModel.credentialSubject.jsonObject[STATUS_PURPOSE] as? String?
val credentialSubject = statusListCredential.vcDataModel.credentialSubject

val statusListCredStatusPurpose: String? = credentialSubject.additionalClaims[STATUS_PURPOSE] as? String?

require(statusListEntryValue.statusPurpose != null) {
"Status purpose in the credential to validate is null"
Expand All @@ -160,7 +169,7 @@
}

val compressedBitstring: String? =
statusListCredential.vcDataModel.credentialSubject.jsonObject[ENCODED_LIST] as? String?
credentialSubject.additionalClaims[ENCODED_LIST] as? String?

require(!compressedBitstring.isNullOrEmpty()) {
"Compressed bitstring is null or empty"
Expand Down Expand Up @@ -203,7 +212,7 @@

try {
val statusListEntryValue: StatusList2021Entry =
StatusList2021Entry.fromJsonObject(credentialToValidate.vcDataModel.credentialStatus.jsonObject)
StatusList2021Entry.fromJsonObject(credentialToValidate.vcDataModel.credentialStatus!!.toJson())
val statusListCredential =
client.fetchStatusListCredential(statusListEntryValue.statusListCredential.toString())

Expand Down Expand Up @@ -261,9 +270,11 @@
requireNotNull(vc.vcDataModel.credentialStatus) { "no credential status found in credential" }

val statusListEntry: StatusList2021Entry =
StatusList2021Entry.fromJsonObject(vc.vcDataModel.credentialStatus.jsonObject)
StatusList2021Entry.fromJsonObject(vc.vcDataModel.credentialStatus.toJson())

require(statusListEntry.statusPurpose == statusPurpose.toString().lowercase()) { "status purpose mismatch" }
require(statusListEntry.statusPurpose == statusPurpose.toString().lowercase()) {
"status purpose mismatch"

Check warning on line 276 in credentials/src/main/kotlin/web5/sdk/credentials/StatusListCredential.kt

View check run for this annotation

Codecov / codecov/patch

credentials/src/main/kotlin/web5/sdk/credentials/StatusListCredential.kt#L276

Added line #L276 was not covered by tests
}

if (!duplicateSet.add(statusListEntry.statusListIndex)) {
throw IllegalArgumentException("duplicate entry found with index: ${statusListEntry.statusListIndex}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package web5.sdk.credentials

import com.danubetech.verifiablecredentials.CredentialSubject
import com.danubetech.verifiablecredentials.credentialstatus.CredentialStatus
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.JsonNode
Expand All @@ -13,19 +11,19 @@ import com.nfeld.jsonpathkt.extension.read
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.JWTParser
import com.nimbusds.jwt.SignedJWT
import web5.sdk.credentials.model.CredentialSubject
import web5.sdk.credentials.model.DEFAULT_STATUS_LIST_CONTEXT
import web5.sdk.credentials.model.DEFAULT_VC_CONTEXT
import web5.sdk.credentials.model.DEFAULT_VC_TYPE
import web5.sdk.credentials.model.StatusList2021Entry
import web5.sdk.credentials.model.VcDataModel
import web5.sdk.credentials.util.JwtUtil
import web5.sdk.dids.Did
import java.net.URI
import java.security.SignatureException
import java.util.Date
import java.util.UUID

/**
* Type alias representing the danubetech Verifiable Credential data model.
* This typealias simplifies the use of the [com.danubetech.verifiablecredentials.VerifiableCredential] class.
*/
public typealias VcDataModel = com.danubetech.verifiablecredentials.VerifiableCredential

/**
* `VerifiableCredential` represents a digitally verifiable credential according to the
* [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/).
Expand All @@ -39,7 +37,7 @@ public typealias VcDataModel = com.danubetech.verifiablecredentials.VerifiableCr
public class VerifiableCredential internal constructor(public val vcDataModel: VcDataModel) {

public val type: String
get() = vcDataModel.types.last()
get() = vcDataModel.type.last()
public val issuer: String
get() = vcDataModel.issuer.toString()

Expand Down Expand Up @@ -123,7 +121,7 @@ public class VerifiableCredential internal constructor(public val vcDataModel: V
issuer: String,
subject: String,
data: T,
credentialStatus: CredentialStatus? = null,
credentialStatus: StatusList2021Entry? = null,
issuanceDate: Date = Date(),
expirationDate: Date? = null
): VerifiableCredential {
Expand All @@ -134,24 +132,25 @@ public class VerifiableCredential internal constructor(public val vcDataModel: V
false -> throw IllegalArgumentException("expected data to be parseable into a JSON object")
}

val credentialSubject = CredentialSubject.builder()
val credentialSubject = CredentialSubject.Builder()
nitro-neal marked this conversation as resolved.
Show resolved Hide resolved
.id(URI.create(subject))
.claims(mapData)
.additionalClaims(mapData)
.build()

val vcDataModel = VcDataModel.builder()
.type(type)
val contexts = mutableListOf(URI.create(DEFAULT_VC_CONTEXT))
if (credentialStatus != null) {
contexts.add(URI.create(DEFAULT_STATUS_LIST_CONTEXT))
}

val vcDataModel = VcDataModel.Builder()
.id(URI.create("urn:uuid:${UUID.randomUUID()}"))
.context(contexts)
.type(mutableListOf(DEFAULT_VC_TYPE, type))
.issuer(URI.create(issuer))
.issuanceDate(issuanceDate)
.apply { expirationDate?.let { expirationDate(it) } }
.expirationDate(expirationDate)
.credentialSubject(credentialSubject)
.apply {
credentialStatus?.let {
credentialStatus(it)
context(URI.create("https://w3id.org/vc/status-list/2021/v1"))
}
}
.credentialStatus(credentialStatus)
.build()

// This should be a no-op just to make sure we've set all the correct fields.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package web5.sdk.credentials.model

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import java.net.URI

public const val DEFAULT_STATUS_LIST_2021_VC_TYPE: String = "StatusList2021"
public const val DEFAULT_STATUS_LIST_2021_ENTRY_TYPE: String = "StatusList2021Entry"
public const val DEFAULT_STATUS_LIST_CONTEXT: String = "https://w3id.org/vc/status-list/2021/v1"
private fun getObjectMapper(): ObjectMapper = jacksonObjectMapper().apply {
registerKotlinModule()
setSerializationInclusion(JsonInclude.Include.NON_NULL)
}

/**
* The [StatusList2021Entry] instance representing the core data model of a bitstring status list entry.
*
* @see [Credential Status List](https://www.w3.org/community/reports/credentials/CG-FINAL-vc-status-list-2021-20230102/)
*/
public class StatusList2021Entry(
public val id: URI,
public val type: String,
public val statusListIndex: String,
public val statusListCredential: URI,
public val statusPurpose: String,
) {
/**
* Builder class for creating [StatusList2021Entry] instances.
*/
public class Builder {
private lateinit var id: URI
private var type: String = DEFAULT_STATUS_LIST_2021_ENTRY_TYPE
private lateinit var statusListIndex: String
private lateinit var statusListCredential: URI
private lateinit var statusPurpose: String

/**
* Sets the ID for the credential status list entry.
* @param id The unique identifier of the credential status list entry.
* @return Returns this builder to allow for chaining.
*/
public fun id(id: URI): Builder = apply { this.id = id }

/**
* Sets the type for the credential status list entry.
* @param type The type of the credential status list entry.
* @return Returns this builder to allow for chaining.
*/
public fun type(type: String): Builder = apply { this.type = type }

Check warning on line 52 in credentials/src/main/kotlin/web5/sdk/credentials/model/StatusList2021Entry.kt

View check run for this annotation

Codecov / codecov/patch

credentials/src/main/kotlin/web5/sdk/credentials/model/StatusList2021Entry.kt#L52

Added line #L52 was not covered by tests

/**
* Sets the status list index for the credential status list entry.
* @param statusListIndex The status list index of the credential status list entry.
* @return Returns this builder to allow for chaining.
*/
public fun statusListIndex(statusListIndex: String): Builder = apply { this.statusListIndex = statusListIndex }

/**
* Sets the status list credential for the credential status list entry.
* @param statusListCredential The status list credential of the credential status list entry.
* @return Returns this builder to allow for chaining.
*/
public fun statusListCredential(statusListCredential: URI): Builder =
apply { this.statusListCredential = statusListCredential }

/**
* Sets the status purpose for the credential status list entry.
* @param statusPurpose The status purpose of the credential status list entry.
* @return Returns this builder to allow for chaining.
*/
public fun statusPurpose(statusPurpose: String): Builder = apply { this.statusPurpose = statusPurpose }

/**
* Builds and returns the [StatusList2021Entry] object.
* @return The constructed [StatusList2021Entry] object.
* @throws IllegalStateException If any required fields are not set.
*/
public fun build(): StatusList2021Entry {
require(id.toString().isNotBlank()) { "Id cannot be blank" }
require(statusListIndex.isNotBlank()) { "StatusListIndex cannot be blank" }
require(statusListCredential.toString().isNotBlank()) { "StatusListCredential cannot be blank" }
require(statusPurpose.isNotBlank()) { "StatusPurpose cannot be blank" }

return StatusList2021Entry(
id = id,
type = type,
statusListIndex = statusListIndex,
statusListCredential = statusListCredential,
statusPurpose = statusPurpose
)
}
}

/**
* Converts the [StatusList2021Entry] instance to a JSON string.
*
* @return A JSON string representation of the [StatusList2021Entry] instance.
*/
public fun toJson(): String = getObjectMapper().writeValueAsString(this)

/**
* Converts the [StatusList2021Entry] instance into a Map representation.
*
* @return A Map containing key-value pairs representing the properties of the [StatusList2021Entry] instance.
*/
public fun toMap(): Map<String, Any> =
getObjectMapper().readValue(this.toJson(), object : TypeReference<Map<String, Any>>() {})

public companion object {
/**
* Parses a JSON string to create an instance of [StatusList2021Entry].
*
* @param jsonString The JSON string representation of a [StatusList2021Entry].
* @return An instance of [StatusList2021Entry].
*/
public fun fromJsonObject(jsonString: String): StatusList2021Entry =
getObjectMapper().readValue(jsonString, StatusList2021Entry::class.java)

/**
* Creates an instance of [StatusList2021Entry] from a map of its properties.
*
* @param map A map containing the properties of a [StatusList2021Entry].
* @return An instance of [StatusList2021Entry].
* @throws IllegalArgumentException If required properties are missing.
*/
public fun fromMap(map: Map<String, Any>): StatusList2021Entry {
val json = getObjectMapper().writeValueAsString(map)
return getObjectMapper().readValue(json, StatusList2021Entry::class.java)

Check warning on line 131 in credentials/src/main/kotlin/web5/sdk/credentials/model/StatusList2021Entry.kt

View check run for this annotation

Codecov / codecov/patch

credentials/src/main/kotlin/web5/sdk/credentials/model/StatusList2021Entry.kt#L130-L131

Added lines #L130 - L131 were not covered by tests
}
}
}
Loading
Loading