Skip to content

Commit

Permalink
Merge pull request #210 from pagopa/PIN-3996
Browse files Browse the repository at this point in the history
PIN-3996 Validate risk analysis on EService publication
  • Loading branch information
nttdata-rtorsoli authored Sep 29, 2023
2 parents a5ab8a0 + dcb791e commit 67f26fc
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -399,18 +399,25 @@ object Converter {
singleAnswers = p.singleAnswers.map(_.toApi),
multiAnswers = p.multiAnswers.map(_.toApi)
)

def toTemplate: Commons.RiskAnalysisForm = Commons.RiskAnalysisForm(
version = p.version,
answers = (p.singleAnswers.map(_.toTemplate).flatten ++ p.multiAnswers.map(_.toTemplate).flatten).toMap
)
}

implicit class CatalogRiskAnalysisSingleAnswerObjectWrapper(private val p: readmodel.CatalogRiskAnalysisSingleAnswer)
extends AnyVal {
def toApi: EServiceRiskAnalysisSingleAnswer =
EServiceRiskAnalysisSingleAnswer(id = p.id, key = p.key, value = p.value)
def toTemplate: Map[String, Seq[String]] = Map(p.key -> p.value.toSeq)
}

implicit class CatalogRiskAnalysisMultiAnswerObjectWrapper(private val p: readmodel.CatalogRiskAnalysisMultiAnswer)
extends AnyVal {
def toApi: EServiceRiskAnalysisMultiAnswer =
EServiceRiskAnalysisMultiAnswer(id = p.id, key = p.key, values = p.values)
def toTemplate: Map[String, Seq[String]] = Map(p.key -> p.values)
}

