Skip to content

Commit

Permalink
Merge pull request #1875 from betagouv/master
Browse files Browse the repository at this point in the history
MEP : TRELLO-2886 securize endpoints where pro operates on a report id
  • Loading branch information
eletallbetagouv authored Feb 6, 2025
2 parents 857b974 + 6e04d19 commit 0bbffe1
Show file tree
Hide file tree
Showing 15 changed files with 158 additions and 86 deletions.
4 changes: 2 additions & 2 deletions app/actors/ReportsExtractActor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/EngagementController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)))
}
}
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/ReportConsumerReviewController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)))
}

Expand Down
7 changes: 4 additions & 3 deletions app/controllers/ReportController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class ReportController(
authenticator: Authenticator[User],
controllerComponents: ControllerComponents,
reportWithDataOrchestrator: ReportWithDataOrchestrator,
visibleReportOrchestrator: VisibleReportOrchestrator,
massImportService: ReportZipExportService,
htmlFromTemplateGenerator: HtmlFromTemplateGenerator
)(implicit val ec: ExecutionContext)
Expand Down Expand Up @@ -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))
Expand All @@ -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]()
Expand All @@ -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))
Expand Down
8 changes: 5 additions & 3 deletions app/controllers/ReportFileController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -34,6 +34,7 @@ import scala.concurrent.Future

class ReportFileController(
reportFileOrchestrator: ReportFileOrchestrator,
visibleReportOrchestrator: VisibleReportOrchestrator,
authenticator: Authenticator[User],
signalConsoConfiguration: SignalConsoConfiguration,
controllerComponents: ControllerComponents,
Expand Down Expand Up @@ -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)
Expand Down
26 changes: 18 additions & 8 deletions app/loader/SignalConsoApplicationLoader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -390,18 +390,26 @@ 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
)

val reportConsumerReviewOrchestrator =
new ReportConsumerReviewOrchestrator(
visibleReportOrchestrator,
reportRepository,
eventRepository,
responseConsumerReviewRepository
Expand All @@ -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,
Expand Down Expand Up @@ -465,7 +473,7 @@ class SignalConsoComponents(
val reportOrchestrator = buildReportOrchestrator(mailService)

val reportAssignmentOrchestrator = new ReportAssignmentOrchestrator(
reportOrchestrator,
visibleReportOrchestrator,
companiesVisibilityOrchestrator,
mailService,
reportMetadataRepository,
Expand All @@ -475,7 +483,7 @@ class SignalConsoComponents(

val reportWithDataOrchestrator =
new ReportWithDataOrchestrator(
reportOrchestrator,
visibleReportOrchestrator,
companyRepository,
eventRepository,
reportFileRepository,
Expand Down Expand Up @@ -844,6 +852,7 @@ class SignalConsoComponents(
val reportFileController =
new ReportFileController(
reportFileOrchestrator,
visibleReportOrchestrator,
cookieAuthenticator,
signalConsoConfiguration,
controllerComponents,
Expand All @@ -864,6 +873,7 @@ class SignalConsoComponents(
cookieAuthenticator,
controllerComponents,
reportWithDataOrchestrator,
visibleReportOrchestrator,
reportZipExportService,
htmlFromTemplateGenerator
)
Expand Down
3 changes: 2 additions & 1 deletion app/models/UserRole.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 16 additions & 5 deletions app/orchestrators/EngagementOrchestrator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import scala.concurrent.Future

class EngagementOrchestrator(
engagementRepository: EngagementRepositoryInterface,
visibleReportOrchestrator: VisibleReportOrchestrator,
companiesVisibilityOrchestrator: CompaniesVisibilityOrchestrator,
eventRepository: EventRepositoryInterface,
reportRepository: ReportRepositoryInterface,
Expand Down Expand Up @@ -107,15 +108,21 @@ 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
}
_ <- 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")
Expand All @@ -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")
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 3 additions & 8 deletions app/orchestrators/EventsOrchestrator.scala
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -44,8 +42,8 @@ trait EventsOrchestratorInterface {
}

class EventsOrchestrator(
visibleReportOrchestrator: VisibleReportOrchestrator,
eventRepository: EventRepositoryInterface,
reportRepository: ReportRepositoryInterface,
companyRepository: CompanyRepositoryInterface
)(implicit
val ec: ExecutionContext
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions app/orchestrators/ReportAssignmentOrchestrator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import scala.concurrent.ExecutionContext
import scala.concurrent.Future

class ReportAssignmentOrchestrator(
reportOrchestrator: ReportOrchestrator,
visibleReportOrchestrator: VisibleReportOrchestrator,
companiesVisibilityOrchestrator: CompaniesVisibilityOrchestrator,
mailService: MailService,
reportMetadataRepository: ReportMetadataRepositoryInterface,
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 0bbffe1

Please sign in to comment.