Skip to content

Commit

Permalink
feat: revocation notification
Browse files Browse the repository at this point in the history
  • Loading branch information
cristianIOHK committed Apr 17, 2024
1 parent 862aef4 commit ead1b6a
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -328,5 +328,15 @@ interface Pluto {
*/
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 @@ -1026,7 +1026,29 @@ 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 @@ -230,7 +230,9 @@ class PrismAgent {
if (flowState.subscriptionCount.value <= 0) {
state = State.STOPPED
} else {
throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError("Agent state only accepts one subscription.")
throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError(
"Agent state only accepts one subscription."
)
// throw Exception("Agent state only accepts one subscription.")
}
}
Expand Down Expand Up @@ -427,7 +429,12 @@ class PrismAgent {

verificationMethods.values.forEach {
if (it.type.contains("X25519")) {
pluto.storePrivateKeys(keyAgreementKeyPair.privateKey as StorableKey, did, 0, it.id.toString())
pluto.storePrivateKeys(
keyAgreementKeyPair.privateKey as StorableKey,
did,
0,
it.id.toString()
)
} else if (it.type.contains("Ed25519")) {
pluto.storePrivateKeys(
authenticationKeyPair.privateKey as StorableKey,
Expand Down Expand Up @@ -565,7 +572,10 @@ class PrismAgent {
* @throws [PolluxError.InvalidPrismDID] if there is a problem creating the request credential.
* @throws [io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError] if credential type is not supported
**/
@Throws(PolluxError.InvalidPrismDID::class, io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError::class)
@Throws(
PolluxError.InvalidPrismDID::class,
io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError::class
)
suspend fun prepareRequestCredentialWithIssuer(
did: DID,
offer: OfferCredential
Expand Down Expand Up @@ -655,7 +665,9 @@ class PrismAgent {

else -> {
// TODO: Create new prism agent error message
throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError("Not supported credential type: $type")
throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError(
"Not supported credential type: $type"
)
// throw Error("Not supported credential type: $type")
}
}
Expand Down Expand Up @@ -686,7 +698,9 @@ class PrismAgent {
val metadata = if (credentialType == CredentialType.ANONCREDS_ISSUE) {
val plutoMetadata =
pluto.getCredentialMetadata(message.thid).first()
?: throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError("Invalid credential metadata")
?: throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError(
"Invalid credential metadata"
)
CredentialRequestMetadata(
plutoMetadata.json
)
Expand All @@ -709,8 +723,13 @@ class PrismAgent {
)
pluto.storeCredential(storableCredential)
return credential
} ?: throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError("Thid should not be null")
} ?: throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError("Cannot find attachment base64 in message")
}
?: throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError(
"Thid should not be null"
)
} ?: throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError(
"Cannot find attachment base64 in message"
)
}

// Message Events
Expand Down Expand Up @@ -811,7 +830,10 @@ class PrismAgent {
prismOnboarding.from = did
return prismOnboarding
} catch (e: Exception) {
throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError(e.message, e.cause)
throw io.iohk.atala.prism.walletsdk.domain.models.UnknownError.SomethingWentWrongError(
e.message,
e.cause
)
}
}

Expand Down Expand Up @@ -913,7 +935,10 @@ class PrismAgent {

val privateKeyKeyPath = pluto.getPrismDIDKeyPathIndex(subjectDID).first()
val keyPair =
Secp256k1KeyPair.generateKeyPair(seed, KeyCurve(Curve.SECP256K1, privateKeyKeyPath))
Secp256k1KeyPair.generateKeyPair(
seed,
KeyCurve(Curve.SECP256K1, privateKeyKeyPath)
)
val requestData = request.attachments.mapNotNull {
when (it.data) {
is AttachmentJsonData -> it.data.data
Expand All @@ -936,7 +961,11 @@ class PrismAgent {
throw PrismAgentError.InvalidCredentialFormatError(CredentialType.ANONCREDS_PROOF_REQUEST)
}
val linkSecret = getLinkSecret()
val presentation = pollux.createVerifiablePresentationAnoncred(request, credential as AnonCredential, linkSecret)
val presentation = pollux.createVerifiablePresentationAnoncred(
request,
credential as AnonCredential,
linkSecret
)
presentationString = presentation.getJson()
}

Expand All @@ -959,6 +988,15 @@ class PrismAgent {
)
}

fun observeRevokedCredentials(): Flow<List<Credential>> {
return pluto.observeRevokedCredentials()
.map { list ->
list.map {
pollux.restoreCredential(it.restorationId, it.credentialData, it.revoked)
}
}
}

/**
* This method retrieves the link secret from Pluto.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ GROUP BY StorableCredential.id;
revokeCredentialById:
UPDATE StorableCredential
SET revoked = 1
WHERE id = :id;
WHERE id = :id;

observeRevokedCredential:
SELECT *
FROM StorableCredential
WHERE revoked = 1;
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,8 @@ class PlutoMock : Pluto {
override fun revokeCredential(credentialId: String) {
TODO("Not yet implemented")
}

override fun observeRevokedCredentials(): Flow<List<CredentialRecovery>> {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,8 @@ class PlutoMock : Pluto {
override fun revokeCredential(credentialId: String) {
TODO("Not yet implemented")
}

override fun observeRevokedCredentials(): Flow<List<CredentialRecovery>> {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ class PrismAgentTests {

@Test
fun testStartPrismAgent_whenCalled_thenStatusIsRunning() = runTest {
val getLinkSecretReturn = flow<String> { "linkSecret" }
val getLinkSecretReturn = flow<String> { emit("linkSecret") }
plutoMock.getLinkSecretReturn = getLinkSecretReturn
val agent = PrismAgent(
apollo = apolloMock,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,14 @@ class CredentialsViewModel(application: Application) : AndroidViewModel(applicat

private var credentials: MutableLiveData<List<Credential>> = MutableLiveData()

init {
fun credentialsStream(): LiveData<List<Credential>> {
viewModelScope.launch {
Sdk.getInstance().agent.let {
it.getAllCredentials().collect { list ->
credentials.postValue(list)
}
}
}
}

fun credentialsStream(): LiveData<List<Credential>> {
return credentials
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import io.iohk.atala.prism.sampleapp.R
Expand Down Expand Up @@ -52,7 +53,8 @@ class MessagesFragment : Fragment() {

// Set up the spinner with the options
context?.let {
val adapter = CustomArrayAdapter(it, android.R.layout.simple_spinner_dropdown_item, credentials)
val adapter =
CustomArrayAdapter(it, android.R.layout.simple_spinner_dropdown_item, credentials)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
dialogBinding.spinner.adapter = adapter

Expand Down Expand Up @@ -81,6 +83,16 @@ class MessagesFragment : Fragment() {
viewModel.preparePresentationProof(credential, message)
}
}
viewModel.revokedCredentialsStream()
.observe(this.viewLifecycleOwner) { revokedCredentials ->
if (revokedCredentials.isNotEmpty()) {
Toast.makeText(
context,
"Credential revoked ID: ${revokedCredentials.last().id}",
Toast.LENGTH_LONG
).show()
}
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application
private val issuedCredentials: ArrayList<String> = arrayListOf()
private val processedOffers: ArrayList<String> = arrayListOf()
private val db: AppDatabase = DatabaseClient.getInstance()
private val revokedCredentialsNotified: MutableList<Credential> = mutableListOf()
private var revokedCredentials: MutableLiveData<List<Credential>> = MutableLiveData()

init {
viewModelScope.launch(Dispatchers.IO) {
Expand Down Expand Up @@ -103,6 +105,27 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application
}
}

fun revokedCredentialsStream(): LiveData<List<Credential>> {
viewModelScope.launch {
Sdk.getInstance().agent.let {
it.observeRevokedCredentials().collect { list ->
val newRevokedCredentials = list.filter { newCredential ->
revokedCredentialsNotified.none { notifiedCredential ->
notifiedCredential.id == newCredential.id
}
}
if (newRevokedCredentials.isNotEmpty()) {
revokedCredentialsNotified.addAll(newRevokedCredentials)
revokedCredentials.postValue(newRevokedCredentials)
} else {
revokedCredentials.postValue(emptyList())
}
}
}
}
return revokedCredentials
}

private suspend fun processMessages(messages: List<Message>) {
val sdk = Sdk.getInstance()
val messageIds: List<String> = messages.map { it.id }
Expand Down

0 comments on commit ead1b6a

Please sign in to comment.