implicit class ReadModelModeWrapper(private val mode: readmodel.CatalogItemMode) extends AnyVal {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ import it.pagopa.interop.catalogmanagement.model.{
Draft,
Suspended,
Deprecated,
Receive
Receive,
Deliver
}

final case class ProcessApiServiceImpl(
Expand Down Expand Up @@ -203,6 +204,23 @@ final case class ProcessApiServiceImpl(
val operationLabel = s"Publishing descriptor $descriptorId for EService $eServiceId"
logger.info(operationLabel)

def verifyRiskAnalysisForPublication(catalogItem: CatalogItem): Future[Unit] = catalogItem.mode match {
case Deliver => Future.unit
case Receive =>
for {
_ <-
if (catalogItem.riskAnalysis.isEmpty) Future.failed(EServiceRiskAnalysisIsRequired(catalogItem.id))
else Future.unit
tenant <- tenantManagementService.getTenantById(catalogItem.producerId)
tenantKind <- tenant.kind.toFuture(TenantKindNotFound(tenant.id))
_ <- catalogItem.riskAnalysis.traverse(risk =>
isRiskAnalysisFormValid(riskAnalysisForm = risk.riskAnalysisForm.toTemplate, schemaOnlyValidation = false)(
tenantKind.toTemplate
)
)
} yield ()
}

val result: Future[Unit] = for {
organizationId <- getOrganizationIdFutureUUID(contexts)
eServiceUuid <- eServiceId.toFutureUUID
Expand All @@ -211,6 +229,7 @@ final case class ProcessApiServiceImpl(
_ <- assertRequesterAllowed(catalogItem.producerId)(organizationId)
descriptor <- assertDescriptorExists(catalogItem, descriptorUuid)
_ <- verifyPublicationEligibility(descriptor)
_ <- verifyRiskAnalysisForPublication(catalogItem)
currentActiveDescriptor = catalogItem.descriptors.find(d => d.state == Published) // Must be at most one
_ <- catalogManagementService.publishDescriptor(eServiceId, descriptorId)
_ <- currentActiveDescriptor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ object ResponseHandlers extends AkkaResponses {
case Failure(ex: EServiceNotFound) => notFound(ex, logMessage)
case Failure(ex: EServiceDescriptorNotFound) => notFound(ex, logMessage)
case Failure(ex: EServiceDescriptorWithoutInterface) => badRequest(ex, logMessage)
case Failure(ex: EServiceRiskAnalysisIsRequired) => badRequest(ex, logMessage)
case Failure(ex: RiskAnalysisNotValid.type) => badRequest(ex, logMessage)
case Failure(ex) => internalServerError(ex, logMessage)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,7 @@ object CatalogProcessErrors {

final case class EServiceRiskAnalysisNotFound(eServiceId: UUID, riskAnalysisId: UUID)
extends ComponentError("0017", s"Risk Analysis $riskAnalysisId not found for EService $eServiceId")

final case class EServiceRiskAnalysisIsRequired(eServiceId: UUID)
extends ComponentError("0018", s"At least one Risk Analysis is required for EService $eServiceId")
}
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,136 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR
}
}
"Descriptor publication" should {
"succeed if mode is Receive" in {
val requesterId = UUID.randomUUID()

implicit val context: Seq[(String, String)] =
Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString)

(mockCatalogManagementService
.getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService))
.expects(SpecData.catalogItem.id, *, *)
.once()
.returns(
Future.successful(
SpecData.catalogItem.copy(
producerId = requesterId,
descriptors =
Seq(SpecData.catalogDescriptor.copy(state = Draft, interface = Option(SpecData.catalogDocument))),
riskAnalysis = Seq(SpecData.catalogRiskAnalysisFullValid),
mode = Receive
)
)
)

(mockTenantManagementService
.getTenantById(_: UUID)(_: ExecutionContext, _: ReadModelService))
.expects(requesterId, *, *)
.once()
.returns(Future.successful(SpecData.persistentTenant.copy(id = requesterId)))

(mockCatalogManagementService
.publishDescriptor(_: String, _: String)(_: Seq[(String, String)]))
.expects(SpecData.catalogItem.id.toString, SpecData.catalogDescriptor.id.toString, *)
.returning(Future.unit)
.once()

(mockAuthorizationManagementService
.updateStateOnClients(
_: UUID,
_: UUID,
_: AuthorizationManagementDependency.ClientComponentState,
_: Seq[String],
_: Int
)(_: Seq[(String, String)]))
.expects(
SpecData.catalogItem.id,
SpecData.catalogDescriptor.id,
AuthorizationManagementDependency.ClientComponentState.ACTIVE,
SpecData.catalogDescriptor.audience,
SpecData.catalogDescriptor.voucherLifespan,
*
)
.returning(Future.unit)
.once()

Post() ~> service.publishDescriptor(
SpecData.catalogItem.id.toString,
SpecData.catalogDescriptor.id.toString
) ~> check {
status shouldEqual StatusCodes.NoContent
}
}
"fail if mode is Receive and Catalog Item has not at least one Risk Analysis" in {
val requesterId = UUID.randomUUID()

implicit val context: Seq[(String, String)] =
Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString)

(mockCatalogManagementService
.getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService))
.expects(SpecData.catalogItem.id, *, *)
.once()
.returns(
Future.successful(
SpecData.catalogItem.copy(
producerId = requesterId,
descriptors =
Seq(SpecData.catalogDescriptor.copy(state = Draft, interface = Option(SpecData.catalogDocument))),
riskAnalysis = Seq.empty,
mode = Receive
)
)
)

Post() ~> service.publishDescriptor(
SpecData.catalogItem.id.toString,
SpecData.catalogDescriptor.id.toString
) ~> check {
status shouldEqual StatusCodes.BadRequest
val problem = responseAs[Problem]
problem.status shouldBe StatusCodes.BadRequest.intValue
problem.errors.head.code shouldBe "009-0018"
}
}
"fail if mode is Receive and Risk Analysis did not pass validation" in {
val requesterId = UUID.randomUUID()

implicit val context: Seq[(String, String)] =
Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString)

(mockCatalogManagementService
.getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService))
.expects(SpecData.catalogItem.id, *, *)
.once()
.returns(
Future.successful(
SpecData.catalogItem.copy(
producerId = requesterId,
descriptors =
Seq(SpecData.catalogDescriptor.copy(state = Draft, interface = Option(SpecData.catalogDocument))),
riskAnalysis = Seq(SpecData.catalogRiskAnalysisSchemaOnly),
mode = Receive
)
)
)

(mockTenantManagementService
.getTenantById(_: UUID)(_: ExecutionContext, _: ReadModelService))
.expects(requesterId, *, *)
.once()
.returns(Future.successful(SpecData.persistentTenant.copy(id = requesterId)))

