From af43c726bbfc47f8f16d2565d10da73093a162d8 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Wed, 27 Sep 2023 14:10:01 +0200 Subject: [PATCH 01/17] PIN-3985 Implemented createRiskAnalisys --- project/Dependencies.scala | 15 +- .../resources/interface-specification.yml | 89 ++++++++ .../catalogprocess/api/impl/Converter.scala | 44 ++++ .../api/impl/ProcessApiMarshallerImpl.scala | 2 + .../api/impl/ProcessApiServiceImpl.scala | 50 ++++- .../api/impl/ResponseHandlers.scala | 15 ++ .../catalogprocess/api/impl/package.scala | 11 + .../readmodel/ReadModelTenantQueries.scala | 16 ++ .../errors/CatalogProcessErrors.scala | 14 ++ .../server/impl/Dependencies.scala | 3 +- .../service/CatalogManagementService.scala | 4 + .../service/TenantManagementService.scala | 13 ++ .../impl/CatalogManagementServiceImpl.scala | 17 ++ .../impl/TenantManagementServiceImpl.scala | 20 ++ src/test/resources/authz.json | 3 +- .../catalogprocess/CatalogProcessSpec.scala | 201 +++++++++++++++++- .../interop/catalogprocess/SpecData.scala | 14 ++ .../interop/catalogprocess/SpecHelper.scala | 2 + .../authz/ProcessApiAuthzSpec.scala | 14 ++ .../util/FakeDependencies.scala | 30 ++- 20 files changed, 567 insertions(+), 10 deletions(-) create mode 100644 src/main/scala/it/pagopa/interop/catalogprocess/common/readmodel/ReadModelTenantQueries.scala create mode 100644 src/main/scala/it/pagopa/interop/catalogprocess/service/TenantManagementService.scala create mode 100644 src/main/scala/it/pagopa/interop/catalogprocess/service/impl/TenantManagementServiceImpl.scala diff --git a/project/Dependencies.scala b/project/Dependencies.scala index da686bd3..bef004e8 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -71,6 +71,10 @@ object Dependencies { lazy val catalogManagementClient = namespace %% "interop-be-catalog-management-client" % catalogManagementVersion + + lazy val tenantManagementModels = + namespace %% "interop-be-tenant-management-models" % tenantManagementVersion + lazy val catalogManagementModels = namespace %% "interop-be-catalog-management-models" % catalogManagementVersion @@ -80,10 +84,11 @@ object Dependencies { lazy val authorizationManagementClient = namespace %% "interop-be-authorization-management-client" % authorizationManagementVersion - lazy val commonsUtils = namespace %% "interop-commons-utils" % commonsVersion - lazy val fileManager = namespace %% "interop-commons-file-manager" % commonsVersion - lazy val commonsJWT = namespace %% "interop-commons-jwt" % commonsVersion - lazy val commonsCqrs = namespace %% "interop-commons-cqrs" % commonsVersion + lazy val commonsUtils = namespace %% "interop-commons-utils" % commonsVersion + lazy val fileManager = namespace %% "interop-commons-file-manager" % commonsVersion + lazy val commonsJWT = namespace %% "interop-commons-jwt" % commonsVersion + lazy val commonsCqrs = namespace %% "interop-commons-cqrs" % commonsVersion + lazy val riskAnalysis = namespace %% "interop-commons-risk-analysis" % commonsVersion } object Jars { @@ -109,10 +114,12 @@ object Dependencies { pagopa.catalogManagementModels % Compile, pagopa.agreementManagementModels % Compile, pagopa.authorizationManagementClient % Compile, + pagopa.tenantManagementModels % Compile, pagopa.commonsUtils % Compile, pagopa.fileManager % Compile, pagopa.commonsJWT % Compile, pagopa.commonsCqrs % Compile, + pagopa.riskAnalysis % Compile, akka.testkit % Test, akka.httpTestkit % Test, scalatest.core % Test, diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index 20949d55..c2426eae 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -864,6 +864,47 @@ paths: application/problem+json: schema: $ref: '#/components/schemas/Problem' + /eservices/{eServiceId}/riskAnalysis: + parameters: + - $ref: '#/components/parameters/CorrelationIdHeader' + - $ref: '#/components/parameters/IpAddress' + post: + security: + - bearerAuth: [] + tags: + - process + summary: Create an e-service risk analysis + operationId: createRiskAnalysis + parameters: + - name: eServiceId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + requestBody: + description: A payload containing the risk analysis + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EServiceRiskAnalysisSeed' + responses: + '204': + description: EService Risk Analysis created. + '404': + description: EService not found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + '400': + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' /status: get: security: [] @@ -1106,6 +1147,54 @@ components: format: uuid name: type: string + EServiceRiskAnalysisSeed: + type: object + required: + - name + - riskAnalysisForm + properties: + name: + type: string + riskAnalysisForm: + $ref: '#/components/schemas/EServiceRiskAnalysisFormSeed' + EServiceRiskAnalysisFormSeed: + type: object + required: + - version + - singleAnswers + - multiAnswers + properties: + version: + type: string + singleAnswers: + type: array + items: + $ref: '#/components/schemas/EServiceRiskAnalysisSingleAnswerSeed' + multiAnswers: + type: array + items: + $ref: '#/components/schemas/EServiceRiskAnalysisMultiAnswerSeed' + EServiceRiskAnalysisSingleAnswerSeed: + type: object + required: + - key + properties: + key: + type: string + value: + type: string + EServiceRiskAnalysisMultiAnswerSeed: + type: object + required: + - key + - values + properties: + key: + type: string + values: + type: array + items: + type: string EService: type: object required: diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/Converter.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/Converter.scala index 57056867..f1da3138 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/Converter.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/Converter.scala @@ -3,8 +3,10 @@ package it.pagopa.interop.catalogprocess.api.impl import it.pagopa.interop.agreementmanagement.model.{agreement => AgreementPersistenceModel} import it.pagopa.interop.catalogmanagement.client.{model => CatalogManagementDependency} import it.pagopa.interop.catalogmanagement.{model => readmodel} +import it.pagopa.interop.commons.riskanalysis.{model => Commons} import it.pagopa.interop.catalogprocess.model._ import it.pagopa.interop.catalogprocess.common.readmodel.Consumers +import it.pagopa.interop.tenantmanagement.model.tenant.PersistentTenantKind import java.util.UUID import it.pagopa.interop.catalogmanagement.model.DELIVER @@ -100,6 +102,48 @@ object Converter { ) } + implicit class EServiceRiskAnalysisSeedWrapper(private val seed: EServiceRiskAnalysisSeed) extends AnyVal { + def toDependency: CatalogManagementDependency.RiskAnalysisSeed = CatalogManagementDependency.RiskAnalysisSeed( + name = seed.name, + riskAnalysisForm = seed.riskAnalysisForm.toDependency + ) + } + + implicit class EServiceRiskAnalysisFormSeedWrapper(private val seed: EServiceRiskAnalysisFormSeed) extends AnyVal { + def toDependency: CatalogManagementDependency.RiskAnalysisFormSeed = + CatalogManagementDependency.RiskAnalysisFormSeed( + version = seed.version, + singleAnswers = seed.singleAnswers.map(_.toDependency), + multiAnswers = seed.multiAnswers.map(_.toDependency) + ) + def toTemplate: Commons.RiskAnalysisForm = Commons.RiskAnalysisForm( + version = seed.version, + answers = (seed.singleAnswers.map(_.toTemplate).flatten ++ seed.multiAnswers.map(_.toTemplate).flatten).toMap + ) + } + + implicit class PersistentTenantKindrapper(private val kind: PersistentTenantKind) extends AnyVal { + def toTemplate: Commons.RiskAnalysisTenantKind = kind match { + case PersistentTenantKind.PA => Commons.RiskAnalysisTenantKind.PA + case PersistentTenantKind.GSP => Commons.RiskAnalysisTenantKind.GSP + case PersistentTenantKind.PRIVATE => Commons.RiskAnalysisTenantKind.PRIVATE + } + } + + implicit class EServiceRiskAnalysisSingleAnswerSeedWrapper(private val seed: EServiceRiskAnalysisSingleAnswerSeed) + extends AnyVal { + def toDependency: CatalogManagementDependency.RiskAnalysisSingleAnswerSeed = + CatalogManagementDependency.RiskAnalysisSingleAnswerSeed(key = seed.key, value = seed.value) + def toTemplate: Map[String, Seq[String]] = Map(seed.key -> seed.value.toSeq) + } + + implicit class EServiceRiskAnalysisMultiAnswerSeedWrapper(private val seed: EServiceRiskAnalysisMultiAnswerSeed) + extends AnyVal { + def toDependency: CatalogManagementDependency.RiskAnalysisMultiAnswerSeed = + CatalogManagementDependency.RiskAnalysisMultiAnswerSeed(key = seed.key, values = seed.values) + def toTemplate: Map[String, Seq[String]] = Map(seed.key -> seed.values) + } + implicit class eServiceDescriptorDocumentSeedWrapper(private val seed: UpdateEServiceDescriptorDocumentSeed) extends AnyVal { def toDependency: CatalogManagementDependency.UpdateEServiceDescriptorDocumentSeed = diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiMarshallerImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiMarshallerImpl.scala index ac84e7df..a50274eb 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiMarshallerImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiMarshallerImpl.scala @@ -44,4 +44,6 @@ object ProcessApiMarshallerImpl extends ProcessApiMarshaller with SprayJsonSuppo override implicit def toEntityMarshallerEServiceConsumers: ToEntityMarshaller[EServiceConsumers] = sprayJsonMarshaller[EServiceConsumers] + override implicit def fromEntityUnmarshallerEServiceRiskAnalysisSeed + : FromEntityUnmarshaller[EServiceRiskAnalysisSeed] = sprayJsonUnmarshaller[EServiceRiskAnalysisSeed] } diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index 00fb01f5..76509dcf 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -22,6 +22,8 @@ import it.pagopa.interop.commons.utils.AkkaUtils._ import it.pagopa.interop.commons.utils.OpenapiUtils.parseArrayParameters import it.pagopa.interop.commons.utils.TypeConversions._ import it.pagopa.interop.commons.utils.errors.GenericComponentErrors +import it.pagopa.interop.commons.riskanalysis.api.impl.RiskAnalysisValidation +import it.pagopa.interop.commons.riskanalysis.{model => Commons} import java.util.UUID import scala.concurrent.{ExecutionContext, Future} @@ -33,13 +35,15 @@ import it.pagopa.interop.catalogmanagement.model.{ Published, Draft, Suspended, - Deprecated + Deprecated, + RECEIVE } final case class ProcessApiServiceImpl( catalogManagementService: CatalogManagementService, agreementManagementService: AgreementManagementService, authorizationManagementService: AuthorizationManagementService, + tenantManagementService: TenantManagementService, fileManager: FileManager )(implicit ec: ExecutionContext, readModel: ReadModelService) extends ProcessApiService { @@ -648,6 +652,31 @@ final case class ProcessApiServiceImpl( suspendDescriptorResponse[Unit](operationLabel)(_ => archiveDescriptor204) } } + + override def createRiskAnalysis(eServiceId: String, seed: EServiceRiskAnalysisSeed)(implicit + contexts: Seq[(String, String)], + toEntityMarshallerProblem: ToEntityMarshaller[Problem] + ): Route = authorize(ADMIN_ROLE, API_ROLE) { + val operationLabel = s"Create Risk Analysis for EService $eServiceId" + logger.info(operationLabel) + + val result = for { + organizationId <- getOrganizationIdFutureUUID(contexts) + eServiceUuid <- eServiceId.toFutureUUID + catalogItem <- catalogManagementService.getEServiceById(eServiceUuid) + _ <- isDraftEService(catalogItem) + _ <- isReceiveEService(catalogItem) + _ <- assertRequesterAllowed(catalogItem.producerId)(organizationId) + tenant <- tenantManagementService.getTenantById(organizationId) + tenantKind <- tenant.kind.toFuture(TenantKindNotFound(tenant.id)) + _ <- isRiskAnalysisFormValid(seed.riskAnalysisForm.toTemplate, true)(tenantKind.toTemplate) + _ <- catalogManagementService.createRiskAnalysis(eServiceUuid, seed.toDependency) + } yield () + + onComplete(result) { + createRiskAnalysisResponse[Unit](operationLabel)(_ => createRiskAnalysis204) + } + } } object ProcessApiServiceImpl { @@ -662,6 +691,25 @@ object ProcessApiServiceImpl { case _ => Future.failed(NotValidDescriptor(descriptor.id.toString, descriptor.state.toString)) } + def isRiskAnalysisFormValid(riskAnalysisForm: Commons.RiskAnalysisForm, schemaOnlyValidation: Boolean)( + kind: Commons.RiskAnalysisTenantKind + )(implicit ec: ExecutionContext): Future[Unit] = + Future + .failed(RiskAnalysisNotValid) + .unlessA( + RiskAnalysisValidation + .validate(riskAnalysisForm, schemaOnlyValidation)(kind) + .isValid + ) + + def isReceiveEService(eService: CatalogItem)(implicit ec: ExecutionContext): Future[Unit] = + Future.failed(EServiceNotInReceiveMode(eService.id)).unlessA(eService.mode == RECEIVE) + + def isDraftEService(eService: CatalogItem)(implicit ec: ExecutionContext): Future[Unit] = + Future + .failed(EServiceNotInDraftState(eService.id)) + .unlessA(eService.descriptors.map(_.state) == Seq(Draft)) + def assertRequesterAllowed(resourceId: UUID)(requesterId: UUID)(implicit ec: ExecutionContext): Future[Unit] = Future.failed(GenericComponentErrors.OperationForbidden).unlessA(resourceId == requesterId) diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala index 00a74051..f7e978a0 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala @@ -48,6 +48,21 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex) => internalServerError(ex, logMessage) } + def createRiskAnalysisResponse[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: OperationForbidden.type) => forbidden(ex, logMessage) + case Failure(ex: EServiceNotFound) => notFound(ex, logMessage) + case Failure(ex: TenantNotFound) => notFound(ex, logMessage) + case Failure(ex: TenantKindNotFound) => notFound(ex, logMessage) + case Failure(ex: EServiceNotInDraftState) => badRequest(ex, logMessage) + case Failure(ex: EServiceNotInReceiveMode) => badRequest(ex, logMessage) + case Failure(ex: RiskAnalysisNotValid.type) => badRequest(ex, logMessage) + case Failure(ex) => internalServerError(ex, logMessage) + } + def updateEServiceByIdResponse[T](logMessage: String)( success: T => Route )(result: Try[T])(implicit contexts: Seq[(String, String)], logger: LoggerTakingImplicit[ContextFieldsToLog]): Route = diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/package.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/package.scala index 38be8791..e7ce1447 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/package.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/package.scala @@ -36,6 +36,17 @@ package object impl extends SprayJsonSupport with DefaultJsonProtocol { implicit val createEServiceDescriptorDocumentSeedFormat: RootJsonFormat[CreateEServiceDescriptorDocumentSeed] = jsonFormat8(CreateEServiceDescriptorDocumentSeed) + implicit val riskAnalysisMultiAnswerSeedFormat: RootJsonFormat[EServiceRiskAnalysisMultiAnswerSeed] = jsonFormat2( + EServiceRiskAnalysisMultiAnswerSeed + ) + implicit val riskAnalysisSingleAnswerSeedFormat: RootJsonFormat[EServiceRiskAnalysisSingleAnswerSeed] = jsonFormat2( + EServiceRiskAnalysisSingleAnswerSeed + ) + implicit val riskAnalysisFormSeedFormat: RootJsonFormat[EServiceRiskAnalysisFormSeed] = jsonFormat3( + EServiceRiskAnalysisFormSeed + ) + implicit val riskAnalysisSeedFormat: RootJsonFormat[EServiceRiskAnalysisSeed] = jsonFormat2(EServiceRiskAnalysisSeed) + implicit val flatAgreementFormat: RootJsonFormat[FlatAgreement] = jsonFormat2(FlatAgreement) implicit val flatAttributeFormat: RootJsonFormat[FlatAttribute] = jsonFormat1(FlatAttribute) implicit val flatAttributesFormat: RootJsonFormat[FlatAttributes] = jsonFormat1(FlatAttributes) diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/common/readmodel/ReadModelTenantQueries.scala b/src/main/scala/it/pagopa/interop/catalogprocess/common/readmodel/ReadModelTenantQueries.scala new file mode 100644 index 00000000..7520f604 --- /dev/null +++ b/src/main/scala/it/pagopa/interop/catalogprocess/common/readmodel/ReadModelTenantQueries.scala @@ -0,0 +1,16 @@ +package it.pagopa.interop.catalogprocess.common.readmodel + +import it.pagopa.interop.commons.cqrs.service.ReadModelService +import it.pagopa.interop.tenantmanagement.model.persistence.JsonFormats._ +import it.pagopa.interop.tenantmanagement.model.tenant.PersistentTenant +import org.mongodb.scala.model.Filters + +import java.util.UUID +import scala.concurrent.{ExecutionContext, Future} + +object ReadModelTenantQueries extends ReadModelQuery { + def getTenantById( + tenantId: UUID + )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[Option[PersistentTenant]] = + readModel.findOne[PersistentTenant]("tenants", Filters.eq("data.id", tenantId.toString)) +} diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala b/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala index 5bcaa849..34a2008d 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala @@ -1,6 +1,7 @@ package it.pagopa.interop.catalogprocess.errors import it.pagopa.interop.commons.utils.errors.ComponentError +import java.util.UUID object CatalogProcessErrors { @@ -46,4 +47,17 @@ object CatalogProcessErrors { final case class OriginIsNotCompliant(origin: String) extends ComponentError("0011", s"Requester has not origin: $origin") + final case class EServiceNotInDraftState(eServiceId: UUID) + extends ComponentError("0012", s"EService $eServiceId is not draft") + + final case class EServiceNotInReceiveMode(eServiceId: UUID) + extends ComponentError("0013", s"EService $eServiceId is not in receive mode") + + final case class TenantNotFound(tenantId: UUID) + extends ComponentError("0014", s"Tenant ${tenantId.toString} not found") + + final case class TenantKindNotFound(tenantId: UUID) + extends ComponentError("0015", s"Tenant kind for tenant ${tenantId.toString} not found") + + final case object RiskAnalysisNotValid extends ComponentError("0016", s"Risk Analysis did not pass validation") } diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/server/impl/Dependencies.scala b/src/main/scala/it/pagopa/interop/catalogprocess/server/impl/Dependencies.scala index e2781a55..9c3c67a1 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/server/impl/Dependencies.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/server/impl/Dependencies.scala @@ -70,7 +70,8 @@ trait Dependencies { catalogManagementService = catalogManagementService(blockingEc), AgreementManagementServiceImpl, authorizationManagementService = authorizationManagementService(blockingEc), - fileManager = fileManager + fileManager = fileManager, + tenantManagementService = TenantManagementServiceImpl ), ProcessApiMarshallerImpl, jwtReader.OAuth2JWTValidatorAsContexts diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala index 639ef688..35c57a50 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala @@ -72,4 +72,8 @@ trait CatalogManagementService { limit: Int, exactMatchOnName: Boolean = false )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PaginatedResult[CatalogItem]] + + def createRiskAnalysis(eServiceId: UUID, riskAnalysisSeed: RiskAnalysisSeed)(implicit + contexts: Seq[(String, String)] + ): Future[Unit] } diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/TenantManagementService.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/TenantManagementService.scala new file mode 100644 index 00000000..cccc2428 --- /dev/null +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/TenantManagementService.scala @@ -0,0 +1,13 @@ +package it.pagopa.interop.catalogprocess.service + +import it.pagopa.interop.tenantmanagement.model.tenant.PersistentTenant +import it.pagopa.interop.commons.cqrs.service.ReadModelService + +import java.util.UUID +import scala.concurrent.{Future, ExecutionContext} + +trait TenantManagementService { + def getTenantById( + tenantId: UUID + )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PersistentTenant] +} diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala index cf684fc3..19da9cdd 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala @@ -297,6 +297,23 @@ final case class CatalogManagementServiceImpl(invoker: CatalogManagementInvoker, exactMatchOnName ) + override def createRiskAnalysis(eServiceId: UUID, riskAnalysisSeed: RiskAnalysisSeed)(implicit + contexts: Seq[(String, String)] + ): Future[Unit] = withHeaders { (bearerToken, correlationId, ip) => + val request = api.createRiskAnalysis( + xCorrelationId = correlationId, + eServiceId = eServiceId, + riskAnalysisSeed = riskAnalysisSeed, + xForwardedFor = ip + )(BearerToken(bearerToken)) + invoker + .invoke(request, s"Create Risk Analysis for E-Services $eServiceId") + .recoverWith { + case err: ApiError[_] if err.code == 404 => + Future.failed(EServiceNotFound(eServiceId.toString)) + } + } + private def getDocument(eService: CatalogItem, descriptorId: UUID, documentId: UUID): Option[CatalogDocument] = { def lookup(catalogDescriptor: CatalogDescriptor): Option[CatalogDocument] = { diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/TenantManagementServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/TenantManagementServiceImpl.scala new file mode 100644 index 00000000..63173102 --- /dev/null +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/TenantManagementServiceImpl.scala @@ -0,0 +1,20 @@ +package it.pagopa.interop.catalogprocess.service.impl + +import it.pagopa.interop.catalogprocess.service.TenantManagementService +import it.pagopa.interop.commons.cqrs.service.ReadModelService +import it.pagopa.interop.catalogprocess.common.readmodel.ReadModelTenantQueries +import it.pagopa.interop.tenantmanagement.model.tenant.PersistentTenant +import it.pagopa.interop.catalogprocess.errors.CatalogProcessErrors.TenantNotFound +import it.pagopa.interop.commons.utils.TypeConversions._ + +import java.util.UUID +import scala.concurrent.{Future, ExecutionContext} + +object TenantManagementServiceImpl extends TenantManagementService { + + override def getTenantById( + tenantId: UUID + )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PersistentTenant] = + ReadModelTenantQueries.getTenantById(tenantId).flatMap(_.toFuture(TenantNotFound(tenantId))) + +} diff --git a/src/test/resources/authz.json b/src/test/resources/authz.json index e2876afd..7e8c287c 100644 --- a/src/test/resources/authz.json +++ b/src/test/resources/authz.json @@ -58,6 +58,7 @@ "roles": ["admin", "api"], "verb": "POST" }, - { "route": "deleteEService", "roles": ["admin", "api"], "verb": "DELETE" } + { "route": "deleteEService", "roles": ["admin", "api"], "verb": "DELETE" }, + { "route": "createRiskAnalysis", "roles": ["admin", "api"], "verb": "POST" } ] } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala index 2f6e7fb3..2c24c417 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala @@ -18,7 +18,9 @@ import it.pagopa.interop.catalogmanagement.model.{ Draft, Deprecated, Archived, - Suspended + Suspended, + RECEIVE, + DELIVER } import it.pagopa.interop.catalogprocess.model._ import it.pagopa.interop.catalogprocess.common.readmodel.{PaginatedResult, Consumers} @@ -2277,4 +2279,201 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR } } } + "Risk Analysis creation" should { + "succeed" in { + val requesterId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), producerId = requesterId, mode = RECEIVE) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + val dependencyRiskAnalysisSeed = + CatalogManagementDependency.RiskAnalysisSeed( + name = "newName", + riskAnalysisForm = CatalogManagementDependency.RiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq( + CatalogManagementDependency.RiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL")) + ), + multiAnswers = Seq( + CatalogManagementDependency + .RiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER")) + ) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + (mockTenantManagementService + .getTenantById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(requesterId, *, *) + .once() + .returns(Future.successful(SpecData.persistentTenant.copy(id = requesterId))) + + (mockCatalogManagementService + .createRiskAnalysis(_: UUID, _: CatalogManagementDependency.RiskAnalysisSeed)(_: Seq[(String, String)])) + .expects(eService.id, dependencyRiskAnalysisSeed, *) + .returning(Future.successful(())) + .once() + + Post() ~> service.createRiskAnalysis(eService.id.toString, riskAnalysisSeed) ~> check { + status shouldEqual StatusCodes.NoContent + } + } + "fail if EService mode is not RECEIVE" in { + val requesterId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), producerId = requesterId, mode = DELIVER) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + Post() ~> service.createRiskAnalysis(eService.id.toString, riskAnalysisSeed) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "009-0013" + } + } + "fail if requester is not the Producer" in { + val requesterId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), mode = RECEIVE) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + Post() ~> service.createRiskAnalysis(eService.id.toString, riskAnalysisSeed) ~> check { + status shouldEqual StatusCodes.Forbidden + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.Forbidden.intValue + problem.errors.head.code shouldBe "009-9989" + } + } + "fail if descriptor is not DRAFT" in { + val requesterId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Published) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), producerId = requesterId, mode = RECEIVE) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + Post() ~> service.createRiskAnalysis(eService.id.toString, riskAnalysisSeed) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "009-0012" + } + } + "fail if 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) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), producerId = requesterId, mode = RECEIVE) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "key1", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + (mockTenantManagementService + .getTenantById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(requesterId, *, *) + .once() + .returns(Future.successful(SpecData.persistentTenant.copy(id = requesterId))) + + Post() ~> service.createRiskAnalysis(eService.id.toString, riskAnalysisSeed) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "009-0016" + } + } + } } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/SpecData.scala b/src/test/scala/it/pagopa/interop/catalogprocess/SpecData.scala index fdf9fe26..d9648c77 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/SpecData.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/SpecData.scala @@ -4,6 +4,7 @@ import it.pagopa.interop.catalogmanagement.client.{model => CatalogManagementDep import it.pagopa.interop.catalogmanagement.{model => CatalogManagement} import it.pagopa.interop.agreementmanagement.model.agreement.{PersistentAgreement, Active, PersistentStamps} import it.pagopa.interop.commons.utils.service.OffsetDateTimeSupplier +import it.pagopa.interop.tenantmanagement.model.tenant.{PersistentTenant, PersistentTenantKind, PersistentExternalId} import java.util.UUID @@ -116,4 +117,17 @@ object SpecData { riskAnalysis = Seq.empty, mode = CatalogManagementDependency.EServiceMode.DELIVER ) + + val persistentTenant: PersistentTenant = PersistentTenant( + id = UUID.randomUUID(), + kind = Some(PersistentTenantKind.PA), + selfcareId = None, + externalId = PersistentExternalId("IPA", "value"), + features = Nil, + attributes = Nil, + createdAt = OffsetDateTimeSupplier.get(), + updatedAt = Some(OffsetDateTimeSupplier.get().plusDays(10)), + mails = Nil, + name = "name" + ) } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/SpecHelper.scala b/src/test/scala/it/pagopa/interop/catalogprocess/SpecHelper.scala index 98ad4b73..d94929cb 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/SpecHelper.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/SpecHelper.scala @@ -28,11 +28,13 @@ trait SpecHelper extends SprayJsonSupport with DefaultJsonProtocol with MockFact val mockAuthorizationManagementService: AuthorizationManagementService = mock[AuthorizationManagementService] val mockCatalogManagementService: CatalogManagementService = mock[CatalogManagementService] val mockAgreementManagementService: AgreementManagementService = mock[AgreementManagementService] + val mockTenantManagementService: TenantManagementService = mock[TenantManagementService] val service: ProcessApiService = ProcessApiServiceImpl( mockCatalogManagementService, mockAgreementManagementService, mockAuthorizationManagementService, + mockTenantManagementService, mockfileManager )(ExecutionContext.global, mockReadModel) diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala index 35a83f3b..e06ce5b4 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala @@ -22,6 +22,7 @@ class ProcessApiAuthzSpec extends AnyWordSpecLike with BeforeAndAfterAll with Au val fakeAgreementManagementService: AgreementManagementService = new FakeAgreementManagementService() val fakeCatalogManagementService: CatalogManagementService = new FakeCatalogManagementService() val fakeAuthorizationManagementService: AuthorizationManagementService = new FakeAuthorizationManagementService() + val fakeTenantManagementService: TenantManagementService = new FakeTenantManagementService() private val threadPool: ExecutorService = Executors.newSingleThreadExecutor() private val blockingEc: ExecutionContextExecutor = ExecutionContext.fromExecutorService(threadPool) val fakeFileManager: FileManager = FileManager.get(FileManager.File)(blockingEc) @@ -39,6 +40,7 @@ class ProcessApiAuthzSpec extends AnyWordSpecLike with BeforeAndAfterAll with Au fakeCatalogManagementService, fakeAgreementManagementService, fakeAuthorizationManagementService, + fakeTenantManagementService, fakeFileManager )(ExecutionContext.global, fakeReadModel) @@ -206,5 +208,17 @@ class ProcessApiAuthzSpec extends AnyWordSpecLike with BeforeAndAfterAll with Au { implicit c: Seq[(String, String)] => service.archiveDescriptor("fake", "fake") } ) } + + "accept authorized roles for createRiskAnalysis" in { + val endpoint = AuthorizedRoutes.endpoints("createRiskAnalysis") + val fakeSeed = EServiceRiskAnalysisSeed( + "test", + EServiceRiskAnalysisFormSeed(version = "fake", singleAnswers = Seq.empty, multiAnswers = Seq.empty) + ) + validateAuthorization( + endpoint, + { implicit c: Seq[(String, String)] => service.createRiskAnalysis(UUID.randomUUID().toString, fakeSeed) } + ) + } } } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala b/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala index 922ca3b7..af3062ce 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala @@ -16,7 +16,8 @@ import it.pagopa.interop.catalogmanagement.client.model.{ UpdateEServiceDescriptorDocumentSeed, UpdateEServiceDescriptorSeed, UpdateEServiceSeed, - CreateEServiceDescriptorDocumentSeed + CreateEServiceDescriptorDocumentSeed, + RiskAnalysisSeed } import it.pagopa.interop.catalogprocess.common.readmodel.{PaginatedResult, Consumers} import it.pagopa.interop.catalogmanagement.model.{ @@ -28,10 +29,12 @@ import it.pagopa.interop.catalogmanagement.model.{ CatalogDescriptorState, DELIVER } +import it.pagopa.interop.tenantmanagement.model.tenant.{PersistentTenant, PersistentTenantKind, PersistentExternalId} import it.pagopa.interop.catalogprocess.service.{ AuthorizationManagementService, CatalogManagementService, - AgreementManagementService + AgreementManagementService, + TenantManagementService } import java.time.OffsetDateTime @@ -270,6 +273,10 @@ object FakeDependencies { exactMatchOnName: Boolean = false )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PaginatedResult[CatalogItem]] = Future.successful(PaginatedResult(results = Seq.empty, totalCount = 0)) + + override def createRiskAnalysis(eServiceId: UUID, riskAnalysisSeed: RiskAnalysisSeed)(implicit + contexts: Seq[(String, String)] + ): Future[Unit] = Future.successful(()) } class FakeAuthorizationManagementService extends AuthorizationManagementService { @@ -291,4 +298,23 @@ object FakeDependencies { )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[Seq[PersistentAgreement]] = Future.successful(Seq.empty) } + + class FakeTenantManagementService extends TenantManagementService { + override def getTenantById( + tenantId: UUID + )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[PersistentTenant] = Future.successful( + PersistentTenant( + id = UUID.randomUUID(), + kind = Some(PersistentTenantKind.PA), + selfcareId = None, + externalId = PersistentExternalId("IPA", "value"), + features = Nil, + attributes = Nil, + createdAt = OffsetDateTime.now(), + updatedAt = Some(OffsetDateTime.now().plusDays(10)), + mails = Nil, + name = "name" + ) + ) + } } From 334139f6776c45e273a1a8c98af73a1913d749ca Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Wed, 27 Sep 2023 16:32:33 +0200 Subject: [PATCH 02/17] PIN-4020 Implemented Update Risk Analisys --- .../resources/interface-specification.yml | 48 +++ .../api/impl/ProcessApiServiceImpl.scala | 26 ++ .../api/impl/ResponseHandlers.scala | 16 + .../errors/CatalogProcessErrors.scala | 3 + .../service/CatalogManagementService.scala | 4 + .../impl/CatalogManagementServiceImpl.scala | 21 +- src/test/resources/authz.json | 3 +- .../catalogprocess/CatalogProcessSpec.scala | 358 +++++++++++++++++- .../authz/ProcessApiAuthzSpec.scala | 14 + .../util/FakeDependencies.scala | 20 +- 10 files changed, 508 insertions(+), 5 deletions(-) diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index c2426eae..3eac4a9f 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -905,6 +905,54 @@ paths: application/problem+json: schema: $ref: '#/components/schemas/Problem' + /eservices/{eServiceId}/riskanalysis/{riskAnalysisId}: + parameters: + - $ref: '#/components/parameters/CorrelationIdHeader' + - $ref: '#/components/parameters/IpAddress' + post: + security: + - bearerAuth: [] + tags: + - process + summary: Update an e-service risk analysis + operationId: updateRiskAnalysis + parameters: + - name: eServiceId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + - name: riskAnalysisId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + requestBody: + description: A payload containing the risk analysis to update + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EServiceRiskAnalysisSeed' + responses: + '204': + description: EService Risk Analysis updated. + '404': + description: EService not found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + '400': + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' /status: get: security: [] diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index 26015070..05551a10 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -677,6 +677,32 @@ final case class ProcessApiServiceImpl( createRiskAnalysisResponse[Unit](operationLabel)(_ => createRiskAnalysis204) } } + + def updateRiskAnalysis(eServiceId: String, riskAnalysisId: String, seed: EServiceRiskAnalysisSeed)(implicit + contexts: Seq[(String, String)], + toEntityMarshallerProblem: ToEntityMarshaller[Problem] + ): Route = authorize(ADMIN_ROLE, API_ROLE) { + val operationLabel = s"Update a Risk Analysis for EService $eServiceId" + logger.info(operationLabel) + + val result = for { + organizationId <- getOrganizationIdFutureUUID(contexts) + eServiceUuid <- eServiceId.toFutureUUID + riskAnalysisUuid <- riskAnalysisId.toFutureUUID + catalogItem <- catalogManagementService.getEServiceById(eServiceUuid) + _ <- isDraftEService(catalogItem) + _ <- isReceiveEService(catalogItem) + _ <- assertRequesterAllowed(catalogItem.producerId)(organizationId) + tenant <- tenantManagementService.getTenantById(organizationId) + tenantKind <- tenant.kind.toFuture(TenantKindNotFound(tenant.id)) + _ <- isRiskAnalysisFormValid(seed.riskAnalysisForm.toTemplate, true)(tenantKind.toTemplate) + _ <- catalogManagementService.updateRiskAnalysis(eServiceUuid, riskAnalysisUuid, seed.toDependency) + } yield () + + onComplete(result) { + updateRiskAnalysisResponse[Unit](operationLabel)(_ => updateRiskAnalysis204) + } + } } object ProcessApiServiceImpl { diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala index f7e978a0..1d4db68f 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala @@ -63,6 +63,22 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex) => internalServerError(ex, logMessage) } + def updateRiskAnalysisResponse[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: OperationForbidden.type) => forbidden(ex, logMessage) + case Failure(ex: EServiceNotFound) => notFound(ex, logMessage) + case Failure(ex: EServiceRiskAnalysisNotFound) => notFound(ex, logMessage) + case Failure(ex: TenantNotFound) => notFound(ex, logMessage) + case Failure(ex: TenantKindNotFound) => notFound(ex, logMessage) + case Failure(ex: EServiceNotInDraftState) => badRequest(ex, logMessage) + case Failure(ex: EServiceNotInReceiveMode) => badRequest(ex, logMessage) + case Failure(ex: RiskAnalysisNotValid.type) => badRequest(ex, logMessage) + case Failure(ex) => internalServerError(ex, logMessage) + } + def updateEServiceByIdResponse[T](logMessage: String)( success: T => Route )(result: Try[T])(implicit contexts: Seq[(String, String)], logger: LoggerTakingImplicit[ContextFieldsToLog]): Route = diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala b/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala index 34a2008d..c443037e 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala @@ -60,4 +60,7 @@ object CatalogProcessErrors { extends ComponentError("0015", s"Tenant kind for tenant ${tenantId.toString} not found") final case object RiskAnalysisNotValid extends ComponentError("0016", s"Risk Analysis did not pass validation") + + final case class EServiceRiskAnalysisNotFound(eServiceId: UUID, riskAnalysisId: UUID) + extends ComponentError("0017", s"Risk Analysis $riskAnalysisId not found for EService $eServiceId") } diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala index 35c57a50..995f754d 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala @@ -76,4 +76,8 @@ trait CatalogManagementService { def createRiskAnalysis(eServiceId: UUID, riskAnalysisSeed: RiskAnalysisSeed)(implicit contexts: Seq[(String, String)] ): Future[Unit] + + def updateRiskAnalysis(eServiceId: UUID, riskAnalysisId: UUID, riskAnalysisSeed: RiskAnalysisSeed)(implicit + contexts: Seq[(String, String)] + ): Future[EServiceRiskAnalysis] } diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala index 19da9cdd..d0777f34 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala @@ -19,7 +19,8 @@ import com.typesafe.scalalogging.{Logger, LoggerTakingImplicit} import it.pagopa.interop.catalogprocess.errors.CatalogProcessErrors.{ DescriptorDocumentNotFound, EServiceDescriptorNotFound, - EServiceNotFound + EServiceNotFound, + EServiceRiskAnalysisNotFound } import it.pagopa.interop.commons.logging.{CanLogContextFields, ContextFieldsToLog} @@ -314,6 +315,24 @@ final case class CatalogManagementServiceImpl(invoker: CatalogManagementInvoker, } } + override def updateRiskAnalysis(eServiceId: UUID, riskAnalysisId: UUID, riskAnalysisSeed: RiskAnalysisSeed)(implicit + contexts: Seq[(String, String)] + ): Future[EServiceRiskAnalysis] = withHeaders { (bearerToken, correlationId, ip) => + val request = api.updateRiskAnalysis( + xCorrelationId = correlationId, + eServiceId = eServiceId, + riskAnalysisId = riskAnalysisId, + riskAnalysisSeed = riskAnalysisSeed, + xForwardedFor = ip + )(BearerToken(bearerToken)) + invoker + .invoke(request, s"Update Risk Analysis $riskAnalysisId for E-Services $eServiceId") + .recoverWith { + case err: ApiError[_] if err.code == 404 => + Future.failed(EServiceRiskAnalysisNotFound(eServiceId, riskAnalysisId)) + } + } + private def getDocument(eService: CatalogItem, descriptorId: UUID, documentId: UUID): Option[CatalogDocument] = { def lookup(catalogDescriptor: CatalogDescriptor): Option[CatalogDocument] = { diff --git a/src/test/resources/authz.json b/src/test/resources/authz.json index 7e8c287c..d4547ef9 100644 --- a/src/test/resources/authz.json +++ b/src/test/resources/authz.json @@ -59,6 +59,7 @@ "verb": "POST" }, { "route": "deleteEService", "roles": ["admin", "api"], "verb": "DELETE" }, - { "route": "createRiskAnalysis", "roles": ["admin", "api"], "verb": "POST" } + { "route": "createRiskAnalysis", "roles": ["admin", "api"], "verb": "POST" }, + { "route": "updateRiskAnalysis", "roles": ["admin", "api"], "verb": "POST" } ] } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala index 9ed31ae0..d02e2a51 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala @@ -10,7 +10,11 @@ import it.pagopa.interop.catalogprocess.api.impl.Converter._ import it.pagopa.interop.catalogprocess.api.impl._ import it.pagopa.interop.commons.utils._ import it.pagopa.interop.commons.cqrs.service.ReadModelService -import it.pagopa.interop.catalogprocess.errors.CatalogProcessErrors.{EServiceNotFound, DescriptorDocumentNotFound} +import it.pagopa.interop.catalogprocess.errors.CatalogProcessErrors.{ + EServiceNotFound, + DescriptorDocumentNotFound, + EServiceRiskAnalysisNotFound +} import it.pagopa.interop.catalogmanagement.model.{ CatalogDescriptorState, CatalogItem, @@ -20,10 +24,16 @@ import it.pagopa.interop.catalogmanagement.model.{ Archived, Suspended, Receive, - Deliver + Deliver, + CatalogRiskAnalysis, + CatalogRiskAnalysisForm, + CatalogRiskAnalysisSingleAnswer, + CatalogRiskAnalysisMultiAnswer } import it.pagopa.interop.catalogprocess.model._ import it.pagopa.interop.catalogprocess.common.readmodel.{PaginatedResult, Consumers} + +import it.pagopa.interop.commons.utils.service.OffsetDateTimeSupplier import org.scalatest.wordspec.AnyWordSpecLike import org.scalatest.matchers.should.Matchers._ @@ -2476,4 +2486,348 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR } } } + "Risk Analysis update" should { + "succeed" in { + val requesterId = UUID.randomUUID() + val riskAnalysisId = UUID.randomUUID() + val riskAnalysisFormId = UUID.randomUUID() + val single = UUID.randomUUID() + val multi = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val createdDate = OffsetDateTimeSupplier.get() + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy( + descriptors = Seq(descriptor), + producerId = requesterId, + riskAnalysis = Seq( + CatalogRiskAnalysis( + id = riskAnalysisId, + name = "OldName", + riskAnalysisForm = CatalogRiskAnalysisForm( + id = riskAnalysisFormId, + version = "3.0", + singleAnswers = + Seq(CatalogRiskAnalysisSingleAnswer(id = single, key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = + Seq(CatalogRiskAnalysisMultiAnswer(id = multi, key = "personalDataTypes", values = Seq("OTHER"))) + ), + createdAt = createdDate + ) + ), + mode = Receive + ) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + val dependencyRiskAnalysisSeed = + CatalogManagementDependency.RiskAnalysisSeed( + name = "newName", + riskAnalysisForm = CatalogManagementDependency.RiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq( + CatalogManagementDependency.RiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL")) + ), + multiAnswers = Seq( + CatalogManagementDependency + .RiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER")) + ) + ) + ) + + val expected = CatalogManagementDependency.EServiceRiskAnalysis( + id = riskAnalysisId, + name = "newName", + riskAnalysisForm = CatalogManagementDependency.RiskAnalysisForm( + id = riskAnalysisFormId, + version = "3.0", + singleAnswers = Seq( + CatalogManagementDependency + .RiskAnalysisSingleAnswer(id = single, key = "purpose", value = Some("INSTITUTIONAL")) + ), + multiAnswers = Seq( + CatalogManagementDependency + .RiskAnalysisMultiAnswer(id = multi, key = "personalDataTypes", values = Seq("OTHER")) + ) + ), + createdAt = createdDate + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + (mockTenantManagementService + .getTenantById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(requesterId, *, *) + .once() + .returns(Future.successful(SpecData.persistentTenant.copy(id = requesterId))) + + (mockCatalogManagementService + .updateRiskAnalysis(_: UUID, _: UUID, _: CatalogManagementDependency.RiskAnalysisSeed)( + _: Seq[(String, String)] + )) + .expects(eService.id, riskAnalysisId, dependencyRiskAnalysisSeed, *) + .returning(Future.successful(expected)) + .once() + + Post() ~> service.updateRiskAnalysis(eService.id.toString, riskAnalysisId.toString, riskAnalysisSeed) ~> check { + status shouldEqual StatusCodes.NoContent + } + } + + "fail if Risk Analysis does not exists on EService" in { + val requesterId = UUID.randomUUID() + val riskAnalysisId = UUID.randomUUID() + val riskAnalysisFormId = UUID.randomUUID() + val single = UUID.randomUUID() + val multi = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val createdDate = OffsetDateTimeSupplier.get() + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy( + descriptors = Seq(descriptor), + producerId = requesterId, + riskAnalysis = Seq( + CatalogRiskAnalysis( + id = UUID.randomUUID(), + name = "OldName", + riskAnalysisForm = CatalogRiskAnalysisForm( + id = riskAnalysisFormId, + version = "3.0", + singleAnswers = + Seq(CatalogRiskAnalysisSingleAnswer(id = single, key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = + Seq(CatalogRiskAnalysisMultiAnswer(id = multi, key = "personalDataTypes", values = Seq("OTHER"))) + ), + createdAt = createdDate + ) + ), + mode = Receive + ) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + val dependencyRiskAnalysisSeed = + CatalogManagementDependency.RiskAnalysisSeed( + name = "newName", + riskAnalysisForm = CatalogManagementDependency.RiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq( + CatalogManagementDependency.RiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL")) + ), + multiAnswers = Seq( + CatalogManagementDependency + .RiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER")) + ) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + (mockTenantManagementService + .getTenantById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(requesterId, *, *) + .once() + .returns(Future.successful(SpecData.persistentTenant.copy(id = requesterId))) + + (mockCatalogManagementService + .updateRiskAnalysis(_: UUID, _: UUID, _: CatalogManagementDependency.RiskAnalysisSeed)( + _: Seq[(String, String)] + )) + .expects(eService.id, riskAnalysisId, dependencyRiskAnalysisSeed, *) + .returning(Future.failed(EServiceRiskAnalysisNotFound(eService.id, riskAnalysisId))) + .once() + + Post() ~> service.updateRiskAnalysis(eService.id.toString, riskAnalysisId.toString, riskAnalysisSeed) ~> check { + status shouldEqual StatusCodes.NotFound + } + } + + "fail if EService mode is not RECEIVE" in { + val requesterId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), producerId = requesterId, mode = Deliver) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + Post() ~> service.updateRiskAnalysis( + eService.id.toString, + UUID.randomUUID().toString, + riskAnalysisSeed + ) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "009-0013" + } + } + "fail if requester is not the Producer" in { + val requesterId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), mode = Receive) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + Post() ~> service.updateRiskAnalysis( + eService.id.toString, + UUID.randomUUID().toString, + riskAnalysisSeed + ) ~> check { + status shouldEqual StatusCodes.Forbidden + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.Forbidden.intValue + problem.errors.head.code shouldBe "009-9989" + } + } + "fail if descriptor is not DRAFT" in { + val requesterId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Published) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), producerId = requesterId, mode = Receive) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + Post() ~> service.updateRiskAnalysis( + eService.id.toString, + UUID.randomUUID().toString, + riskAnalysisSeed + ) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "009-0012" + } + } + "fail if 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) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), producerId = requesterId, mode = Receive) + + val riskAnalysisSeed = EServiceRiskAnalysisSeed( + name = "newName", + riskAnalysisForm = EServiceRiskAnalysisFormSeed( + version = "3.0", + singleAnswers = Seq(EServiceRiskAnalysisSingleAnswerSeed(key = "key1", value = Some("INSTITUTIONAL"))), + multiAnswers = Seq(EServiceRiskAnalysisMultiAnswerSeed(key = "personalDataTypes", values = Seq("OTHER"))) + ) + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + (mockTenantManagementService + .getTenantById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(requesterId, *, *) + .once() + .returns(Future.successful(SpecData.persistentTenant.copy(id = requesterId))) + + Post() ~> service.updateRiskAnalysis( + eService.id.toString, + UUID.randomUUID().toString, + riskAnalysisSeed + ) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "009-0016" + } + } + } } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala index e06ce5b4..c9f81fd7 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala @@ -220,5 +220,19 @@ class ProcessApiAuthzSpec extends AnyWordSpecLike with BeforeAndAfterAll with Au { implicit c: Seq[(String, String)] => service.createRiskAnalysis(UUID.randomUUID().toString, fakeSeed) } ) } + + "accept authorized roles for updateRiskAnalysis" in { + val endpoint = AuthorizedRoutes.endpoints("updateRiskAnalysis") + val fakeSeed = EServiceRiskAnalysisSeed( + "test", + EServiceRiskAnalysisFormSeed(version = "fake", singleAnswers = Seq.empty, multiAnswers = Seq.empty) + ) + validateAuthorization( + endpoint, + { implicit c: Seq[(String, String)] => + service.updateRiskAnalysis(UUID.randomUUID().toString, UUID.randomUUID().toString, fakeSeed) + } + ) + } } } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala b/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala index a9a82d27..690bc630 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala @@ -36,7 +36,7 @@ import it.pagopa.interop.catalogprocess.service.{ AgreementManagementService, TenantManagementService } -import it.pagopa.interop.catalogmanagement.client.model.EServiceMode +import it.pagopa.interop.catalogmanagement.client.model.{EServiceMode, EServiceRiskAnalysis, RiskAnalysisForm} import java.time.OffsetDateTime import java.util.UUID @@ -277,6 +277,24 @@ object FakeDependencies { override def createRiskAnalysis(eServiceId: UUID, riskAnalysisSeed: RiskAnalysisSeed)(implicit contexts: Seq[(String, String)] ): Future[Unit] = Future.successful(()) + + override def updateRiskAnalysis(eServiceId: UUID, riskAnalysisId: UUID, riskAnalysisSeed: RiskAnalysisSeed)(implicit + contexts: Seq[(String, String)] + ): Future[EServiceRiskAnalysis] = Future.successful( + ( + EServiceRiskAnalysis( + id = UUID.randomUUID(), + name = "name", + riskAnalysisForm = RiskAnalysisForm( + id = UUID.randomUUID(), + version = "version", + singleAnswers = Seq.empty, + multiAnswers = Seq.empty + ), + createdAt = OffsetDateTime.now() + ) + ) + ) } class FakeAuthorizationManagementService extends AuthorizationManagementService { From edcb3a89c208c8bd8c758608e169ec81e4025b5e Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Wed, 27 Sep 2023 16:42:07 +0200 Subject: [PATCH 03/17] PIN-3985 Cleaning --- src/main/resources/interface-specification.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index c2426eae..2024e37a 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -864,7 +864,7 @@ paths: application/problem+json: schema: $ref: '#/components/schemas/Problem' - /eservices/{eServiceId}/riskAnalysis: + /eservices/{eServiceId}/riskanalysis: parameters: - $ref: '#/components/parameters/CorrelationIdHeader' - $ref: '#/components/parameters/IpAddress' From 506d9cda75a2844a45b47117f0203083db156272 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Wed, 27 Sep 2023 17:06:41 +0200 Subject: [PATCH 04/17] PIN-4023 Implemented Delete Risk Analysis --- .../resources/interface-specification.yml | 45 +++++- .../api/impl/ProcessApiServiceImpl.scala | 20 +++ .../api/impl/ResponseHandlers.scala | 11 ++ .../service/CatalogManagementService.scala | 2 + .../impl/CatalogManagementServiceImpl.scala | 17 +++ src/test/resources/authz.json | 3 +- .../catalogprocess/CatalogProcessSpec.scala | 131 ++++++++++++++++++ .../authz/ProcessApiAuthzSpec.scala | 10 ++ .../util/FakeDependencies.scala | 5 +- 9 files changed, 241 insertions(+), 3 deletions(-) diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index 5cbf84c0..d81722d4 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -952,7 +952,50 @@ paths: content: application/problem+json: schema: - $ref: '#/components/schemas/Problem' + $ref: '#/components/schemas/Problem' + delete: + security: + - bearerAuth: [] + tags: + - process + summary: Delete an e-service risk analysis + operationId: deleteRiskAnalysis + parameters: + - name: eServiceId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + - name: riskAnalysisId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + responses: + '204': + description: EService Risk Analysis deleted. + '403': + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + '404': + description: EService not found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' + '400': + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' /status: get: security: [] diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index 05551a10..ff9531c1 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -703,6 +703,26 @@ final case class ProcessApiServiceImpl( updateRiskAnalysisResponse[Unit](operationLabel)(_ => updateRiskAnalysis204) } } + + override def deleteRiskAnalysis(eServiceId: String, riskAnalysisId: String)(implicit + contexts: Seq[(String, String)], + toEntityMarshallerProblem: ToEntityMarshaller[Problem] + ): Route = authorize(ADMIN_ROLE, API_ROLE) { + val operationLabel = s"Delete a Risk Analysis $riskAnalysisId for EService $eServiceId" + logger.info(operationLabel) + + val result = for { + eServiceUuid <- eServiceId.toFutureUUID + riskAnalysisUuid <- riskAnalysisId.toFutureUUID + catalogItem <- catalogManagementService.getEServiceById(eServiceUuid) + _ <- isDraftEService(catalogItem) + _ <- catalogManagementService.deleteRiskAnalysis(eServiceUuid, riskAnalysisUuid) + } yield () + + onComplete(result) { + deleteRiskAnalysisResponse[Unit](operationLabel)(_ => deleteRiskAnalysis204) + } + } } object ProcessApiServiceImpl { diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala index 1d4db68f..769e78fb 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala @@ -79,6 +79,17 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex) => internalServerError(ex, logMessage) } + def deleteRiskAnalysisResponse[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: EServiceNotFound) => notFound(ex, logMessage) + case Failure(ex: EServiceRiskAnalysisNotFound) => notFound(ex, logMessage) + case Failure(ex: EServiceNotInDraftState) => badRequest(ex, logMessage) + case Failure(ex) => internalServerError(ex, logMessage) + } + def updateEServiceByIdResponse[T](logMessage: String)( success: T => Route )(result: Try[T])(implicit contexts: Seq[(String, String)], logger: LoggerTakingImplicit[ContextFieldsToLog]): Route = diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala index 995f754d..fc4c59a3 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/CatalogManagementService.scala @@ -80,4 +80,6 @@ trait CatalogManagementService { def updateRiskAnalysis(eServiceId: UUID, riskAnalysisId: UUID, riskAnalysisSeed: RiskAnalysisSeed)(implicit contexts: Seq[(String, String)] ): Future[EServiceRiskAnalysis] + + def deleteRiskAnalysis(eServiceId: UUID, riskAnalysisId: UUID)(implicit contexts: Seq[(String, String)]): Future[Unit] } diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala index d0777f34..1a8fffc1 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/CatalogManagementServiceImpl.scala @@ -333,6 +333,23 @@ final case class CatalogManagementServiceImpl(invoker: CatalogManagementInvoker, } } + override def deleteRiskAnalysis(eServiceId: UUID, riskAnalysisId: UUID)(implicit + contexts: Seq[(String, String)] + ): Future[Unit] = withHeaders { (bearerToken, correlationId, ip) => + val request = api.deleteRiskAnalysis( + xCorrelationId = correlationId, + eServiceId = eServiceId, + riskAnalysisId = riskAnalysisId, + xForwardedFor = ip + )(BearerToken(bearerToken)) + invoker + .invoke(request, s"Delete Risk Analysis $riskAnalysisId for E-Services $eServiceId") + .recoverWith { + case err: ApiError[_] if err.code == 404 => + Future.failed(EServiceRiskAnalysisNotFound(eServiceId, riskAnalysisId)) + } + } + private def getDocument(eService: CatalogItem, descriptorId: UUID, documentId: UUID): Option[CatalogDocument] = { def lookup(catalogDescriptor: CatalogDescriptor): Option[CatalogDocument] = { diff --git a/src/test/resources/authz.json b/src/test/resources/authz.json index d4547ef9..dd77ba6d 100644 --- a/src/test/resources/authz.json +++ b/src/test/resources/authz.json @@ -60,6 +60,7 @@ }, { "route": "deleteEService", "roles": ["admin", "api"], "verb": "DELETE" }, { "route": "createRiskAnalysis", "roles": ["admin", "api"], "verb": "POST" }, - { "route": "updateRiskAnalysis", "roles": ["admin", "api"], "verb": "POST" } + { "route": "updateRiskAnalysis", "roles": ["admin", "api"], "verb": "POST" }, + { "route": "deleteRiskAnalysis", "roles": ["admin", "api"], "verb": "DELETE" } ] } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala index d02e2a51..8df89b51 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala @@ -2830,4 +2830,135 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR } } } + "Risk Analysis delete" should { + "succeed" in { + val requesterId = UUID.randomUUID() + val riskAnalysisId = UUID.randomUUID() + val riskAnalysisFormId = UUID.randomUUID() + val single = UUID.randomUUID() + val multi = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val createdDate = OffsetDateTimeSupplier.get() + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy( + descriptors = Seq(descriptor), + producerId = requesterId, + riskAnalysis = Seq( + CatalogRiskAnalysis( + id = riskAnalysisId, + name = "OldName", + riskAnalysisForm = CatalogRiskAnalysisForm( + id = riskAnalysisFormId, + version = "3.0", + singleAnswers = + Seq(CatalogRiskAnalysisSingleAnswer(id = single, key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = + Seq(CatalogRiskAnalysisMultiAnswer(id = multi, key = "personalDataTypes", values = Seq("OTHER"))) + ), + createdAt = createdDate + ) + ), + mode = Receive + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + (mockCatalogManagementService + .deleteRiskAnalysis(_: UUID, _: UUID)(_: Seq[(String, String)])) + .expects(eService.id, riskAnalysisId, *) + .returning(Future.successful(())) + .once() + + Post() ~> service.deleteRiskAnalysis(eService.id.toString, riskAnalysisId.toString) ~> check { + status shouldEqual StatusCodes.NoContent + } + } + + "fail if Risk Analysis does not exists on EService" in { + val requesterId = UUID.randomUUID() + val riskAnalysisId = UUID.randomUUID() + val riskAnalysisFormId = UUID.randomUUID() + val single = UUID.randomUUID() + val multi = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val createdDate = OffsetDateTimeSupplier.get() + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy( + descriptors = Seq(descriptor), + producerId = requesterId, + riskAnalysis = Seq( + CatalogRiskAnalysis( + id = UUID.randomUUID(), + name = "OldName", + riskAnalysisForm = CatalogRiskAnalysisForm( + id = riskAnalysisFormId, + version = "3.0", + singleAnswers = + Seq(CatalogRiskAnalysisSingleAnswer(id = single, key = "purpose", value = Some("INSTITUTIONAL"))), + multiAnswers = + Seq(CatalogRiskAnalysisMultiAnswer(id = multi, key = "personalDataTypes", values = Seq("OTHER"))) + ), + createdAt = createdDate + ) + ), + mode = Receive + ) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + (mockCatalogManagementService + .deleteRiskAnalysis(_: UUID, _: UUID)(_: Seq[(String, String)])) + .expects(eService.id, riskAnalysisId, *) + .returning(Future.failed(EServiceRiskAnalysisNotFound(eService.id, riskAnalysisId))) + .once() + + Post() ~> service.deleteRiskAnalysis(eService.id.toString, riskAnalysisId.toString) ~> check { + status shouldEqual StatusCodes.NotFound + } + } + "fail if descriptor is not DRAFT" in { + val requesterId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Published) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), producerId = requesterId, mode = Receive) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + Post() ~> service.deleteRiskAnalysis(eService.id.toString, UUID.randomUUID().toString) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "009-0012" + } + } + } } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala index c9f81fd7..752e504b 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/authz/ProcessApiAuthzSpec.scala @@ -234,5 +234,15 @@ class ProcessApiAuthzSpec extends AnyWordSpecLike with BeforeAndAfterAll with Au } ) } + + "accept authorized roles for deleteRiskAnalysis" in { + val endpoint = AuthorizedRoutes.endpoints("deleteRiskAnalysis") + validateAuthorization( + endpoint, + { implicit c: Seq[(String, String)] => + service.deleteRiskAnalysis(UUID.randomUUID().toString, UUID.randomUUID().toString) + } + ) + } } } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala b/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala index 690bc630..28d27f07 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala @@ -295,8 +295,11 @@ object FakeDependencies { ) ) ) - } + override def deleteRiskAnalysis(eServiceId: UUID, riskAnalysisId: UUID)(implicit + contexts: Seq[(String, String)] + ): Future[Unit] = Future.successful(()) + } class FakeAuthorizationManagementService extends AuthorizationManagementService { override def updateStateOnClients( eServiceId: UUID, From 19399acfdd8f6a6f9e8a371f519c6439dbcd8b61 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Wed, 27 Sep 2023 17:11:29 +0200 Subject: [PATCH 05/17] PIN-4020 Add 403 on openapi --- src/main/resources/interface-specification.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index 5cbf84c0..604ea742 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -941,6 +941,12 @@ paths: responses: '204': description: EService Risk Analysis updated. + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' '404': description: EService not found content: From b9672be3497536dca378d7fe31bbe31a2322d63a Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Wed, 27 Sep 2023 17:12:17 +0200 Subject: [PATCH 06/17] PIN-395 Add 403 to openapi --- src/main/resources/interface-specification.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index 2024e37a..da6e5890 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -893,6 +893,12 @@ paths: responses: '204': description: EService Risk Analysis created. + '403': + description: Forbidden + content: + application/problem+json: + schema: + $ref: '#/components/schemas/Problem' '404': description: EService not found content: From 89ad8309ea9da579d64ca16692d92a3d2f5c5bc3 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Wed, 27 Sep 2023 17:33:01 +0200 Subject: [PATCH 07/17] PIN-3985 Resolved PR issues --- .../api/impl/ProcessApiServiceImpl.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index 26015070..d678eebf 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -693,22 +693,22 @@ object ProcessApiServiceImpl { def isRiskAnalysisFormValid(riskAnalysisForm: Commons.RiskAnalysisForm, schemaOnlyValidation: Boolean)( kind: Commons.RiskAnalysisTenantKind - )(implicit ec: ExecutionContext): Future[Unit] = - Future - .failed(RiskAnalysisNotValid) - .unlessA( - RiskAnalysisValidation - .validate(riskAnalysisForm, schemaOnlyValidation)(kind) - .isValid - ) + ): Future[Unit] = + if ( + RiskAnalysisValidation + .validate(riskAnalysisForm, schemaOnlyValidation)(kind) + .isValid + ) Future.unit + else Future.failed(RiskAnalysisNotValid) def isReceiveEService(eService: CatalogItem)(implicit ec: ExecutionContext): Future[Unit] = Future.failed(EServiceNotInReceiveMode(eService.id)).unlessA(eService.mode == Receive) - def isDraftEService(eService: CatalogItem)(implicit ec: ExecutionContext): Future[Unit] = - Future - .failed(EServiceNotInDraftState(eService.id)) - .unlessA(eService.descriptors.map(_.state) == Seq(Draft)) + def isDraftEService(eService: CatalogItem): Future[Unit] = + if (eService.descriptors.map(_.state) == Seq(Draft)) Future.unit + else + Future + .failed(EServiceNotInDraftState(eService.id)) def assertRequesterAllowed(resourceId: UUID)(requesterId: UUID)(implicit ec: ExecutionContext): Future[Unit] = Future.failed(GenericComponentErrors.OperationForbidden).unlessA(resourceId == requesterId) From 9e6fa24b222d8dff05f76f2e6e0c85882e2bacad Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Thu, 28 Sep 2023 09:05:52 +0200 Subject: [PATCH 08/17] PIN-3996 Check when EService is published --- .../catalogprocess/api/impl/Converter.scala | 7 + .../api/impl/ProcessApiServiceImpl.scala | 18 ++ .../api/impl/ResponseHandlers.scala | 4 + .../errors/CatalogProcessErrors.scala | 3 + .../catalogprocess/CatalogProcessSpec.scala | 171 +++++++++++++++++- .../interop/catalogprocess/SpecData.scala | 110 ++++++++++- 6 files changed, 311 insertions(+), 2 deletions(-) diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/Converter.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/Converter.scala index 33078b34..d282a22a 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/Converter.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/Converter.scala @@ -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 { diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index a2f2a904..00d6a414 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -203,6 +203,23 @@ final case class ProcessApiServiceImpl( val operationLabel = s"Publishing descriptor $descriptorId for EService $eServiceId" logger.info(operationLabel) + def verifyRiskAnalysisForPublication(catalogItem: CatalogItem): Future[Unit] = { + if (catalogItem.mode == 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 () + } else Future.unit + } + val result: Future[Unit] = for { organizationId <- getOrganizationIdFutureUUID(contexts) eServiceUuid <- eServiceId.toFutureUUID @@ -211,6 +228,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 diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala index 769e78fb..05eeb512 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala @@ -188,7 +188,11 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: OperationForbidden.type) => forbidden(ex, logMessage) case Failure(ex: EServiceNotFound) => notFound(ex, logMessage) case Failure(ex: EServiceDescriptorNotFound) => notFound(ex, logMessage) + case Failure(ex: TenantNotFound) => notFound(ex, logMessage) + case Failure(ex: TenantKindNotFound) => 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) } diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala b/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala index c443037e..641ea039 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/errors/CatalogProcessErrors.scala @@ -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") } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala index 8df89b51..8a5f1bb3 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala @@ -13,7 +13,8 @@ import it.pagopa.interop.commons.cqrs.service.ReadModelService import it.pagopa.interop.catalogprocess.errors.CatalogProcessErrors.{ EServiceNotFound, DescriptorDocumentNotFound, - EServiceRiskAnalysisNotFound + EServiceRiskAnalysisNotFound, + TenantNotFound } import it.pagopa.interop.catalogmanagement.model.{ CatalogDescriptorState, @@ -1107,6 +1108,174 @@ 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 Tenant is not found" 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.failed(TenantNotFound(requesterId))) + + Post() ~> service.publishDescriptor( + SpecData.catalogItem.id.toString, + SpecData.catalogDescriptor.id.toString + ) ~> check { + status shouldEqual StatusCodes.NotFound + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.NotFound.intValue + problem.errors.head.code shouldBe "009-0014" + } + } + "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() diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/SpecData.scala b/src/test/scala/it/pagopa/interop/catalogprocess/SpecData.scala index 47b98a90..be762037 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/SpecData.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/SpecData.scala @@ -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", @@ -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", From ada4c7ef0d8647ee52997c303efa9672bd4f0755 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Thu, 28 Sep 2023 10:25:57 +0200 Subject: [PATCH 09/17] PR-4023 Resolved PR issue, added check of the receive mode for eService --- .../api/impl/ProcessApiServiceImpl.scala | 1 + .../api/impl/ResponseHandlers.scala | 1 + .../catalogprocess/CatalogProcessSpec.scala | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index a2f2a904..636adec0 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -716,6 +716,7 @@ final case class ProcessApiServiceImpl( riskAnalysisUuid <- riskAnalysisId.toFutureUUID catalogItem <- catalogManagementService.getEServiceById(eServiceUuid) _ <- isDraftEService(catalogItem) + _ <- isReceiveEService(catalogItem) _ <- catalogManagementService.deleteRiskAnalysis(eServiceUuid, riskAnalysisUuid) } yield () diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala index 769e78fb..99a667e7 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala @@ -87,6 +87,7 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: EServiceNotFound) => notFound(ex, logMessage) case Failure(ex: EServiceRiskAnalysisNotFound) => notFound(ex, logMessage) case Failure(ex: EServiceNotInDraftState) => badRequest(ex, logMessage) + case Failure(ex: EServiceNotInReceiveMode) => badRequest(ex, logMessage) case Failure(ex) => internalServerError(ex, logMessage) } diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala index 8df89b51..9192e9ae 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala @@ -2960,5 +2960,29 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR problem.errors.head.code shouldBe "009-0012" } } + "fail if EService is not Receive" in { + val requesterId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + val descriptor = + SpecData.catalogDescriptor.copy(state = Draft) + + val eService = SpecData.catalogItem.copy(descriptors = Seq(descriptor), producerId = requesterId, mode = Deliver) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(eService.id, *, *) + .once() + .returns(Future.successful(eService)) + + Post() ~> service.deleteRiskAnalysis(eService.id.toString, UUID.randomUUID().toString) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "009-0013" + } + } } } From 444dfd92299da75d568ec70ec8794547613a6a96 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Thu, 28 Sep 2023 16:34:45 +0200 Subject: [PATCH 10/17] PR-3996 Resolved PR issues --- .../catalogprocess/api/impl/ProcessApiServiceImpl.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index 50674fe9..59bb98c3 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -36,7 +36,8 @@ import it.pagopa.interop.catalogmanagement.model.{ Draft, Suspended, Deprecated, - Receive + Receive, + Deliver } final case class ProcessApiServiceImpl( @@ -203,8 +204,9 @@ final case class ProcessApiServiceImpl( val operationLabel = s"Publishing descriptor $descriptorId for EService $eServiceId" logger.info(operationLabel) - def verifyRiskAnalysisForPublication(catalogItem: CatalogItem): Future[Unit] = { - if (catalogItem.mode == Receive) { + 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)) @@ -217,7 +219,6 @@ final case class ProcessApiServiceImpl( ) ) } yield () - } else Future.unit } val result: Future[Unit] = for { From c9593e8bdd8483bf557477ead738c420bb8d1aa6 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Fri, 29 Sep 2023 11:33:57 +0200 Subject: [PATCH 11/17] PIN-3985 Resolved PR issues --- src/main/resources/interface-specification.yml | 2 +- .../catalogprocess/api/impl/ProcessApiServiceImpl.scala | 8 ++++---- .../catalogprocess/api/impl/ResponseHandlers.scala | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index da6e5890..3b24462b 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -864,7 +864,7 @@ paths: application/problem+json: schema: $ref: '#/components/schemas/Problem' - /eservices/{eServiceId}/riskanalysis: + /eservices/{eServiceId}/riskAnalysis: parameters: - $ref: '#/components/parameters/CorrelationIdHeader' - $ref: '#/components/parameters/IpAddress' diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index d678eebf..ecc27e94 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -669,8 +669,8 @@ final case class ProcessApiServiceImpl( _ <- assertRequesterAllowed(catalogItem.producerId)(organizationId) tenant <- tenantManagementService.getTenantById(organizationId) tenantKind <- tenant.kind.toFuture(TenantKindNotFound(tenant.id)) - _ <- isRiskAnalysisFormValid(seed.riskAnalysisForm.toTemplate, true)(tenantKind.toTemplate) - _ <- catalogManagementService.createRiskAnalysis(eServiceUuid, seed.toDependency) + _ <- isRiskAnalysisFormValid(seed.riskAnalysisForm.toTemplate, schemaOnlyValidation = true)(tenantKind.toTemplate) + _ <- catalogManagementService.createRiskAnalysis(eServiceUuid, seed.toDependency) } yield () onComplete(result) { @@ -701,8 +701,8 @@ object ProcessApiServiceImpl { ) Future.unit else Future.failed(RiskAnalysisNotValid) - def isReceiveEService(eService: CatalogItem)(implicit ec: ExecutionContext): Future[Unit] = - Future.failed(EServiceNotInReceiveMode(eService.id)).unlessA(eService.mode == Receive) + def isReceiveEService(eService: CatalogItem): Future[Unit] = + if (eService.mode == Receive) Future.unit else Future.failed(EServiceNotInReceiveMode(eService.id)) def isDraftEService(eService: CatalogItem): Future[Unit] = if (eService.descriptors.map(_.state) == Seq(Draft)) Future.unit diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala index f7e978a0..1162cd18 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala @@ -55,8 +55,6 @@ object ResponseHandlers extends AkkaResponses { case Success(s) => success(s) case Failure(ex: OperationForbidden.type) => forbidden(ex, logMessage) case Failure(ex: EServiceNotFound) => notFound(ex, logMessage) - case Failure(ex: TenantNotFound) => notFound(ex, logMessage) - case Failure(ex: TenantKindNotFound) => notFound(ex, logMessage) case Failure(ex: EServiceNotInDraftState) => badRequest(ex, logMessage) case Failure(ex: EServiceNotInReceiveMode) => badRequest(ex, logMessage) case Failure(ex: RiskAnalysisNotValid.type) => badRequest(ex, logMessage) From b25f2591b42f1fc07d1d2e150cacea8163f0d033 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Fri, 29 Sep 2023 11:36:40 +0200 Subject: [PATCH 12/17] PIN-4021 Resolved PR issues --- .../catalogprocess/api/impl/ProcessApiServiceImpl.scala | 5 +++-- .../interop/catalogprocess/api/impl/ResponseHandlers.scala | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index 7889e16f..483782b6 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -695,8 +695,9 @@ final case class ProcessApiServiceImpl( _ <- assertRequesterAllowed(catalogItem.producerId)(organizationId) tenant <- tenantManagementService.getTenantById(organizationId) tenantKind <- tenant.kind.toFuture(TenantKindNotFound(tenant.id)) - _ <- isRiskAnalysisFormValid(seed.riskAnalysisForm.toTemplate, true)(tenantKind.toTemplate) - _ <- catalogManagementService.updateRiskAnalysis(eServiceUuid, riskAnalysisUuid, seed.toDependency) + _ <- isRiskAnalysisFormValid(seed.riskAnalysisForm.toTemplate, schemaOnlyValidation = true)(tenantKind.toTemplate) + + _ <- catalogManagementService.updateRiskAnalysis(eServiceUuid, riskAnalysisUuid, seed.toDependency) } yield () onComplete(result) { diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala index 1d4db68f..aedb1447 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala @@ -71,8 +71,6 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: OperationForbidden.type) => forbidden(ex, logMessage) case Failure(ex: EServiceNotFound) => notFound(ex, logMessage) case Failure(ex: EServiceRiskAnalysisNotFound) => notFound(ex, logMessage) - case Failure(ex: TenantNotFound) => notFound(ex, logMessage) - case Failure(ex: TenantKindNotFound) => notFound(ex, logMessage) case Failure(ex: EServiceNotInDraftState) => badRequest(ex, logMessage) case Failure(ex: EServiceNotInReceiveMode) => badRequest(ex, logMessage) case Failure(ex: RiskAnalysisNotValid.type) => badRequest(ex, logMessage) From 706f1d1f8053ef551b629a6f26e17d9138466d03 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Fri, 29 Sep 2023 11:42:40 +0200 Subject: [PATCH 13/17] PIN-3996 Resolved PR issues --- .../interop/catalogprocess/api/impl/ResponseHandlers.scala | 2 -- .../pagopa/interop/catalogprocess/CatalogProcessSpec.scala | 5 +---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala index b42a0f82..f66259ba 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ResponseHandlers.scala @@ -185,8 +185,6 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: OperationForbidden.type) => forbidden(ex, logMessage) case Failure(ex: EServiceNotFound) => notFound(ex, logMessage) case Failure(ex: EServiceDescriptorNotFound) => notFound(ex, logMessage) - case Failure(ex: TenantNotFound) => notFound(ex, logMessage) - case Failure(ex: TenantKindNotFound) => 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) diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala index 3a1484e8..6a885db2 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala @@ -1232,10 +1232,7 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR SpecData.catalogItem.id.toString, SpecData.catalogDescriptor.id.toString ) ~> check { - status shouldEqual StatusCodes.NotFound - val problem = responseAs[Problem] - problem.status shouldBe StatusCodes.NotFound.intValue - problem.errors.head.code shouldBe "009-0014" + status shouldEqual StatusCodes.InternalServerError } } "fail if mode is Receive and Risk Analysis did not pass validation" in { From 4aff227c88dd1ae390098d04773217331ece84ae Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Fri, 29 Sep 2023 12:26:55 +0200 Subject: [PATCH 14/17] PIN-4020 Resolved PR issue --- .../interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index b83f042c..d86bd4ae 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -696,7 +696,6 @@ final case class ProcessApiServiceImpl( tenant <- tenantManagementService.getTenantById(organizationId) tenantKind <- tenant.kind.toFuture(TenantKindNotFound(tenant.id)) _ <- isRiskAnalysisFormValid(seed.riskAnalysisForm.toTemplate, schemaOnlyValidation = true)(tenantKind.toTemplate) - _ <- catalogManagementService.updateRiskAnalysis(eServiceUuid, riskAnalysisUuid, seed.toDependency) } yield () From dd5f3a4e9434b6e22629dd548dbc8cbb1f36d558 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Fri, 29 Sep 2023 12:32:24 +0200 Subject: [PATCH 15/17] PIN-3996 Resolved PR issue --- .../catalogprocess/CatalogProcessSpec.scala | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala index 6a885db2..979b6871 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala @@ -1200,41 +1200,6 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR problem.errors.head.code shouldBe "009-0018" } } - "fail if mode is Receive and Tenant is not found" 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.failed(TenantNotFound(requesterId))) - - Post() ~> service.publishDescriptor( - SpecData.catalogItem.id.toString, - SpecData.catalogDescriptor.id.toString - ) ~> check { - status shouldEqual StatusCodes.InternalServerError - } - } "fail if mode is Receive and Risk Analysis did not pass validation" in { val requesterId = UUID.randomUUID() From dd4b7462b58aec15d5c473894368ba53dec4905e Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Fri, 29 Sep 2023 12:46:40 +0200 Subject: [PATCH 16/17] PIN-4020 Resolved endpoint in camel case --- src/main/resources/interface-specification.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index 5b38808a..80ea30f4 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -911,7 +911,7 @@ paths: application/problem+json: schema: $ref: '#/components/schemas/Problem' - /eservices/{eServiceId}/riskanalysis/{riskAnalysisId}: + /eservices/{eServiceId}/riskAnalysis/{riskAnalysisId}: parameters: - $ref: '#/components/parameters/CorrelationIdHeader' - $ref: '#/components/parameters/IpAddress' From dcb791e8a3cce6ec77fa100305de301e17c91cba Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Fri, 29 Sep 2023 12:49:24 +0200 Subject: [PATCH 17/17] PIN-3996 Cleaning --- .../it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala index 979b6871..9b190146 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala @@ -13,8 +13,7 @@ import it.pagopa.interop.commons.cqrs.service.ReadModelService import it.pagopa.interop.catalogprocess.errors.CatalogProcessErrors.{ EServiceNotFound, DescriptorDocumentNotFound, - EServiceRiskAnalysisNotFound, - TenantNotFound + EServiceRiskAnalysisNotFound } import it.pagopa.interop.catalogmanagement.model.{ CatalogDescriptorState,