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 presentation-exchange endpoints #1365

Merged
merged 4 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
Loading