Skip to content

Commit

Permalink
Credential status list repo in memory implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Shota Jolbordi <shota.jolbordi@iohk.io>
  • Loading branch information
Shota Jolbordi committed Dec 15, 2023
1 parent 8809249 commit 9ea570a
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package io.iohk.atala.pollux.core.repository

import io.iohk.atala.pollux.core.model.CredentialStatusList
import io.iohk.atala.pollux.vc.jwt.{Issuer, StatusPurpose, revocation}
import io.iohk.atala.shared.models.{WalletAccessContext, WalletId}
import zio.*
import io.iohk.atala.pollux.core.model.*
import io.iohk.atala.pollux.vc.jwt.revocation.BitStringError.{
DecodingError,
EncodingError,
IndexOutOfBounds,
InvalidSize
}
import io.iohk.atala.castor.core.model.did.{CanonicalPrismDID, PrismDID}
import io.iohk.atala.pollux.vc.jwt.revocation.{BitString, VCStatusList2021}

import java.time.Instant
import java.util.UUID

class CredentialStatusListRepositoryInMemory(
walletToStatusListRefs: Ref[Map[WalletId, Ref[Map[UUID, CredentialStatusList]]]],
statusListToCredInStatusListRefs: Ref[Map[UUID, Ref[Map[UUID, CredentialInStatusList]]]]
) extends CredentialStatusListRepository {

private def walletToStatusListStorageRefs: URIO[WalletAccessContext, Ref[Map[UUID, CredentialStatusList]]] =
for {
walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId)
refs <- walletToStatusListRefs.get
maybeWalletRef = refs.get(walletId)
walletRef <- maybeWalletRef
.fold {
for {
ref <- Ref.make(Map.empty[UUID, CredentialStatusList])
_ <- walletToStatusListRefs.set(refs.updated(walletId, ref))
} yield ref
}(ZIO.succeed)
} yield walletRef

private def statusListToCredInStatusListStorageRefs(
statusListId: UUID
): Task[Ref[Map[UUID, CredentialInStatusList]]] =
for {
refs <- statusListToCredInStatusListRefs.get
maybeStatusListIdRef = refs.get(statusListId)
statusListIdRef <- maybeStatusListIdRef.fold {
for {
ref <- Ref.make(Map.empty[UUID, CredentialInStatusList])
_ <- statusListToCredInStatusListRefs.set(refs.updated(statusListId, ref))
} yield ref
}(ZIO.succeed)
} yield statusListIdRef

def getLatestOfTheWallet: RIO[WalletAccessContext, Option[CredentialStatusList]] = for {
storageRef <- walletToStatusListStorageRefs
storage <- storageRef.get
latest = storage.toSeq
.sortBy(_._2.createdAt) { (x, y) => if x.isAfter(y) then -1 else 1 /* DESC */ }
.headOption
.map(_._2)
} yield latest

def createNewForTheWallet(
jwtIssuer: Issuer,
statusListRegistryUrl: String
): RIO[WalletAccessContext, CredentialStatusList] = {

val id = UUID.randomUUID()
val issued = Instant.now()
val issuerDid = jwtIssuer.did.value
val canonical = PrismDID.fromString(issuerDid).fold(e => throw RuntimeException(e), _.asCanonical)

val encodedJwtCredential = for {
bitString <- BitString.getInstance().mapError {
case InvalidSize(message) => new Throwable(message)
case EncodingError(message) => new Throwable(message)
case DecodingError(message) => new Throwable(message)
case IndexOutOfBounds(message) => new Throwable(message)
}
emptyJwtCredential <- VCStatusList2021
.build(
vcId = s"$statusListRegistryUrl/credential-status/$id",
slId = "",
revocationData = bitString,
jwtIssuer = jwtIssuer
)
.mapError(x => new Throwable(x.msg))

encodedJwtCredential <- emptyJwtCredential.encoded
} yield encodedJwtCredential

for {
credential <- encodedJwtCredential
storageRef <- walletToStatusListStorageRefs
walletId <- ZIO.serviceWith[WalletAccessContext](_.walletId)
newCredentialStatusList = CredentialStatusList(
id = id,
walletId = walletId,
issuer = canonical,
issued = issued,
purpose = StatusPurpose.Revocation,
statusListJwtCredential = credential.value,
size = BitString.MIN_SL2021_SIZE,
lastUsedIndex = 0,
createdAt = Instant.now(),
updatedAt = None
)
_ <- storageRef.update(r => r + (newCredentialStatusList.id -> newCredentialStatusList))
} yield newCredentialStatusList

}

