Skip to content

Commit

Permalink
docs(prism-agent): add verification policy OpenAPI description (#835)
Browse files Browse the repository at this point in the history
Signed-off-by: Yurii Shynbuiev <[email protected]>
  • Loading branch information
yshyn-iohk authored Jan 10, 2024
1 parent 614e811 commit b48fe3c
Show file tree
Hide file tree
Showing 12 changed files with 469 additions and 218 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.iohk.atala.agent.server.http

import io.iohk.atala.connect.controller.ConnectionEndpoints
import io.iohk.atala.pollux.credentialschema.VerificationPolicyEndpoints
import sttp.apispec.openapi.*
import sttp.apispec.{SecurityScheme, Tag}
import sttp.model.headers.AuthenticationScheme
Expand Down Expand Up @@ -50,9 +51,21 @@ object DocModels {
Info(
title = "Open Enterprise Agent API Reference",
version = "1.0", // Will be replaced dynamically by 'Tapir2StaticOAS'
summary = Some("Info - Summary"),
description = Some("Info - Description"),
termsOfService = Some("Info - Terms Of Service"),
summary = Some("""
|This API provides interfaces for managing decentralized identities and secure communications in a self-sovereign identity framework.
|It enables seamless interaction with various decentralized identity protocols and services using the [Open Enterprise Agent](https://github.com/hyperledger-labs/open-enterprise-agent)
|""".stripMargin),
description = Some("""
|The Open Enterprise Agent API facilitates the integration and management of self-sovereign identity capabilities within applications.
|It supports DID (Decentralized Identifiers) management, verifiable credential exchange, and secure messaging based on DIDComm standards.
|The API is designed to be interoperable with various blockchain and DLT (Distributed Ledger Technology) platforms, ensuring wide compatibility and flexibility.
|Key features include connection management, credential issuance and verification, and secure, privacy-preserving communication between entities.
|Additional information and the full list of capabilities can be found in the [Open Enterprise Agent documentation](https://docs.atalaprism.io/docs/category/prism-cloud-agent)
|""".stripMargin),
termsOfService = Some("""
|Users of the Open Enterprise Agent API must adhere to the terms and conditions outlined in [Link to Terms of Service](/).
|This includes compliance with relevant data protection regulations, responsible usage policies, and adherence to the principles of decentralized identity management.
|""".stripMargin),
contact = Some(
Contact(
name = Some("Contact - Name"),
Expand All @@ -63,8 +76,8 @@ object DocModels {
),
license = Some(
License(
name = "License - Name",
url = Some("License - URL"),
name = "Apache 2.0",
url = Some("https://www.apache.org/licenses/LICENSE-2.0"),
extensions = ListMap.empty
)
),
Expand Down Expand Up @@ -101,23 +114,8 @@ object DocModels {
)
.tags(
List(
Tag(
ConnectionEndpoints.TAG,
Some(
s"""
|The '${ConnectionEndpoints.TAG}' endpoints facilitate the initiation of connection flows between the current agent and peer agents, regardless of whether they reside in cloud or edge environments.
|<br>
|This implementation adheres to the DIDComm Messaging v2.0 - [Out of Band Messages](https://identity.foundation/didcomm-messaging/spec/v2.0/#out-of-band-messages) specification [section 9.5.4](https://identity.foundation/didcomm-messaging/spec/v2.0/#invitation) - to generate invitations.
|The <b>from</b> field of the out-of-band invitation message contains a freshly generated Peer DID that complies with the [did:peer:2](https://identity.foundation/peer-did-method-spec/#generating-a-didpeer2) specification.
|This Peer DID includes the 'uri' location of the DIDComm messaging service, essential for the invitee's subsequent execution of the connection flow.
|<br>
|Upon accepting an invitation, the invitee sends a connection request to the inviter's DIDComm messaging service endpoint.
|The connection request's 'type' attribute must be specified as "https://atalaprism.io/mercury/connections/1.0/request".
|The inviter agent responds with a connection response message, indicated by a 'type' attribute of "https://atalaprism.io/mercury/connections/1.0/response".
|Both request and response types are proprietary to the Open Enterprise Agent ecosystem.
|""".stripMargin
)
)
ConnectionEndpoints.tag,
VerificationPolicyEndpoints.tag
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package io.iohk.atala.agent.server.http

import sttp.apispec.SecurityScheme
import sttp.apispec.openapi.{OpenAPI, Server}
import sttp.model.headers.AuthenticationScheme
import io.iohk.atala.agent.server.buildinfo.BuildInfo
import sttp.apispec.openapi.OpenAPI
import sttp.tapir.redoc.RedocUIOptions
import sttp.tapir.redoc.bundle.RedocInterpreter
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.swagger.SwaggerUIOptions
import sttp.tapir.swagger.bundle.SwaggerInterpreter

import scala.collection.immutable.ListMap

object ZHttpEndpoints {

private val swaggerUIOptions = SwaggerUIOptions.default
Expand All @@ -21,11 +18,11 @@ object ZHttpEndpoints {

def swaggerEndpoints[F[_]](apiEndpoints: List[ServerEndpoint[Any, F]]): List[ServerEndpoint[Any, F]] =
SwaggerInterpreter(swaggerUIOptions = swaggerUIOptions, customiseDocsModel = DocModels.customiseDocsModel)
.fromServerEndpoints[F](apiEndpoints, "Prism Agent", "1.0.0")
.fromServerEndpoints[F](apiEndpoints, "Prism Agent", BuildInfo.version)

def redocEndpoints[F[_]](apiEndpoints: List[ServerEndpoint[Any, F]]): List[ServerEndpoint[Any, F]] =
RedocInterpreter(redocUIOptions = redocUIOptions, customiseDocsModel = DocModels.customiseDocsModel)
.fromServerEndpoints[F](apiEndpoints, "Prism Agent", "1.0.0")
.fromServerEndpoints[F](apiEndpoints, "Prism Agent", BuildInfo.version)

def withDocumentations[F[_]](apiEndpoints: List[ServerEndpoint[Any, F]]): List[ServerEndpoint[Any, F]] = {
apiEndpoints ++ swaggerEndpoints[F](apiEndpoints) ++ redocEndpoints[F](apiEndpoints)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.iohk.atala.api.http.model
import sttp.tapir.EndpointIO.annotations.{description, query}
import sttp.tapir.Schema.annotations.validateEach
import sttp.tapir.{Schema, Validator}

case class PaginationInput(
@query
@validateEach(Validator.positiveOrZero[Int])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import io.iohk.atala.iam.authentication.apikey.ApiKeyCredentials
import io.iohk.atala.iam.authentication.apikey.ApiKeyEndpointSecurityLogic.apiKeyHeader
import io.iohk.atala.iam.authentication.oidc.JwtCredentials
import io.iohk.atala.iam.authentication.oidc.JwtSecurityLogic.jwtAuthHeader
import sttp.apispec.Tag
import sttp.model.StatusCode
import sttp.tapir.*
import sttp.tapir.json.zio.jsonBody
Expand All @@ -21,7 +22,22 @@ import java.util.UUID

object ConnectionEndpoints {

val TAG: String = "Connections Management"
private val tagName = "Connections Management"
private val tagDescription =
s"""
|The '$tagName' endpoints facilitate the initiation of connection flows between the current agent and peer agents, regardless of whether they reside in cloud or edge environments.
|<br>
|This implementation adheres to the DIDComm Messaging v2.0 - [Out of Band Messages](https://identity.foundation/didcomm-messaging/spec/v2.0/#out-of-band-messages) specification [section 9.5.4](https://identity.foundation/didcomm-messaging/spec/v2.0/#invitation) - to generate invitations.
|The <b>from</b> field of the out-of-band invitation message contains a freshly generated Peer DID that complies with the [did:peer:2](https://identity.foundation/peer-did-method-spec/#generating-a-didpeer2) specification.
|This Peer DID includes the 'uri' location of the DIDComm messaging service, essential for the invitee's subsequent execution of the connection flow.
|<br>
|Upon accepting an invitation, the invitee sends a connection request to the inviter's DIDComm messaging service endpoint.
|The connection request's 'type' attribute must be specified as "https://atalaprism.io/mercury/connections/1.0/request".
|The inviter agent responds with a connection response message, indicated by a 'type' attribute of "https://atalaprism.io/mercury/connections/1.0/response".
|Both request and response types are proprietary to the Open Enterprise Agent ecosystem.
|""".stripMargin

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

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

Expand Down Expand Up @@ -61,7 +77,7 @@ object ConnectionEndpoints {
|In the agent database, the created connection record has an initial state set to `InvitationGenerated`.
|The request body may contain a `label` that can be used as a human readable alias for the connection, for example `{'label': "Connection with Bob"}`
|""".stripMargin)
.tag(TAG)
.tag(tagName)

val getConnection
: Endpoint[(ApiKeyCredentials, JwtCredentials), (RequestContext, UUID), ErrorResponse, Connection, Any] =
Expand All @@ -85,7 +101,7 @@ object ConnectionEndpoints {
|The API returns a comprehensive collection of connection flow records within the system, regardless of their state.
|The returned connection item includes essential metadata such as connection ID, thread ID, state, role, participant information, and other relevant details.
|""".stripMargin)
.tag(TAG)
.tag(tagName)

val getConnections: Endpoint[
(ApiKeyCredentials, JwtCredentials),
Expand Down Expand Up @@ -117,7 +133,7 @@ object ConnectionEndpoints {
|Each connection item includes essential metadata such as connection ID, thread ID, state, role, participant information, and other relevant details.
|Pagination support is available, allowing for efficient handling of large datasets.
|""".stripMargin)
.tag(TAG)
.tag(tagName)

val acceptConnectionInvitation: Endpoint[
(ApiKeyCredentials, JwtCredentials),
Expand Down Expand Up @@ -154,6 +170,6 @@ object ConnectionEndpoints {
|The created record will contain a newly generated pairwise Peer DID used for that connection.
|A connection request will then be sent to the peer agent to actually establish the connection, moving the record state to `ConnectionRequestSent`, and waiting the connection response from the peer agent.
|""".stripMargin)
.tag(TAG)
.tag(tagName)

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.iohk.atala.iam.authentication.apikey.ApiKeyEndpointSecurityLogic.apiKe
import io.iohk.atala.iam.authentication.oidc.JwtCredentials
import io.iohk.atala.iam.authentication.oidc.JwtSecurityLogic.jwtAuthHeader
import io.iohk.atala.pollux.credentialschema.http.*
import sttp.apispec.Tag
import sttp.model.StatusCode
import sttp.tapir.*
import sttp.tapir.json.zio.jsonBody
Expand All @@ -17,11 +18,23 @@ import java.util.UUID

object VerificationPolicyEndpoints {

private val tagName = "Verification"
private val tagDescription = """
|<p>The `Verification` endpoints enable the management and querying of verification policies,
|which are applied to W3C Verifiable Credentials in JWT format.</p>
|<p>Users can retrieve and paginate existing policies or create new ones.
|These policies determine the verification criteria, allowing users to specify constraints such as `schemaId` and `trustedIssuers` in the current implementation.</p>
|<p>The constraints are defined using the `schemaId` and a sequence of `trustedIssuers`.
|This functionality ensures the system's integrity and adherence to specific verification requirements.</p>
|<p>Endpoints are secured by `apiKeyAuth` or `jwtAuth` authentication</p>""".stripMargin

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

val createVerificationPolicyEndpoint: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(RequestContext, VerificationPolicyInput),
ErrorResponse,
VerificationPolicy,
VerificationPolicyResponse,
Any
] = endpoint.post
.securityIn(apiKeyHeader)
Expand All @@ -39,21 +52,21 @@ object VerificationPolicyEndpoints {
)
)
.out(
jsonBody[VerificationPolicy].description(
jsonBody[VerificationPolicyResponse].description(
"Created verification policy entity"
)
)
.errorOut(basicFailuresAndForbidden)
.name("createVerificationPolicy")
.summary("Create the new verification policy")
.description("Create the new verification policy")
.tag("Verification")
.tag(tagName)

val updateVerificationPolicyEndpoint: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(RequestContext, UUID, Int, VerificationPolicyInput),
ErrorResponse,
VerificationPolicy,
VerificationPolicyResponse,
Any
] =
endpoint.put
Expand All @@ -72,20 +85,20 @@ object VerificationPolicyEndpoints {
)
)
.out(statusCode(StatusCode.Ok))
.out(jsonBody[VerificationPolicy])
.out(jsonBody[VerificationPolicyResponse])
.errorOut(basicFailureAndNotFoundAndForbidden)
.name("updateVerificationPolicy")
.summary("Update the verification policy object by id")
.description(
"Update the verification policy entry"
)
.tag("Verification")
.tag(tagName)

val getVerificationPolicyByIdEndpoint: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(RequestContext, UUID),
ErrorResponse,
VerificationPolicy,
VerificationPolicyResponse,
Any
] =
endpoint.get
Expand All @@ -96,14 +109,14 @@ object VerificationPolicyEndpoints {
"verification" / "policies" / path[UUID]("id")
.description("Get the verification policy by id")
)
.out(jsonBody[VerificationPolicy])
.out(jsonBody[VerificationPolicyResponse])
.errorOut(basicFailureAndNotFoundAndForbidden)
.name("getVerificationPolicyById")
.summary("Fetch the verification policy by id")
.description(
"Get the verification policy by id"
)
.tag("Verification")
.tag(tagName)

val deleteVerificationPolicyByIdEndpoint: Endpoint[
(ApiKeyCredentials, JwtCredentials),
Expand Down Expand Up @@ -131,13 +144,13 @@ object VerificationPolicyEndpoints {
.description(
"Delete the verification policy by id"
)
.tag("Verification")
.tag(tagName)

val lookupVerificationPoliciesByQueryEndpoint: Endpoint[
(ApiKeyCredentials, JwtCredentials),
(RequestContext, VerificationPolicy.Filter, PaginationInput, Option[Order]),
(RequestContext, VerificationPolicyResponse.Filter, PaginationInput, Option[Order]),
ErrorResponse,
VerificationPolicyPage,
VerificationPolicyResponsePage,
Any
] =
endpoint.get
Expand All @@ -148,19 +161,23 @@ object VerificationPolicyEndpoints {
"verification" / "policies"
.description("Lookup verification policy by query")
)
.in(query[Option[String]]("name").mapTo[VerificationPolicy.Filter])
.in(
query[Option[String]]("name")
.description(VerificationPolicyResponse.annotations.name.description)
.mapTo[VerificationPolicyResponse.Filter]
)
.in(
query[Option[Int]]("offset")
.and(query[Option[Int]]("limit"))
.mapTo[PaginationInput]
)
.in(query[Option[Order]]("order"))
.out(jsonBody[VerificationPolicyPage])
.out(jsonBody[VerificationPolicyResponsePage])
.errorOut(basicFailuresAndForbidden)
.name("lookupVerificationPoliciesByQuery")
.summary("Lookup verification policies by query")
.description(
"Lookup verification policies by `name`, and control the pagination by `offset` and `limit` parameters"
)
.tag("Verification")
.tag(tagName)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import io.iohk.atala.iam.authentication.DefaultAuthenticator
import io.iohk.atala.iam.authentication.SecurityLogic
import io.iohk.atala.pollux.credentialschema.VerificationPolicyEndpoints.*
import io.iohk.atala.pollux.credentialschema.controller.VerificationPolicyController
import io.iohk.atala.pollux.credentialschema.http.{VerificationPolicy, VerificationPolicyInput}
import io.iohk.atala.pollux.credentialschema.http.{VerificationPolicyResponse, VerificationPolicyInput}
import io.iohk.atala.shared.models.WalletAccessContext
import java.util.UUID
import sttp.tapir.ztapir.*
Expand Down Expand Up @@ -82,7 +82,7 @@ class VerificationPolicyServerEndpoints(
{
case (
ctx: RequestContext,
filter: VerificationPolicy.Filter,
filter: VerificationPolicyResponse.Filter,
paginationInput: PaginationInput,
order: Option[Order]
) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package io.iohk.atala.pollux.credentialschema.controller

import io.iohk.atala.api.http.model.{Order, Pagination}
import io.iohk.atala.api.http.{ErrorResponse, RequestContext}
import io.iohk.atala.pollux.credentialschema.http.{VerificationPolicy, VerificationPolicyInput, VerificationPolicyPage}
import io.iohk.atala.pollux.credentialschema.http.{
VerificationPolicyResponse,
VerificationPolicyInput,
VerificationPolicyResponsePage
}
import io.iohk.atala.shared.models.WalletAccessContext
import zio.*

Expand All @@ -12,19 +16,19 @@ trait VerificationPolicyController {
def createVerificationPolicy(
ctx: RequestContext,
in: VerificationPolicyInput
): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicy]
): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyResponse]

def getVerificationPolicyById(
ctx: RequestContext,
id: UUID
): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicy]
): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyResponse]

def updateVerificationPolicyById(
ctx: RequestContext,
id: UUID,
nonce: Int,
update: VerificationPolicyInput
): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicy]
): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyResponse]

def deleteVerificationPolicyById(
ctx: RequestContext,
Expand All @@ -33,8 +37,8 @@ trait VerificationPolicyController {

def lookupVerificationPolicies(
ctx: RequestContext,
filter: VerificationPolicy.Filter,
filter: VerificationPolicyResponse.Filter,
pagination: Pagination,
order: Option[Order]
): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyPage]
): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyResponsePage]
}
Loading

0 comments on commit b48fe3c

Please sign in to comment.