Skip to content

Commit

Permalink
feat(agent): implement the VC issuance flow with the dummy data
Browse files Browse the repository at this point in the history
Signed-off-by: Yurii Shynbuiev <yurii.shynbuiev@iohk.io>
  • Loading branch information
yshyn-iohk authored and patlo-iog committed Apr 29, 2024
1 parent ef18620 commit c1eb4cc
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 5 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,7 @@ lazy val cloudAgentServer = project
eventNotification
)
.dependsOn(sharedTest % "test->test")
.dependsOn(polluxCore % "compile->compile;test->test")

// ############################
// #### Release process #####
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package io.iohk.atala.agent.walletapi.storage

import io.iohk.atala.agent.walletapi.model.*
import io.iohk.atala.castor.core.model.did.{PrismDID, ScheduledDIDOperationStatus}
import io.iohk.atala.mercury.model.DidId
import io.iohk.atala.shared.models.{WalletAccessContext, WalletId}
import zio.mock.{Expectation, Mock, Proxy}
import zio.test.Assertion.equalTo
import zio.{RIO, Task, URLayer, ZIO, ZLayer, mock}

import scala.collection.immutable.ArraySeq

case class MockDIDNonSecretStorage(proxy: Proxy) extends DIDNonSecretStorage {
override def insertDIDUpdateLineage(did: PrismDID, updateLineage: DIDUpdateLineage): RIO[WalletAccessContext, Unit] =
proxy(MockDIDNonSecretStorage.InsertDIDUpdateLineage, (did, updateLineage))

override def listUpdateLineage(
did: Option[PrismDID],
status: Option[ScheduledDIDOperationStatus]
): RIO[WalletAccessContext, Seq[DIDUpdateLineage]] = proxy(MockDIDNonSecretStorage.ListUpdateLineage, (did, status))

override def setDIDUpdateLineageStatus(
operationId: Array[Byte],
status: ScheduledDIDOperationStatus
): RIO[WalletAccessContext, Unit] = proxy(MockDIDNonSecretStorage.SetDIDUpdateLineageStatus, (operationId, status))

override def createPeerDIDRecord(did: DidId): RIO[WalletAccessContext, Int] =
proxy(MockDIDNonSecretStorage.CreatePeerDIDRecord, did)

override def getPeerDIDRecord(did: DidId): Task[Option[PeerDIDRecord]] =
proxy(MockDIDNonSecretStorage.GetPeerDIDRecord, did)

override def getPrismDidWalletId(prismDid: PrismDID): Task[Option[WalletId]] =
proxy(MockDIDNonSecretStorage.GetPrismDidWalletId, prismDid)

override def getManagedDIDState(did: PrismDID): RIO[WalletAccessContext, Option[ManagedDIDState]] =
proxy(MockDIDNonSecretStorage.GetManagedDIDState, did)

override def insertManagedDID(
did: PrismDID,
state: ManagedDIDState,
hdKey: Map[String, ManagedDIDHdKeyPath]
): RIO[WalletAccessContext, Unit] = proxy(MockDIDNonSecretStorage.InsertManagedDID, (did, state, hdKey))

override def updateManagedDID(did: PrismDID, patch: ManagedDIDStatePatch): RIO[WalletAccessContext, Unit] =
proxy(MockDIDNonSecretStorage.UpdateManagedDID, (did, patch))

override def getMaxDIDIndex(): RIO[WalletAccessContext, Option[Int]] =
proxy(MockDIDNonSecretStorage.GetMaxDIDIndex)

override def getHdKeyCounter(did: PrismDID): RIO[WalletAccessContext, Option[HdKeyIndexCounter]] =
proxy(MockDIDNonSecretStorage.GetHdKeyCounter, did)

override def getHdKeyPath(did: PrismDID, keyId: String): RIO[WalletAccessContext, Option[ManagedDIDHdKeyPath]] =
proxy(MockDIDNonSecretStorage.GetHdKeyPath, (did, keyId))

override def insertHdKeyPath(
did: PrismDID,
keyId: String,
hdKeyPath: ManagedDIDHdKeyPath,
operationHash: Array[Byte]
): RIO[WalletAccessContext, Unit] =
proxy(MockDIDNonSecretStorage.InsertHdKeyPath, (did, keyId, hdKeyPath, operationHash))

override def listHdKeyPath(
did: PrismDID
): RIO[WalletAccessContext, Seq[(String, ArraySeq[Byte], ManagedDIDHdKeyPath)]] =
proxy(MockDIDNonSecretStorage.ListHdKeyPath, did)

override def listManagedDID(
offset: Option[Int],
limit: Option[Int]
): RIO[WalletAccessContext, (Seq[(PrismDID, ManagedDIDState)], Int)] =
proxy(MockDIDNonSecretStorage.ListManagedDID, (offset, limit))
}
object MockDIDNonSecretStorage extends Mock[DIDNonSecretStorage] {
object InsertDIDUpdateLineage extends Effect[(PrismDID, DIDUpdateLineage), Throwable, Unit]
object ListUpdateLineage
extends Effect[(Option[PrismDID], Option[ScheduledDIDOperationStatus]), Throwable, Seq[DIDUpdateLineage]]
object SetDIDUpdateLineageStatus extends Effect[(Array[Byte], ScheduledDIDOperationStatus), Throwable, Unit]
object CreatePeerDIDRecord extends Effect[DidId, Throwable, Int]
object GetPeerDIDRecord extends Effect[DidId, Throwable, Option[PeerDIDRecord]]
object GetPrismDidWalletId extends Effect[PrismDID, Throwable, Option[WalletId]]
object GetManagedDIDState extends Effect[PrismDID, Throwable, Option[ManagedDIDState]]
object InsertManagedDID extends Effect[(PrismDID, ManagedDIDState, Map[String, ManagedDIDHdKeyPath]), Throwable, Unit]
object UpdateManagedDID extends Effect[(PrismDID, ManagedDIDStatePatch), Throwable, Unit]
object GetMaxDIDIndex extends Effect[Unit, Throwable, Option[Int]]
object GetHdKeyCounter extends Effect[PrismDID, Throwable, Option[HdKeyIndexCounter]]
object GetHdKeyPath extends Effect[(PrismDID, String), Throwable, Option[ManagedDIDHdKeyPath]]
object InsertHdKeyPath extends Effect[(PrismDID, String, ManagedDIDHdKeyPath, Array[Byte]), Throwable, Unit]
object ListHdKeyPath extends Effect[PrismDID, Throwable, Seq[(String, ArraySeq[Byte], ManagedDIDHdKeyPath)]]
object ListManagedDID extends Effect[(Option[Int], Option[Int]), Throwable, (Seq[(PrismDID, ManagedDIDState)], Int)]

override val compose: URLayer[mock.Proxy, DIDNonSecretStorage] = ZLayer.fromFunction(MockDIDNonSecretStorage(_))

def getPrismDidWalletIdExpectation(prismDID: PrismDID, walletId: WalletId): Expectation[DIDNonSecretStorage] =
MockDIDNonSecretStorage.GetPrismDidWalletId(
assertion = equalTo(prismDID),
result = Expectation.value(Some(walletId))
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,11 @@ class CredentialServiceImpl(
// Automatically infer keyId to use by resolving DID and choose the corresponding VerificationRelationship
issuingKeyId <- didService
.resolveDID(jwtIssuerDID)
.mapError(e => UnexpectedError(s"Error occured while resolving Issuing DID during VC creation: ${e.toString}"))
.mapError(e => UnexpectedError(s"Error occurred while resolving Issuing DID during VC creation: ${e.toString}"))
.someOrFail(UnexpectedError(s"Issuing DID resolution result is not found"))
.map { case (_, didData) => didData.publicKeys.find(_.purpose == verificationRelationship).map(_.id) }
.map { case (_, didData) =>
didData.publicKeys.find(_.purpose == verificationRelationship).map(_.id)
}
.someOrFail(
UnexpectedError(s"Issuing DID doesn't have a key in ${verificationRelationship.name} to use: $jwtIssuerDID")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ trait CredentialServiceSpecHelper {
protected val defaultWalletLayer = ZLayer.succeed(WalletAccessContext(WalletId.default))

protected val credentialDefinitionServiceLayer =
CredentialDefinitionRepositoryInMemory.layer ++ ResourceURIDereferencerImpl.layer >>>
CredentialDefinitionRepositoryInMemory.layer ++ ResourceURIDereferencerImpl.layer >+>
CredentialDefinitionServiceImpl.layer ++ defaultWalletLayer

protected val credentialServiceLayer
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.hyperledger.identus.pollux.vc.jwt

import com.nimbusds.jose.{JWSAlgorithm, JWSHeader}
import com.nimbusds.jose.{JOSEObjectType, JWSAlgorithm, JWSHeader}
import com.nimbusds.jose.crypto.ECDSASigner
import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton
import com.nimbusds.jose.jwk.{Curve, ECKey}
Expand Down Expand Up @@ -59,7 +59,7 @@ class ES256KSigner(privateKey: PrivateKey) extends Signer {
override def encode(claim: Json): JWT = {
val claimSet = JWTClaimsSet.parse(claim.noSpaces)
val signedJwt = SignedJWT(
new JWSHeader.Builder(JWSAlgorithm.ES256K).build(),
new JWSHeader.Builder(JWSAlgorithm.ES256K).`type`(JOSEObjectType.JWT).build(),
claimSet
)
signedJwt.sign(signer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,7 @@ class PrismDidResolver(didService: DIDService) extends DidResolver {
}

}

object PrismDidResolver {
val layer: URLayer[DIDService, DidResolver] = ZLayer.fromFunction(PrismDidResolver(_))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.iohk.atala.iam.oidc.domain

import io.iohk.atala.agent.walletapi.memory.GenericSecretStorageInMemory
import io.iohk.atala.agent.walletapi.service.{ManagedDIDService, MockManagedDIDService}
import io.iohk.atala.agent.walletapi.storage.{DIDNonSecretStorage, MockDIDNonSecretStorage}
import io.iohk.atala.castor.core.model.did.VerificationRelationship
import io.iohk.atala.castor.core.service.{DIDService, MockDIDService}
import io.iohk.atala.iam.oidc.http.{ClaimDescriptor, CredentialDefinition, Localization}
import io.iohk.atala.pollux.core.repository.{CredentialRepository, CredentialRepositoryInMemory}
import io.iohk.atala.pollux.core.service.*
import io.iohk.atala.pollux.vc.jwt.PrismDidResolver
import io.iohk.atala.shared.models.{WalletAccessContext, WalletId}
import zio.mock.MockSpecDefault
import zio.test.*
import zio.test.Assertion.*
import zio.{URLayer, ZIO, ZLayer}

object OIDCCredentialIssuerServiceSpec extends MockSpecDefault with CredentialServiceSpecHelper {

val layers: URLayer[
DIDService & ManagedDIDService & DIDNonSecretStorage,
CredentialService & CredentialDefinitionService & OIDCCredentialIssuerService
] =
ZLayer.makeSome[
DIDService & ManagedDIDService & DIDNonSecretStorage,
CredentialService & CredentialDefinitionService & OIDCCredentialIssuerService
](
CredentialRepositoryInMemory.layer,
PrismDidResolver.layer,
credentialDefinitionServiceLayer,
GenericSecretStorageInMemory.layer,
LinkSecretServiceImpl.layer,
CredentialServiceImpl.layer,
OIDCCredentialIssuerServiceImpl.layer
)

override def spec = suite("CredentialServiceImpl")(
OIDCCredentialIssuerServiceSpec
)

private val (issuerOp, issuerKp, issuerDidMetadata, issuerDidData) =
MockDIDService.createDID(VerificationRelationship.AssertionMethod)

// private val (holderOp, holderKp, holderDidMetadata, holderDidData) =
// MockDIDService.createDID(VerificationRelationship.Authentication)
//
// private val holderDidServiceExpectations =
// MockDIDService.resolveDIDExpectation(holderDidMetadata, holderDidData)

private val issuerDidServiceExpectations =
MockDIDService.resolveDIDExpectation(issuerDidMetadata, issuerDidData)

private val issuerManagedDIDServiceExpectations =
MockManagedDIDService.javaKeyPairWithDIDExpectation(issuerKp)

private val getIssuerPrismDidWalletIdExpectation =
MockDIDNonSecretStorage.getPrismDidWalletIdExpectation(issuerDidData.id, WalletId.default)

private val OIDCCredentialIssuerServiceSpec =
suite("Simple JWT credential issuance")(
test("should issue a JWT credential") {
for {
oidcCredentialIssuerService <- ZIO.service[OIDCCredentialIssuerService]
credentialDefinition = CredentialDefinition(
`@context` = Seq("https://www.w3.org/2018/credentials/v1"),
`type` = Seq("VerifiableCredential", "CertificateOfBirth"),
credentialSubject = Some(
Map(
"name" ->
ClaimDescriptor(mandatory = Some(true), valueType = Some("string"), display = Seq.empty[Localization])
)
)
)
jwt <- oidcCredentialIssuerService.issueJwtCredential(issuerDidData.id, None, credentialDefinition)
_ <- zio.Console.printLine(jwt)
} yield assert(jwt.toString)(isNonEmptyString)
}.provideSomeLayer(
issuerDidServiceExpectations.toLayer ++
issuerManagedDIDServiceExpectations.toLayer ++
getIssuerPrismDidWalletIdExpectation.toLayer >+> layers
)
)
}

0 comments on commit c1eb4cc

Please sign in to comment.