Skip to content

Commit

Permalink
feat: revocation notification event (#148)
Browse files Browse the repository at this point in the history
Co-authored-by: Ahmed Moussa <ahmed.moussa@iohk.io>
Signed-off-by: Cristian G <cristian.castro@iohk.io>
  • Loading branch information
cristianIOHK and hamada147 committed May 13, 2024
1 parent 823b8b3 commit e1753ec
Show file tree
Hide file tree
Showing 27 changed files with 411 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class AnoncredsTests {
polluxMock = PolluxMock()
mediationHandlerMock = MediationHandlerMock()
// Pairing will be removed in the future
connectionManager = ConnectionManager(mercuryMock, castorMock, plutoMock, mediationHandlerMock, mutableListOf())
connectionManager = ConnectionManager(mercuryMock, castorMock, plutoMock, mediationHandlerMock, mutableListOf(), polluxMock)
json = Json {
ignoreUnknownKeys = true
prettyPrint = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,16 @@ interface Pluto {
* or null if no metadata is found.
*/
fun getCredentialMetadata(linkSecretName: String): Flow<CredentialRequestMeta?>

/**
* Revokes an existing credential using the credential ID.
*
* @param credentialId The ID of the credential to be revoked
*/
fun revokeCredential(credentialId: String)

/**
* Provides a flow to listen for revoked credentials.
*/
fun observeRevokedCredentials(): Flow<List<CredentialRecovery>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ interface Pollux {
* @param credentialData The byte array containing the credential data.
* @return The restored credential.
*/
fun restoreCredential(restorationIdentifier: String, credentialData: ByteArray): Credential
fun restoreCredential(restorationIdentifier: String, credentialData: ByteArray, revoked: Boolean): Credential

/**
* Converts a [Credential] object to a [StorableCredential] object of the specified [CredentialType].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ interface Credential {
val subject: String?
val claims: Array<Claim>
val properties: Map<String, Any?>
var revoked: Boolean?
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ interface StorableCredential : Credential {
val credentialUpdated: String?
val credentialSchema: String?
val validUntil: String?
val revoked: Boolean?
val availableClaims: Array<String>

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ package io.iohk.atala.prism.walletsdk.pluto
* @property restorationId The restoration ID associated with the credential recovery.
* @property credentialData The credential data as a byte array.
*/
class CredentialRecovery(val restorationId: String, val credentialData: ByteArray)
class CredentialRecovery(val restorationId: String, val credentialData: ByteArray, val revoked: Boolean)
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ class PlutoImpl(private val connection: DbConnection) : Pluto {
PrismPlutoDb.Schema.version,
AfterVersion(1) {
it.execute(null, "ALTER TABLE CredentialMetadata DROB COLUMN nonce;", 0)
it.execute(null, "ALTER TABLE CredentialMetadata DROB COLUMN linkSecretBlindingData;", 0)
it.execute(
null,
"ALTER TABLE CredentialMetadata DROB COLUMN linkSecretBlindingData;",
0
)
it.execute(null, "ALTER TABLE CredentialMetadata ADD COLUMN json TEXT;", 0)
}
)
Expand Down Expand Up @@ -926,7 +930,8 @@ class PlutoImpl(private val connection: DbConnection) : Pluto {
it.executeAsList().map { credential ->
CredentialRecovery(
restorationId = credential.recoveryId,
credentialData = credential.credentialData
credentialData = credential.credentialData,
revoked = credential.revoked != 0
)
}
}
Expand Down Expand Up @@ -1020,4 +1025,30 @@ class PlutoImpl(private val connection: DbConnection) : Pluto {
)
}
}

/**
* Revokes an existing credential using the credential ID.
*
* @param credentialId The ID of the credential to be revoked
*/
override fun revokeCredential(credentialId: String) {
getInstance().storableCredentialQueries.revokeCredentialById(credentialId)
}

/**
* Provides a flow to listen for revoked credentials.
*/
override fun observeRevokedCredentials(): Flow<List<CredentialRecovery>> {
return getInstance().storableCredentialQueries.observeRevokedCredential()
.asFlow()
.map {
it.executeAsList().map { credential ->
CredentialRecovery(
restorationId = credential.recoveryId,
credentialData = credential.credentialData,
revoked = true
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,25 +147,29 @@ class PolluxImpl(
*/
override fun restoreCredential(
restorationIdentifier: String,
credentialData: ByteArray
credentialData: ByteArray,
revoked: Boolean
): Credential {
return when (restorationIdentifier) {
val cred: Credential
when (restorationIdentifier) {
"jwt+credential" -> {
JWTCredential(credentialData.decodeToString())
cred = JWTCredential(credentialData.decodeToString())
}

"anon+credential" -> {
AnonCredential.fromStorableData(credentialData)
cred = AnonCredential.fromStorableData(credentialData)
}

"w3c+credential" -> {
Json.decodeFromString<W3CCredential>(credentialData.decodeToString())
cred = Json.decodeFromString<W3CCredential>(credentialData.decodeToString())
}

else -> {
throw PolluxError.InvalidCredentialError()
}
}
cred.revoked = revoked
return cred
}

/**
Expand Down Expand Up @@ -258,8 +262,10 @@ class PolluxImpl(
val presentationRequest = PresentationRequest(attachmentBase64.base64.base64UrlDecoded)
val cred = anoncreds_wrapper.Credential(credential.id)

val requestedAttributes = presentationRequest.getRequestedAttributes().toListRequestedAttribute()
val requestedPredicate = presentationRequest.getRequestedPredicates().toListRequestedPredicate()
val requestedAttributes =
presentationRequest.getRequestedAttributes().toListRequestedAttribute()
val requestedPredicate =
presentationRequest.getRequestedPredicates().toListRequestedPredicate()

val credentialRequests = CredentialRequests(
credential = cred,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ data class AnonCredential(
return properties.toMap()
}

override var revoked: Boolean? = null

/**
* Converts the current credential object into a storable credential object.
*
Expand Down Expand Up @@ -119,8 +121,7 @@ data class AnonCredential(
override val validUntil: String?
get() = null

override val revoked: Boolean?
get() = null
override var revoked: Boolean? = c.revoked

override val availableClaims: Array<String>
get() = c.claims.map { it.key }.toTypedArray()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ data class JWTCredential(val data: String) : Credential {
return properties.toMap()
}

override var revoked: Boolean? = null

/**
* Converts the current instance of [JWTCredential] to a [StorableCredential].
*
Expand Down Expand Up @@ -105,8 +107,7 @@ data class JWTCredential(val data: String) : Credential {
get() = c.jwtPayload.verifiableCredential.credentialSchema?.type
override val validUntil: String?
get() = null
override val revoked: Boolean?
get() = null
override var revoked: Boolean? = c.revoked
override val availableClaims: Array<String>
get() = c.claims.map { it.key }.toTypedArray()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ data class W3CCredential @JvmOverloads constructor(
return properties.toMap()
}

override var revoked: Boolean? = null

/**
* Converts the current W3CCredential object to a StorableCredential object that can be stored and retrieved from a storage system.
*
Expand All @@ -102,8 +104,7 @@ data class W3CCredential @JvmOverloads constructor(
get() = c.credentialSchema?.type
override val validUntil: String?
get() = null
override val revoked: Boolean?
get() = null
override var revoked: Boolean? = c.revoked
override val availableClaims: Array<String>
get() = claims.map { it.key }.toTypedArray()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@

package io.iohk.atala.prism.walletsdk.prismagent

import io.iohk.atala.prism.apollo.base64.base64UrlDecoded
import io.iohk.atala.prism.walletsdk.domain.buildingblocks.Castor
import io.iohk.atala.prism.walletsdk.domain.buildingblocks.Mercury
import io.iohk.atala.prism.walletsdk.domain.buildingblocks.Pluto
import io.iohk.atala.prism.walletsdk.domain.buildingblocks.Pollux
import io.iohk.atala.prism.walletsdk.domain.models.AttachmentBase64
import io.iohk.atala.prism.walletsdk.domain.models.CredentialType
import io.iohk.atala.prism.walletsdk.domain.models.DID
import io.iohk.atala.prism.walletsdk.domain.models.DIDPair
import io.iohk.atala.prism.walletsdk.domain.models.Message
import io.iohk.atala.prism.walletsdk.prismagent.connectionsmanager.ConnectionsManager
import io.iohk.atala.prism.walletsdk.prismagent.connectionsmanager.DIDCommConnection
import io.iohk.atala.prism.walletsdk.prismagent.mediation.MediationHandler
import io.iohk.atala.prism.walletsdk.prismagent.protocols.ProtocolType
import io.iohk.atala.prism.walletsdk.prismagent.protocols.issueCredential.IssueCredential
import io.iohk.atala.prism.walletsdk.prismagent.protocols.revocation.RevocationNotification
import java.time.Duration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -36,6 +43,7 @@ class ConnectionManager(
private val pluto: Pluto,
internal val mediationHandler: MediationHandler,
private var pairings: MutableList<DIDPair>,
private val pollux: Pollux,
private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
) : ConnectionsManager, DIDCommConnection {

Expand Down Expand Up @@ -73,22 +81,7 @@ class ConnectionManager(
mediationHandler.listenUnreadMessages(
serviceEndpointUrl
) { arrayMessages ->
// Process the received messages
val messagesIds = mutableListOf<String>()
val messages = mutableListOf<Message>()
arrayMessages.map { pair ->
messagesIds.add(pair.first)
messages.add(pair.second)
}
// If there are any messages, mark them as read and store them
scope.launch {
if (messagesIds.isNotEmpty()) {
mediationHandler.registerMessagesAsRead(
messagesIds.toTypedArray()
)
pluto.storeMessages(messages)
}
}
processMessages(arrayMessages)
}
}

Expand All @@ -97,18 +90,7 @@ class ConnectionManager(
while (true) {
// Continuously await and process new messages
awaitMessages().collect { array ->
val messagesIds = mutableListOf<String>()
val messages = mutableListOf<Message>()
array.map { pair ->
messagesIds.add(pair.first)
messages.add(pair.second)
}
if (messagesIds.isNotEmpty()) {
mediationHandler.registerMessagesAsRead(
messagesIds.toTypedArray()
)
pluto.storeMessages(messages)
}
processMessages(array)
}
// Wait for the specified request interval before fetching new messages
delay(Duration.ofSeconds(requestInterval.toLong()).toMillis())
Expand Down Expand Up @@ -198,6 +180,48 @@ class ConnectionManager(
return null
}

internal fun processMessages(arrayMessages: Array<Pair<String, Message>>) {
scope.launch {
val messagesIds = mutableListOf<String>()
val messages = mutableListOf<Message>()
arrayMessages.map { pair ->
messagesIds.add(pair.first)
messages.add(pair.second)
}

val allMessages = pluto.getAllMessages().first()

val revokedMessages = messages.filter { it.piuri == ProtocolType.PrismRevocation.value }
revokedMessages.forEach { msg ->
val revokedMessage = RevocationNotification.fromMessage(msg)
val threadId = revokedMessage.body.threadId
val matchingMessages =
allMessages.filter { it.piuri == ProtocolType.DidcommIssueCredential.value && it.thid == threadId }
if (matchingMessages.isNotEmpty()) {
matchingMessages.forEach { message ->
val issueMessage = IssueCredential.fromMessage(message)
if (pollux.extractCredentialFormatFromMessage(issueMessage.attachments) == CredentialType.JWT) {
val attachment =
issueMessage.attachments.firstOrNull()?.data as? AttachmentBase64
attachment?.let {
val credentialId = it.base64.base64UrlDecoded
pluto.revokeCredential(credentialId)
}
}
}
}
}

// If there are any messages, mark them as read and store them
if (messagesIds.isNotEmpty()) {
mediationHandler.registerMessagesAsRead(
messagesIds.toTypedArray()
)
pluto.storeMessages(messages)
}
}
}

/**
* Awaits a response to a specified message ID from the connection.
*
Expand Down
Loading

0 comments on commit e1753ec

Please sign in to comment.