def allocateSpaceForCredential(
issueCredentialRecordId: DidCommID,
credentialStatusListId: UUID,
statusListIndex: Int
): RIO[WalletAccessContext, Unit] = {
val newCredentialInStatusList = CredentialInStatusList(
id = UUID.randomUUID(),
issueCredentialRecordId = issueCredentialRecordId,
credentialStatusListId = credentialStatusListId,
statusListIndex = statusListIndex,
isCanceled = false,
createdAt = Instant.now(),
updatedAt = None
)

for {
credentialInStatusListStorageRef <- statusListToCredInStatusListStorageRefs(credentialStatusListId)
_ <- credentialInStatusListStorageRef.update(r => r + (newCredentialInStatusList.id -> newCredentialInStatusList))
walletToStatusListStorageRef <- walletToStatusListStorageRefs
_ <- walletToStatusListStorageRef.update(r => {
val value = r.get(credentialStatusListId)
value.fold(r) { v =>
val updated = v.copy(lastUsedIndex = statusListIndex, updatedAt = Some(Instant.now))
r.updated(credentialStatusListId, updated)
}
})
} yield ()

}

}

object CredentialStatusListRepositoryInMemory {
val layer: ULayer[CredentialStatusListRepositoryInMemory] = ZLayer.fromZIO(
for {
walletToStatusList <- Ref
.make(Map.empty[WalletId, Ref[Map[UUID, CredentialStatusList]]])
statusListIdToCredInStatusList <- Ref.make(Map.empty[UUID, Ref[Map[UUID, CredentialInStatusList]]])
} yield CredentialStatusListRepositoryInMemory(walletToStatusList, statusListIdToCredInStatusList)
)
}

