Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add anoncreds credential definition rest api #624

Merged
merged 2 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests-common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
name: "Build and unit tests for ${{ inputs.component-name }}"
runs-on: self-hosted
container:
image: ghcr.io/input-output-hk/atala-qa-automation
image: ghcr.io/input-output-hk/agent-ci-ubuntu-22-jdk-11:0.1.0
volumes:
- /nix:/nix
credentials:
Expand Down
31 changes: 31 additions & 0 deletions infrastructure/charts/agent/templates/apisixroute.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,37 @@ spec:

---

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: agent-credential-definition-registry-route
namespace: "{{ .Release.Namespace }}"
labels:
{{ template "labels.common" . }}
spec:
http:
- name: agent-credential-definition-registry-rule
match:
hosts:
{{- range .Values.ingress.applicationUrls }}
- {{ . }}
{{- end }}
paths:
- /prism-agent/credential-definition-registry/definitions/*
methods:
- GET
backends:
- serviceName: agent-server-tapir-service
servicePort: 8085
plugins:
- name: proxy-rewrite
enable: true
config:
regex_uri: ["^/prism-agent/credential-definition-registry/definitions/(.*)", "credential-definition-registry/definitions/$1"]
{{ template "cors" . }}

---

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
Expand Down
2 changes: 1 addition & 1 deletion pollux/lib/anoncredsTest/src/test/scala/Uniffy.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import uniffi.anoncreds._
import uniffi.anoncreds.*
import uniffi.anoncreds.CredentialDefinition
object Uniffy extends App {
val prover = new Prover()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.iohk.atala.pollux.anoncreds

import org.scalatest.flatspec.AnyFlatSpec

import scala.jdk.CollectionConverters.*

/** polluxAnoncredsTest/Test/testOnly io.iohk.atala.pollux.anoncreds.PoCNewLib
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package io.iohk.atala.pollux.core.model.error
import io.iohk.atala.pollux.core.model.schema.validator.JsonSchemaError

sealed trait CredentialSchemaError {
def userMessage: String
def message: String
}

object CredentialSchemaError {
case class SchemaError(schemaError: JsonSchemaError) extends CredentialSchemaError {
def userMessage: String = schemaError.error
def message: String = schemaError.error
}
case class URISyntaxError(userMessage: String) extends CredentialSchemaError
case class CredentialSchemaParsingError(userMessage: String) extends CredentialSchemaError
case class UnsupportedCredentialSchemaType(userMessage: String) extends CredentialSchemaError
case class UnexpectedError(userMessage: String) extends CredentialSchemaError
case class URISyntaxError(message: String) extends CredentialSchemaError
case class CredentialSchemaParsingError(message: String) extends CredentialSchemaError
case class UnsupportedCredentialSchemaType(message: String) extends CredentialSchemaError
case class UnexpectedError(message: String) extends CredentialSchemaError
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package io.iohk.atala.pollux.core.model.schema

import io.iohk.atala.pollux.core.model.error.CredentialSchemaError
import io.iohk.atala.pollux.core.model.error.CredentialSchemaError.*
import zio.*
import zio.json.*

import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.UUID

type Definition = zio.json.ast.Json
type CorrectnessProof = zio.json.ast.Json

/** @param guid
* Globally unique identifier of the CredentialDefinition object. It's calculated as a UUID from a string that
* contains the following fields: author, id, and version.
* @param id
* Locally unique identifier of the CredentialDefinition. It is a UUID. When the version of the credential definition
* changes, this `id` keeps the same value.
* @param name
* Human-readable name of the CredentialDefinition.
* @param description
* Human-readable description of the CredentialDefinition.
* @param version
* Version of the CredentialDefinition.
* @param author
* DID of the CredentialDefinition's author.
* @param authored
* Datetime stamp of the schema creation.
* @param tags
* Tags of the CredentialDefinition, used for convenient lookup.
* @param schemaId
* Schema ID that identifies the schema associated with this definition.
* @param definition
* Definition object that represents the actual definition of the credential.
* @param keyCorrectnessProof
* A proof that validates the correctness of the key within the context of the credential definition.
* @param signatureType
* Signature type used in the CredentialDefinition.
* @param supportRevocation
* Boolean flag indicating whether revocation is supported for this CredentialDefinition.
*/
case class CredentialDefinition(
guid: UUID,
id: UUID,
name: String,
description: String,
version: String,
author: String,
authored: OffsetDateTime,
tag: String,
schemaId: String,
definitionJsonSchemaId: String,
definition: Definition,
keyCorrectnessProofJsonSchemaId: String,
keyCorrectnessProof: CorrectnessProof,
signatureType: String,
supportRevocation: Boolean
) {
def longId = CredentialDefinition.makeLongId(author, id, version)
}

