diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/DocModels.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/DocModels.scala index a45080eca0..d5c9430db5 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/DocModels.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/DocModels.scala @@ -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 @@ -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"), @@ -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 ) ), @@ -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. - |
- |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 from 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. - |
- |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 ) ) diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/ZHttpEndpoints.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/ZHttpEndpoints.scala index 721dcae0a0..384976e388 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/ZHttpEndpoints.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/agent/server/http/ZHttpEndpoints.scala @@ -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 @@ -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) diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/api/http/model/PaginationInput.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/api/http/model/PaginationInput.scala index ed6d17dba4..b56112d6d4 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/api/http/model/PaginationInput.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/api/http/model/PaginationInput.scala @@ -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]) diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/connect/controller/ConnectionEndpoints.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/connect/controller/ConnectionEndpoints.scala index 91b611f0a4..d19c5d9bed 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/connect/controller/ConnectionEndpoints.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/connect/controller/ConnectionEndpoints.scala @@ -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 @@ -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. + |
+ |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 from 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. + |
+ |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] @@ -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] = @@ -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), @@ -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), @@ -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) } diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/VerificationPolicyEndpoints.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/VerificationPolicyEndpoints.scala index 781771bae5..a6231608aa 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/VerificationPolicyEndpoints.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/VerificationPolicyEndpoints.scala @@ -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 @@ -17,11 +18,23 @@ import java.util.UUID object VerificationPolicyEndpoints { + private val tagName = "Verification" + private val tagDescription = """ + |

The `Verification` endpoints enable the management and querying of verification policies, + |which are applied to W3C Verifiable Credentials in JWT format.

+ |

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.

+ |

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.

+ |

Endpoints are secured by `apiKeyAuth` or `jwtAuth` authentication

""".stripMargin + + val tag = Tag(tagName, Some(tagDescription)) + val createVerificationPolicyEndpoint: Endpoint[ (ApiKeyCredentials, JwtCredentials), (RequestContext, VerificationPolicyInput), ErrorResponse, - VerificationPolicy, + VerificationPolicyResponse, Any ] = endpoint.post .securityIn(apiKeyHeader) @@ -39,7 +52,7 @@ object VerificationPolicyEndpoints { ) ) .out( - jsonBody[VerificationPolicy].description( + jsonBody[VerificationPolicyResponse].description( "Created verification policy entity" ) ) @@ -47,13 +60,13 @@ object VerificationPolicyEndpoints { .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 @@ -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 @@ -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), @@ -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 @@ -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) } diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/VerificationPolicyServerEndpoints.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/VerificationPolicyServerEndpoints.scala index 7192a1f950..a913467547 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/VerificationPolicyServerEndpoints.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/VerificationPolicyServerEndpoints.scala @@ -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.* @@ -82,7 +82,7 @@ class VerificationPolicyServerEndpoints( { case ( ctx: RequestContext, - filter: VerificationPolicy.Filter, + filter: VerificationPolicyResponse.Filter, paginationInput: PaginationInput, order: Option[Order] ) => diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyController.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyController.scala index 2754a28a63..1c5eb92b3c 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyController.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyController.scala @@ -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.* @@ -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, @@ -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] } diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyControllerImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyControllerImpl.scala index 30d528a263..05723af890 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyControllerImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyControllerImpl.scala @@ -7,8 +7,12 @@ import io.iohk.atala.pollux.core.model.CredentialSchemaAndTrustedIssuersConstrai import io.iohk.atala.pollux.core.model.error.VerificationPolicyError import io.iohk.atala.pollux.core.model.error.VerificationPolicyError.* import io.iohk.atala.pollux.core.service.VerificationPolicyService -import io.iohk.atala.pollux.credentialschema.http.VerificationPolicy.* -import io.iohk.atala.pollux.credentialschema.http.{VerificationPolicy, VerificationPolicyInput, VerificationPolicyPage} +import io.iohk.atala.pollux.credentialschema.http.VerificationPolicyResponse.* +import io.iohk.atala.pollux.credentialschema.http.{ + VerificationPolicyResponse, + VerificationPolicyInput, + VerificationPolicyResponsePage +} import io.iohk.atala.shared.models.WalletAccessContext import zio.* import zio.ZIO.* @@ -32,7 +36,7 @@ class VerificationPolicyControllerImpl(service: VerificationPolicyService) exten override def createVerificationPolicy( ctx: RequestContext, in: VerificationPolicyInput - ): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicy] = { + ): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyResponse] = { val constraints = in.constraints .map(c => CredentialSchemaAndTrustedIssuersConstraint( @@ -56,7 +60,7 @@ class VerificationPolicyControllerImpl(service: VerificationPolicyService) exten override def getVerificationPolicyById( ctx: RequestContext, id: UUID - ): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicy] = { + ): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyResponse] = { service.get(id).flatMap { case Some(vp) => succeed(vp.toSchema().withUri(ctx.request.uri)) case None => fail(NotFoundError(id)) @@ -68,7 +72,7 @@ class VerificationPolicyControllerImpl(service: VerificationPolicyService) exten id: UUID, nonce: Int, update: VerificationPolicyInput - ): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicy] = { + ): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyResponse] = { val updatedZIO = for { constraints <- zio.ZIO.succeed( update.constraints.toVector // TODO: refactor to Seq @@ -111,10 +115,10 @@ class VerificationPolicyControllerImpl(service: VerificationPolicyService) exten override def lookupVerificationPolicies( ctx: RequestContext, - filter: VerificationPolicy.Filter, + filter: VerificationPolicyResponse.Filter, pagination: Pagination, order: Option[Order] - ): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyPage] = { + ): ZIO[WalletAccessContext, ErrorResponse, VerificationPolicyResponsePage] = { for { filteredDomainRecords <- service .lookup(filter.name, Some(pagination.offset), Some(pagination.limit)) diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyPageRequestLogic.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyPageRequestLogic.scala index 2accee460d..7c142990df 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyPageRequestLogic.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/controller/VerificationPolicyPageRequestLogic.scala @@ -2,28 +2,28 @@ package io.iohk.atala.pollux.credentialschema.controller import io.iohk.atala.api.http.RequestContext import io.iohk.atala.api.http.model.{CollectionStats, Pagination} -import io.iohk.atala.pollux.credentialschema.http.{VerificationPolicy, VerificationPolicyPage} +import io.iohk.atala.pollux.credentialschema.http.{VerificationPolicyResponse, VerificationPolicyResponsePage} import sttp.model.Uri import io.iohk.atala.api.util.PaginationUtils case class VerificationPolicyPageRequestLogic( ctx: RequestContext, pagination: Pagination, - items: List[VerificationPolicy], + items: List[VerificationPolicyResponse], stats: CollectionStats ) { def composeNextUri(uri: Uri): Option[Uri] = PaginationUtils.composeNextUri(uri, items, pagination, stats) def composePreviousUri(uri: Uri): Option[Uri] = PaginationUtils.composePreviousUri(uri, items, pagination, stats) - def result: VerificationPolicyPage = { + def result: VerificationPolicyResponsePage = { val self = ctx.request.uri.toString val pageOf = ctx.request.uri.copy(querySegments = Seq.empty).toString val next = composeNextUri(ctx.request.uri).map(_.toString) val previous = composePreviousUri(ctx.request.uri).map(_.toString) val baseUri = ctx.request.uri.copy(querySegments = Seq.empty) - val pageResult = VerificationPolicyPage( + val pageResult = VerificationPolicyResponsePage( self = self, kind = "VerificationPolicyPage", pageOf = pageOf, diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/http/VerificationPolicy.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/http/VerificationPolicy.scala deleted file mode 100644 index e5b3003fb3..0000000000 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/http/VerificationPolicy.scala +++ /dev/null @@ -1,150 +0,0 @@ -package io.iohk.atala.pollux.credentialschema.http - -import io.iohk.atala.pollux.core.model -import io.iohk.atala.pollux.core.model.CredentialSchemaAndTrustedIssuersConstraint -import sttp.model.Uri -import sttp.tapir.Schema -import sttp.tapir.Schema.annotations.{description, encodedName} -import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder} - -import java.time.{OffsetDateTime, ZoneOffset} -import java.util.UUID - -//TODO: All these classes should be moved to the Pollux library into api package -case class VerificationPolicyConstraint(schemaId: String, trustedIssuers: Seq[String]) - -object VerificationPolicyConstraint { - given encoder: zio.json.JsonEncoder[VerificationPolicyConstraint] = - DeriveJsonEncoder.gen[VerificationPolicyConstraint] - - given decoder: zio.json.JsonDecoder[VerificationPolicyConstraint] = - DeriveJsonDecoder.gen[VerificationPolicyConstraint] - - given schema: Schema[VerificationPolicyConstraint] = Schema.derived - - extension (constraint: model.VerificationPolicyConstraint) { - def toSchema(): VerificationPolicyConstraint = { - VerificationPolicyConstraint(constraint) - } - } - - given Conversion[model.VerificationPolicyConstraint, VerificationPolicyConstraint] with { - override def apply(constraint: model.VerificationPolicyConstraint): VerificationPolicyConstraint = - VerificationPolicyConstraint(constraint) - } - - def apply(constraint: model.VerificationPolicyConstraint): VerificationPolicyConstraint = { - constraint match { - case CredentialSchemaAndTrustedIssuersConstraint(schemaId, trustedIssuers) => - VerificationPolicyConstraint(schemaId, trustedIssuers) - } - } -} - -case class VerificationPolicy( - self: String, - kind: String, - id: UUID, - nonce: Int = 0, - name: String, - description: String, - createdAt: OffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC), - updatedAt: OffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC), - constraints: Seq[VerificationPolicyConstraint] -) { - def update(in: VerificationPolicyInput): VerificationPolicy = { - copy( - id = in.id.getOrElse(UUID.randomUUID()), - name = in.name, - description = in.description, - constraints = in.constraints - ) - } - - def withBaseUri(base: Uri) = withSelf(base.addPath(id.toString).toString) - - def withUri(uri: Uri) = withSelf(uri.toString) - - def withSelf(self: String) = copy(self = self) -} - -object VerificationPolicy { - def apply(in: VerificationPolicyInput): VerificationPolicy = - VerificationPolicy( - self = "", - kind = "VerificationPolicy", - id = in.id.getOrElse(UUID.randomUUID()), - name = in.name, - description = in.description, - constraints = in.constraints - ) - - given encoder: zio.json.JsonEncoder[VerificationPolicy] = DeriveJsonEncoder.gen[VerificationPolicy] - given decoder: zio.json.JsonDecoder[VerificationPolicy] = DeriveJsonDecoder.gen[VerificationPolicy] - given schema: Schema[VerificationPolicy] = Schema.derived - - import VerificationPolicyConstraint._ - extension (vp: model.VerificationPolicy) { - def toSchema(): VerificationPolicy = { - VerificationPolicy( - self = "", - kind = "VerificationPolicy", - id = vp.id, - nonce = vp.nonce, - name = vp.name, - description = vp.description, - createdAt = vp.createdAt, - updatedAt = vp.updatedAt, - constraints = vp.constrains.map(VerificationPolicyConstraint(_)).toList - ) - } - } - - // TODO: This filter is used for InMemory implementation only, think about removing it - case class Filter( - name: Option[String] - ) { - - // TODO: This filter is used for InMemory implementation only, think about removing it - def predicate(vp: VerificationPolicy): Boolean = { - name.forall(vp.name == _) - } - } -} - -case class VerificationPolicyPage( - self: String, - kind: String, - pageOf: String, - next: Option[String], - previous: Option[String], - contents: List[VerificationPolicy] -) - -object VerificationPolicyPage { - given encoder: zio.json.JsonEncoder[VerificationPolicyPage] = - DeriveJsonEncoder.gen[VerificationPolicyPage] - - given decoder: zio.json.JsonDecoder[VerificationPolicyPage] = - DeriveJsonDecoder.gen[VerificationPolicyPage] - - given schema: Schema[VerificationPolicyPage] = - Schema.derived -} - -case class VerificationPolicyInput( - id: Option[UUID], - name: String, - description: String, - constraints: List[VerificationPolicyConstraint], -) - -object VerificationPolicyInput { - given encoder: zio.json.JsonEncoder[VerificationPolicyInput] = - DeriveJsonEncoder.gen[VerificationPolicyInput] - - given decoder: zio.json.JsonDecoder[VerificationPolicyInput] = - DeriveJsonDecoder.gen[VerificationPolicyInput] - - given schema: Schema[VerificationPolicyInput] = Schema.derived -} diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/http/VerificationPolicyResponse.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/http/VerificationPolicyResponse.scala new file mode 100644 index 0000000000..543ed83e59 --- /dev/null +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/pollux/credentialschema/http/VerificationPolicyResponse.scala @@ -0,0 +1,363 @@ +package io.iohk.atala.pollux.credentialschema.http + +import io.iohk.atala.api.http.Annotation +import io.iohk.atala.pollux.core.model +import io.iohk.atala.pollux.core.model.CredentialSchemaAndTrustedIssuersConstraint +import io.iohk.atala.pollux.credentialschema.http +import sttp.model.Uri +import sttp.tapir.Schema +import sttp.tapir.Schema.annotations.{description, encodedExample, encodedName, validate} +import sttp.tapir.Validator.nonEmptyString +import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonEncoder} + +import java.time.{OffsetDateTime, ZoneOffset} +import java.util.UUID + +//TODO: All these classes should be moved to the Pollux library into api package +case class VerificationPolicyConstraint( + @description(VerificationPolicyConstraint.annotations.schemaId.description) + @encodedExample(VerificationPolicyConstraint.annotations.schemaId.example) + @validate(nonEmptyString) + schemaId: String, + @description(VerificationPolicyConstraint.annotations.trustedIssuers.description) + @encodedExample(VerificationPolicyConstraint.annotations.trustedIssuers.example) + trustedIssuers: Seq[String] +) + +object VerificationPolicyConstraint { + given encoder: zio.json.JsonEncoder[VerificationPolicyConstraint] = + DeriveJsonEncoder.gen[VerificationPolicyConstraint] + + given decoder: zio.json.JsonDecoder[VerificationPolicyConstraint] = + DeriveJsonDecoder.gen[VerificationPolicyConstraint] + + @encodedExample(JsonEncoder[VerificationPolicyConstraint].encodeJson(VerificationPolicyConstraint.example)) + given schema: Schema[VerificationPolicyConstraint] = Schema.derived + + extension (constraint: model.VerificationPolicyConstraint) { + def toSchema(): VerificationPolicyConstraint = { + VerificationPolicyConstraint(constraint) + } + } + + given Conversion[model.VerificationPolicyConstraint, VerificationPolicyConstraint] with { + override def apply(constraint: model.VerificationPolicyConstraint): VerificationPolicyConstraint = + VerificationPolicyConstraint(constraint) + } + + def apply(constraint: model.VerificationPolicyConstraint): VerificationPolicyConstraint = { + constraint match { + case CredentialSchemaAndTrustedIssuersConstraint(schemaId, trustedIssuers) => + VerificationPolicyConstraint(schemaId, trustedIssuers) + } + } + + object annotations { + object schemaId + extends Annotation[String]( + description = "The schema ID of the credential that is being verified.", + example = "https://example.com/driving-license-1.0" + ) + + object trustedIssuers + extends Annotation[Seq[String]]( + description = "A list of DIDs of the trusted issuers.", + example = Seq("did:example:123456789abcdefghi") + ) + } + + val example = VerificationPolicyConstraint( + schemaId = "https://example.com/driving-license-1.0", + trustedIssuers = Seq("did:example:123456789abcdefghi") + ) +} + +case class VerificationPolicyResponse( + @description(VerificationPolicyResponse.annotations.self.description) + @encodedExample(VerificationPolicyResponse.annotations.self.example) + self: String, + @description(VerificationPolicyResponse.annotations.kind.description) + @encodedExample(VerificationPolicyResponse.annotations.kind.example) + kind: String, + @description(VerificationPolicyResponse.annotations.id.description) + @encodedExample(VerificationPolicyResponse.annotations.id.example) + id: UUID, + @description(VerificationPolicyResponse.annotations.nonce.description) + @encodedExample(VerificationPolicyResponse.annotations.nonce.example) + nonce: Int = 0, + @description(VerificationPolicyResponse.annotations.name.description) + @encodedExample(VerificationPolicyResponse.annotations.name.example) + @validate(nonEmptyString) + name: String, + @description(VerificationPolicyResponse.annotations.description.description) + @encodedExample(VerificationPolicyResponse.annotations.description.example) + description: String, + @description(VerificationPolicyResponse.annotations.createdAt.description) + @encodedExample(VerificationPolicyResponse.annotations.createdAt.example) + createdAt: OffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC), + @description(VerificationPolicyResponse.annotations.updatedAt.description) + @encodedExample(VerificationPolicyResponse.annotations.updatedAt.example) + updatedAt: OffsetDateTime = OffsetDateTime.now(ZoneOffset.UTC), + @description(VerificationPolicyResponse.annotations.constraints.description) + @encodedExample( + JsonEncoder[Seq[VerificationPolicyConstraint]].encodeJson( + VerificationPolicyResponse.annotations.constraints.example + ) + ) + constraints: Seq[VerificationPolicyConstraint] +) { + def update(in: VerificationPolicyInput): VerificationPolicyResponse = { + copy( + id = in.id.getOrElse(UUID.randomUUID()), + name = in.name, + description = in.description, + constraints = in.constraints + ) + } + + def withBaseUri(base: Uri) = withSelf(base.addPath(id.toString).toString) + + def withUri(uri: Uri) = withSelf(uri.toString) + + def withSelf(self: String) = copy(self = self) +} + +object VerificationPolicyResponse { + def apply(in: VerificationPolicyInput): VerificationPolicyResponse = + VerificationPolicyResponse( + self = "", + kind = "VerificationPolicy", + id = in.id.getOrElse(UUID.randomUUID()), + name = in.name, + description = in.description, + constraints = in.constraints + ) + + given encoder: zio.json.JsonEncoder[VerificationPolicyResponse] = DeriveJsonEncoder.gen[VerificationPolicyResponse] + given decoder: zio.json.JsonDecoder[VerificationPolicyResponse] = DeriveJsonDecoder.gen[VerificationPolicyResponse] + + @encodedExample(JsonEncoder[VerificationPolicyResponse].encodeJson(VerificationPolicyResponse.example)) + given schema: Schema[VerificationPolicyResponse] = Schema.derived + + import VerificationPolicyConstraint._ + extension (vp: model.VerificationPolicy) { + def toSchema(): VerificationPolicyResponse = { + VerificationPolicyResponse( + self = "", + kind = "VerificationPolicy", + id = vp.id, + nonce = vp.nonce, + name = vp.name, + description = vp.description, + createdAt = vp.createdAt, + updatedAt = vp.updatedAt, + constraints = vp.constrains.map(VerificationPolicyConstraint(_)).toList + ) + } + } + + case class Filter( + @description(VerificationPolicyResponse.annotations.name.description) + @encodedExample(VerificationPolicyResponse.annotations.name.example) + @validate(nonEmptyString) + name: Option[String] + ) + + object annotations { + object id + extends Annotation[UUID]( + description = + "A unique identifier to address the verification policy instance. UUID is generated by the backend.", + example = UUID.fromString("0527aea1-d131-3948-a34d-03af39aba8b5") + ) + + object self + extends Annotation[String]( + description = "The URL that uniquely identifies the resource being returned in the response.", + example = "/prism-agent/verification/policies/0527aea1-d131-3948-a34d-03af39aba8b4" + ) + + object kind + extends Annotation[String]( + description = "A string that identifies the type of resource being returned in the response.", + example = "VerificationPolicy" + ) + + object name + extends Annotation[String]( + description = "A human-readable name for the verification policy. The `name` cannot be empty.", + example = "Trusted Issuers Verification Policy" + ) + + object nonce + extends Annotation[Int]( + description = "A number that is changed every time the verification policy is updated.", + example = 1234 + ) + + object description + extends Annotation[String]( + description = "A human-readable description of the verification policy.", + example = "Verification policy that checks if the credential was issued by a trusted issuer." + ) + + object createdAt + extends Annotation[OffsetDateTime]( + description = + "[RFC3339](https://www.rfc-editor.org/rfc/rfc3339) date on which the verification policy was created.", + example = OffsetDateTime.parse("2022-03-10T12:00:00Z") + ) + + object updatedAt + extends Annotation[OffsetDateTime]( + description = + "[RFC3339](https://www.rfc-editor.org/rfc/rfc3339) date on which the verification policy was updated.", + example = OffsetDateTime.parse("2022-03-10T12:00:00Z") + ) + + object constraints + extends Annotation[Seq[VerificationPolicyConstraint]]( + description = + "The object that describes the constraints of the verification policy. Each constraint is a tuple of the `schemaId` and a set of DIDs of the trusted issuers.", + example = Seq[VerificationPolicyConstraint]( + VerificationPolicyConstraint( + schemaId = "https://example.com/driving-license-1.0", + trustedIssuers = Seq("did:example:123456789abcdefghi") + ) + ) + ) + } + + val example = VerificationPolicyResponse( + self = "/prism-agent/verification/policies", + kind = "VerificationPolicy", + id = UUID.fromString("0527aea1-d131-3948-a34d-03af39aba8b4"), + nonce = 0, + name = "Trusted Issuers Verification Policy", + description = "Verification policy that checks if the credential was issued by a trusted issuer.", + createdAt = OffsetDateTime.parse("2022-03-10T12:00:00Z"), + updatedAt = OffsetDateTime.parse("2022-03-10T12:00:00Z"), + constraints = Seq( + VerificationPolicyConstraint( + schemaId = "https://example.com/driving-license-1.0", + trustedIssuers = Seq("did:example:123456789abcdefghi") + ) + ) + ) +} + +case class VerificationPolicyResponsePage( + @description(VerificationPolicyResponsePage.annotations.self.description) + @encodedExample(VerificationPolicyResponsePage.annotations.self.example) + self: String, + @description(VerificationPolicyResponsePage.annotations.kind.description) + @encodedExample(VerificationPolicyResponsePage.annotations.kind.example) + kind: String, + @description(VerificationPolicyResponsePage.annotations.pageOf.description) + @encodedExample(VerificationPolicyResponsePage.annotations.pageOf.example) + pageOf: String, + @description(VerificationPolicyResponsePage.annotations.next.description) + @encodedExample(VerificationPolicyResponsePage.annotations.next.example) + next: Option[String], + @description(VerificationPolicyResponsePage.annotations.previous.description) + @encodedExample(VerificationPolicyResponsePage.annotations.previous.example) + previous: Option[String], + @description(VerificationPolicyResponsePage.annotations.contents.description) + @encodedExample( + JsonEncoder[Seq[VerificationPolicyResponse]].encodeJson( + VerificationPolicyResponsePage.annotations.contents.example + ) + ) + contents: List[VerificationPolicyResponse] +) + +object VerificationPolicyResponsePage { + given encoder: zio.json.JsonEncoder[VerificationPolicyResponsePage] = + DeriveJsonEncoder.gen[VerificationPolicyResponsePage] + + given decoder: zio.json.JsonDecoder[VerificationPolicyResponsePage] = + DeriveJsonDecoder.gen[VerificationPolicyResponsePage] + + @encodedExample(JsonEncoder[VerificationPolicyResponsePage].encodeJson(VerificationPolicyResponsePage.example)) + given schema: Schema[VerificationPolicyResponsePage] = + Schema.derived + + object annotations { + object self + extends Annotation[String]( + description = "The URL that uniquely identifies the resource being returned in the response.", + example = "/prism-agent/verification/policies?name=Trusted&offset=0&limit=10" + ) + + object kind + extends Annotation[String]( + description = "A string that identifies the type of resource being returned in the response.", + example = "VerificationPolicyPage" + ) + + object pageOf + extends Annotation[String]( + description = "A string field indicating the type of resource that the contents field contains", + example = "/prism-agent/verification/policies" + ) + + object next + extends Annotation[String]( + description = "An optional string field containing the URL of the next page of results. " + + "If the API response does not contain any more pages, this field should be set to None.", + example = "/prism-agent/verification/policies?skip=20&limit=10" + ) + + object previous + extends Annotation[String]( + description = "An optional string field containing the URL of the previous page of results. " + + "If the API response is the first page of results, this field should be set to None.", + example = "/prism-agent/verification/policies?skip=0&limit=10" + ) + object contents + extends Annotation[Seq[VerificationPolicyResponse]]( + description = + "A sequence of VerificationPolicyResponse objects representing the list of verification policies that the paginated response contains", + example = Seq(VerificationPolicyResponse.example) + ) + } + + val example = VerificationPolicyResponsePage( + self = "/prism-agent/verification/policies?name=Trusted&offset=0&limit=10", + kind = "VerificationPolicyPage", + pageOf = "/prism-agent/verification/policies", + next = Some("/prism-agent/verification/policies?skip=20&limit=10"), + previous = Some("/prism-agent/verification/policies?skip=0&limit=10"), + contents = List(VerificationPolicyResponse.example) + ) +} + +case class VerificationPolicyInput( + @description(VerificationPolicyResponse.annotations.id.description) + @encodedExample(VerificationPolicyResponse.annotations.id.example) + id: Option[UUID], + @description(VerificationPolicyResponse.annotations.name.description) + @encodedExample(VerificationPolicyResponse.annotations.name.example) + @validate(nonEmptyString) + name: String, + @description(VerificationPolicyResponse.annotations.description.description) + @encodedExample(VerificationPolicyResponse.annotations.description.example) + description: String, + @description(VerificationPolicyResponse.annotations.constraints.description) + @encodedExample( + JsonEncoder[Seq[VerificationPolicyConstraint]].encodeJson( + VerificationPolicyResponse.annotations.constraints.example + ) + ) + constraints: List[VerificationPolicyConstraint], +) + +object VerificationPolicyInput { + given encoder: zio.json.JsonEncoder[VerificationPolicyInput] = + DeriveJsonEncoder.gen[VerificationPolicyInput] + + given decoder: zio.json.JsonDecoder[VerificationPolicyInput] = + DeriveJsonDecoder.gen[VerificationPolicyInput] + + given schema: Schema[VerificationPolicyInput] = Schema.derived +} diff --git a/prism-agent/service/server/src/test/scala/io/iohk/atala/api/util/Tapir2StaticOAS.scala b/prism-agent/service/server/src/test/scala/io/iohk/atala/api/util/Tapir2StaticOAS.scala index b26bd2fea1..318a9d801a 100644 --- a/prism-agent/service/server/src/test/scala/io/iohk/atala/api/util/Tapir2StaticOAS.scala +++ b/prism-agent/service/server/src/test/scala/io/iohk/atala/api/util/Tapir2StaticOAS.scala @@ -27,6 +27,7 @@ object Tapir2StaticOAS extends ZIOAppDefault { @main override def run: ZIO[Any with ZIOAppArgs with Scope, Any, Any] = { val effect = for { args <- getArgs + _ <- ZIO.when(args.length != 2)(ZIO.fail("Usage: Tapir2StaticOAS ")) allEndpoints <- AgentHttpServer.agentRESTServiceEndpoints } yield { import sttp.apispec.openapi.circe.yaml.*