private case class CredentialInStatusList(
id: UUID,
issueCredentialRecordId: DidCommID,
credentialStatusListId: UUID,
statusListIndex: Int,
isCanceled: Boolean,
createdAt: Instant = Instant.now(),
updatedAt: Option[Instant] = None
)
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,7 @@ private class CredentialServiceImpl(
.mapError(RepositoryError.apply)
size = currentStatusList.size
lastUsedIndex = currentStatusList.lastUsedIndex
// TODO: concurrency issue
statusListToBeUsed <-
if lastUsedIndex < size then ZIO.succeed(currentStatusList)
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ object CredentialServiceImplSpec extends MockSpecDefault with CredentialServiceS
// Issuer accepts request
requestAcceptedRecord <- issuerSvc.acceptCredentialRequest(issuerRecordId)
// Issuer generates credential
credentialGenerateRecord <- issuerSvc.generateJWTCredential(issuerRecordId)
credentialGenerateRecord <- issuerSvc.generateJWTCredential(issuerRecordId, "https://test-status-list.registry")
// Issuer sends credential
_ <- issuerSvc.markCredentialSent(issuerRecordId)
msg <- ZIO.fromEither(credentialGenerateRecord.issueCredentialData.get.makeMessage.asJson.as[Message])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ object CredentialServiceNotifierSpec extends MockSpecDefault with CredentialServ
_ <- svc.markOfferSent(issuerRecordId)
_ <- svc.receiveCredentialRequest(requestCredential())
_ <- svc.acceptCredentialRequest(issuerRecordId)
_ <- svc.generateJWTCredential(issuerRecordId)
_ <- svc.generateJWTCredential(issuerRecordId, "https://test-status-list.registry")
_ <- svc.markCredentialSent(issuerRecordId)
consumer <- ens.consumer[IssueCredentialRecord]("Issue")
events <- consumer.poll(50)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import io.iohk.atala.mercury.model.{AttachmentDescriptor, DidId}
import io.iohk.atala.mercury.protocol.issuecredential.*
import io.iohk.atala.pollux.core.model.*
import io.iohk.atala.pollux.core.model.presentation.{ClaimFormat, Ldp, Options, PresentationDefinition}
import io.iohk.atala.pollux.core.repository.{CredentialDefinitionRepositoryInMemory, CredentialRepositoryInMemory}
import io.iohk.atala.pollux.core.repository.{
CredentialDefinitionRepositoryInMemory,
CredentialRepositoryInMemory,
CredentialStatusListRepositoryInMemory
}
import io.iohk.atala.pollux.vc.jwt.*
import io.iohk.atala.shared.models.{WalletAccessContext, WalletId}
import zio.*
Expand All @@ -31,6 +35,7 @@ trait CredentialServiceSpecHelper {
: URLayer[DIDService & ManagedDIDService & URIDereferencer, CredentialService & CredentialDefinitionService] =
ZLayer.makeSome[DIDService & ManagedDIDService & URIDereferencer, CredentialService & CredentialDefinitionService](
CredentialRepositoryInMemory.layer,
CredentialStatusListRepositoryInMemory.layer,
ZLayer.fromFunction(PrismDidResolver(_)),
credentialDefinitionServiceLayer,
GenericSecretStorageInMemory.layer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ final case class CreateIssueCredentialRecordRequest(
@description(annotations.credentialFormat.description)
@encodedExample(annotations.credentialFormat.example)
credentialFormat: Option[String],
@description(annotations.isRevocable.description)
@encodedExample(annotations.isRevocable.example)
isRevocable: Option[Boolean],
@description(annotations.claims.description)
@encodedExample(annotations.claims.example)
claims: zio.json.ast.Json,
Expand All @@ -66,17 +63,6 @@ object CreateIssueCredentialRecordRequest {
example = 3600
)

object isRevocable
extends Annotation[Option[Boolean]](
description = "Specifies if the credential is revocable or not",
example = Some(true),
validator = Validator.enumeration(
List(
Some(true),
Some(false)
)
)
)
object schemaId
extends Annotation[Option[String]](
description = "The unique identifier of the schema used for this credential offer.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,13 @@ object IssueControllerSpec extends ZIOSpecDefault {
"email" -> Json.fromString("email")
),
maybeCredentialStatus = Some(

CredentialStatus(
id = "did:work:MDP8AsFhHzhwUvGNuYkX7T;id=06e126d1-fa44-4882-a243-1e326fbe21db;version=1.0",
`type` = "CredentialStatusList2017"
`type` = "StatusList2021Entry",
statusPurpose = StatusPurpose.Revocation,
statusListIndex = 0,
statusListCredential = "https://example.com/credentials/status/3"
)
),
maybeRefreshService = Some(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import io.iohk.atala.issue.controller.http.{
}
import io.iohk.atala.pollux.anoncreds.LinkSecretWithId
import io.iohk.atala.pollux.core.model.CredentialFormat
import io.iohk.atala.pollux.core.repository.{CredentialDefinitionRepositoryInMemory, CredentialRepositoryInMemory}
import io.iohk.atala.pollux.core.repository.{
CredentialDefinitionRepositoryInMemory,
CredentialRepositoryInMemory,
CredentialStatusListRepositoryInMemory
}
import io.iohk.atala.pollux.core.service.*
import io.iohk.atala.pollux.vc.jwt.*
import io.iohk.atala.shared.models.{WalletAccessContext, WalletId}
Expand All @@ -35,7 +39,6 @@ import zio.config.{ReadError, read}
import zio.json.ast.Json
import zio.json.ast.Json.*
import zio.test.*

import java.util.UUID

trait IssueControllerTestTools extends PostgresTestContainerSupport {
Expand Down Expand Up @@ -81,6 +84,7 @@ trait IssueControllerTestTools extends PostgresTestContainerSupport {
didResolverLayer >+>
ResourceURIDereferencerImpl.layer >+>
CredentialRepositoryInMemory.layer >+>
CredentialStatusListRepositoryInMemory.layer >+>
ZLayer.succeed(LinkSecretWithId("Unused Linked Secret ID")) >+>
MockDIDService.empty >+>
MockManagedDIDService.empty >+>
Expand Down

0 comments on commit 9ea570a

Please sign in to comment.