Skip to content

Commit

Permalink
feat: add presentation-exchange endpoints (#1365)
Browse files Browse the repository at this point in the history
Signed-off-by: Pat Losoponkul <[email protected]>
  • Loading branch information
patlo-iog authored Sep 19, 2024
1 parent c18385c commit 49f7ab3
Show file tree
Hide file tree
Showing 61 changed files with 614 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,8 @@ import org.hyperledger.identus.castor.core.model.did.{
UpdateDIDAction,
VerificationRelationship
}
import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.value
import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.UriOrJsonEndpoint
import org.hyperledger.identus.shared.models.Base64UrlString
import org.hyperledger.identus.shared.models.KeyId
import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.{value, UriOrJsonEndpoint}
import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId}
import org.hyperledger.identus.shared.utils.Traverse.*
import zio.*

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.hyperledger.identus.castor.core.model.did

import org.hyperledger.identus.shared.models.Base64UrlString
import org.hyperledger.identus.shared.models.KeyId
import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId}

final case class PublicKey(
id: KeyId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package org.hyperledger.identus.castor.core.service
import org.hyperledger.identus.castor.core.model.did.*
import org.hyperledger.identus.castor.core.model.error
import org.hyperledger.identus.shared.crypto.{Apollo, Secp256k1KeyPair}
import org.hyperledger.identus.shared.models.Base64UrlString
import org.hyperledger.identus.shared.models.KeyId
import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId}
import zio.{mock, IO, URLayer, ZIO, ZLayer}
import zio.mock.{Expectation, Mock, Proxy}
import zio.test.Assertion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package org.hyperledger.identus.castor.core.util
import org.hyperledger.identus.castor.core.model.did.*
import org.hyperledger.identus.castor.core.model.error.OperationValidationError
import org.hyperledger.identus.castor.core.util.DIDOperationValidator.Config
import org.hyperledger.identus.shared.models.Base64UrlString
import org.hyperledger.identus.shared.models.KeyId
import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId}
import zio.*
import zio.test.*
import zio.test.Assertion.*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import io.circe.Json
import org.hyperledger.identus.castor.core.model.did.*
import org.hyperledger.identus.castor.core.model.did.ServiceEndpoint.{UriOrJsonEndpoint, UriValue}
import org.hyperledger.identus.shared.crypto.Apollo
import org.hyperledger.identus.shared.models.Base64UrlString
import org.hyperledger.identus.shared.models.KeyId
import org.hyperledger.identus.shared.models.{Base64UrlString, KeyId}
import zio.*
import zio.test.Gen

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.hyperledger.identus.pollux.credentialschema.{
SchemaRegistryServerEndpoints,
VerificationPolicyServerEndpoints
}
import org.hyperledger.identus.pollux.prex.PresentationExchangeServerEndpoints
import org.hyperledger.identus.pollux.vc.jwt.DidResolver as JwtDidResolver
import org.hyperledger.identus.presentproof.controller.PresentProofServerEndpoints
import org.hyperledger.identus.resolvers.DIDResolver
Expand All @@ -47,7 +48,7 @@ object CloudAgentApp {
_ <- syncRevocationStatusListsJob.debug.fork
_ <- AgentHttpServer.run.tapDefect(e => ZIO.logErrorCause("Agent HTTP Server failure", e)).fork
fiber <- DidCommHttpServer.run.tapDefect(e => ZIO.logErrorCause("DIDComm HTTP Server failure", e)).fork
_ <- WebhookPublisher.layer.build.map(_.get[WebhookPublisher]).flatMap(_.run.debug.fork)
_ <- WebhookPublisher.layer.build.map(_.get[WebhookPublisher]).flatMap(_.run.fork)
_ <- fiber.join *> ZIO.log(s"Server End")
_ <- ZIO.never
} yield ()
Expand Down Expand Up @@ -137,6 +138,7 @@ object AgentHttpServer {
allWalletManagementEndpoints <- WalletManagementServerEndpoints.all
allEventEndpoints <- EventServerEndpoints.all
allOIDCEndpoints <- CredentialIssuerServerEndpoints.all
allPresentationExchangeEndpoints <- PresentationExchangeServerEndpoints.all
} yield allCredentialDefinitionRegistryEndpoints ++
allSchemaRegistryEndpoints ++
allVerificationPolicyEndpoints ++
Expand All @@ -147,11 +149,13 @@ object AgentHttpServer {
allStatusListEndpoints ++
allPresentProofEndpoints ++
allVcVerificationEndpoints ++
allPresentationExchangeEndpoints ++
allSystemEndpoints ++
allEntityEndpoints ++
allWalletManagementEndpoints ++
allEventEndpoints ++
allOIDCEndpoints

def run =
for {
allEndpoints <- agentRESTServiceEndpoints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,15 @@ import org.hyperledger.identus.pollux.credentialschema.controller.{
CredentialSchemaControllerImpl,
VerificationPolicyControllerImpl
}
import org.hyperledger.identus.pollux.prex.controller.PresentationExchangeControllerImpl
import org.hyperledger.identus.pollux.prex.PresentationDefinitionValidatorImpl
import org.hyperledger.identus.pollux.sql.repository.{
JdbcCredentialDefinitionRepository,
JdbcCredentialRepository,
JdbcCredentialSchemaRepository,
JdbcCredentialStatusListRepository,
JdbcOID4VCIIssuerMetadataRepository,
JdbcPresentationExchangeRepository,
JdbcPresentationRepository,
JdbcVerificationPolicyRepository,
Migrations as PolluxMigrations
Expand Down Expand Up @@ -169,12 +172,14 @@ object MainApp extends ZIOAppDefault {
WalletManagementControllerImpl.layer,
EventControllerImpl.layer,
DIDCommControllerImpl.layer,
PresentationExchangeControllerImpl.layer,
// domain
AppModule.apolloLayer,
AppModule.didJwtResolverLayer,
DIDOperationValidator.layer(),
DIDResolver.layer,
HttpURIDereferencerImpl.layer,
PresentationDefinitionValidatorImpl.layer,
// service
ConnectionServiceImpl.layer >>> ConnectionServiceNotifier.layer,
CredentialSchemaServiceImpl.layer,
Expand All @@ -188,6 +193,7 @@ object MainApp extends ZIOAppDefault {
VerificationPolicyServiceImpl.layer,
WalletManagementServiceImpl.layer,
VcVerificationServiceImpl.layer,
PresentationExchangeServiceImpl.layer,
// authentication
AppModule.builtInAuthenticatorLayer,
AppModule.keycloakAuthenticatorLayer,
Expand All @@ -211,6 +217,7 @@ object MainApp extends ZIOAppDefault {
RepoModule.polluxContextAwareTransactorLayer ++ RepoModule.polluxTransactorLayer >>> JdbcCredentialDefinitionRepository.layer,
RepoModule.polluxContextAwareTransactorLayer ++ RepoModule.polluxTransactorLayer >>> JdbcPresentationRepository.layer,
RepoModule.polluxContextAwareTransactorLayer ++ RepoModule.polluxTransactorLayer >>> JdbcOID4VCIIssuerMetadataRepository.layer,
RepoModule.polluxContextAwareTransactorLayer ++ RepoModule.polluxTransactorLayer >>> JdbcPresentationExchangeRepository.layer,
RepoModule.polluxContextAwareTransactorLayer >>> JdbcVerificationPolicyRepository.layer,
// oidc
CredentialIssuerControllerImpl.layer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.hyperledger.identus.iam.wallet.http.WalletManagementEndpoints
import org.hyperledger.identus.issue.controller.IssueEndpoints
import org.hyperledger.identus.pollux.credentialdefinition.CredentialDefinitionRegistryEndpoints
import org.hyperledger.identus.pollux.credentialschema.{SchemaRegistryEndpoints, VerificationPolicyEndpoints}
import org.hyperledger.identus.pollux.prex.PresentationExchangeEndpoints
import org.hyperledger.identus.system.controller.SystemEndpoints
import sttp.apispec.{SecurityScheme, Tag}
import sttp.apispec.openapi.*
Expand Down Expand Up @@ -122,7 +123,8 @@ object DocModels {
WalletManagementEndpoints.tag,
SystemEndpoints.tag,
EventEndpoints.tag,
EntityEndpoints.tag
EntityEndpoints.tag,
PresentationExchangeEndpoints.tag
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import org.hyperledger.identus.mercury.protocol.revocationnotificaiton.Revocatio
import org.hyperledger.identus.pollux.core.service.{CredentialService, CredentialStatusListService}
import org.hyperledger.identus.pollux.vc.jwt.revocation.{VCStatusList2021, VCStatusList2021Error}
import org.hyperledger.identus.shared.models.*
import org.hyperledger.identus.shared.models.WalletAccessContext
import org.hyperledger.identus.shared.utils.DurationOps.toMetricsSeconds
import zio.*
import zio.metrics.Metric
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.hyperledger.identus.didcomm.controller

import org.hyperledger.identus.mercury.model.DidId
import org.hyperledger.identus.shared.models.{Failure, StatusCode}
import org.hyperledger.identus.shared.models.KeyId
import org.hyperledger.identus.shared.models.{Failure, KeyId, StatusCode}

sealed trait DIDCommControllerError extends Failure {
override def namespace = "DIDCommControllerError"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.hyperledger.identus.pollux.prex

import org.hyperledger.identus.api.http.{EndpointOutputs, ErrorResponse, RequestContext}
import org.hyperledger.identus.api.http.model.PaginationInput
import org.hyperledger.identus.iam.authentication.apikey.ApiKeyCredentials
import org.hyperledger.identus.iam.authentication.apikey.ApiKeyEndpointSecurityLogic.apiKeyHeader
import org.hyperledger.identus.iam.authentication.oidc.JwtCredentials
import org.hyperledger.identus.iam.authentication.oidc.JwtSecurityLogic.jwtAuthHeader
import org.hyperledger.identus.pollux.prex.http.{CreatePresentationDefinition, PresentationDefinitionPage}
import org.hyperledger.identus.pollux.prex.http.PresentationExchangeTapirSchemas.given
import sttp.apispec.Tag
import sttp.model.StatusCode
import sttp.tapir.*
import sttp.tapir.json.zio.jsonBody

import java.util.UUID

object PresentationExchangeEndpoints {

private val tagName = "Presentation Exchange"
private val tagDescription =
s"""
|The __${tagName}__ endpoints offers a way to manage resources related to [presentation exchange protocol](https://identity.foundation/presentation-exchange/spec/v2.1.1/).
|
|The verifier can create the resources such as `presentation-definition` that can be publicly referenced
|in various protocols such as [OpenID for Verificable Presentation](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html).
|""".stripMargin

val tag = Tag(tagName, Some(tagDescription))

private val paginationInput: EndpointInput[PaginationInput] = EndpointInput.derived[PaginationInput]

private val baseEndpoint = endpoint
.tag(tagName)
.in("presentation-exchange")
.in(extractFromRequest[RequestContext](RequestContext.apply))

private val basePrivateEndpoint = baseEndpoint
.securityIn(apiKeyHeader)
.securityIn(jwtAuthHeader)

val getPresentationDefinition: Endpoint[
Unit,
(RequestContext, UUID),
ErrorResponse,
PresentationDefinition,
Any
] =
baseEndpoint.get
.in("presentation-definitions" / path[UUID]("id"))
.out(statusCode(StatusCode.Ok).description("Presentation Definition retrieved successfully"))
.out(jsonBody[PresentationDefinition])
.errorOut(EndpointOutputs.basicFailuresAndNotFound)
.name("getPresentationDefinition")
.summary("Get a presentation-definition")

val listPresentationDefinition: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(RequestContext, PaginationInput),
ErrorResponse,
PresentationDefinitionPage,
Any,
] =
basePrivateEndpoint.get
.in("presentation-definitions")
.in(paginationInput)
.out(statusCode(StatusCode.Ok).description("Presentation Definitions retrieved successfully"))
.out(jsonBody[PresentationDefinitionPage])
.errorOut(EndpointOutputs.basicFailuresAndForbidden)
.name("listPresentationDefinition")
.summary("List all presentation-definitions")
.description(
"""List all `presentation-definitions` in the wallet.
|Return a paginated items ordered by created timestamp.""".stripMargin
)

val createPresentationDefinition: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(RequestContext, CreatePresentationDefinition),
ErrorResponse,
PresentationDefinition,
Any
] =
basePrivateEndpoint.post
.in("presentation-definitions")
.in(jsonBody[CreatePresentationDefinition])
.out(statusCode(StatusCode.Created).description("Presentation Definition created successfully"))
.out(jsonBody[PresentationDefinition])
.errorOut(EndpointOutputs.basicFailureAndNotFoundAndForbidden)
.name("createPresentationDefinition")
.summary("Create a new presentation-definition")
.description(
"""Create a `presentation-definition` object according to the [presentation exchange protocol](https://identity.foundation/presentation-exchange/spec/v2.1.1/).
|The `POST` endpoint is restricted to the owner of the wallet. The `presentation-definition` object, however can be referenced by publicly by `id` returned in the response.""".stripMargin
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.hyperledger.identus.pollux.prex

import org.hyperledger.identus.agent.walletapi.model.BaseEntity
import org.hyperledger.identus.iam.authentication.{Authenticator, Authorizer, DefaultAuthenticator, SecurityLogic}
import org.hyperledger.identus.pollux.prex.controller.PresentationExchangeController
import org.hyperledger.identus.LogUtils.*
import sttp.tapir.ztapir.*
import zio.*

class PresentationExchangeServerEndpoints(
controller: PresentationExchangeController,
authenticator: Authenticator[BaseEntity],
authorizer: Authorizer[BaseEntity]
) {

private val getPresentationDefinitionServerEndpoint: ZServerEndpoint[Any, Any] =
PresentationExchangeEndpoints.getPresentationDefinition
.zServerLogic { case (rc, id) =>
controller.getPresentationDefinition(id).logTrace(rc)
}

private val listPresentationDefinitionServerEndpoint: ZServerEndpoint[Any, Any] =
PresentationExchangeEndpoints.listPresentationDefinition
.zServerSecurityLogic(SecurityLogic.authorizeWalletAccessWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, pagination) =>
controller
.listPresentationDefinition(pagination)(rc)
.provideSomeLayer(ZLayer.succeed(wac))
.logTrace(rc)
}
}

private val createPresentationDefinitionServerEndpoint: ZServerEndpoint[Any, Any] =
PresentationExchangeEndpoints.createPresentationDefinition
.zServerSecurityLogic(SecurityLogic.authorizeWalletAccessWith(_)(authenticator, authorizer))
.serverLogic { wac =>
{ case (rc, pd) =>
controller
.createPresentationDefinition(pd)
.provideSomeLayer(ZLayer.succeed(wac))
.logTrace(rc)
}
}

val all: List[ZServerEndpoint[Any, Any]] = List(
getPresentationDefinitionServerEndpoint,
listPresentationDefinitionServerEndpoint,
createPresentationDefinitionServerEndpoint
)
}

object PresentationExchangeServerEndpoints {
def all: URIO[DefaultAuthenticator & PresentationExchangeController, List[ZServerEndpoint[Any, Any]]] = {
for {
controller <- ZIO.service[PresentationExchangeController]
authenticator <- ZIO.service[DefaultAuthenticator]
endpoints = PresentationExchangeServerEndpoints(controller, authenticator, authenticator)
} yield endpoints.all
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.hyperledger.identus.pollux.prex.controller

import org.hyperledger.identus.api.http.{ErrorResponse, RequestContext}
import org.hyperledger.identus.api.http.model.{CollectionStats, PaginationInput}
import org.hyperledger.identus.api.util.PaginationUtils
import org.hyperledger.identus.pollux.core.service.PresentationExchangeService
import org.hyperledger.identus.pollux.prex.http.{CreatePresentationDefinition, PresentationDefinitionPage}
import org.hyperledger.identus.pollux.prex.PresentationDefinition
import org.hyperledger.identus.shared.models.WalletAccessContext
import zio.*

import java.util.UUID
import scala.language.implicitConversions

trait PresentationExchangeController {
def createPresentationDefinition(
cpd: CreatePresentationDefinition
): ZIO[WalletAccessContext, ErrorResponse, PresentationDefinition]

def getPresentationDefinition(id: UUID): IO[ErrorResponse, PresentationDefinition]

def listPresentationDefinition(paginationInput: PaginationInput)(implicit
rc: RequestContext
): ZIO[WalletAccessContext, ErrorResponse, PresentationDefinitionPage]
}

class PresentationExchangeControllerImpl(service: PresentationExchangeService) extends PresentationExchangeController {

override def createPresentationDefinition(
cpd: CreatePresentationDefinition
): ZIO[WalletAccessContext, ErrorResponse, PresentationDefinition] = {
val pd: PresentationDefinition = cpd
service.createPresentationDefinititon(pd).as(pd)
}

override def getPresentationDefinition(id: UUID): IO[ErrorResponse, PresentationDefinition] =
service.getPresentationDefinition(id)

override def listPresentationDefinition(
paginationInput: PaginationInput
)(implicit rc: RequestContext): ZIO[WalletAccessContext, ErrorResponse, PresentationDefinitionPage] = {
val uri = rc.request.uri
val pagination = paginationInput.toPagination
for {
pageResult <- service.listPresentationDefinition(offset = paginationInput.offset, limit = paginationInput.limit)
(items, totalCount) = pageResult
stats = CollectionStats(totalCount = totalCount, filteredCount = totalCount)
} yield PresentationDefinitionPage(
self = uri.toString(),
pageOf = PaginationUtils.composePageOfUri(uri).toString,
next = PaginationUtils.composeNextUri(uri, items, pagination, stats).map(_.toString),
previous = PaginationUtils.composePreviousUri(uri, items, pagination, stats).map(_.toString),
contents = items,
)
}

}

object PresentationExchangeControllerImpl {
def layer: URLayer[PresentationExchangeService, PresentationExchangeController] =
ZLayer.fromFunction(PresentationExchangeControllerImpl(_))
}
Loading

0 comments on commit 49f7ab3

Please sign in to comment.