From 6e04d19d215a99ab2611d3a7eef3170cbffec90c Mon Sep 17 00:00:00 2001 From: eletallbetagouv <107104509+eletallbetagouv@users.noreply.github.com> Date: Tue, 4 Feb 2025 09:50:23 +0200 Subject: [PATCH] TRELLO-2886 securize endpoints where pro operates on a report id --- app/actors/ReportsExtractActor.scala | 4 +- app/controllers/EngagementController.scala | 4 +- .../ReportConsumerReviewController.scala | 4 +- app/controllers/ReportController.scala | 7 +- app/controllers/ReportFileController.scala | 8 ++- app/loader/SignalConsoApplicationLoader.scala | 26 +++++--- app/models/UserRole.scala | 3 +- .../EngagementOrchestrator.scala | 21 ++++-- app/orchestrators/EventsOrchestrator.scala | 11 +--- .../ReportAssignmentOrchestrator.scala | 4 +- .../ReportConsumerReviewOrchestrator.scala | 39 +++++++---- app/orchestrators/ReportOrchestrator.scala | 32 +-------- .../ReportWithDataOrchestrator.scala | 4 +- .../VisibleReportOrchestrator.scala | 66 +++++++++++++++++++ app/utils/Constants.scala | 11 ++-- 15 files changed, 158 insertions(+), 86 deletions(-) create mode 100644 app/orchestrators/VisibleReportOrchestrator.scala diff --git a/app/actors/ReportsExtractActor.scala b/app/actors/ReportsExtractActor.scala index 0c955988..d94849ba 100644 --- a/app/actors/ReportsExtractActor.scala +++ b/app/actors/ReportsExtractActor.scala @@ -151,8 +151,8 @@ object ReportsExtractActor { reportIds = paginatedReports.map(_.id) reportFilesMap <- reportFileRepository.prefetchReportsFiles(reportIds) reportEventsMap <- eventRepository.getEventsWithUsersMap(reportIds, EventFilter.Empty) - consumerReviewsMap <- reportConsumerReviewOrchestrator.find(reportIds) - engagementReviewsMap <- engagementOrchestrator.findEngagementReviews(reportIds) + consumerReviewsMap <- reportConsumerReviewOrchestrator.getReviews(reportIds) + engagementReviewsMap <- engagementOrchestrator.getEngagementReviews(reportIds) companyAdminsMap <- companyAccessRepository.fetchUsersByCompanyIds( paginatedReports.flatMap(_.companyId), Seq(AccessLevel.ADMIN) diff --git a/app/controllers/EngagementController.scala b/app/controllers/EngagementController.scala index 0c3f6418..1e4bdca1 100644 --- a/app/controllers/EngagementController.scala +++ b/app/controllers/EngagementController.scala @@ -53,7 +53,7 @@ class EngagementController( def getEngagementReview(reportUUID: UUID): Action[AnyContent] = SecuredAction.async { request => logger.debug(s"Get report engagement review for report id : $reportUUID") for { - maybeReview <- engagementOrchestrator.findEngagementReview(reportUUID) + maybeReview <- engagementOrchestrator.getVisibleEngagementReview(reportUUID, request.identity) } yield maybeReview .map { review => val writes = engagementReviewWrites(Some(request.identity.userRole)) @@ -64,7 +64,7 @@ class EngagementController( def engagementReviewExists(reportUUID: UUID): Action[AnyContent] = IpRateLimitedAction2.async { _ => logger.debug(s"Check if engagement review exists for report id : $reportUUID") - engagementOrchestrator.findEngagementReview(reportUUID).map(_.exists(_.details.nonEmpty)).map { exists => + engagementOrchestrator.doesEngagementReviewExists(reportUUID).map { exists => Ok(Json.toJson(ConsumerReviewExistApi(exists))) } } diff --git a/app/controllers/ReportConsumerReviewController.scala b/app/controllers/ReportConsumerReviewController.scala index 8e261113..c0442c61 100644 --- a/app/controllers/ReportConsumerReviewController.scala +++ b/app/controllers/ReportConsumerReviewController.scala @@ -37,7 +37,7 @@ class ReportConsumerReviewController( def getReview(reportUUID: UUID): Action[AnyContent] = SecuredAction.async { request => logger.debug(s"Get report response review for report id : ${reportUUID}") for { - maybeReview <- reportConsumerReviewOrchestrator.find(reportUUID) + maybeReview <- reportConsumerReviewOrchestrator.getVisibleReview(reportUUID, request.identity) } yield maybeReview .map { review => val writes = responseConsumerReviewWrites(Some(request.identity.userRole)) @@ -49,7 +49,7 @@ class ReportConsumerReviewController( def reviewExists(reportUUID: UUID): Action[AnyContent] = IpRateLimitedAction2.async { _ => logger.debug(s"Check if review exists for report id : ${reportUUID}") - reportConsumerReviewOrchestrator.find(reportUUID).map(_.exists(_.details.nonEmpty)).map { exists => + reportConsumerReviewOrchestrator.doesReviewExists(reportUUID).map { exists => Ok(Json.toJson(ConsumerReviewExistApi(exists))) } diff --git a/app/controllers/ReportController.scala b/app/controllers/ReportController.scala index 652ed70c..d7cb52b6 100644 --- a/app/controllers/ReportController.scala +++ b/app/controllers/ReportController.scala @@ -50,6 +50,7 @@ class ReportController( authenticator: Authenticator[User], controllerComponents: ControllerComponents, reportWithDataOrchestrator: ReportWithDataOrchestrator, + visibleReportOrchestrator: VisibleReportOrchestrator, massImportService: ReportZipExportService, htmlFromTemplateGenerator: HtmlFromTemplateGenerator )(implicit val ec: ExecutionContext) @@ -118,7 +119,7 @@ class ReportController( logger.debug(s"reportResponse ${uuid}") for { reportResponse <- request.parseBody[IncomingReportResponse]() - visibleReportExtra <- reportOrchestrator.getVisibleReportForUser(uuid, request.identity) + visibleReportExtra <- visibleReportOrchestrator.getVisibleReportForUser(uuid, request.identity) visibleReport = visibleReportExtra.map(_.report) updatedReport <- visibleReport .map(reportOrchestrator.handleReportResponse(_, reportResponse, request.identity)) @@ -130,7 +131,7 @@ class ReportController( } def createReportAction(uuid: UUID): Action[JsValue] = - SecuredAction.andThen(WithRole(UserRole.EveryoneButReadOnlyAdmin)).andThen(ForbidImpersonation).async(parse.json) { + SecuredAction.andThen(WithRole(UserRole.AdminsAndAgents)).andThen(ForbidImpersonation).async(parse.json) { implicit request => for { reportAction <- request.parseBody[ReportAction]() @@ -150,7 +151,7 @@ class ReportController( SecuredAction.async { implicit request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) for { - maybeReportWithMetadata <- reportOrchestrator.getVisibleReportForUser(uuid, request.identity) + maybeReportWithMetadata <- visibleReportOrchestrator.getVisibleReportForUser(uuid, request.identity) viewedReportWithMetadata <- maybeReportWithMetadata .map(r => reportOrchestrator.handleReportView(r, request.identity).map(Some(_))) .getOrElse(Future.successful(None)) diff --git a/app/controllers/ReportFileController.scala b/app/controllers/ReportFileController.scala index 7e4b00bf..ea882d79 100644 --- a/app/controllers/ReportFileController.scala +++ b/app/controllers/ReportFileController.scala @@ -4,7 +4,6 @@ import org.apache.pekko.Done import authentication.Authenticator import cats.implicits.catsSyntaxOption import config.SignalConsoConfiguration -import controllers.error.AppError import controllers.error.AppError.FileNameTooLong import controllers.error.AppError.FileTooLarge import controllers.error.AppError.InvalidFileExtension @@ -14,6 +13,7 @@ import models.report.ReportFile.MaxFileNameLength import models.report._ import models.report.reportfile.ReportFileId import orchestrators.ReportFileOrchestrator +import orchestrators.VisibleReportOrchestrator import play.api.Logger import play.api.libs.Files import play.api.libs.json.JsError @@ -34,6 +34,7 @@ import scala.concurrent.Future class ReportFileController( reportFileOrchestrator: ReportFileOrchestrator, + visibleReportOrchestrator: VisibleReportOrchestrator, authenticator: Authenticator[User], signalConsoConfiguration: SignalConsoConfiguration, controllerComponents: ControllerComponents, @@ -66,9 +67,10 @@ class ReportFileController( ) } - def downloadZip(reportId: UUID, origin: Option[ReportFileOrigin]) = IpRateLimitedAction1.async { _ => + def downloadZip(reportId: UUID, origin: Option[ReportFileOrigin]) = SecuredAction.async { request => for { - report <- reportRepository.get(reportId).flatMap(_.liftTo[Future](AppError.ReportNotFound(reportId))) + reportExtra <- visibleReportOrchestrator.getVisibleReportOrThrow(reportId, request.identity) + report = reportExtra.report stream <- reportFileOrchestrator.downloadReportFilesArchive(report, origin) } yield Ok .chunked(stream) diff --git a/app/loader/SignalConsoApplicationLoader.scala b/app/loader/SignalConsoApplicationLoader.scala index 6baf67e8..5cd4799d 100644 --- a/app/loader/SignalConsoApplicationLoader.scala +++ b/app/loader/SignalConsoApplicationLoader.scala @@ -390,11 +390,18 @@ class SignalConsoComponents( tokenConfiguration ) + val visibleReportOrchestrator = new VisibleReportOrchestrator( + reportRepository, + companyRepository, + companiesVisibilityOrchestrator + ) + val dataEconomieOrchestrator = new DataEconomieOrchestrator(reportRepository) val emailValidationOrchestrator = new EmailValidationOrchestrator(mailService, emailValidationRepository, emailConfiguration, messagesApi) - val eventsOrchestrator = new EventsOrchestrator(eventRepository, reportRepository, companyRepository) + val eventsOrchestrator = + new EventsOrchestrator(visibleReportOrchestrator, eventRepository, companyRepository) val reportBlockedNotificationOrchestrator = new ReportBlockedNotificationOrchestrator( reportNotificationBlockedRepository @@ -402,6 +409,7 @@ class SignalConsoComponents( val reportConsumerReviewOrchestrator = new ReportConsumerReviewOrchestrator( + visibleReportOrchestrator, reportRepository, eventRepository, responseConsumerReviewRepository @@ -424,19 +432,19 @@ class SignalConsoComponents( antivirusService ) + val bookmarkOrchestrator = new BookmarkOrchestrator(reportRepository, bookmarkRepository) + + val emailNotificationOrchestrator = new EmailNotificationOrchestrator(mailService, subscriptionRepository) + val engagementOrchestrator = new EngagementOrchestrator( engagementRepository, + visibleReportOrchestrator, companiesVisibilityOrchestrator, eventRepository, reportRepository, reportEngagementReviewRepository ) - - val bookmarkOrchestrator = new BookmarkOrchestrator(reportRepository, bookmarkRepository) - - val emailNotificationOrchestrator = new EmailNotificationOrchestrator(mailService, subscriptionRepository) - private def buildReportOrchestrator(emailService: MailServiceInterface) = new ReportOrchestrator( emailService, reportConsumerReviewOrchestrator, @@ -465,7 +473,7 @@ class SignalConsoComponents( val reportOrchestrator = buildReportOrchestrator(mailService) val reportAssignmentOrchestrator = new ReportAssignmentOrchestrator( - reportOrchestrator, + visibleReportOrchestrator, companiesVisibilityOrchestrator, mailService, reportMetadataRepository, @@ -475,7 +483,7 @@ class SignalConsoComponents( val reportWithDataOrchestrator = new ReportWithDataOrchestrator( - reportOrchestrator, + visibleReportOrchestrator, companyRepository, eventRepository, reportFileRepository, @@ -844,6 +852,7 @@ class SignalConsoComponents( val reportFileController = new ReportFileController( reportFileOrchestrator, + visibleReportOrchestrator, cookieAuthenticator, signalConsoConfiguration, controllerComponents, @@ -864,6 +873,7 @@ class SignalConsoComponents( cookieAuthenticator, controllerComponents, reportWithDataOrchestrator, + visibleReportOrchestrator, reportZipExportService, htmlFromTemplateGenerator ) diff --git a/app/models/UserRole.scala b/app/models/UserRole.scala index 1b292e82..7a21a8b4 100644 --- a/app/models/UserRole.scala +++ b/app/models/UserRole.scala @@ -19,7 +19,8 @@ object UserRole extends PlayEnum[UserRole] { val AdminsAndReadOnly = ReadOnlyAdmin +: Admins val AdminsAndReadOnlyAndCCRF = DGCCRF +: AdminsAndReadOnly val AdminsAndReadOnlyAndAgents = DGAL +: AdminsAndReadOnlyAndCCRF - val EveryoneButReadOnlyAdmin = List(SuperAdmin, Admin, DGCCRF, DGAL, Professionnel) + val Agents = List(DGAL, DGCCRF) + val AdminsAndAgents = Admins ++ Agents def isAdminOrAgent(userRole: UserRole) = AdminsAndReadOnlyAndAgents.contains(userRole) def isAdmin(userRole: UserRole) = AdminsAndReadOnly.contains(userRole) diff --git a/app/orchestrators/EngagementOrchestrator.scala b/app/orchestrators/EngagementOrchestrator.scala index a7dbe325..15b7be15 100644 --- a/app/orchestrators/EngagementOrchestrator.scala +++ b/app/orchestrators/EngagementOrchestrator.scala @@ -30,6 +30,7 @@ import scala.concurrent.Future class EngagementOrchestrator( engagementRepository: EngagementRepositoryInterface, + visibleReportOrchestrator: VisibleReportOrchestrator, companiesVisibilityOrchestrator: CompaniesVisibilityOrchestrator, eventRepository: EventRepositoryInterface, reportRepository: ReportRepositoryInterface, @@ -107,7 +108,7 @@ class EngagementOrchestrator( def removeEngagement(reportId: UUID): Future[Unit] = for { - _ <- findEngagementReview(reportId).flatMap { + _ <- getEngagementReview(reportId).flatMap { case Some(engagementReview) => reportEngagementReviewRepository.delete(engagementReview.id).map(_ => ()) case None => Future.unit @@ -115,7 +116,13 @@ class EngagementOrchestrator( _ <- engagementRepository.remove(reportId) } yield () - def findEngagementReview(reportId: UUID): Future[Option[EngagementReview]] = + def getVisibleEngagementReview(reportId: UUID, user: User): Future[Option[EngagementReview]] = + for { + _ <- visibleReportOrchestrator.checkReportIsVisible(reportId, user) + maybeReview <- getEngagementReview(reportId) + } yield maybeReview + + private def getEngagementReview(reportId: UUID): Future[Option[EngagementReview]] = reportEngagementReviewRepository.findByReportId(reportId) map { case Nil => logger.info(s"No engagement review found for report $reportId") @@ -130,16 +137,14 @@ class EngagementOrchestrator( engagementReviews.maxBy(_.creationDate).some } - def findEngagementReviews(reportIds: Seq[UUID]): Future[Map[UUID, Option[EngagementReview]]] = + def getEngagementReviews(reportIds: Seq[UUID]): Future[Map[UUID, Option[EngagementReview]]] = reportEngagementReviewRepository.findByReportIds(reportIds) def handleEngagementReview( reportId: UUID, reviewApi: ConsumerReviewApi ): Future[Unit] = { - logger.info(s"Engagement for report $reportId - the consumer give a review on engagement") - for { report <- reportRepository.get(reportId) _ = logger.debug(s"Validating report") @@ -172,6 +177,12 @@ class EngagementOrchestrator( } } yield () + def doesEngagementReviewExists(reportId: UUID): Future[Boolean] = + for { + maybeReview <- getEngagementReview(reportId) + hasNonEmptyReview = maybeReview.exists(_.details.nonEmpty) + } yield hasNonEmptyReview + private def updateEngagementReview(review: EngagementReview) = reportEngagementReviewRepository.update( review.id, diff --git a/app/orchestrators/EventsOrchestrator.scala b/app/orchestrators/EventsOrchestrator.scala index 9c6ae168..2ff7f851 100644 --- a/app/orchestrators/EventsOrchestrator.scala +++ b/app/orchestrators/EventsOrchestrator.scala @@ -1,7 +1,6 @@ package orchestrators import cats.implicits.catsSyntaxOption import controllers.error.AppError.CompanySiretNotFound -import controllers.error.AppError.ReportNotFound import io.scalaland.chimney.dsl._ import models.User import models.UserRole @@ -12,7 +11,6 @@ import play.api.Logger import repositories.company.CompanyRepositoryInterface import repositories.event.EventFilter import repositories.event.EventRepositoryInterface -import repositories.report.ReportRepositoryInterface import utils.Constants.ActionEvent.REPORT_ASSIGNED import utils.Constants.ActionEvent.REPORT_CLOSED_BY_NO_ACTION import utils.Constants.ActionEvent.REPORT_CLOSED_BY_NO_READING @@ -44,8 +42,8 @@ trait EventsOrchestratorInterface { } class EventsOrchestrator( + visibleReportOrchestrator: VisibleReportOrchestrator, eventRepository: EventRepositoryInterface, - reportRepository: ReportRepositoryInterface, companyRepository: CompanyRepositoryInterface )(implicit val ec: ExecutionContext @@ -59,11 +57,8 @@ class EventsOrchestrator( user: User ): Future[List[EventWithUser]] = for { - maybeReportWithMetadata <- reportRepository.getFor(Some(user), reportId) - maybeReport = maybeReportWithMetadata.map(_.report) - _ = logger.debug("Checking if report exists") - _ <- maybeReport.liftTo[Future](ReportNotFound(reportId)) - _ = logger.debug("Found report") + _ <- Future.successful(()) + _ <- visibleReportOrchestrator.checkReportIsVisible(reportId, user) filter = buildEventFilter(eventType) _ = logger.debug("Fetching events") events <- eventRepository.getEventsWithUsers(List(reportId), filter) diff --git a/app/orchestrators/ReportAssignmentOrchestrator.scala b/app/orchestrators/ReportAssignmentOrchestrator.scala index 30f8759f..f0cb4caa 100644 --- a/app/orchestrators/ReportAssignmentOrchestrator.scala +++ b/app/orchestrators/ReportAssignmentOrchestrator.scala @@ -22,7 +22,7 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future class ReportAssignmentOrchestrator( - reportOrchestrator: ReportOrchestrator, + visibleReportOrchestrator: VisibleReportOrchestrator, companiesVisibilityOrchestrator: CompaniesVisibilityOrchestrator, mailService: MailService, reportMetadataRepository: ReportMetadataRepositoryInterface, @@ -41,7 +41,7 @@ class ReportAssignmentOrchestrator( ): Future[User] = { val assigningToSelf = assigningUser.id == newAssignedUserId for { - maybeReportExtra <- reportOrchestrator.getVisibleReportForUser(reportId, assigningUser) + maybeReportExtra <- visibleReportOrchestrator.getVisibleReportForUser(reportId, assigningUser) reportExtra <- maybeReportExtra.liftTo[Future](AppError.ReportNotFound(reportId)) newAssignedUser <- checkAssignableToUser(reportExtra, newAssignedUserId) _ <- reportMetadataRepository.setAssignedUser(reportId, newAssignedUserId) diff --git a/app/orchestrators/ReportConsumerReviewOrchestrator.scala b/app/orchestrators/ReportConsumerReviewOrchestrator.scala index ed5f581c..eb989454 100644 --- a/app/orchestrators/ReportConsumerReviewOrchestrator.scala +++ b/app/orchestrators/ReportConsumerReviewOrchestrator.scala @@ -3,9 +3,10 @@ package orchestrators import org.apache.pekko.Done import controllers.error.AppError.CannotReviewReportResponse import controllers.error.AppError.ServerError +import models.User import models.report.ReportStatus.hasResponse -import models.report.review.ResponseConsumerReview import models.report.review.ConsumerReviewApi +import models.report.review.ResponseConsumerReview import models.report.review.ResponseConsumerReviewId import play.api.Logger import utils.Constants.ActionEvent @@ -21,6 +22,7 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future class ReportConsumerReviewOrchestrator( + visibleReportOrchestrator: VisibleReportOrchestrator, reportRepository: ReportRepositoryInterface, eventRepository: EventRepositoryInterface, responseConsumerReviewRepository: ResponseConsumerReviewRepositoryInterface @@ -30,22 +32,37 @@ class ReportConsumerReviewOrchestrator( val logger = Logger(this.getClass) def remove(reportId: UUID): Future[Done] = - find(reportId).flatMap { + getReview(reportId).flatMap { case Some(responseConsumerReview) => responseConsumerReviewRepository.delete(responseConsumerReview.id).map(_ => Done) case None => Future.successful(Done) } - def find(reportId: UUID): Future[Option[ResponseConsumerReview]] = - responseConsumerReviewRepository.findByReportId(reportId) map { - case Nil => - logger.info(s"No review found for report $reportId") - None - case review :: Nil => Some(review) - case _ => throw ServerError(s"More than one consumer review for report id $reportId") - } + def getVisibleReview(reportId: UUID, user: User): Future[Option[ResponseConsumerReview]] = + for { + _ <- visibleReportOrchestrator.checkReportIsVisible(reportId, user) + maybeReview <- getReview(reportId) + } yield maybeReview + + def doesReviewExists(reportId: UUID): Future[Boolean] = + for { + maybeReview <- getReview(reportId) + hasNonEmptyReview = maybeReview.exists(_.details.nonEmpty) + } yield hasNonEmptyReview + + private def getReview(reportId: UUID): Future[Option[ResponseConsumerReview]] = + for { + reviews <- responseConsumerReviewRepository.findByReportId(reportId) + maybeReview = reviews match { + case Nil => + logger.info(s"No review found for report $reportId") + None + case review :: Nil => Some(review) + case _ => throw ServerError(s"More than one consumer review for report id $reportId") + } + } yield maybeReview - def find(reportIds: List[UUID]): Future[Map[UUID, Option[ResponseConsumerReview]]] = + def getReviews(reportIds: List[UUID]): Future[Map[UUID, Option[ResponseConsumerReview]]] = responseConsumerReviewRepository.findByReportIds(reportIds) def handleReviewOnReportResponse( diff --git a/app/orchestrators/ReportOrchestrator.scala b/app/orchestrators/ReportOrchestrator.scala index cc22875a..5f28015c 100644 --- a/app/orchestrators/ReportOrchestrator.scala +++ b/app/orchestrators/ReportOrchestrator.scala @@ -11,7 +11,6 @@ import config.SignalConsoConfiguration import config.TokenConfiguration import controllers.error.AppError import controllers.error.AppError._ -import models.UserRole.Professionnel import models._ import models.company.AccessLevel import models.company.Address @@ -1013,8 +1012,8 @@ class ReportOrchestrator( assignedUsersIds = reportsWithFiles.entities.flatMap(_.metadata.flatMap(_.assignedUserId)) assignedUsers <- userRepository.findByIds(assignedUsersIds) - consumerReviewsMap <- reportConsumerReviewOrchestrator.find(reportsId) - engagementReviews <- engagementOrchestrator.findEngagementReviews(reportsId) + consumerReviewsMap <- reportConsumerReviewOrchestrator.getReviews(reportsId) + engagementReviews <- engagementOrchestrator.getEngagementReviews(reportsId) } yield reportsWithFiles.copy( entities = reportsWithFiles.entities.map { reportWithFiles => val maybeAssignedUserId = reportWithFiles.metadata.flatMap(_.assignedUserId) @@ -1076,33 +1075,6 @@ class ReportOrchestrator( .convert(endGetReportFiles - startGetReportFiles, TimeUnit.NANOSECONDS)} ------------------") } yield paginatedReports.mapEntities(r => toApi(r, reportFilesMap)) - def getVisibleReportForUser(reportId: UUID, user: User): Future[Option[ReportExtra]] = - for { - reportWithMetadata <- reportRepository.getFor(Some(user), reportId) - report = reportWithMetadata.map(_.report) - company <- report.flatMap(_.companyId).map(r => companyRepository.get(r)).flatSequence - address = Address.merge(company.map(_.address), report.map(_.companyAddress)) - reportExtra = reportWithMetadata.map(r => - ReportExtra - .from(r, company) - .setAddress(address) - ) - visibleReportExtra <- - user.userRole match { - case UserRole.DGCCRF | UserRole.DGAL | UserRole.SuperAdmin | UserRole.Admin | UserRole.ReadOnlyAdmin => - Future.successful(reportExtra) - case Professionnel => - companiesVisibilityOrchestrator - .fetchVisibleCompanies(user) - .map(_.map(v => Some(v.company.siret))) - .map { visibleSirets => - reportExtra - .filter(r => visibleSirets.contains(r.report.companySiret)) - .map(_.copy(companyAlbertActivityLabel = None)) - } - } - } yield visibleReportExtra - def getCloudWord(companyId: UUID): Future[List[ReportWordOccurrence]] = for { maybeCompany <- companyRepository.get(companyId) diff --git a/app/orchestrators/ReportWithDataOrchestrator.scala b/app/orchestrators/ReportWithDataOrchestrator.scala index 54561bb6..608118d7 100644 --- a/app/orchestrators/ReportWithDataOrchestrator.scala +++ b/app/orchestrators/ReportWithDataOrchestrator.scala @@ -35,7 +35,7 @@ case class ReportWithData( ) class ReportWithDataOrchestrator( - reportOrchestrator: ReportOrchestrator, + visibleReportOrchestrator: VisibleReportOrchestrator, companyRepository: CompanyRepositoryInterface, eventRepository: EventRepositoryInterface, reportFileRepository: ReportFileRepositoryInterface, @@ -45,7 +45,7 @@ class ReportWithDataOrchestrator( val logger = Logger(this.getClass) def getReportFull(uuid: UUID, userToCheckAuthorization: User): Future[Option[ReportWithData]] = - reportOrchestrator + visibleReportOrchestrator .getVisibleReportForUser(uuid, userToCheckAuthorization) .flatMap { maybeReport => maybeReport.map { reportExtra => diff --git a/app/orchestrators/VisibleReportOrchestrator.scala b/app/orchestrators/VisibleReportOrchestrator.scala new file mode 100644 index 00000000..39e1984a --- /dev/null +++ b/app/orchestrators/VisibleReportOrchestrator.scala @@ -0,0 +1,66 @@ +package orchestrators + +import cats.implicits.catsSyntaxOption +import cats.implicits.toTraverseOps +import controllers.error.AppError.ReportNotFound +import models.UserRole.Professionnel +import models._ +import models.company.Address +import models.report.reportmetadata.ReportExtra +import play.api.Logger +import repositories.company.CompanyRepositoryInterface +import repositories.report.ReportRepositoryInterface + +import java.util.UUID +import scala.concurrent.ExecutionContext +import scala.concurrent.Future +import scala.concurrent.duration._ + +class VisibleReportOrchestrator( + reportRepository: ReportRepositoryInterface, + companyRepository: CompanyRepositoryInterface, + companiesVisibilityOrchestrator: CompaniesVisibilityOrchestrator +)(implicit val executionContext: ExecutionContext) { + val logger = Logger(this.getClass) + + implicit val timeout: org.apache.pekko.util.Timeout = 5.seconds + + def getVisibleReportForUser(reportId: UUID, user: User): Future[Option[ReportExtra]] = + for { + reportWithMetadata <- reportRepository.getFor(Some(user), reportId) + report = reportWithMetadata.map(_.report) + company <- report.flatMap(_.companyId).map(r => companyRepository.get(r)).flatSequence + address = Address.merge(company.map(_.address), report.map(_.companyAddress)) + reportExtra = reportWithMetadata.map(r => + ReportExtra + .from(r, company) + .setAddress(address) + ) + visibleReportExtra <- + user.userRole match { + case UserRole.DGCCRF | UserRole.DGAL | UserRole.SuperAdmin | UserRole.Admin | UserRole.ReadOnlyAdmin => + Future.successful(reportExtra) + case Professionnel => + companiesVisibilityOrchestrator + .fetchVisibleCompanies(user) + .map(_.map(v => Some(v.company.siret))) + .map { visibleSirets => + reportExtra + .filter(r => visibleSirets.contains(r.report.companySiret)) + .map(_.copy(companyAlbertActivityLabel = None)) + } + } + } yield visibleReportExtra + + def getVisibleReportOrThrow(reportId: UUID, user: User): Future[ReportExtra] = + for { + maybeReport <- getVisibleReportForUser(reportId, user) + report <- maybeReport.liftTo[Future](ReportNotFound(reportId)) + } yield report + + def checkReportIsVisible(reportId: UUID, user: User): Future[Unit] = + for { + _ <- getVisibleReportOrThrow(reportId, user) + } yield () + +} diff --git a/app/utils/Constants.scala b/app/utils/Constants.scala index 2b7a8d96..53d1a74f 100644 --- a/app/utils/Constants.scala +++ b/app/utils/Constants.scala @@ -161,13 +161,10 @@ object Constants { ) val actionsForUserRole: Map[UserRole, List[ActionEventValue]] = - Map( - UserRole.Professionnel -> List(COMMENT), - UserRole.SuperAdmin -> List(COMMENT, CONSUMER_ATTACHMENTS, PROFESSIONAL_ATTACHMENTS), - UserRole.Admin -> List(COMMENT, CONSUMER_ATTACHMENTS, PROFESSIONAL_ATTACHMENTS), - UserRole.DGCCRF -> List(COMMENT, CONTROL), - UserRole.DGAL -> List(COMMENT, CONTROL) - ) + List( + UserRole.Admins.map(_ -> List(COMMENT, CONSUMER_ATTACHMENTS, PROFESSIONAL_ATTACHMENTS)), + UserRole.Agents.map(_ -> List(COMMENT, CONTROL)) + ).flatten.toMap def fromValue(value: String) = actionEvents.find(_.value == value).getOrElse(ActionEventValue("")) }