Skip to content

Commit

Permalink
PIN-4392 BKE - Tenant process added route GET /tenants/attributes/cer…
Browse files Browse the repository at this point in the history
…tified (#92)
  • Loading branch information
nttdata-rtorsoli authored Jan 29, 2024
1 parent 993389f commit 4c98802
Show file tree
Hide file tree
Showing 16 changed files with 283 additions and 9 deletions.
77 changes: 75 additions & 2 deletions src/main/resources/interface-specification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,48 @@ paths:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
/tenants/attributes/certified:
parameters:
- $ref: '#/components/parameters/CorrelationIdHeader'
- in: query
name: offset
required: true
schema:
type: integer
format: int32
minimum: 0
- in: query
name: limit
required: true
schema:
type: integer
format: int32
minimum: 1
maximum: 50
get:
tags:
- tenant
operationId: getCertifiedAttributes
description: Retrieve the certified attributes
responses:
'200':
description: Certified Attributes retrieved
content:
application/json:
schema:
$ref: '#/components/schemas/CertifiedAttributes'
'400':
description: Bad Request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
'403':
description: Forbidden
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
/tenants/{tenantId}/attributes/certified:
parameters:
- $ref: '#/components/parameters/CorrelationIdHeader'
Expand Down Expand Up @@ -499,7 +541,7 @@ paths:
required: true
schema:
type: string
format: uuid
format: uuid
delete:
tags:
- tenant
Expand Down Expand Up @@ -969,6 +1011,37 @@ components:
required:
- results
- totalCount
CertifiedAttribute:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
attributeId:
type: string
format: uuid
attributeName:
type: string
required:
- id
- name
- attributeId
- attributeName
CertifiedAttributes:
type: object
properties:
results:
type: array
items:
$ref: '#/components/schemas/CertifiedAttribute'
totalCount:
type: integer
format: int32
required:
- results
- totalCount
TenantFeature:
type: object
properties:
Expand Down Expand Up @@ -1072,7 +1145,7 @@ components:
type: string
format: uuid
required:
- id
- id
UpdateVerifiedTenantAttributeSeed:
type: object
properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cats.syntax.all._
import it.pagopa.interop.tenantmanagement.model.tenant.PersistentTenantFeature.PersistentCertifier
import it.pagopa.interop.tenantmanagement.model.tenant._
import it.pagopa.interop.tenantmanagement.client.{model => Management}
import it.pagopa.interop.tenantprocess.common._
import it.pagopa.interop.tenantprocess.model._
import spray.json._

Expand Down Expand Up @@ -164,6 +165,16 @@ object ReadModelTenantAdapters extends SprayJsonSupport with DefaultJsonProtocol
)
}

implicit class CertifiedAttributeWrapper(private val attr: readmodel.CertifiedAttribute) extends AnyVal {

def toApi: CertifiedAttribute = CertifiedAttribute(
id = attr.id,
name = attr.name,
attributeName = attr.attributeName,
attributeId = attr.attributeId
)
}

implicit class PersistentTenantRevokerWrapper(private val t: PersistentTenantRevoker) extends AnyVal {
def toApi: TenantRevoker = TenantRevoker(
id = t.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ object TenantApiMarshallerImpl extends TenantApiMarshaller with SprayJsonSupport
override implicit def toEntityMarshallerResourceId: ToEntityMarshaller[ResourceId] =
sprayJsonMarshaller[ResourceId]

override implicit def toEntityMarshallerCertifiedAttributes: ToEntityMarshaller[CertifiedAttributes] =
sprayJsonMarshaller[CertifiedAttributes]

override implicit def fromEntityUnmarshallerCertifiedTenantAttributeSeed
: FromEntityUnmarshaller[CertifiedTenantAttributeSeed] = sprayJsonUnmarshaller[CertifiedTenantAttributeSeed]
}
Original file line number Diff line number Diff line change
Expand Up @@ -1073,4 +1073,27 @@ final case class TenantApiServiceImpl(
deleteTenantMailResponse[Unit](operationLabel)(_ => deleteTenantMail204)
}
}

