diff --git a/app/authentication/actions/ImpersonationAction.scala b/app/authentication/actions/ImpersonationAction.scala index 521f2815..a34b600f 100644 --- a/app/authentication/actions/ImpersonationAction.scala +++ b/app/authentication/actions/ImpersonationAction.scala @@ -1,9 +1,11 @@ package authentication.actions +import authentication.actions.MaybeUserAction.MaybeUserRequest import models.User import play.api.mvc.Results.Forbidden import play.api.mvc.ActionFilter import play.api.mvc.Result +import utils.EmailAddress import scala.concurrent.ExecutionContext import scala.concurrent.Future @@ -11,15 +13,27 @@ import scala.concurrent.Future object ImpersonationAction { type UserRequest[A] = IdentifiedRequest[User, A] - def ForbidImpersonation(implicit ec: ExecutionContext): ActionFilter[UserRequest] = new ActionFilter[UserRequest] { - override protected def executionContext: ExecutionContext = ec - - override protected def filter[A](request: UserRequest[A]): Future[Option[Result]] = - Future.successful( - request.identity.impersonator match { - case Some(_) => Some(Forbidden) - case None => None - } - ) - } + def forbidImpersonationFilter(implicit ec: ExecutionContext): ActionFilter[UserRequest] = + new ActionFilter[UserRequest] { + override protected def executionContext: ExecutionContext = ec + override protected def filter[A](request: UserRequest[A]): Future[Option[Result]] = + handleImpersonator(request.identity.impersonator) + + } + + def forbidImpersonationOnMaybeUserFilter(implicit ec: ExecutionContext): ActionFilter[MaybeUserRequest] = + new ActionFilter[MaybeUserRequest] { + override protected def executionContext: ExecutionContext = ec + override protected def filter[A](request: MaybeUserRequest[A]): Future[Option[Result]] = + handleImpersonator(request.identity.flatMap(_.impersonator)) + } + + private def handleImpersonator(maybeImpersonator: Option[EmailAddress]) = + Future.successful( + maybeImpersonator match { + case Some(_) => Some(Forbidden) + case None => None + } + ) + } diff --git a/app/controllers/AccountController.scala b/app/controllers/AccountController.scala index c73abd25..1ea2a0bc 100644 --- a/app/controllers/AccountController.scala +++ b/app/controllers/AccountController.scala @@ -1,7 +1,7 @@ package controllers import authentication.CookieAuthenticator -import authentication.actions.ImpersonationAction.ForbidImpersonation +import authentication.actions.ImpersonationAction.forbidImpersonationFilter import cats.implicits.catsSyntaxOption import config.EmailConfiguration import models._ @@ -13,8 +13,6 @@ import play.api.mvc.ControllerComponents import repositories.user.UserRepositoryInterface import utils.EmailAddress import error.AppError.MalformedFileKey -import authentication.actions.UserAction.WithAuthProvider -import authentication.actions.UserAction.WithRole import java.util.UUID import scala.concurrent.ExecutionContext @@ -37,7 +35,7 @@ class AccountController( implicit val contactAddress: EmailAddress = emailConfiguration.contactAddress - def activateAccount = IpRateLimitedAction2.async(parse.json) { implicit request => + def activateAccount = Act.public.standardLimit.async(parse.json) { implicit request => for { activationRequest <- request.parseBody[ActivationRequest]() createdUser <- activationRequest.companySiret match { @@ -52,7 +50,7 @@ class AccountController( } def sendAgentInvitation(role: UserRole) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + Act.secured.admins.async(parse.json) { implicit request => role match { case UserRole.DGCCRF => request @@ -69,7 +67,7 @@ class AccountController( } def sendAgentsInvitations(role: UserRole) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.multipartFormData) { implicit request => + Act.secured.admins.async(parse.multipartFormData) { implicit request => for { filePart <- request.body.file("emails").liftTo[Future](MalformedFileKey("emails")) source = Source.fromFile(filePart.ref.path.toFile) @@ -80,7 +78,7 @@ class AccountController( } def sendAdminInvitation(role: UserRole) = - SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async(parse.json) { implicit request => + Act.secured.superAdmins.async(parse.json) { implicit request => role match { case UserRole.SuperAdmin => request @@ -99,7 +97,7 @@ class AccountController( } def fetchPendingAgent(role: Option[UserRole]) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async { _ => + Act.secured.adminsAndReadonly.async { _ => role match { case Some(UserRole.DGCCRF) | Some(UserRole.DGAL) | None => accessesOrchestrator @@ -110,14 +108,14 @@ class AccountController( } def fetchAgentUsers = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async { _ => + Act.secured.adminsAndReadonly.async { _ => for { users <- userRepository.listForRoles(Seq(UserRole.DGCCRF, UserRole.DGAL)) } yield Ok(Json.toJson(users)) } def fetchAdminUsers = - SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async { _ => + Act.secured.superAdmins.async { _ => for { users <- userRepository.listForRoles(Seq(UserRole.SuperAdmin, UserRole.Admin, UserRole.ReadOnlyAdmin)) } yield Ok(Json.toJson(users)) @@ -125,19 +123,19 @@ class AccountController( // This data is not displayed anywhere // The endpoint might be useful to debug without accessing the prod DB - def fetchAllSoftDeletedUsers = SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async { _ => + def fetchAllSoftDeletedUsers = Act.secured.superAdmins.async { _ => for { users <- userRepository.listDeleted() } yield Ok(Json.toJson(users)) } - def fetchTokenInfo(token: String) = IpRateLimitedAction2.async { _ => + def fetchTokenInfo(token: String) = Act.public.standardLimit.async { _ => accessesOrchestrator .fetchDGCCRFUserActivationToken(token) .map(token => Ok(Json.toJson(token))) } - def validateEmail() = IpRateLimitedAction2.async(parse.json) { implicit request => + def validateEmail() = Act.public.standardLimit.async(parse.json) { implicit request => for { token <- request.parseBody[String](JsPath \ "token") user <- accessesOrchestrator.validateAgentEmail(token) @@ -146,24 +144,23 @@ class AccountController( } def forceValidateEmail(email: String) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async { _ => + Act.secured.admins.async { _ => accessesOrchestrator.resetLastEmailValidation(EmailAddress(email)).map(_ => NoContent) } def edit() = - SecuredAction.andThen(WithAuthProvider(AuthProvider.SignalConso)).andThen(ForbidImpersonation).async(parse.json) { - implicit request => - for { - userUpdate <- request.parseBody[UserUpdate]() - updatedUserOpt <- userOrchestrator.edit(request.identity.id, userUpdate) - } yield updatedUserOpt match { - case Some(updatedUser) => Ok(Json.toJson(updatedUser)) - case _ => NotFound - } + Act.secured.restrictByProvider.signalConso.andThen(forbidImpersonationFilter).async(parse.json) { implicit request => + for { + userUpdate <- request.parseBody[UserUpdate]() + updatedUserOpt <- userOrchestrator.edit(request.identity.id, userUpdate) + } yield updatedUserOpt match { + case Some(updatedUser) => Ok(Json.toJson(updatedUser)) + case _ => NotFound + } } def sendEmailAddressUpdateValidation() = - SecuredAction.andThen(ForbidImpersonation).async(parse.json) { implicit request => + Act.secured.all.forbidImpersonation.async(parse.json) { implicit request => for { emailAddress <- request.parseBody[EmailAddress](JsPath \ "email") _ <- accessesOrchestrator.sendEmailAddressUpdateValidation(request.identity, emailAddress) @@ -171,9 +168,8 @@ class AccountController( } def updateEmailAddress(token: String) = - SecuredAction - .andThen(WithAuthProvider(AuthProvider.SignalConso)) - .andThen(ForbidImpersonation) + Act.secured.restrictByProvider.signalConso + .andThen(forbidImpersonationFilter) .async { implicit request => for { updatedUser <- accessesOrchestrator.updateEmailAddress(request.identity, token) @@ -182,7 +178,7 @@ class AccountController( } def softDelete(id: UUID) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async { request => + Act.secured.admins.async { request => userOrchestrator.softDelete(targetUserId = id, currentUserId = request.identity.id).map(_ => NoContent) } diff --git a/app/controllers/AdminController.scala b/app/controllers/AdminController.scala index 0b5ea1b5..9e130c72 100644 --- a/app/controllers/AdminController.scala +++ b/app/controllers/AdminController.scala @@ -1,7 +1,6 @@ package controllers import authentication.Authenticator -import authentication.actions.UserAction.WithRole import cats.data.NonEmptyList import cats.implicits.catsSyntaxOption import cats.implicits.toTraverseOps @@ -131,11 +130,11 @@ class AdminController( private val allEmailExamples: Seq[(String, (EmailAddress, MessagesApi) => BaseEmail)] = allEmailDefinitions.flatMap(readExamplesWithFullKey) - def getEmailCodes = SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async { _ => + def getEmailCodes = Act.secured.superAdmins.async { _ => val keys = allEmailExamples.map(_._1) Future.successful(Ok(Json.toJson(keys))) } - def sendTestEmail(templateRef: String, to: String) = SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async { _ => + def sendTestEmail(templateRef: String, to: String) = Act.secured.superAdmins.async { _ => val maybeEmail = allEmailExamples .find(_._1 == templateRef) .map(_._2(EmailAddress(to), controllerComponents.messagesApi)) @@ -154,10 +153,10 @@ class AdminController( s"${emailDefinition.category.toString.toLowerCase}.$key" -> fn } - def getPdfCodes = SecuredAction.andThen(WithRole(UserRole.SuperAdmin)) { _ => + def getPdfCodes = Act.secured.superAdmins { _ => Ok(Json.toJson(availablePdfs.map(_._1))) } - def sendTestPdf(templateRef: String) = SecuredAction.andThen(WithRole(UserRole.SuperAdmin)) { _ => + def sendTestPdf(templateRef: String) = Act.secured.superAdmins { _ => availablePdfs.toMap .get(templateRef) .map { html => @@ -265,7 +264,7 @@ class AdminController( } def resend(start: OffsetDateTime, end: OffsetDateTime, emailType: ResendEmailType) = - SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async { implicit request => + Act.secured.superAdmins.async { implicit request => for { reports <- reportRepository.getReportsWithFiles( Some(request.identity), @@ -284,16 +283,16 @@ class AdminController( } yield NoContent } - def blackListedIPs() = SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async { _ => + def blackListedIPs() = Act.secured.superAdmins.async { _ => ipBlackListRepository.list().map(blackListedIps => Ok(Json.toJson(blackListedIps))) } - def deleteBlacklistedIp(ip: String) = SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async { _ => + def deleteBlacklistedIp(ip: String) = Act.secured.superAdmins.async { _ => ipBlackListRepository.delete(ip).map(_ => NoContent) } def createBlacklistedIp() = - SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async(parse.json) { implicit request => + Act.secured.superAdmins.async(parse.json) { implicit request => for { blacklistedIpRequest <- request.parseBody[BlackListedIp]() blackListedIp <- ipBlackListRepository.create(blacklistedIpRequest) @@ -301,7 +300,7 @@ class AdminController( } def classifyAndSummarize(reportId: UUID) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { _ => + Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { _ => for { maybeReport <- reportRepository.get(reportId) report <- maybeReport.liftTo[Future](AppError.ReportNotFound(reportId)) @@ -323,13 +322,13 @@ class AdminController( } def getAlbertClassification(reportId: UUID) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { _ => + Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { _ => albertClassificationRepository .getByReportId(reportId) .map(maybeClassification => Ok(Json.toJson(maybeClassification))) } - def regenSampleData() = SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async { _ => + def regenSampleData() = Act.secured.superAdmins.async { _ => if (taskConfiguration.sampleData.active) { for { _ <- diff --git a/app/controllers/AsyncFileController.scala b/app/controllers/AsyncFileController.scala index 20a7500b..8e3c6a60 100644 --- a/app/controllers/AsyncFileController.scala +++ b/app/controllers/AsyncFileController.scala @@ -17,7 +17,7 @@ class AsyncFileController( )(implicit val ec: ExecutionContext) extends BaseController(authenticator, controllerComponents) { - def listAsyncFiles(kind: Option[String]) = SecuredAction.async { implicit request => + def listAsyncFiles(kind: Option[String]) = Act.secured.all.allowImpersonation.async { implicit request => for { asyncFiles <- asyncFileRepository.list(request.identity, kind.map(AsyncFileKind.withName)) } yield Ok(Json.toJson(asyncFiles.map { asyncFile: AsyncFile => diff --git a/app/controllers/AuthController.scala b/app/controllers/AuthController.scala index 633e5b40..fbf9c578 100644 --- a/app/controllers/AuthController.scala +++ b/app/controllers/AuthController.scala @@ -1,9 +1,7 @@ package controllers import authentication.CookieAuthenticator -import models.AuthProvider import models.PaginatedResult -import models.UserRole import orchestrators.AuthOrchestrator import play.api._ import play.api.libs.json.JsPath @@ -16,17 +14,13 @@ import models.auth.UserPassword import play.api.mvc.Action import play.api.mvc.AnyContent import play.api.mvc.ControllerComponents -import authentication.actions.UserAction.WithAuthProvider -import authentication.actions.UserAction.WithRole import cats.implicits.catsSyntaxOption import cats.implicits.toFunctorOps import orchestrators.proconnect.ProConnectOrchestrator import utils.EmailAddress import cats.syntax.either._ import _root_.controllers.error.AppError._ -import authentication.actions.ImpersonationAction.ForbidImpersonation -import models.AuthProvider.ProConnect -import models.AuthProvider.SignalConso +import authentication.actions.ImpersonationAction.forbidImpersonationFilter import java.util.UUID import scala.concurrent.ExecutionContext @@ -46,7 +40,7 @@ class AuthController( implicit val timeout: org.apache.pekko.util.Timeout = 5.seconds - def authenticate: Action[JsValue] = IpRateLimitedAction2.async(parse.json) { implicit request => + def authenticate: Action[JsValue] = Act.public.standardLimit.async(parse.json) { implicit request => for { userLogin <- request.parseBody[UserCredentials]() userSession <- authOrchestrator.signalConsoLogin(userLogin) @@ -54,26 +48,26 @@ class AuthController( } def startProConnectAuthentication(state: String, nonce: String) = - IpRateLimitedAction2.async(parse.empty) { _ => + Act.public.standardLimit.async(parse.empty) { _ => proConnectOrchestrator.saveState(state, nonce).as(NoContent) } def proConnectAuthenticate(code: String, state: String) = - IpRateLimitedAction2.async(parse.empty) { _ => + Act.public.standardLimit.async(parse.empty) { _ => for { (token_id, user) <- proConnectOrchestrator.login(code, state) userSession <- authOrchestrator.proConnectLogin(user, token_id, state) } yield authenticator.embed(userSession.cookie, Ok(Json.toJson(userSession.user))) } - def logAs() = SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + def logAs() = Act.secured.admins.async(parse.json) { implicit request => for { userEmail <- request.parseBody[EmailAddress](JsPath \ "email") userSession <- authOrchestrator.logAs(userEmail, request) } yield authenticator.embed(userSession.cookie, Ok(Json.toJson(userSession.user))) } - def logout(): Action[AnyContent] = SecuredAction.andThen(WithAuthProvider(SignalConso)).async { implicit request => + def logout(): Action[AnyContent] = Act.secured.restrictByProvider.signalConso.async { implicit request => request.identity.impersonator match { case Some(impersonator) => authOrchestrator @@ -85,7 +79,7 @@ class AuthController( } def logoutProConnect(): Action[AnyContent] = - SecuredAction.andThen(WithAuthProvider(ProConnect)).async { implicit request => + Act.secured.restrictByProvider.proConnect.async { implicit request => request.identity.impersonator match { case Some(impersonator) => authOrchestrator @@ -102,18 +96,18 @@ class AuthController( } } - def getUser(): Action[AnyContent] = SecuredAction.async { implicit request => + def getUser(): Action[AnyContent] = Act.secured.all.allowImpersonation.async { implicit request => Future.successful(Ok(Json.toJson(request.identity))) } def listAuthAttempts(login: Option[String], offset: Option[Long], limit: Option[Int]) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async(parse.empty) { _ => + Act.secured.adminsAndReadonly.async(parse.empty) { _ => authOrchestrator .listAuthenticationAttempts(login, offset, limit) .map(authAttempts => Ok(Json.toJson(authAttempts)(PaginatedResult.paginatedResultWrites))) } - def forgotPassword: Action[JsValue] = IpRateLimitedAction2.async(parse.json) { implicit request => + def forgotPassword: Action[JsValue] = Act.public.standardLimit.async(parse.json) { implicit request => for { userLogin <- request.parseBody[UserLogin]() _ <- authOrchestrator.forgotPassword(userLogin) @@ -121,7 +115,7 @@ class AuthController( } def resetPassword(token: UUID): Action[JsValue] = - IpRateLimitedAction2.async(parse.json) { implicit request => + Act.public.standardLimit.async(parse.json) { implicit request => for { userPassword <- request.parseBody[UserPassword]() _ <- authOrchestrator.resetPassword(token, userPassword) @@ -129,12 +123,11 @@ class AuthController( } def changePassword = - SecuredAction.andThen(WithAuthProvider(AuthProvider.SignalConso)).andThen(ForbidImpersonation).async(parse.json) { - implicit request => - for { - updatePassword <- request.parseBody[PasswordChange]() - _ <- authOrchestrator.changePassword(request.identity, updatePassword) - } yield NoContent + Act.secured.restrictByProvider.signalConso.andThen(forbidImpersonationFilter).async(parse.json) { implicit request => + for { + updatePassword <- request.parseBody[PasswordChange]() + _ <- authOrchestrator.changePassword(request.identity, updatePassword) + } yield NoContent } diff --git a/app/controllers/BarcodeController.scala b/app/controllers/BarcodeController.scala index dc22e0b4..66811513 100644 --- a/app/controllers/BarcodeController.scala +++ b/app/controllers/BarcodeController.scala @@ -17,13 +17,13 @@ class BarcodeController( )(implicit val ec: ExecutionContext) extends BaseController(authenticator, controllerComponents) { - def getProductByGTIN(gtin: String) = IpRateLimitedAction3.async { _ => + def getProductByGTIN(gtin: String) = Act.public.tightLimit.async { _ => barcodeOrchestrator .getByGTIN(gtin) .map(_.map(product => Ok(Json.toJson(product)(BarcodeProduct.writesToWebsite))).getOrElse(NotFound)) } - def getById(id: UUID) = SecuredAction.async { _ => + def getById(id: UUID) = Act.secured.all.allowImpersonation.async { _ => barcodeOrchestrator .get(id) .map(_.map(product => Ok(Json.toJson(product)(BarcodeProduct.writesToDashboard))).getOrElse(NotFound)) diff --git a/app/controllers/BaseController.scala b/app/controllers/BaseController.scala index 6d5ad97c..bad04208 100644 --- a/app/controllers/BaseController.scala +++ b/app/controllers/BaseController.scala @@ -9,14 +9,28 @@ import models._ import models.company.AccessLevel import models.company.Company import orchestrators.CompaniesVisibilityOrchestrator +import play.api.mvc.ActionBuilder import play.api.mvc._ import repositories.company.CompanyRepositoryInterface import utils.SIRET import ConsumerAction.ConsumerRequest import MaybeUserAction.MaybeUserRequest import UserAction.UserRequest +import UserAction.WithAuthProvider +import UserAction.WithRole +import authentication.actions.ImpersonationAction.forbidImpersonationFilter +import authentication.actions.ImpersonationAction.forbidImpersonationOnMaybeUserFilter import com.digitaltangible.playguard.IpRateLimitFilter import com.digitaltangible.ratelimit.RateLimiter +import models.AuthProvider.ProConnect +import models.AuthProvider.SignalConso +import models.UserRole.Admins +import models.UserRole.AdminsAndAgents +import models.UserRole.AdminsAndReadOnly +import models.UserRole.AdminsAndReadOnlyAndAgents +import models.UserRole.AdminsAndReadOnlyAndCCRF +import models.UserRole.Professionnel +import models.UserRole.SuperAdmin import java.util.UUID import scala.concurrent.ExecutionContext @@ -57,10 +71,16 @@ abstract class ApiKeyBaseController( implicit val ec: ExecutionContext - def SecuredAction = new ConsumerAction( + private val securedByApiKeyAction = new ConsumerAction( new BodyParsers.Default(controllerComponents.parsers), authenticator ) andThen new ErrorHandlerActionFunction[ConsumerRequest]() + + trait ActDslApiKey { + val securedbyApiKey = securedByApiKeyAction + + } + val Act = new ActDslApiKey {} } abstract class BaseController( @@ -86,23 +106,65 @@ abstract class BaseController( override val Action: ActionBuilder[Request, AnyContent] = super.Action andThen new ErrorHandlerActionFunction[Request]() - def SecuredAction: ActionBuilder[UserRequest, AnyContent] = new UserAction( + protected val securedAction: ActionBuilder[UserRequest, AnyContent] = new UserAction( new BodyParsers.Default(controllerComponents.parsers), authenticator ) andThen new ErrorHandlerActionFunction[UserRequest]() - def UserAwareAction: ActionBuilder[MaybeUserRequest, AnyContent] = new MaybeUserAction( + private val userAwareAction: ActionBuilder[MaybeUserRequest, AnyContent] = new MaybeUserAction( new BodyParsers.Default(controllerComponents.parsers), authenticator ) andThen new ErrorHandlerActionFunction[MaybeUserRequest]() - // 72 = 12 pièces jointes * 3 pour la marge d'erreur * 2 car POST + GET systématique à chaque PJ - val IpRateLimitedAction1: ActionBuilder[Request, AnyContent] = - if (enableRateLimit) Action andThen ipRateLimitFilter[Request](72, 1f / 5) else Action - val IpRateLimitedAction2: ActionBuilder[Request, AnyContent] = - if (enableRateLimit) Action andThen ipRateLimitFilter[Request](16, 1f / 5) else Action - val IpRateLimitedAction3: ActionBuilder[Request, AnyContent] = - if (enableRateLimit) Action andThen ipRateLimitFilter[Request](3, 1f / 5) else Action + case class AskImpersonationDsl(private val actionBuilder: ActionBuilder[UserRequest, AnyContent]) { + val allowImpersonation = actionBuilder + val forbidImpersonation = actionBuilder.andThen(forbidImpersonationFilter) + } + + case class AskImpersonationDslOnMaybeUser( + private val actionBuilder: ActionBuilder[MaybeUserRequest, AnyContent] + ) { + val allowImpersonation = actionBuilder + val forbidImpersonation = actionBuilder.andThen(forbidImpersonationOnMaybeUserFilter) + } + + trait ActDsl { + object public { + val generousLimit: ActionBuilder[Request, AnyContent] = + // 72 = 12 pièces jointes * 3 pour la marge d'erreur * 2 car POST + GET systématique à chaque PJ + if (enableRateLimit) Action andThen ipRateLimitFilter[Request](72, 1f / 5) else Action + val standardLimit: ActionBuilder[Request, AnyContent] = + if (enableRateLimit) Action andThen ipRateLimitFilter[Request](16, 1f / 5) else Action + val tightLimit: ActionBuilder[Request, AnyContent] = + // for potentially expensive endpoints (call to external apis) + if (enableRateLimit) Action andThen ipRateLimitFilter[Request](3, 1f / 5) else Action + + } + object secured { + val all = AskImpersonationDsl(securedAction) + val adminsAndReadonlyAndAgents = AskImpersonationDsl( + securedAction.andThen(WithRole(AdminsAndReadOnlyAndAgents)) + ) + val adminsAndReadonlyAndDgccrf = AskImpersonationDsl( + securedAction.andThen(WithRole(AdminsAndReadOnlyAndCCRF)) + ) + val pros = AskImpersonationDsl(securedAction.andThen(WithRole(Professionnel))) + val adminsAndAgents = AskImpersonationDsl(securedAction.andThen(WithRole(AdminsAndAgents))) + // these roles cannot be impersonated + val admins = securedAction.andThen(WithRole(Admins)) + val superAdmins = securedAction.andThen(WithRole(SuperAdmin)) + val adminsAndReadonly = securedAction.andThen(WithRole(AdminsAndReadOnly)) + + object restrictByProvider { + val signalConso = securedAction.andThen(WithAuthProvider(SignalConso)) + val proConnect = securedAction.andThen(WithAuthProvider(ProConnect)) + } + + } + val userAware = AskImpersonationDslOnMaybeUser(userAwareAction) + } + val Act = new ActDsl {} + } abstract class BaseCompanyController( @@ -117,14 +179,16 @@ abstract class BaseCompanyController( def identity = request.identity } - def withCompanyAccess(siret: String, adminLevelOnly: Boolean = false) = - SecuredAction andThen new ActionRefiner[UserRequest, CompanyRequest] { + private def companyAccessActionRefiner(siret: String, adminLevelOnly: Boolean) = + new ActionRefiner[UserRequest, CompanyRequest] { val authorizedLevels = if (adminLevelOnly) Seq(AccessLevel.ADMIN) else Seq(AccessLevel.ADMIN, AccessLevel.MEMBER) + def executionContext = ec + def refine[A](request: UserRequest[A]) = for { company <- companyRepository.findBySiret(SIRET.fromUnsafe(siret)) @@ -147,4 +211,11 @@ abstract class BaseCompanyController( .map { case (c, l) => Right(new CompanyRequest[A](c, l, request)) } .getOrElse(Left(Forbidden)) } + + trait ActDslWithCompanyAccess extends ActDsl { + def securedWithCompanyAccess(siret: String, adminLevelOnly: Boolean = false) = + securedAction.andThen(companyAccessActionRefiner(siret, adminLevelOnly)) + } + override val Act = new ActDslWithCompanyAccess {} + } diff --git a/app/controllers/BlacklistedEmailsController.scala b/app/controllers/BlacklistedEmailsController.scala index c2a15192..1c32658e 100644 --- a/app/controllers/BlacklistedEmailsController.scala +++ b/app/controllers/BlacklistedEmailsController.scala @@ -4,13 +4,11 @@ import authentication.Authenticator import models.BlacklistedEmail import models.BlacklistedEmailInput import models.User -import models.UserRole import play.api.Logger import play.api.libs.json.JsError import play.api.libs.json.Json import play.api.mvc.ControllerComponents import repositories.blacklistedemails.BlacklistedEmailsRepositoryInterface -import authentication.actions.UserAction.WithRole import java.util.UUID import scala.concurrent.ExecutionContext @@ -25,13 +23,13 @@ class BlacklistedEmailsController( ) extends BaseController(authenticator, controllerComponents) { val logger: Logger = Logger(this.getClass) - def list = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async { + def list = Act.secured.adminsAndReadonly.async { for { res <- blacklistedEmailsRepository.list() } yield Ok(Json.toJson(res)) } - def add() = SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + def add() = Act.secured.admins.async(parse.json) { implicit request => request.body .validate[BlacklistedEmailInput] .fold( @@ -51,7 +49,7 @@ class BlacklistedEmailsController( } - def delete(uuid: UUID) = SecuredAction.andThen(WithRole(UserRole.Admins)).async { + def delete(uuid: UUID) = Act.secured.admins.async { for { item <- blacklistedEmailsRepository.get(uuid) _ <- blacklistedEmailsRepository.delete(uuid) diff --git a/app/controllers/BookmarkController.scala b/app/controllers/BookmarkController.scala index 354cd284..83a58633 100644 --- a/app/controllers/BookmarkController.scala +++ b/app/controllers/BookmarkController.scala @@ -1,8 +1,6 @@ package controllers import authentication.Authenticator -import authentication.actions.ImpersonationAction.ForbidImpersonation -import authentication.actions.UserAction.WithRole import models._ import orchestrators._ import play.api.Logger @@ -22,20 +20,20 @@ class BookmarkController( val logger: Logger = Logger(this.getClass) def addBookmark(uuid: UUID) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).andThen(ForbidImpersonation).async { request => + Act.secured.adminsAndReadonlyAndAgents.forbidImpersonation.async { request => for { _ <- bookmarkOrchestrator.addBookmark(uuid, request.identity) } yield Ok } def removeBookmark(uuid: UUID) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).andThen(ForbidImpersonation).async { request => + Act.secured.adminsAndReadonlyAndAgents.forbidImpersonation.async { request => for { _ <- bookmarkOrchestrator.removeBookmark(uuid, request.identity) } yield Ok } - def countBookmarks() = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { request => + def countBookmarks() = Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { request => for { count <- bookmarkOrchestrator.countBookmarks(request.identity) } yield Ok(Json.toJson(count)) diff --git a/app/controllers/CompanyAccessController.scala b/app/controllers/CompanyAccessController.scala index e673e2d9..69714ded 100644 --- a/app/controllers/CompanyAccessController.scala +++ b/app/controllers/CompanyAccessController.scala @@ -1,13 +1,10 @@ package controllers import authentication.Authenticator -import authentication.actions.ImpersonationAction.ForbidImpersonation -import authentication.actions.UserAction.WithRole import cats.implicits.catsSyntaxOption import cats.implicits.toTraverseOps import controllers.error.AppError.UserNotFoundById import models.User -import models.UserRole import models.access.ActivationLinkRequest import models.company.AccessLevel import models.event.Event @@ -48,19 +45,19 @@ class CompanyAccessController( val logger: Logger = Logger(this.getClass) - def listAccesses(siret: String) = withCompanyAccess(siret).async { implicit request => + def listAccesses(siret: String) = Act.securedWithCompanyAccess(siret).async { implicit request => companyAccessOrchestrator .listAccesses(request.company, request.identity) .map(userWithAccessLevel => Ok(Json.toJson(userWithAccessLevel))) } - def listAccessesMostActive(siret: String) = withCompanyAccess(siret).async { implicit request => + def listAccessesMostActive(siret: String) = Act.securedWithCompanyAccess(siret).async { implicit request => companyAccessOrchestrator .listAccessesMostActive(request.company, request.identity) .map(mostActive => Ok(Json.toJson(mostActive))) } - def countAccesses(siret: String) = withCompanyAccess(siret).async { implicit request => + def countAccesses(siret: String) = Act.securedWithCompanyAccess(siret).async { implicit request => companyAccessOrchestrator .listAccesses(request.company, request.identity) .map(_.length) @@ -68,13 +65,13 @@ class CompanyAccessController( } // Is this used ?? - def myCompanies = SecuredAction.async { implicit request => + def myCompanies = Act.secured.all.allowImpersonation.async { implicit request => companyAccessRepository .fetchCompaniesWithLevel(request.identity) .map(companies => Ok(Json.toJson(companies))) } - def visibleUsersToPro = SecuredAction.andThen(WithRole(UserRole.Professionnel)).async { implicit request => + def visibleUsersToPro = Act.secured.pros.allowImpersonation.async { implicit request => for { companiesWithAccesses <- companyVisibilityOrch.fetchVisibleCompanies(request.identity) onlyAdminCompanies = companiesWithAccesses.filter(_.level == AccessLevel.ADMIN) @@ -89,7 +86,7 @@ class CompanyAccessController( } def inviteProToMyCompanies(email: String) = - SecuredAction.andThen(WithRole(UserRole.Professionnel)).andThen(ForbidImpersonation).async { implicit request => + Act.secured.pros.forbidImpersonation.async { implicit request => for { accesses <- companyAccessRepository.fetchCompaniesWithLevel(request.identity) maybeUser <- userRepository.findByEmail(email) @@ -104,7 +101,7 @@ class CompanyAccessController( } def revokeProFromMyCompanies(userId: UUID) = - SecuredAction.andThen(WithRole(UserRole.Professionnel)).andThen(ForbidImpersonation).async { implicit request => + Act.secured.pros.forbidImpersonation.async { implicit request => for { maybeUser <- userRepository.get(userId) user <- maybeUser.liftTo[Future](UserNotFoundById(userId)) @@ -132,8 +129,8 @@ class CompanyAccessController( ) } yield () - def updateAccess(siret: String, userId: UUID) = withCompanyAccess(siret, adminLevelOnly = true).async { - implicit request => + def updateAccess(siret: String, userId: UUID) = + Act.securedWithCompanyAccess(siret, adminLevelOnly = true).async { implicit request => request.body.asJson .map(json => (json \ "level").as[AccessLevel]) .map(level => @@ -145,10 +142,10 @@ class CompanyAccessController( } yield if (user.isDefined) Ok else NotFound ) .getOrElse(Future.successful(NotFound)) - } + } - def removeAccess(siret: String, userId: UUID) = withCompanyAccess(siret, adminLevelOnly = true).async { - implicit request => + def removeAccess(siret: String, userId: UUID) = + Act.securedWithCompanyAccess(siret, adminLevelOnly = true).async { implicit request => for { maybeUser <- userRepository.get(userId) user <- maybeUser.liftTo[Future](UserNotFoundById(userId)) @@ -168,12 +165,12 @@ class CompanyAccessController( // this operation may leave some reports assigned to this user, to which he doesn't have access anymore // in theory here we should find these reports and de-assign them } yield NoContent - } + } case class AccessInvitation(email: EmailAddress, level: AccessLevel) - def sendInvitation(siret: String) = withCompanyAccess(siret, adminLevelOnly = true).async(parse.json) { - implicit request => + def sendInvitation(siret: String) = + Act.securedWithCompanyAccess(siret, adminLevelOnly = true).async(parse.json) { implicit request => implicit val reads = Json.reads[AccessInvitation] request.body .validate[AccessInvitation] @@ -184,29 +181,30 @@ class CompanyAccessController( .addUserOrInvite(request.company, invitation.email, invitation.level, Some(request.identity)) .map(_ => Ok) ) - } + } - def listPendingTokens(siret: String) = withCompanyAccess(siret, adminLevelOnly = true).async { implicit request => - accessesOrchestrator - .listProPendingToken(request.company, request.identity) - .map(tokens => Ok(Json.toJson(tokens))) - } + def listPendingTokens(siret: String) = + Act.securedWithCompanyAccess(siret, adminLevelOnly = true).async { implicit request => + accessesOrchestrator + .listProPendingToken(request.company, request.identity) + .map(tokens => Ok(Json.toJson(tokens))) + } - def removePendingToken(siret: String, tokenId: UUID) = withCompanyAccess(siret, adminLevelOnly = true).async { - implicit request => + def removePendingToken(siret: String, tokenId: UUID) = + Act.securedWithCompanyAccess(siret, adminLevelOnly = true).async { implicit request => for { token <- accessTokenRepository.getToken(request.company, tokenId) _ <- token.map(accessTokenRepository.invalidateToken).getOrElse(Future.unit) } yield if (token.isDefined) Ok else NotFound - } + } - def fetchTokenInfo(siret: String, token: String) = IpRateLimitedAction2.async { _ => + def fetchTokenInfo(siret: String, token: String) = Act.public.standardLimit.async { _ => accessesOrchestrator .fetchCompanyUserActivationToken(SIRET.fromUnsafe(siret), token) .map(token => Ok(Json.toJson(token))) } - def sendActivationLink(siret: String) = IpRateLimitedAction2.async(parse.json) { implicit request => + def sendActivationLink(siret: String) = Act.public.standardLimit.async(parse.json) { implicit request => for { activationLinkRequest <- request.parseBody[ActivationLinkRequest]() _ <- companyAccessOrchestrator.sendActivationLink(SIRET.fromUnsafe(siret), activationLinkRequest) @@ -216,7 +214,7 @@ class CompanyAccessController( case class AcceptTokenRequest(token: String) - def acceptToken(siret: String) = SecuredAction.async(parse.json) { implicit request => + def acceptToken(siret: String) = Act.secured.all.allowImpersonation.async(parse.json) { implicit request => implicit val reads = Json.reads[AcceptTokenRequest] request.body .validate[AcceptTokenRequest] @@ -246,7 +244,7 @@ class CompanyAccessController( ) } - def proFirstActivationCount(ticks: Option[Int]) = SecuredAction.async(parse.empty) { _ => + def proFirstActivationCount(ticks: Option[Int]) = Act.secured.all.allowImpersonation.async(parse.empty) { _ => accessesOrchestrator.proFirstActivationCount(ticks).map(x => Ok(Json.toJson(x))) } diff --git a/app/controllers/CompanyController.scala b/app/controllers/CompanyController.scala index 24d6d202..6f6e20f4 100644 --- a/app/controllers/CompanyController.scala +++ b/app/controllers/CompanyController.scala @@ -14,7 +14,6 @@ import play.api.Logger import play.api.libs.json._ import play.api.mvc.ControllerComponents import repositories.company.CompanyRepositoryInterface -import authentication.actions.UserAction.WithRole import java.time.OffsetDateTime import java.util.UUID @@ -33,16 +32,16 @@ class CompanyController( val logger: Logger = Logger(this.getClass) - def fetchHosts(companyId: UUID) = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndCCRF)).async { + def fetchHosts(companyId: UUID) = Act.secured.adminsAndReadonlyAndDgccrf.allowImpersonation.async { companyOrchestrator.fetchHosts(companyId).map(x => Ok(Json.toJson(x))) } - def fetchPhones(companyId: UUID) = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndCCRF)).async { + def fetchPhones(companyId: UUID) = Act.secured.adminsAndReadonlyAndDgccrf.allowImpersonation.async { companyOrchestrator.fetchPhones(companyId).map(x => Ok(Json.toJson(x))) } def create() = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + Act.secured.admins.async(parse.json) { implicit request => request.body .validate[CompanyCreation] .fold( @@ -54,7 +53,7 @@ class CompanyController( ) } - def searchRegistered() = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndCCRF)).async { request => + def searchRegistered() = Act.secured.adminsAndReadonlyAndDgccrf.allowImpersonation.async { request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) CompanyRegisteredSearch .fromQueryString(request.queryString) @@ -71,7 +70,7 @@ class CompanyController( ) } - def searchById(companyId: UUID) = SecuredAction.async { request => + def searchById(companyId: UUID) = Act.secured.all.allowImpersonation.async { request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) companyOrchestrator .searchRegisteredById(companyId, request.identity) @@ -79,44 +78,44 @@ class CompanyController( } - def searchCompanyByWebsite(url: String) = IpRateLimitedAction2.async { _ => + def searchCompanyByWebsite(url: String) = Act.public.standardLimit.async { _ => companyOrchestrator .searchCompanyByWebsite(url) .map(results => Ok(Json.toJson(results))) } - def searchCompanyOrSimilarWebsite(url: String) = IpRateLimitedAction2.async { _ => + def searchCompanyOrSimilarWebsite(url: String) = Act.public.standardLimit.async { _ => companyOrchestrator .searchSimilarCompanyByWebsite(url) .map(results => Ok(Json.toJson(results))) } - def getResponseRate(companyId: UUID) = SecuredAction.async { request => + def getResponseRate(companyId: UUID) = Act.secured.all.allowImpersonation.async { request => companyOrchestrator .getCompanyResponseRate(companyId, request.identity) .map(results => Ok(Json.toJson(results))) } - def companiesToActivate() = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async { _ => + def companiesToActivate() = Act.secured.adminsAndReadonly.async { _ => companyOrchestrator .companiesToActivate() .map(result => Ok(Json.toJson(result))) } - def inactiveCompanies() = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async { _ => + def inactiveCompanies() = Act.secured.adminsAndReadonly.async { _ => companyOrchestrator.getInactiveCompanies .map(_.sortBy(_.ignoredReportCount)(Ordering.Int.reverse)) .map(result => Ok(Json.toJson(result))) } - def visibleCompanies() = SecuredAction.andThen(WithRole(UserRole.Professionnel)).async { implicit request => + def visibleCompanies() = Act.secured.pros.allowImpersonation.async { implicit request => companyVisibilityOrch .fetchVisibleCompanies(request.identity) .map(x => Ok(Json.toJson(x))) } def getActivationDocument() = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async(parse.json) { implicit request => + Act.secured.adminsAndReadonly.async(parse.json) { implicit request => import CompanyObjects.CompanyList request.body .validate[CompanyList](Json.reads[CompanyList]) @@ -139,7 +138,7 @@ class CompanyController( } def getFollowUpDocument() = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async(parse.json) { implicit request => + Act.secured.adminsAndReadonly.async(parse.json) { implicit request => import CompanyObjects.CompanyList request.body .validate[CompanyList](Json.reads[CompanyList]) @@ -162,7 +161,7 @@ class CompanyController( } def confirmContactByPostOnCompanyList() = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + Act.secured.admins.async(parse.json) { implicit request => import CompanyObjects.CompanyList request.body .validate[CompanyList](Json.reads[CompanyList]) @@ -175,7 +174,7 @@ class CompanyController( ) } - def confirmFollowUp() = SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + def confirmFollowUp() = Act.secured.admins.async(parse.json) { implicit request => import CompanyObjects.CompanyList request.body .validate[CompanyList](Json.reads[CompanyList]) @@ -189,7 +188,7 @@ class CompanyController( } def updateCompanyAddress(id: UUID) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + Act.secured.admins.async(parse.json) { implicit request => request.body .validate[CompanyAddressUpdate] .fold( @@ -204,7 +203,7 @@ class CompanyController( ) } - def getProblemsSeenByAlbert(id: UUID) = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { + def getProblemsSeenByAlbert(id: UUID) = Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { for { maybeResult <- albertOrchestrator.genProblemsForCompany(id) } yield Ok(Json.toJson(maybeResult)) diff --git a/app/controllers/ConstantController.scala b/app/controllers/ConstantController.scala index 41500f1f..bce75841 100644 --- a/app/controllers/ConstantController.scala +++ b/app/controllers/ConstantController.scala @@ -16,15 +16,15 @@ class ConstantController(authenticator: Authenticator[User], controllerComponent ) extends BaseController(authenticator, controllerComponents) { val logger: Logger = Logger(this.getClass) - def getCountries = IpRateLimitedAction2 { + def getCountries = Act.public.standardLimit { Ok(Json.toJson(Country.countries)) } - def getCategories = IpRateLimitedAction2 { + def getCategories = Act.public.standardLimit { Ok(Json.toJson(ReportCategory.values.filter(_.status != ReportCategoryStatus.Legacy))) } - def getCategoriesByStatus() = IpRateLimitedAction2 { + def getCategoriesByStatus() = Act.public.standardLimit { val legacy = ReportCategory.values.filter(_.status == ReportCategoryStatus.Legacy) val closed = ReportCategory.values.filter(_.status == ReportCategoryStatus.Closed) val inactive = ReportCategory.values.filter(_.status == ReportCategoryStatus.Inactive) diff --git a/app/controllers/DataEconomieController.scala b/app/controllers/DataEconomieController.scala index 179e579e..223cc983 100644 --- a/app/controllers/DataEconomieController.scala +++ b/app/controllers/DataEconomieController.scala @@ -24,7 +24,7 @@ class DataEconomieController( val logger: Logger = Logger(this.getClass) - def reportDataEcomonie() = SecuredAction.async(parse.empty) { _ => + def reportDataEcomonie() = Act.securedbyApiKey.async(parse.empty) { _ => val source: Source[ByteString, Any] = service .getReportDataEconomie() diff --git a/app/controllers/EmailValidationController.scala b/app/controllers/EmailValidationController.scala index 6051f6f8..b872716c 100644 --- a/app/controllers/EmailValidationController.scala +++ b/app/controllers/EmailValidationController.scala @@ -5,7 +5,6 @@ import models.email.ValidateEmailCode import models.EmailValidationFilter import models.PaginatedSearch import models.User -import models.UserRole import orchestrators.EmailValidationOrchestrator import play.api._ import _root_.controllers.error.AppError.MalformedQueryParams @@ -15,7 +14,6 @@ import play.api.libs.json.Json import play.api.mvc.Action import play.api.mvc.ControllerComponents import models.PaginatedResult.paginatedResultWrites -import authentication.actions.UserAction.WithRole import scala.concurrent.ExecutionContext import scala.concurrent.Future @@ -29,7 +27,7 @@ class EmailValidationController( val logger: Logger = Logger(this.getClass) - def check(): Action[JsValue] = IpRateLimitedAction2.async(parse.json) { implicit request => + def check(): Action[JsValue] = Act.public.standardLimit.async(parse.json) { implicit request => logger.debug("Calling checking email API") for { validateEmail <- request.parseBody[ValidateEmail]() @@ -40,7 +38,7 @@ class EmailValidationController( } yield Ok(Json.toJson(validationResult)) } - def checkAndValidate(): Action[JsValue] = IpRateLimitedAction2.async(parse.json) { implicit request => + def checkAndValidate(): Action[JsValue] = Act.public.standardLimit.async(parse.json) { implicit request => logger.debug("Calling validate email API") for { validateEmailCode <- request.parseBody[ValidateEmailCode]() @@ -49,7 +47,7 @@ class EmailValidationController( } def validate(): Action[JsValue] = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + Act.secured.admins.async(parse.json) { implicit request => logger.debug("Calling validate email API") for { body <- request.parseBody[ValidateEmail]() @@ -57,7 +55,7 @@ class EmailValidationController( } yield Ok(Json.toJson(validationResult)) } - def search() = SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async { implicit request => + def search() = Act.secured.adminsAndReadonly.async { implicit request => EmailValidationFilter .fromQueryString(request.queryString) .flatMap(filters => PaginatedSearch.fromQueryString(request.queryString).map((filters, _))) diff --git a/app/controllers/EngagementController.scala b/app/controllers/EngagementController.scala index 1e4bdca1..e83da8b8 100644 --- a/app/controllers/EngagementController.scala +++ b/app/controllers/EngagementController.scala @@ -1,8 +1,6 @@ package controllers import authentication.Authenticator -import authentication.actions.ImpersonationAction.ForbidImpersonation -import authentication.actions.UserAction.WithRole import models.User import models.UserRole import models.engagement.EngagementId @@ -26,22 +24,22 @@ class EngagementController( )(implicit val ec: ExecutionContext) extends BaseController(authenticator, controllerComponents) { - def list() = SecuredAction.andThen(WithRole(UserRole.Professionnel)).async { implicit request => + def list() = Act.secured.pros.allowImpersonation.async { implicit request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) engagementOrchestrator.listForUser(request.identity).map(engagements => Ok(Json.toJson(engagements))) } def check(id: EngagementId) = - SecuredAction.andThen(WithRole(UserRole.Professionnel)).andThen(ForbidImpersonation).async { implicit request => + Act.secured.pros.forbidImpersonation.async { implicit request => engagementOrchestrator.check(request.identity, id).map(_ => NoContent) } def uncheck(id: EngagementId) = - SecuredAction.andThen(WithRole(UserRole.Professionnel)).andThen(ForbidImpersonation).async { implicit request => + Act.secured.pros.forbidImpersonation.async { implicit request => engagementOrchestrator.uncheck(request.identity, id).map(_ => NoContent) } - def reviewEngagementOnReportResponse(reportUUID: UUID): Action[JsValue] = IpRateLimitedAction2.async(parse.json) { + def reviewEngagementOnReportResponse(reportUUID: UUID): Action[JsValue] = Act.public.standardLimit.async(parse.json) { implicit request => logger.debug(s"Push report engagement review for report id : $reportUUID") for { @@ -50,7 +48,7 @@ class EngagementController( } yield Ok } - def getEngagementReview(reportUUID: UUID): Action[AnyContent] = SecuredAction.async { request => + def getEngagementReview(reportUUID: UUID): Action[AnyContent] = Act.secured.all.allowImpersonation.async { request => logger.debug(s"Get report engagement review for report id : $reportUUID") for { maybeReview <- engagementOrchestrator.getVisibleEngagementReview(reportUUID, request.identity) @@ -62,7 +60,7 @@ class EngagementController( .getOrElse(NotFound) } - def engagementReviewExists(reportUUID: UUID): Action[AnyContent] = IpRateLimitedAction2.async { _ => + def engagementReviewExists(reportUUID: UUID): Action[AnyContent] = Act.public.standardLimit.async { _ => logger.debug(s"Check if engagement review exists for report id : $reportUUID") engagementOrchestrator.doesEngagementReviewExists(reportUUID).map { exists => Ok(Json.toJson(ConsumerReviewExistApi(exists))) diff --git a/app/controllers/EventsController.scala b/app/controllers/EventsController.scala index f880574e..e24d3f77 100644 --- a/app/controllers/EventsController.scala +++ b/app/controllers/EventsController.scala @@ -21,7 +21,7 @@ class EventsController( ) extends BaseController(authenticator, controllerComponents) { def getCompanyEvents(siret: SIRET, eventType: Option[String]): Action[AnyContent] = - SecuredAction.async { implicit request => + Act.secured.all.allowImpersonation.async { implicit request => logger.info(s"Fetching events for company $siret with eventType $eventType") eventsOrchestrator .getCompanyEvents(siret = siret, eventType = eventType, userRole = request.identity.userRole) @@ -29,7 +29,7 @@ class EventsController( } def getReportEvents(reportId: UUID, eventType: Option[String]): Action[AnyContent] = - SecuredAction.async { implicit request => + Act.secured.all.allowImpersonation.async { implicit request => logger.info(s"Fetching events for report $reportId with eventType $eventType") eventsOrchestrator .getReportsEvents(reportId = reportId, eventType = eventType, user = request.identity) diff --git a/app/controllers/HealthController.scala b/app/controllers/HealthController.scala index b3437500..3a96fca8 100644 --- a/app/controllers/HealthController.scala +++ b/app/controllers/HealthController.scala @@ -1,20 +1,21 @@ package controllers +import authentication.Authenticator +import models.User import play.api.libs.json.Json -import play.api.mvc.AbstractController import play.api.mvc.ControllerComponents import scala.concurrent.ExecutionContext -import scala.concurrent.Future class HealthController( + authenticator: Authenticator[User], controllerComponents: ControllerComponents )(implicit val ec: ExecutionContext) - extends AbstractController(controllerComponents) { + extends BaseController(authenticator, controllerComponents) { def health = - Action.async(parse.empty) { _ => - Future.successful(Ok(Json.obj("name" -> "signalconso-api"))) + Act.public.standardLimit { _ => + Ok(Json.obj("name" -> "signalconso-api")) } } diff --git a/app/controllers/ImportController.scala b/app/controllers/ImportController.scala index 07e74ddc..fa5b8667 100644 --- a/app/controllers/ImportController.scala +++ b/app/controllers/ImportController.scala @@ -3,7 +3,6 @@ package controllers import authentication.Authenticator import controllers.error.AppError.EmptyEmails import models.User -import models.UserRole import orchestrators.ImportOrchestratorInterface import play.api.libs.json.Json import play.api.libs.json.OFormat @@ -11,7 +10,6 @@ import play.api.mvc.ControllerComponents import utils.EmailAddress import utils.SIREN import utils.SIRET -import authentication.actions.UserAction.WithRole import models.company.AccessLevel import scala.concurrent.ExecutionContext @@ -52,7 +50,7 @@ class ImportController( case (siren, sirets, emails) => Future.successful((siren, sirets, emails, input.onlyHeadOffice, input.level)) } - def importUsers = SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + def importUsers = Act.secured.admins.async(parse.json) { implicit request => for { importInput <- request.parseBody[ImportInput]() (siren, sirets, emails, onlyHeadOffice, level) <- validateInput(importInput) @@ -61,7 +59,7 @@ class ImportController( } def importMarketplaces() = - SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async(parse.json) { implicit request => + Act.secured.superAdmins.async(parse.json) { implicit request => for { input <- request.parseBody[List[MarketplaceImportInput]]() res <- importOrchestrator.importMarketplaces(input.map(i => i.siret -> i.host), request.identity) diff --git a/app/controllers/MobileAppController.scala b/app/controllers/MobileAppController.scala index eb03aebf..1c86222f 100644 --- a/app/controllers/MobileAppController.scala +++ b/app/controllers/MobileAppController.scala @@ -19,7 +19,7 @@ class MobileAppController( ) extends BaseController(authenticator, controllerComponents) { val logger: Logger = Logger(this.getClass) - def getRequirements = IpRateLimitedAction2 { + def getRequirements = Act.public.standardLimit { val json = JsObject( Seq( "minAppVersion" -> JsObject( diff --git a/app/controllers/RatingController.scala b/app/controllers/RatingController.scala index 47a6c568..2f223516 100644 --- a/app/controllers/RatingController.scala +++ b/app/controllers/RatingController.scala @@ -24,7 +24,7 @@ class RatingController( val logger: Logger = Logger(this.getClass) - def rate = IpRateLimitedAction2.async(parse.json) { implicit request => + def rate = Act.public.standardLimit.async(parse.json) { implicit request => request.body .validate[Rating] .fold( diff --git a/app/controllers/ReportBlockedNotificationController.scala b/app/controllers/ReportBlockedNotificationController.scala index 853226d1..e3b33628 100644 --- a/app/controllers/ReportBlockedNotificationController.scala +++ b/app/controllers/ReportBlockedNotificationController.scala @@ -1,15 +1,12 @@ package controllers import authentication.Authenticator -import authentication.actions.ImpersonationAction.ForbidImpersonation import models.User -import models.UserRole import models.report.ReportBlockedNotificationBody import orchestrators.ReportBlockedNotificationOrchestrator import play.api.libs.json.JsError import play.api.libs.json.Json import play.api.mvc.ControllerComponents -import authentication.actions.UserAction.WithRole import scala.concurrent.ExecutionContext import scala.concurrent.Future @@ -22,12 +19,12 @@ class ReportBlockedNotificationController( val ec: ExecutionContext ) extends BaseController(authenticator, controllerComponents) { - def getAll() = SecuredAction.andThen(WithRole(UserRole.Professionnel)).async { implicit request => + def getAll() = Act.secured.pros.allowImpersonation.async { implicit request => orchestrator.findByUserId(request.identity.id).map(entities => Ok(Json.toJson(entities))) } def create() = - SecuredAction.andThen(WithRole(UserRole.Professionnel)).andThen(ForbidImpersonation).async(parse.json) { + Act.secured.pros.forbidImpersonation.async(parse.json) { implicit request => request.body .validate[ReportBlockedNotificationBody] @@ -41,7 +38,7 @@ class ReportBlockedNotificationController( } def delete() = - SecuredAction.andThen(WithRole(UserRole.Professionnel)).andThen(ForbidImpersonation).async(parse.json) { + Act.secured.pros.forbidImpersonation.async(parse.json) { implicit request => request.body .validate[ReportBlockedNotificationBody] diff --git a/app/controllers/ReportConsumerReviewController.scala b/app/controllers/ReportConsumerReviewController.scala index c0442c61..ff1fd44a 100644 --- a/app/controllers/ReportConsumerReviewController.scala +++ b/app/controllers/ReportConsumerReviewController.scala @@ -26,7 +26,7 @@ class ReportConsumerReviewController( val logger: Logger = Logger(this.getClass) - def reviewOnReportResponse(reportUUID: UUID): Action[JsValue] = IpRateLimitedAction2.async(parse.json) { + def reviewOnReportResponse(reportUUID: UUID): Action[JsValue] = Act.public.standardLimit.async(parse.json) { implicit request => for { review <- request.parseBody[ConsumerReviewApi]() @@ -34,7 +34,7 @@ class ReportConsumerReviewController( } yield Ok } - def getReview(reportUUID: UUID): Action[AnyContent] = SecuredAction.async { request => + def getReview(reportUUID: UUID): Action[AnyContent] = Act.secured.all.allowImpersonation.async { request => logger.debug(s"Get report response review for report id : ${reportUUID}") for { maybeReview <- reportConsumerReviewOrchestrator.getVisibleReview(reportUUID, request.identity) @@ -47,7 +47,7 @@ class ReportConsumerReviewController( } - def reviewExists(reportUUID: UUID): Action[AnyContent] = IpRateLimitedAction2.async { _ => + def reviewExists(reportUUID: UUID): Action[AnyContent] = Act.public.standardLimit.async { _ => logger.debug(s"Check if review exists for report id : ${reportUUID}") reportConsumerReviewOrchestrator.doesReviewExists(reportUUID).map { exists => Ok(Json.toJson(ConsumerReviewExistApi(exists))) diff --git a/app/controllers/ReportController.scala b/app/controllers/ReportController.scala index 162ddd23..51aa92bc 100644 --- a/app/controllers/ReportController.scala +++ b/app/controllers/ReportController.scala @@ -1,8 +1,6 @@ package controllers import authentication.Authenticator -import authentication.actions.ImpersonationAction.ForbidImpersonation -import authentication.actions.UserAction.WithRole import cats.implicits.catsSyntaxOption import cats.implicits.toTraverseOps import controllers.error.AppError @@ -58,7 +56,7 @@ class ReportController( val logger: Logger = Logger(this.getClass) - def createReport: Action[JsValue] = IpRateLimitedAction2.async(parse.json) { implicit request => + def createReport: Action[JsValue] = Act.public.standardLimit.async(parse.json) { implicit request => implicit val userRole: Option[UserRole] = None for { @@ -75,7 +73,7 @@ class ReportController( } def updateReportCompany(uuid: UUID): Action[JsValue] = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + Act.secured.admins.async(parse.json) { implicit request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) for { reportCompany <- request.parseBody[ReportCompany]() @@ -88,7 +86,7 @@ class ReportController( } def updateReportCountry(uuid: UUID, countryCode: String) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async { implicit request => + Act.secured.admins.async { implicit request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) reportOrchestrator .updateReportCountry(uuid, countryCode, request.identity.id) @@ -99,7 +97,7 @@ class ReportController( } def updateReportConsumer(uuid: UUID): Action[JsValue] = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + Act.secured.admins.async(parse.json) { implicit request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) for { reportConsumer <- request.parseBody[ReportConsumerUpdate]() @@ -113,42 +111,40 @@ class ReportController( } def reportResponse(uuid: UUID): Action[JsValue] = - SecuredAction.andThen(WithRole(UserRole.Professionnel)).andThen(ForbidImpersonation).async(parse.json) { - implicit request => - implicit val userRole: Option[UserRole] = Some(request.identity.userRole) - logger.debug(s"reportResponse ${uuid}") - for { - reportResponse <- request.parseBody[IncomingReportResponse]() - visibleReportExtra <- visibleReportOrchestrator.getVisibleReportForUser(uuid, request.identity) - visibleReport = visibleReportExtra.map(_.report) - updatedReport <- visibleReport - .map(reportOrchestrator.handleReportResponse(_, reportResponse, request.identity)) - .sequence - } yield updatedReport - .map(r => Ok(Json.toJson(r))) - .getOrElse(NotFound) + Act.secured.pros.forbidImpersonation.async(parse.json) { implicit request => + implicit val userRole: Option[UserRole] = Some(request.identity.userRole) + logger.debug(s"reportResponse ${uuid}") + for { + reportResponse <- request.parseBody[IncomingReportResponse]() + visibleReportExtra <- visibleReportOrchestrator.getVisibleReportForUser(uuid, request.identity) + visibleReport = visibleReportExtra.map(_.report) + updatedReport <- visibleReport + .map(reportOrchestrator.handleReportResponse(_, reportResponse, request.identity)) + .sequence + } yield updatedReport + .map(r => Ok(Json.toJson(r))) + .getOrElse(NotFound) } def createReportAction(uuid: UUID): Action[JsValue] = - SecuredAction.andThen(WithRole(UserRole.AdminsAndAgents)).andThen(ForbidImpersonation).async(parse.json) { - implicit request => - for { - reportAction <- request.parseBody[ReportAction]() - reportWithMetadata <- reportRepository.getFor(Some(request.identity), uuid) - report = reportWithMetadata.map(_.report) - newEvent <- - report - .filter(_ => actionsForUserRole(request.identity.userRole).contains(reportAction.actionType)) - .map(reportOrchestrator.handleReportAction(_, reportAction, request.identity)) - .sequence - } yield newEvent - .map(e => Ok(Json.toJson(e))) - .getOrElse(NotFound) + Act.secured.adminsAndAgents.forbidImpersonation.async(parse.json) { implicit request => + for { + reportAction <- request.parseBody[ReportAction]() + reportWithMetadata <- reportRepository.getFor(Some(request.identity), uuid) + report = reportWithMetadata.map(_.report) + newEvent <- + report + .filter(_ => actionsForUserRole(request.identity.userRole).contains(reportAction.actionType)) + .map(reportOrchestrator.handleReportAction(_, reportAction, request.identity)) + .sequence + } yield newEvent + .map(e => Ok(Json.toJson(e))) + .getOrElse(NotFound) } def getReport(uuid: UUID) = - SecuredAction.async { implicit request => + Act.secured.all.allowImpersonation.async { implicit request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) for { maybeReportWithMetadata <- visibleReportOrchestrator.getVisibleReportForUser(uuid, request.identity) @@ -181,7 +177,7 @@ class ReportController( .getOrElse(NotFound) } - def reportsAsPDF() = SecuredAction.async { implicit request => + def reportsAsPDF() = Act.secured.all.allowImpersonation.async { implicit request => val reportFutures = new QueryStringMapper(request.queryString) .seq("ids") .map(extractUUID) @@ -201,7 +197,7 @@ class ReportController( } def reportAsZip(reportId: UUID) = - SecuredAction.async(parse.empty) { implicit request => + Act.secured.all.allowImpersonation.async(parse.empty) { implicit request => reportWithDataOrchestrator .getReportFull(reportId, request.identity) .flatMap(_.liftTo[Future](AppError.ReportNotFound(reportId))) @@ -215,14 +211,14 @@ class ReportController( ) } - def cloudWord(companyId: UUID) = IpRateLimitedAction2.async(parse.empty) { _ => + def cloudWord(companyId: UUID) = Act.public.standardLimit.async(parse.empty) { _ => reportOrchestrator .getCloudWord(companyId) .map(cloudword => Ok(Json.toJson(cloudword))) } def deleteReport(uuid: UUID) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { request => + Act.secured.admins.async(parse.json) { request => for { reportDeletionReason <- request.parseBody[ReportAdminAction]() _ <- reportAdminActionOrchestrator.reportDeletion( @@ -234,7 +230,7 @@ class ReportController( } def deleteSpamReport() = - SecuredAction.andThen(WithRole(UserRole.SuperAdmin)).async(parse.json) { request => + Act.secured.superAdmins.async(parse.json) { request => for { reportsIds <- request.parseBody[List[UUID]]() deleted <- reportAdminActionOrchestrator.deleteSpammedReport( @@ -245,7 +241,7 @@ class ReportController( } def reopenReport() = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { request => + Act.secured.admins.async(parse.json) { request => for { reportsIds <- request.parseBody[List[UUID]]() _ <- reportAdminActionOrchestrator.reportsReOpening( @@ -256,22 +252,21 @@ class ReportController( } def updateReportAssignedUser(uuid: UUID, userId: UUID) = - SecuredAction.andThen(WithRole(UserRole.Professionnel)).andThen(ForbidImpersonation).async(parse.json) { - implicit request => - for { - reportComment <- request.parseBody[ReportComment]() - updatedReportWithMetadata <- reportAssignmentOrchestrator - .assignReportToUser( - reportId = uuid, - assigningUser = request.identity, - newAssignedUserId = userId, - reportComment - ) - } yield Ok(Json.toJson(updatedReportWithMetadata)) + Act.secured.pros.forbidImpersonation.async(parse.json) { implicit request => + for { + reportComment <- request.parseBody[ReportComment]() + updatedReportWithMetadata <- reportAssignmentOrchestrator + .assignReportToUser( + reportId = uuid, + assigningUser = request.identity, + newAssignedUserId = userId, + reportComment + ) + } yield Ok(Json.toJson(updatedReportWithMetadata)) } def generateConsumerReportEmailAsPDF(uuid: UUID) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async { implicit request => + Act.secured.adminsAndReadonly.async { implicit request => for { maybeReportWithMetadata <- reportRepository.getFor(Some(request.identity), uuid) company <- maybeReportWithMetadata.flatMap(_.report.companyId).flatTraverse(r => companyRepository.get(r)) diff --git a/app/controllers/ReportFileController.scala b/app/controllers/ReportFileController.scala index ea882d79..bcb0e539 100644 --- a/app/controllers/ReportFileController.scala +++ b/app/controllers/ReportFileController.scala @@ -46,13 +46,14 @@ class ReportFileController( val reportFileMaxSizeInBytes = signalConsoConfiguration.reportFileMaxSize * 1024 * 1024 - def downloadReportFile(uuid: ReportFileId, filename: String): Action[AnyContent] = IpRateLimitedAction1.async { _ => - reportFileOrchestrator - .downloadReportAttachment(uuid, filename) - .map(signedUrl => Redirect(signedUrl)) + def downloadReportFile(uuid: ReportFileId, filename: String): Action[AnyContent] = Act.public.generousLimit.async { + _ => + reportFileOrchestrator + .downloadReportAttachment(uuid, filename) + .map(signedUrl => Redirect(signedUrl)) } - def retrieveReportFiles(): Action[JsValue] = UserAwareAction.async(parse.json) { request => + def retrieveReportFiles(): Action[JsValue] = Act.userAware.allowImpersonation.async(parse.json) { request => // Validate the incoming JSON request body against the expected format request.body .validate[List[ReportFileId]] @@ -67,7 +68,7 @@ class ReportFileController( ) } - def downloadZip(reportId: UUID, origin: Option[ReportFileOrigin]) = SecuredAction.async { request => + def downloadZip(reportId: UUID, origin: Option[ReportFileOrigin]) = Act.secured.all.allowImpersonation.async { request => for { reportExtra <- visibleReportOrchestrator.getVisibleReportOrThrow(reportId, request.identity) report = reportExtra.report @@ -81,15 +82,15 @@ class ReportFileController( } - def deleteReportFile(uuid: ReportFileId, filename: String): Action[AnyContent] = UserAwareAction.async { - implicit request => + def deleteReportFile(uuid: ReportFileId, filename: String): Action[AnyContent] = + Act.userAware.forbidImpersonation.async { implicit request => reportFileOrchestrator .removeReportFile(uuid, filename, request.identity) .map(_ => NoContent) - } + } def uploadReportFile(reportFileId: Option[UUID]): Action[MultipartFormData[Files.TemporaryFile]] = - IpRateLimitedAction1.async(parse.multipartFormData) { request => + Act.public.generousLimit.async(parse.multipartFormData) { request => for { filePart <- request.body.file("reportFile").liftTo[Future](MalformedFileKey("reportFile")) _ <- diff --git a/app/controllers/ReportListController.scala b/app/controllers/ReportListController.scala index 6d54bac8..ee6ae615 100644 --- a/app/controllers/ReportListController.scala +++ b/app/controllers/ReportListController.scala @@ -34,7 +34,7 @@ class ReportListController( implicit val timeout: org.apache.pekko.util.Timeout = 5.seconds val logger: Logger = Logger(this.getClass) - def getReports() = SecuredAction.async { implicit request => + def getReports() = Act.secured.all.allowImpersonation.async { implicit request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) ReportFilter .fromQueryString(request.queryString) @@ -58,7 +58,7 @@ class ReportListController( ) } - def extractReports = SecuredAction.async { implicit request => + def extractReports = Act.secured.all.allowImpersonation.async { implicit request => for { reportFilter <- ReportFilter .fromQueryString(request.queryString) diff --git a/app/controllers/ReportToExternalController.scala b/app/controllers/ReportToExternalController.scala index 47af5183..2b3bd71a 100644 --- a/app/controllers/ReportToExternalController.scala +++ b/app/controllers/ReportToExternalController.scala @@ -34,7 +34,7 @@ class ReportToExternalController( val logger: Logger = Logger(this.getClass) - def getReportToExternal(uuid: String) = SecuredAction.async { _ => + def getReportToExternal(uuid: String) = Act.securedbyApiKey.async { _ => logger.debug("Calling report to external") Try(UUID.fromString(uuid)) match { case Failure(_) => Future.successful(PreconditionFailed) @@ -56,7 +56,7 @@ class ReportToExternalController( } } - def searchReportsToExternal() = SecuredAction.async { implicit request => + def searchReportsToExternal() = Act.securedbyApiKey.async { implicit request => val qs = new QueryStringMapper(request.queryString) val filter = ReportFilter( siretSirenList = qs.string("siret").map(List(_)).getOrElse(List()), @@ -82,7 +82,7 @@ class ReportToExternalController( ) } - def searchReportsToExternalV2() = SecuredAction.async { implicit request => + def searchReportsToExternalV2() = Act.securedbyApiKey.async { implicit request => val qs = new QueryStringMapper(request.queryString) val filter = ReportFilter( siretSirenList = qs.string("siret").map(List(_)).getOrElse(List()), @@ -116,7 +116,7 @@ class ReportToExternalController( /** @deprecated * Keep it for retro-compatibility purpose but searchReportsToExternal() is the good one. */ - def searchReportsToExternalBySiret(siret: String) = SecuredAction.async { implicit request => + def searchReportsToExternalBySiret(siret: String) = Act.securedbyApiKey.async { implicit request => val qs = new QueryStringMapper(request.queryString) val filter = ReportFilter( siretSirenList = List(siret), diff --git a/app/controllers/ReportedPhoneController.scala b/app/controllers/ReportedPhoneController.scala index cd97548f..7d817004 100644 --- a/app/controllers/ReportedPhoneController.scala +++ b/app/controllers/ReportedPhoneController.scala @@ -14,7 +14,6 @@ import repositories.company.CompanyRepositoryInterface import repositories.report.ReportRepositoryInterface import utils.DateUtils import utils.PhoneNumberUtils -import authentication.actions.UserAction.WithRole import scala.concurrent.ExecutionContext import scala.concurrent.duration._ @@ -39,7 +38,7 @@ class ReportedPhoneController( offset: Option[Long], limit: Option[Int] ) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndCCRF)).async { _ => + Act.secured.adminsAndReadonlyAndDgccrf.allowImpersonation.async { _ => reportRepository .getPhoneReports( readPhoneParam(q), @@ -66,7 +65,7 @@ class ReportedPhoneController( } def extractPhonesGroupBySIRET(q: Option[String], start: Option[String], end: Option[String]) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndCCRF)).async { implicit request => + Act.secured.adminsAndReadonlyAndDgccrf.allowImpersonation.async { implicit request => logger.debug(s"Requesting reportedPhones for user ${request.identity.email}") asyncFileRepository .create(AsyncFile.build(request.identity, kind = AsyncFileKind.ReportedPhones)) diff --git a/app/controllers/SignalConsoReviewController.scala b/app/controllers/SignalConsoReviewController.scala index 299c6d73..72d6fa86 100644 --- a/app/controllers/SignalConsoReviewController.scala +++ b/app/controllers/SignalConsoReviewController.scala @@ -19,7 +19,7 @@ class SignalConsoReviewController( val ec: ExecutionContext ) extends BaseController(authenticator, controllerComponents) { - def signalConsoReview() = IpRateLimitedAction2.async(parse.json) { implicit request => + def signalConsoReview() = Act.public.standardLimit.async(parse.json) { implicit request => for { reviewCreate <- request.parseBody[SignalConsoReviewCreate]() review = reviewCreate.into[SignalConsoReview].withFieldConst(_.id, SignalConsoReviewId.generateId()).transform diff --git a/app/controllers/SiretExtractorController.scala b/app/controllers/SiretExtractorController.scala index 5d5fcd24..997945b1 100644 --- a/app/controllers/SiretExtractorController.scala +++ b/app/controllers/SiretExtractorController.scala @@ -2,10 +2,8 @@ package controllers import authentication.Authenticator import models.User -import models.UserRole import play.api.mvc.ControllerComponents import services.SiretExtractorService -import authentication.actions.UserAction.WithRole import play.api.libs.json.Json import repositories.siretextraction.SiretExtractionRepositoryInterface @@ -20,7 +18,7 @@ class SiretExtractorController( )(implicit val ec: ExecutionContext) extends BaseController(authenticator, controllerComponents) { - def extractSiret() = SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { request => + def extractSiret() = Act.secured.admins.async(parse.json) { request => val maybeWebsite = (request.body \ "website").asOpt[String] maybeWebsite match { diff --git a/app/controllers/SocialNetworkController.scala b/app/controllers/SocialNetworkController.scala index 645c50d5..512e86d3 100644 --- a/app/controllers/SocialNetworkController.scala +++ b/app/controllers/SocialNetworkController.scala @@ -20,7 +20,7 @@ class SocialNetworkController( ) extends BaseController(authenticator, controllerComponents) { val logger: Logger = Logger(this.getClass) - def get(name: String, socialNetwork: String) = IpRateLimitedAction3.async { _ => + def get(name: String, socialNetwork: String) = Act.public.tightLimit.async { _ => SocialNetworkSlug .withNameInsensitiveOption(socialNetwork) .traverse(socialNetworkSlug => influencerOrchestrator.exist(name, socialNetworkSlug)) diff --git a/app/controllers/StatisticController.scala b/app/controllers/StatisticController.scala index af12930f..3319062b 100644 --- a/app/controllers/StatisticController.scala +++ b/app/controllers/StatisticController.scala @@ -16,7 +16,6 @@ import play.api.libs.json.Json import play.api.mvc.ControllerComponents import play.api.mvc.Results import utils.QueryStringMapper -import authentication.actions.UserAction.WithRole import java.time.OffsetDateTime import java.util.Locale @@ -35,7 +34,7 @@ class StatisticController( val logger: Logger = Logger(this.getClass) - def getReportsCount() = SecuredAction.async { request => + def getReportsCount() = Act.secured.all.allowImpersonation.async { request => ReportFilter .fromQueryString(request.queryString) .fold( @@ -52,7 +51,7 @@ class StatisticController( /** Nom de fonction adoubé par Saïd. En cas d'incompréhension, merci de le contacter directement */ - def getReportsCountCurve() = SecuredAction.async { request => + def getReportsCountCurve() = Act.secured.all.allowImpersonation.async { request => ReportFilter .fromQueryString(request.queryString) .fold( @@ -74,37 +73,37 @@ class StatisticController( ) } - def getDelayReportResponseInHours(companyId: Option[UUID]) = SecuredAction.async { request => + def getDelayReportResponseInHours(companyId: Option[UUID]) = Act.secured.all.allowImpersonation.async { request => statsOrchestrator .getResponseAvgDelay(companyId: Option[UUID], request.identity.userRole) .map(count => Ok(Json.toJson(StatsValue(count.map(_.toHours.toInt))))) } - def getReportResponseReviews(companyId: Option[UUID]) = SecuredAction.async { + def getReportResponseReviews(companyId: Option[UUID]) = Act.secured.all.allowImpersonation.async { statsOrchestrator.getReportResponseReview(companyId).map(x => Ok(Json.toJson(x))) } - def getReportEngagementReviews(companyId: Option[UUID]) = SecuredAction.async { + def getReportEngagementReviews(companyId: Option[UUID]) = Act.secured.all.allowImpersonation.async { statsOrchestrator.getReportEngagementReview(companyId).map(x => Ok(Json.toJson(x))) } - def getReportsTagsDistribution(companyId: Option[UUID]) = SecuredAction.async { request => + def getReportsTagsDistribution(companyId: Option[UUID]) = Act.secured.all.allowImpersonation.async { request => statsOrchestrator.getReportsTagsDistribution(companyId, request.identity).map(x => Ok(Json.toJson(x))) } - def getReportsStatusDistribution(companyId: Option[UUID]) = SecuredAction.async { request => + def getReportsStatusDistribution(companyId: Option[UUID]) = Act.secured.all.allowImpersonation.async { request => statsOrchestrator.getReportsStatusDistribution(companyId, request.identity).map(x => Ok(Json.toJson(x))) } def getAcceptedResponsesDistribution(companyId: UUID) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { request => + Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { request => statsOrchestrator .getAcceptedResponsesDistribution(companyId, request.identity) .map(x => Ok(Json.toJson(x))) } def getProReportToTransmitStat() = - SecuredAction.async { request => + Act.secured.all.allowImpersonation.async { request => // Includes the reports that we want to transmit to a pro // but we have not identified the company val filter = ReportFilter( @@ -115,14 +114,14 @@ class StatisticController( .map(curve => Ok(Json.toJson(curve))) } - def getProReportTransmittedStat() = SecuredAction.async { request => + def getProReportTransmittedStat() = Act.secured.all.allowImpersonation.async { request => statsOrchestrator .getReportsCountCurve(Some(request.identity), transmittedReportsFilter) .map(curve => Ok(Json.toJson(curve))) } def getProReportResponseStat(responseTypeQuery: Option[List[ReportResponseType]]) = - SecuredAction.async(parse.empty) { request => + Act.secured.all.allowImpersonation.async(parse.empty) { request => val statusFilter = responseTypeQuery .filter(_.nonEmpty) .map(_.map(ReportStatus.fromResponseType)) @@ -133,24 +132,24 @@ class StatisticController( .map(curve => Ok(Json.toJson(curve))) } - def dgccrfAccountsCurve(ticks: Option[Int]) = SecuredAction.async(parse.empty) { _ => + def dgccrfAccountsCurve(ticks: Option[Int]) = Act.secured.all.allowImpersonation.async(parse.empty) { _ => statsOrchestrator.dgccrfAccountsCurve(ticks.getOrElse(12)).map(x => Ok(Json.toJson(x))) } - def dgccrfActiveAccountsCurve(ticks: Option[Int]) = SecuredAction.async(parse.empty) { _ => + def dgccrfActiveAccountsCurve(ticks: Option[Int]) = Act.secured.all.allowImpersonation.async(parse.empty) { _ => statsOrchestrator.dgccrfActiveAccountsCurve(ticks.getOrElse(12)).map(x => Ok(Json.toJson(x))) } - def dgccrfSubscription(ticks: Option[Int]) = SecuredAction.async(parse.empty) { _ => + def dgccrfSubscription(ticks: Option[Int]) = Act.secured.all.allowImpersonation.async(parse.empty) { _ => statsOrchestrator.dgccrfSubscription(ticks.getOrElse(12)).map(x => Ok(Json.toJson(x))) } - def dgccrfControlsCurve(ticks: Option[Int]) = SecuredAction.async(parse.empty) { _ => + def dgccrfControlsCurve(ticks: Option[Int]) = Act.secured.all.allowImpersonation.async(parse.empty) { _ => statsOrchestrator.dgccrfControlsCurve(ticks.getOrElse(12)).map(x => Ok(Json.toJson(x))) } def countByDepartments() = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndCCRF)).async { implicit request => + Act.secured.adminsAndReadonlyAndDgccrf.allowImpersonation.async { implicit request => val mapper = new QueryStringMapper(request.queryString) val start = mapper.timeWithLocalDateRetrocompatStartOfDay("start") val end = mapper.timeWithLocalDateRetrocompatEndOfDay("end") @@ -158,7 +157,7 @@ class StatisticController( } def reportsCountBySubcategories() = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { implicit request => + Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { implicit request => ReportsCountBySubcategoriesFilter.fromQueryString(request.queryString) match { case Failure(error) => logger.error("Cannot parse querystring" + request.queryString, error) @@ -171,7 +170,7 @@ class StatisticController( } def downloadReportsCountBySubcategories(lang: String) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { implicit request => + Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { implicit request => ReportsCountBySubcategoriesFilter.fromQueryString(request.queryString) match { case Failure(error) => logger.error("Cannot parse querystring" + request.queryString, error) @@ -185,7 +184,7 @@ class StatisticController( } def fetchAdminActionEvents(companyId: UUID, reportAdminActionType: ReportAdminActionType) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { _ => + Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { _ => statsOrchestrator .fetchAdminActionEvents(companyId, reportAdminActionType) .map(count => Ok(Json.obj("value" -> count))) diff --git a/app/controllers/SubscriptionController.scala b/app/controllers/SubscriptionController.scala index 02db5ab7..634cfb82 100644 --- a/app/controllers/SubscriptionController.scala +++ b/app/controllers/SubscriptionController.scala @@ -1,17 +1,14 @@ package controllers import authentication.Authenticator -import authentication.actions.ImpersonationAction.ForbidImpersonation import models.SubscriptionCreation import models.SubscriptionUpdate import models.User -import models.UserRole import orchestrators.SubscriptionOrchestrator import play.api.Logger import play.api.libs.json.JsError import play.api.libs.json.Json import play.api.mvc.ControllerComponents -import authentication.actions.UserAction.WithRole import java.util.UUID import scala.concurrent.ExecutionContext @@ -27,9 +24,7 @@ class SubscriptionController( val logger: Logger = Logger(this.getClass) def createSubscription = - SecuredAction - .andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)) - .andThen(ForbidImpersonation) + Act.secured.adminsAndReadonlyAndAgents.forbidImpersonation .async(parse.json) { implicit request => request.body .validate[SubscriptionCreation] @@ -43,9 +38,7 @@ class SubscriptionController( } def updateSubscription(uuid: UUID) = - SecuredAction - .andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)) - .andThen(ForbidImpersonation) + Act.secured.adminsAndReadonlyAndAgents.forbidImpersonation .async(parse.json) { implicit request => request.body .validate[SubscriptionUpdate] @@ -62,23 +55,22 @@ class SubscriptionController( } def getSubscriptions = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { implicit request => + Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { implicit request => subscriptionOrchestrator.getSubscriptions(request.identity).map(subscriptions => Ok(Json.toJson(subscriptions))) } def getSubscription(uuid: UUID) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).async { implicit request => + Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { implicit request => subscriptionOrchestrator .getSubscription(uuid, request.identity) .map(_.map(s => Ok(Json.toJson(s))).getOrElse(NotFound)) } def removeSubscription(uuid: UUID) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndAgents)).andThen(ForbidImpersonation).async { - implicit request => - subscriptionOrchestrator - .removeSubscription(uuid, request.identity) - .map(deletedCount => if (deletedCount > 0) Ok else NotFound) + Act.secured.adminsAndReadonlyAndAgents.forbidImpersonation.async { implicit request => + subscriptionOrchestrator + .removeSubscription(uuid, request.identity) + .map(deletedCount => if (deletedCount > 0) Ok else NotFound) } } diff --git a/app/controllers/WebsiteController.scala b/app/controllers/WebsiteController.scala index ea19c609..cf8cf832 100644 --- a/app/controllers/WebsiteController.scala +++ b/app/controllers/WebsiteController.scala @@ -17,7 +17,6 @@ import play.api.mvc.Action import play.api.mvc.AnyContent import play.api.mvc.ControllerComponents import repositories.company.CompanyRepositoryInterface -import authentication.actions.UserAction.WithRole import utils.URL import java.time.OffsetDateTime @@ -37,7 +36,7 @@ class WebsiteController( implicit val timeout: org.apache.pekko.util.Timeout = 5.seconds val logger: Logger = Logger(this.getClass) - def create() = SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { request => + def create() = Act.secured.admins.async(parse.json) { request => request.body .validate[WebsiteCreation] .fold( @@ -61,7 +60,7 @@ class WebsiteController( isOpen: Option[Boolean], isMarketplace: Option[Boolean] ) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)).async { _ => + Act.secured.adminsAndReadonly.async { _ => for { result <- websitesOrchestrator.getWebsiteCompanyCount( @@ -87,14 +86,14 @@ class WebsiteController( offset: Option[Long], limit: Option[Int] ) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndCCRF)).async { _ => + Act.secured.adminsAndReadonlyAndDgccrf.allowImpersonation.async { _ => websitesOrchestrator .fetchUnregisteredHost(host, start, end, offset, limit) .map(websiteHostCount => Ok(Json.toJson(websiteHostCount)(paginatedResultWrites[WebsiteHostCount]))) } def extractUnregisteredHost(q: Option[String], start: Option[String], end: Option[String]) = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnlyAndCCRF)).async { implicit request => + Act.secured.adminsAndReadonlyAndDgccrf.allowImpersonation.async { implicit request => logger.debug(s"Requesting websites for user ${request.identity.email}") websitesExtractActor ! WebsiteExtractActor.ExtractRequest( request.identity, @@ -103,7 +102,7 @@ class WebsiteController( Future.successful(Ok) } - def searchByHost(url: String) = IpRateLimitedAction2.async { + def searchByHost(url: String) = Act.public.standardLimit.async { websitesOrchestrator .searchByHost(url) .map(countries => Ok(Json.toJson(countries))) @@ -114,7 +113,7 @@ class WebsiteController( identificationStatus: Option[IdentificationStatus], isMarketPlace: Option[Boolean] ) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async { implicit request => + Act.secured.admins.async { implicit request => (identificationStatus, isMarketPlace) match { case (Some(identificationStatus), None) => websitesOrchestrator @@ -130,7 +129,7 @@ class WebsiteController( } def updateCompany(websiteId: WebsiteId) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + Act.secured.admins.async(parse.json) { implicit request => request.body .validate[CompanyCreation] .fold( @@ -143,20 +142,20 @@ class WebsiteController( } def updateCompanyCountry(websiteId: WebsiteId, companyCountry: String) = - SecuredAction.andThen(WithRole(UserRole.Admins)).async { request => + Act.secured.admins.async { request => websitesOrchestrator .updateCompanyCountry(websiteId, companyCountry, request.identity) .map(websiteAndCompany => Ok(Json.toJson(websiteAndCompany))) } - def remove(websiteId: WebsiteId) = SecuredAction.andThen(WithRole(UserRole.Admins)).async { _ => + def remove(websiteId: WebsiteId) = Act.secured.admins.async { _ => websitesOrchestrator .delete(websiteId) .map(_ => Ok) } - def updateInvestigation() = SecuredAction.andThen(WithRole(UserRole.Admins)).async(parse.json) { implicit request => + def updateInvestigation() = Act.secured.admins.async(parse.json) { implicit request => for { websiteInvestigationApi <- request.parseBody[WebsiteInvestigationApi]() updated <- websitesOrchestrator.updateInvestigation(websiteInvestigationApi) @@ -165,7 +164,7 @@ class WebsiteController( } def listInvestigationStatus(): Action[AnyContent] = - SecuredAction.andThen(WithRole(UserRole.AdminsAndReadOnly)) { _ => + Act.secured.adminsAndReadonly { _ => Ok(Json.toJson(websitesOrchestrator.listInvestigationStatus())) } diff --git a/app/loader/SignalConsoApplicationLoader.scala b/app/loader/SignalConsoApplicationLoader.scala index 83d52e9d..2c3032ba 100644 --- a/app/loader/SignalConsoApplicationLoader.scala +++ b/app/loader/SignalConsoApplicationLoader.scala @@ -860,6 +860,8 @@ class SignalConsoComponents( reportRepository ) + val healthController = new HealthController(cookieAuthenticator, controllerComponents) + val reportController = new ReportController( reportOrchestrator, reportAssignmentOrchestrator, @@ -960,7 +962,7 @@ class SignalConsoComponents( lazy val router: Router = new _root_.router.Routes( httpErrorHandler, - new HealthController(controllerComponents), + healthController, assets, reportController, socialNetworkController,