Post() ~> service.publishDescriptor(
SpecData.catalogItem.id.toString,
SpecData.catalogDescriptor.id.toString
) ~> check {
status shouldEqual StatusCodes.BadRequest
val problem = responseAs[Problem]
problem.status shouldBe StatusCodes.BadRequest.intValue
problem.errors.head.code shouldBe "009-0016"
}
}
"succeed if descriptor is Draft" in {
val requesterId = UUID.randomUUID()

Expand Down
110 changes: 109 additions & 1 deletion src/test/scala/it/pagopa/interop/catalogprocess/SpecData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ object SpecData {
mode = CatalogManagement.Deliver
)

val catalogDocument: CatalogManagement.CatalogDocument = CatalogManagement.CatalogDocument(
val catalogDocument: CatalogManagement.CatalogDocument = CatalogManagement.CatalogDocument(
id = docId,
name = "name",
contentType = "application/pdf",
Expand All @@ -58,6 +58,114 @@ object SpecData {
checksum = "checksum",
uploadDate = OffsetDateTimeSupplier.get().minusDays(30)
)

val catalogRiskAnalysisSchemaOnly = CatalogManagement.CatalogRiskAnalysis(
id = UUID.randomUUID(),
name = "name",
riskAnalysisForm = CatalogManagement.CatalogRiskAnalysisForm(
id = UUID.randomUUID(),
version = "3.0",
singleAnswers = Seq(
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "purpose", value = Some("INSTITUTIONAL"))
),
multiAnswers = Seq(
CatalogManagement
.CatalogRiskAnalysisMultiAnswer(id = UUID.randomUUID(), key = "personalDataTypes", values = Seq("OTHER"))
)
),
createdAt = OffsetDateTimeSupplier.get()
)

val catalogRiskAnalysisFullValid = CatalogManagement.CatalogRiskAnalysis(
id = UUID.randomUUID(),
name = "name",
riskAnalysisForm = CatalogManagement.CatalogRiskAnalysisForm(
id = UUID.randomUUID(),
version = "3.0",
singleAnswers = Seq(
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "purpose", value = Some("INSTITUTIONAL")),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "legalObligationReference",
value = Some("YES")
),
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "dataDownload", value = Some("YES")),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "checkedExistenceMereCorrectnessInteropCatalogue",
value = Some("true")
),
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "deliveryMethod", value = Some("CLEARTEXT")),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "legalBasisPublicInterest",
value = Some("RULE_OF_LAW")
),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "confirmPricipleIntegrityAndDiscretion",
value = Some("true")
),
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "ruleOfLawText", value = Some("TheLaw")),
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "confirmDataRetentionPeriod",
value = Some("true")
),
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "usesThirdPartyData", value = Some("YES")),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "otherPersonalDataTypes",
value = Some("MyThirdPartyData")
),
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "doesUseThirdPartyData", value = Some("YES")),
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "knowsDataQuantity", value = Some("NO")),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "institutionalPurpose",
value = Some("MyPurpose")
),
CatalogManagement
.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "policyProvided", value = Some("NO")),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "reasonPolicyNotProvided",
value = Some("Because")
),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(id = UUID.randomUUID(), key = "doneDpia", value = Some("NO")),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "declarationConfirmGDPR",
value = Some("true")
),
CatalogManagement.CatalogRiskAnalysisSingleAnswer(
id = UUID.randomUUID(),
key = "purposePursuit",
value = Some("MERE_CORRECTNESS")
)
),
multiAnswers = Seq(
CatalogManagement
.CatalogRiskAnalysisMultiAnswer(id = UUID.randomUUID(), key = "personalDataTypes", values = Seq("OTHER")),
CatalogManagement.CatalogRiskAnalysisMultiAnswer(
id = UUID.randomUUID(),
key = "legalBasis",
values = Seq("LEGAL_OBLIGATION", "PUBLIC_INTEREST")
)
)
),
createdAt = OffsetDateTimeSupplier.get()
)

val catalogDescriptor: CatalogManagement.CatalogDescriptor = CatalogManagement.CatalogDescriptor(
id = descriptorId,
version = "1",
Expand Down

0 comments on commit 67f26fc

Please sign in to comment.