override def getCertifiedAttributes(offset: Int, limit: Int)(implicit
contexts: Seq[(String, String)],
toEntityMarshallerCertifiedAttributes: ToEntityMarshaller[CertifiedAttributes],
toEntityMarshallerProblem: ToEntityMarshaller[Problem]
): Route = authorize(ADMIN_ROLE, API_ROLE, SECURITY_ROLE, M2M_ROLE, SUPPORT_ROLE) {
val operationLabel = s"Retrieving certified attributes"
logger.info(operationLabel)

val result: Future[CertifiedAttributes] = for {
requesterTenantUuid <- getOrganizationIdFutureUUID(contexts)
requesterTenant <- tenantManagementService.getTenantById(requesterTenantUuid).map(_.toManagement)
certifier <- requesterTenant.features
.collectFirstSome(_.certifier.map(_.certifierId))
.toFuture(TenantIsNotACertifier(requesterTenantUuid))
attributes <- tenantManagementService
.getCertifiedAttributes(certifier, offset, limit)
} yield CertifiedAttributes(results = attributes.results.map(_.toApi), totalCount = attributes.totalCount)

onComplete(result) {
getCertifiedAttributesResponse[CertifiedAttributes](operationLabel)(getCertifiedAttributes200)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ package object impl extends SprayJsonSupport with DefaultJsonProtocol {
implicit def tenantFeatureFormat: RootJsonFormat[TenantFeature] = jsonFormat1(TenantFeature)
implicit def tenantAttributeFormat: RootJsonFormat[TenantAttribute] = jsonFormat3(TenantAttribute)

implicit def declaredTenantAttributeFormat: RootJsonFormat[DeclaredTenantAttribute] =
implicit def certifiedAttributeFormat: RootJsonFormat[CertifiedAttribute] = jsonFormat4(CertifiedAttribute)
implicit def certifiedAttributesFormat: RootJsonFormat[CertifiedAttributes] = jsonFormat2(CertifiedAttributes)
implicit def declaredTenantAttributeFormat: RootJsonFormat[DeclaredTenantAttribute] =
jsonFormat3(DeclaredTenantAttribute)
implicit def certifiedTenantAttributeFormat: RootJsonFormat[CertifiedTenantAttribute] =
jsonFormat3(CertifiedTenantAttribute)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package it.pagopa.interop.tenantprocess.common.readmodel

import it.pagopa.interop.commons.utils.SprayCommonFormats.uuidFormat

import spray.json.DefaultJsonProtocol._
import spray.json.RootJsonFormat
import java.util.UUID

final case class CertifiedAttribute(id: UUID, name: String, attributeId: UUID, attributeName: String)

object CertifiedAttribute {
implicit val format: RootJsonFormat[CertifiedAttribute] = jsonFormat4(CertifiedAttribute.apply)
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,75 @@
package it.pagopa.interop.tenantprocess.common.readmodel

import it.pagopa.interop.tenantprocess.common.readmodel.CertifiedAttribute
import it.pagopa.interop.agreementmanagement.model.agreement.{Active, Suspended}
import it.pagopa.interop.attributeregistrymanagement.model.persistence.attribute.Certified
import it.pagopa.interop.commons.cqrs.service.ReadModelService
import it.pagopa.interop.tenantmanagement.model.tenant.PersistentTenant
import it.pagopa.interop.tenantmanagement.model.tenant.{PersistentTenant, PersistentCertifiedAttribute}
import it.pagopa.interop.tenantmanagement.model.persistence.JsonFormats._
import org.mongodb.scala.Document
import org.mongodb.scala.bson.conversions.Bson
import org.mongodb.scala.model.Aggregates.{`match`, count, lookup, project, sort}
import org.mongodb.scala.model.Aggregates.{`match`, count, lookup, project, sort, unwind}
import org.mongodb.scala.model.Filters
import org.mongodb.scala.model.Projections.{computed, fields, include}
import org.mongodb.scala.model.Projections.{computed, fields, include, excludeId}
import org.mongodb.scala.model.Sorts.ascending

import java.util.UUID
import scala.concurrent.{ExecutionContext, Future}

object ReadModelTenantQueries extends ReadModelQuery {

def getCertifiedAttributes(certifier: String, offset: Int, limit: Int)(implicit
ec: ExecutionContext,
readModel: ReadModelService
): Future[PaginatedResult[CertifiedAttribute]] = {

val query: Bson = Filters.eq("data.attributes.type", PersistentCertifiedAttribute.toString)

val filterPipeline: Seq[Bson] = Seq(
`match`(query),
lookup(from = "attributes", localField = "data.id", foreignField = "data.attributes.id", as = "attributes"),
unwind("$attributes"),
`match`(
Filters.and(
Filters.eq("attributes.data.kind", Certified.toString),
Filters.eq("attributes.data.origin", certifier.toString)
)
)
)

val projection: Bson = project(
fields(
computed("id", "$data.id"),
computed("name", "$data.name"),
computed("attributeId", "$attributes.data.id"),
computed("attributeName", "$attributes.data.name"),
computed("lowerName", Document("""{ "$toLower" : "$data.name" }""")),
excludeId()
)
)

for {
// Using aggregate to perform case insensitive sorting
// N.B.: Required because DocumentDB does not support collation
consumers <- readModel.aggregateRaw[CertifiedAttribute](
"tenants",
filterPipeline ++
Seq(projection, sort(ascending("lowerName"))),
offset = offset,
limit = limit
)
// Note: This could be obtained using $facet function (avoiding to execute the query twice),
// but it is not supported by DocumentDB
count <- readModel.aggregate[TotalCountResult](
"tenants",
filterPipeline ++
Seq(count("totalCount"), project(computed("data", Document("""{ "totalCount" : "$totalCount" }""")))),
offset = 0,
limit = Int.MaxValue
)
} yield PaginatedResult(results = consumers, totalCount = count.headOption.map(_.totalCount).getOrElse(0))
}

def getTenantBySelfcareId(
selfcareId: UUID
)(implicit ec: ExecutionContext, readModel: ReadModelService): Future[Option[PersistentTenant]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ object ResponseHandlers extends AkkaResponses {
case Failure(ex) => internalServerError(ex, logMessage)
}

def getCertifiedAttributesResponse[T](logMessage: String)(
success: T => Route
)(result: Try[T])(implicit contexts: Seq[(String, String)], logger: LoggerTakingImplicit[ContextFieldsToLog]): Route =
result match {
case Success(s) => success(s)
case Failure(ex: TenantIsNotACertifier) => forbidden(ex, logMessage)
case Failure(ex) => internalServerError(ex, logMessage)
}

def getTenantResponse[T](logMessage: String)(
success: T => Route
)(result: Try[T])(implicit contexts: Seq[(String, String)], logger: LoggerTakingImplicit[ContextFieldsToLog]): Route =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import it.pagopa.interop.tenantmanagement.model.tenant.{
PersistentTenantAttribute,
PersistentExternalId
}
import it.pagopa.interop.tenantprocess.common.readmodel.CertifiedAttribute
import it.pagopa.interop.tenantprocess.common.readmodel.PaginatedResult
import it.pagopa.interop.commons.cqrs.service.ReadModelService

Expand Down Expand Up @@ -48,6 +49,11 @@ trait TenantManagementService {
readModel: ReadModelService
): Future[PaginatedResult[PersistentTenant]]

def getCertifiedAttributes(certifier: String, offset: Int, limit: Int)(implicit
ec: ExecutionContext,
readModel: ReadModelService
): Future[PaginatedResult[CertifiedAttribute]]

def listConsumers(name: Option[String], producerId: UUID, offset: Int, limit: Int)(implicit
ec: ExecutionContext,
readModel: ReadModelService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import it.pagopa.interop.tenantprocess.service.{
TenantManagementInvoker,
TenantManagementService
}
import it.pagopa.interop.tenantprocess.common.readmodel.CertifiedAttribute

import java.util.UUID
import scala.concurrent.{ExecutionContext, Future}
Expand Down Expand Up @@ -103,6 +104,12 @@ final case class TenantManagementServiceImpl(
.toFuture(TenantAttributeNotFound(tenantId, attributeId))
} yield attribute

override def getCertifiedAttributes(certifier: String, offset: Int, limit: Int)(implicit
ec: ExecutionContext,
readModel: ReadModelService
): Future[PaginatedResult[CertifiedAttribute]] =
ReadModelTenantQueries.getCertifiedAttributes(certifier, offset, limit)

override def getTenantBySelfcareId(
selfcareId: UUID
)(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PersistentTenant] =
Expand Down
13 changes: 12 additions & 1 deletion src/test/resources/authz.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,17 @@
"api"
]
},
{
"route": "getCertifiedAttributes",
"verb": "GET",
"roles": [
"admin",
"api",
"m2m",
"security",
"support"
]
},
{
"route": "addCertifiedAttribute",
"verb": "POST",
Expand All @@ -180,6 +191,6 @@
"admin",
"m2m"
]
}
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,15 @@ class TenantApiServiceAuthzSpec extends ClusteredMUnitRouteTest with SpecData {
)
}

test("Tenant api should accept authorized roles for getCertifiedAttributes") {
validateAuthorization(
endpoints("getCertifiedAttributes"),
{ implicit c: Seq[(String, String)] =>
tenantService.getCertifiedAttributes(0, 0)
}
)
}

test("Tenant api should accept authorized roles for addCertifiedAttribute") {
validateAuthorization(
endpoints("addCertifiedAttribute"),
Expand Down
Loading

0 comments on commit 4c98802

Please sign in to comment.