object CredentialDefinition {

def makeLongId(author: String, id: UUID, version: String) =
s"$author/${id.toString}?version=${version}"

def makeGUID(author: String, id: UUID, version: String) =
UUID.nameUUIDFromBytes(makeLongId(author, id, version).getBytes)

def make(
in: Input,
definitionSchemaId: String,
definition: Definition,
proofSchemaId: String,
proof: CorrectnessProof
): ZIO[Any, Nothing, CredentialDefinition] = {
for {
id <- zio.Random.nextUUID
cs <- make(id, in, definitionSchemaId, definition, proofSchemaId, proof)
} yield cs
}

def make(
id: UUID,
in: Input,
definitionSchemaId: String,
definition: Definition,
keyCorrectnessProofSchemaId: String,
keyCorrectnessProof: CorrectnessProof
): ZIO[Any, Nothing, CredentialDefinition] = {
for {
ts <- zio.Clock.currentDateTime.map(
_.atZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime
)
guid = makeGUID(in.author, id, in.version)
} yield CredentialDefinition(
guid = guid,
id = id,
name = in.name,
description = in.description,
version = in.version,
schemaId = in.schemaId,
author = in.author,
authored = in.authored.map(_.atZoneSameInstant(ZoneOffset.UTC).toOffsetDateTime).getOrElse(ts),
tag = in.tag,
definitionJsonSchemaId = definitionSchemaId,
definition = definition,
keyCorrectnessProofJsonSchemaId = keyCorrectnessProofSchemaId,
keyCorrectnessProof = keyCorrectnessProof,
signatureType = in.signatureType,
supportRevocation = in.supportRevocation
)
}

val defaultAgentDid = "did:prism:agent"

case class Input(
name: String,
description: String,
version: String,
authored: Option[OffsetDateTime],
tag: String,
author: String = defaultAgentDid,
schemaId: String,
signatureType: String,
supportRevocation: Boolean
)

case class Filter(
author: Option[String] = None,
name: Option[String] = None,
version: Option[String] = None,
tag: Option[String] = None
)

case class FilteredEntries(entries: Seq[CredentialDefinition], count: Long, totalCount: Long)

given JsonEncoder[CredentialDefinition] = DeriveJsonEncoder.gen[CredentialDefinition]

given JsonDecoder[CredentialDefinition] = DeriveJsonDecoder.gen[CredentialDefinition]
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ class SchemaSerDes[S](jsonSchemaSchemaStr: String) {
} yield json
}

def validate(jsonString: String): IO[JsonSchemaError, Unit] = {
def validate(jsonString: String): IO[JsonSchemaError, Boolean] = {
for {
jsonSchemaSchema <- JsonSchemaUtils.jsonSchema(jsonSchemaSchemaStr)
schemaValidator = JsonSchemaValidatorImpl(jsonSchemaSchema)
_ <- schemaValidator.validate(jsonString)
} yield {}
} yield true
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.iohk.atala.pollux.core.repository

import io.iohk.atala.pollux.core.model.schema.CredentialDefinition
import io.iohk.atala.pollux.core.repository.Repository.SearchCapability

import java.util.UUID

trait CredentialDefinitionRepository[F[_]]
extends Repository[F, CredentialDefinition]
with SearchCapability[F, CredentialDefinition.Filter, CredentialDefinition] {
def create(cs: CredentialDefinition): F[CredentialDefinition]

def getByGuid(guid: UUID): F[Option[CredentialDefinition]]

def update(cs: CredentialDefinition): F[Option[CredentialDefinition]]

def getAllVersions(id: UUID, author: String): F[Seq[String]]

def delete(guid: UUID): F[Option[CredentialDefinition]]

def deleteAll(): F[Long]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.iohk.atala.pollux.core.repository

import io.iohk.atala.pollux.core.model.*
import io.iohk.atala.pollux.core.model.error.CredentialRepositoryError.*
import io.iohk.atala.pollux.core.model.schema.CredentialDefinition
import zio.*

import java.util.UUID

class CredentialDefinitionRepositoryInMemory(
storeRef: Ref[Map[UUID, CredentialDefinition]]
) extends CredentialDefinitionRepository[Task] {
override def create(record: CredentialDefinition): Task[CredentialDefinition] = {
for {
_ <- for {
store <- storeRef.get
maybeRecord = store.values.find(_.id == record.guid)
_ <- maybeRecord match
case None => ZIO.unit
case Some(value) => ZIO.fail(UniqueConstraintViolation("Unique Constraint Violation on 'id'"))
} yield ()
_ <- storeRef.update(r => r + (record.guid -> record))
} yield record
}

override def getByGuid(guid: UUID): Task[Option[CredentialDefinition]] = {
for {
store <- storeRef.get
record = store.get(guid)
} yield record
}

override def update(cs: CredentialDefinition): Task[Option[CredentialDefinition]] = {
for {
store <- storeRef.get
maybeExisting = store.get(cs.id)
_ <- maybeExisting match {
case Some(existing) =>
val updatedStore = store.updated(cs.id, cs)
storeRef.set(updatedStore)
case None => ZIO.unit
}
} yield maybeExisting
}

override def getAllVersions(id: UUID, author: String): Task[Seq[String]] = {
storeRef.get.map { store =>
store.values
.filter(credDef => credDef.id == id && credDef.author == author)
.map(_.version)
.toSeq
}
}

override def delete(guid: UUID): Task[Option[CredentialDefinition]] = {
for {
store <- storeRef.get
maybeRecord = store.get(guid)
_ <- maybeRecord match {
case Some(record) => storeRef.update(r => r - record.id)
case None => ZIO.unit
}
} yield maybeRecord
}

override def deleteAll(): Task[Long] = {
for {
store <- storeRef.get
deleted = store.size
_ <- storeRef.update(Map.empty)
} yield deleted.toLong
}

override def search(
query: Repository.SearchQuery[CredentialDefinition.Filter]
): Task[Repository.SearchResult[CredentialDefinition]] = {
storeRef.get.map { store =>
val filtered = store.values.filter { credDef =>
query.filter.author.forall(_ == credDef.author) &&
query.filter.name.forall(_ == credDef.name) &&
query.filter.version.forall(_ == credDef.version) &&
query.filter.tag.forall(tag => credDef.tag == tag)
}
val paginated = filtered.slice(query.skip, query.skip + query.limit)
Repository.SearchResult(paginated.toSeq, paginated.size, filtered.size)
}
}
}

object CredentialDefinitionRepositoryInMemory {
val layer: ULayer[CredentialDefinitionRepository[Task]] = ZLayer.fromZIO(
Ref
.make(Map.empty[UUID, CredentialDefinition])
.map(CredentialDefinitionRepositoryInMemory(_))
)
}
Loading
Loading