feat: store link secret
Signed-off-by: Bassam Riman <[email protected]>
Signed-off-by: Bassam Riman <[email protected]>
object CredentialServiceError {
final case class RepositoryError(cause: Throwable) extends CredentialServiceError
final case class LinkSecretError(cause: Throwable) extends CredentialServiceError
final case class RecordIdNotFound(recordId: DidCommID) extends CredentialServiceError
final case class OperationNotExecuted(recordId: DidCommID, info: String) extends CredentialServiceError
final case class ThreadIdNotFound(thid: DidCommID) extends CredentialServiceError
package io.iohk.atala.pollux.core.model.error
final case class LinkSecretError(cause: Throwable)
import io.iohk.atala.mercury.model.*
import io.iohk.atala.mercury.protocol.issuecredential.*
import io.iohk.atala.pollux.*
import io.iohk.atala.pollux.anoncreds.{AnoncredLib, CreateCredentialDefinition, CredentialOffer, LinkSecretWithId}
import io.iohk.atala.pollux.anoncreds.{AnoncredLib, CreateCredentialDefinition, CredentialOffer}
import io.iohk.atala.pollux.core.model.*
import io.iohk.atala.pollux.core.model.CredentialFormat.AnonCreds
import io.iohk.atala.pollux.core.model.IssueCredentialRecord.ProtocolState.OfferReceived
object CredentialServiceImpl {
val layer: URLayer[
CredentialRepository & DidResolver & URIDereferencer & GenericSecretStorage & CredentialDefinitionService &
LinkSecretWithId & DIDService & ManagedDIDService,
LinkSecretService & DIDService & ManagedDIDService,
CredentialService
] =
ZLayer.fromFunction(CredentialServiceImpl(_, _, _, _, _, _, _, _))
uriDereferencer: URIDereferencer,
genericSecretStorage: GenericSecretStorage,
credentialDefinitionService: CredentialDefinitionService,
linkSecretWithId: LinkSecretWithId,
linkSecretService: LinkSecretService,
didService: DIDService,
managedDIDService: ManagedDIDService,
maxRetries: Int = 5 // TODO move to config
.mapError(err => UnexpectedError(err.toString))
_ <- ZIO.logInfo(s"Cred Def Content => $credDefContent")
credentialDefinition = anoncreds.CredentialDefinition(credDefContent)
linkSecret = linkSecretWithId
linkSecret <- linkSecretService
.fetchOrCreate()
.mapError(e => CredentialServiceError.LinkSecretError.apply(e.cause))
createCredentialRequest = AnoncredLib.createCredentialRequest(linkSecret, credentialDefinition, credentialOffer)
} yield createCredentialRequest
}
metadata <- ZIO
.fromOption(record.anonCredsRequestMetadata)
.mapError(_ => CredentialServiceError.UnexpectedError(s"No request metadata Id found un record: ${record.id}"))
linkSecret = linkSecretWithId
linkSecret <- linkSecretService
.fetchOrCreate()
.mapError(e => CredentialServiceError.LinkSecretError.apply(e.cause))
_ <- ZIO
.attempt(
AnoncredLib.processCredential(
package io.iohk.atala.pollux.core.service
import io.iohk.atala.pollux.anoncreds.LinkSecretWithId
import io.iohk.atala.pollux.core.model.error.LinkSecretError
import io.iohk.atala.shared.models.WalletAccessContext
import zio.ZIO
trait LinkSecretService {
type Result[T] = ZIO[WalletAccessContext, LinkSecretError, T]
def fetchOrCreate(): Result[LinkSecretWithId]
}
package io.iohk.atala.pollux.core.service
import io.iohk.atala.agent.walletapi.storage.{GenericSecret, GenericSecretStorage}
import io.iohk.atala.pollux.anoncreds.{LinkSecret, LinkSecretWithId}
import io.iohk.atala.pollux.core.model.error.LinkSecretError
import io.iohk.atala.shared.models.WalletAccessContext
import zio.*
import zio.json.ast.Json
import scala.util.Try
class LinkSecretServiceImpl(genericSecretStorage: GenericSecretStorage) extends LinkSecretService {
import LinkSecretServiceImpl.given
type Result[T] = ZIO[WalletAccessContext, LinkSecretError, T]
override def fetchOrCreate(): Result[LinkSecretWithId] = {
genericSecretStorage
.get[String, LinkSecret](LinkSecretServiceImpl.defaultLinkSecretId)
.flatMap {
case Some(secret) => ZIO.succeed(secret)
case None =>
val linkSecret = LinkSecret()
genericSecretStorage
.set[String, LinkSecret](LinkSecretServiceImpl.defaultLinkSecretId, linkSecret)
.as(linkSecret)
}
.map(linkSecret => LinkSecretWithId(LinkSecretServiceImpl.defaultLinkSecretId, linkSecret))
.mapError(LinkSecretError.apply)
}
}
object LinkSecretServiceImpl {
val defaultLinkSecretId = "default-link-secret-id"
val layer: URLayer[
GenericSecretStorage,
LinkSecretService
] =
ZLayer.fromFunction(LinkSecretServiceImpl(_))
given GenericSecret[String, LinkSecret] = new {
override def keyPath(id: String): String = s"link-secret/${id.toString}"
override def encodeValue(secret: LinkSecret): Json = Json.Str(secret.data)
override def decodeValue(json: Json): Try[LinkSecret] = json match {
case Json.Str(data) => Try(LinkSecret(data))
case _ => scala.util.Failure(new Exception("Invalid JSON format for LinkSecret"))
}
}
}
import io.iohk.atala.iris.proto.service.IrisServiceGrpc
import io.iohk.atala.mercury.model.{AttachmentDescriptor, DidId}
import io.iohk.atala.mercury.protocol.issuecredential.*
import io.iohk.atala.pollux.anoncreds.LinkSecretWithId
import io.iohk.atala.pollux.core.model.*
import io.iohk.atala.pollux.core.model.error.CredentialServiceError
import io.iohk.atala.pollux.core.model.error.CredentialServiceError.*
ResourceURIDereferencerImpl.layer ++
GenericSecretStorageInMemory.layer >+>
credentialDefinitionServiceLayer ++
ZLayer.succeed(LinkSecretWithId("Unused Link Secret Id")) >>>
GenericSecretStorageInMemory.layer >+>
LinkSecretServiceImpl.layer >>>
CredentialServiceImpl.layer
protected def offerCredential(
package io.iohk.atala.pollux.core.service
import io.iohk.atala.agent.walletapi.memory.GenericSecretStorageInMemory
import io.iohk.atala.agent.walletapi.storage.GenericSecretStorage
import io.iohk.atala.pollux.anoncreds.LinkSecret
import io.iohk.atala.shared.models.WalletId.*
import io.iohk.atala.shared.models.{WalletAccessContext, WalletId}
import zio.*
import zio.test.*
import zio.test.TestAspect.*
object LinkSecretServiceImplSpec extends ZIOSpecDefault {
protected val defaultWalletLayer = ZLayer.succeed(WalletAccessContext(WalletId.default))
protected val linkSecretServiceServiceLayer =
ZLayer.make[GenericSecretStorage & LinkSecretService & WalletAccessContext](
GenericSecretStorageInMemory.layer,
LinkSecretServiceImpl.layer,
defaultWalletLayer
)
override def spec = {
suite("LinkSecretServiceImpl")(
test("fetchOrCreate") {
import LinkSecretServiceImpl.given
for {
svc <- ZIO.service[LinkSecretService]
record <- svc.fetchOrCreate()
record1 <- svc.fetchOrCreate()
storage <- ZIO.service[GenericSecretStorage]
maybeDidSecret <- storage
.get[String, LinkSecret](LinkSecretServiceImpl.defaultLinkSecretId)
} yield {
assertTrue(record.id == LinkSecretServiceImpl.defaultLinkSecretId)
assertTrue(record == record1)
assertTrue(maybeDidSecret.map(_.data).contains(record.secret.data))
}
}
).provide(linkSecretServiceServiceLayer)
} @@ samples(1)
}
WalletManagementServiceImpl
}
import io.iohk.atala.agent.walletapi.sql.{JdbcDIDNonSecretStorage, JdbcEntityRepository, JdbcWalletNonSecretStorage}
import io.iohk.atala.agent.walletapi.storage.GenericSecretStorage
import io.iohk.atala.castor.controller.{DIDControllerImpl, DIDRegistrarControllerImpl}
import io.iohk.atala.castor.core.service.DIDServiceImpl
import io.iohk.atala.castor.core.util.DIDOperationValidator
import io.iohk.atala.iam.wallet.http.controller.WalletManagementControllerImpl
import io.iohk.atala.issue.controller.IssueControllerImpl
import io.iohk.atala.mercury.*
import io.iohk.atala.pollux.anoncreds.LinkSecretWithId
import io.iohk.atala.pollux.core.service.*
import io.iohk.atala.pollux.credentialdefinition.controller.CredentialDefinitionControllerImpl
import io.iohk.atala.pollux.credentialschema.controller.{
_ <- preMigrations
_ <- migrations
linkSecretLayer = ZLayer.succeed(LinkSecretWithId("Unused Link Secret ID"))
app <- PrismAgentApp
.run(didCommServicePort)
.provide(
ConnectionServiceImpl.layer >>> ConnectionServiceNotifier.layer,
CredentialSchemaServiceImpl.layer,
CredentialDefinitionServiceImpl.layer,
linkSecretLayer >>> CredentialServiceImpl.layer >>> CredentialServiceNotifier.layer,
LinkSecretServiceImpl.layer >>> CredentialServiceImpl.layer >>> CredentialServiceNotifier.layer,
DIDServiceImpl.layer,
EntityServiceImpl.layer,
ManagedDIDServiceWithEventNotificationImpl.layer,
error match
case CredentialServiceError.RepositoryError(cause) =>
ErrorResponse.internalServerError(title = "RepositoryError", detail = Some(cause.toString))
case CredentialServiceError.LinkSecretError(cause) =>
ErrorResponse.internalServerError(title = "LinkSecretError", detail = Some(cause.toString))
case CredentialServiceError.RecordIdNotFound(recordId) =>
ErrorResponse.notFound(detail = Some(s"Record Id not found: $recordId"))
case CredentialServiceError.OperationNotExecuted(recordId, info) =>
val testEnvironmentLayer = zio.test.testEnvironment ++
pgContainerLayer ++
contextAwareTransactorLayer ++
GenericSecretStorageInMemory.layer >+> LinkSecretServiceImpl.layer ++
GenericSecretStorageInMemory.layer >+> credentialDefinitionServiceLayer >+>
controllerLayer ++
DefaultEntityAuthenticator.layer
Alonzo builds