diff --git a/app/authentication/actions/ImpersonationAction.scala b/app/authentication/actions/ImpersonationAction.scala index a34b600f..6165e07a 100644 --- a/app/authentication/actions/ImpersonationAction.scala +++ b/app/authentication/actions/ImpersonationAction.scala @@ -1,6 +1,7 @@ package authentication.actions import authentication.actions.MaybeUserAction.MaybeUserRequest +import controllers.CompanyRequest import models.User import play.api.mvc.Results.Forbidden import play.api.mvc.ActionFilter @@ -28,6 +29,13 @@ object ImpersonationAction { handleImpersonator(request.identity.flatMap(_.impersonator)) } + def forbidImpersonationOnCompanyRequestFilter(implicit ec: ExecutionContext): ActionFilter[CompanyRequest] = + new ActionFilter[CompanyRequest] { + override protected def executionContext: ExecutionContext = ec + override protected def filter[A](request: CompanyRequest[A]): Future[Option[Result]] = + handleImpersonator(request.identity.impersonator) + } + private def handleImpersonator(maybeImpersonator: Option[EmailAddress]) = Future.successful( maybeImpersonator match { diff --git a/app/controllers/BaseController.scala b/app/controllers/BaseController.scala index a99e5bd2..fa2be5e2 100644 --- a/app/controllers/BaseController.scala +++ b/app/controllers/BaseController.scala @@ -19,6 +19,7 @@ import UserAction.UserRequest import UserAction.WithAuthProvider import UserAction.WithRole import authentication.actions.ImpersonationAction.forbidImpersonationFilter +import authentication.actions.ImpersonationAction.forbidImpersonationOnCompanyRequestFilter import authentication.actions.ImpersonationAction.forbidImpersonationOnMaybeUserFilter import com.digitaltangible.playguard.IpRateLimitFilter import com.digitaltangible.ratelimit.RateLimiter @@ -167,6 +168,11 @@ abstract class BaseController( } +class CompanyRequest[A](val company: Company, val accessLevel: AccessLevel, request: UserRequest[A]) + extends WrappedRequest[A](request) { + def identity = request.identity +} + abstract class BaseCompanyController( authenticator: Authenticator[User], override val controllerComponents: ControllerComponents @@ -174,11 +180,6 @@ abstract class BaseCompanyController( def companyOrchestrator: CompanyOrchestrator def companiesVisibilityOrchestrator: CompaniesVisibilityOrchestrator - class CompanyRequest[A](val company: Company, val accessLevel: AccessLevel, request: UserRequest[A]) - extends WrappedRequest[A](request) { - def identity = request.identity - } - private def companyAccessActionRefiner(idOrSiret: Either[SIRET, UUID], adminLevelOnly: Boolean) = new ActionRefiner[UserRequest, CompanyRequest] { val authorizedLevels = @@ -212,11 +213,22 @@ abstract class BaseCompanyController( .getOrElse(Left(Forbidden)) } + case class AskImpersonationDslOnCompanyRequest( + private val actionBuilder: ActionBuilder[CompanyRequest, AnyContent] + ) { + val allowImpersonation = actionBuilder + val forbidImpersonation = actionBuilder.andThen(forbidImpersonationOnCompanyRequestFilter) + } + trait ActDslWithCompanyAccess extends ActDsl { def securedWithCompanyAccessBySiret(siret: String, adminLevelOnly: Boolean = false) = - securedAction.andThen(companyAccessActionRefiner(Left(SIRET.fromUnsafe(siret)), adminLevelOnly)) + AskImpersonationDslOnCompanyRequest( + securedAction.andThen(companyAccessActionRefiner(Left(SIRET.fromUnsafe(siret)), adminLevelOnly)) + ) def securedWithCompanyAccessById(id: UUID, adminLevelOnly: Boolean = false) = - securedAction.andThen(companyAccessActionRefiner(Right(id), adminLevelOnly)) + AskImpersonationDslOnCompanyRequest( + securedAction.andThen(companyAccessActionRefiner(Right(id), adminLevelOnly)) + ) } override val Act = new ActDslWithCompanyAccess {} diff --git a/app/controllers/CompanyAccessController.scala b/app/controllers/CompanyAccessController.scala index 84b0762d..69789703 100644 --- a/app/controllers/CompanyAccessController.scala +++ b/app/controllers/CompanyAccessController.scala @@ -47,24 +47,27 @@ class CompanyAccessController( val logger: Logger = Logger(this.getClass) - def listAccesses(siret: String) = Act.securedWithCompanyAccessBySiret(siret).async { implicit request => - companyAccessOrchestrator - .listAccesses(request.company, request.identity) - .map(userWithAccessLevel => Ok(Json.toJson(userWithAccessLevel))) - } + def listAccesses(siret: String) = + Act.securedWithCompanyAccessBySiret(siret).allowImpersonation.async { implicit request => + companyAccessOrchestrator + .listAccesses(request.company, request.identity) + .map(userWithAccessLevel => Ok(Json.toJson(userWithAccessLevel))) + } - def listAccessesMostActive(siret: String) = Act.securedWithCompanyAccessBySiret(siret).async { implicit request => - companyAccessOrchestrator - .listAccessesMostActive(request.company, request.identity) - .map(mostActive => Ok(Json.toJson(mostActive))) - } + def listAccessesMostActive(siret: String) = + Act.securedWithCompanyAccessBySiret(siret).allowImpersonation.async { implicit request => + companyAccessOrchestrator + .listAccessesMostActive(request.company, request.identity) + .map(mostActive => Ok(Json.toJson(mostActive))) + } - def countAccesses(siret: String) = Act.securedWithCompanyAccessBySiret(siret).async { implicit request => - companyAccessOrchestrator - .listAccesses(request.company, request.identity) - .map(_.length) - .map(count => Ok(Json.toJson(count))) - } + def countAccesses(siret: String) = + Act.securedWithCompanyAccessBySiret(siret).allowImpersonation.async { implicit request => + companyAccessOrchestrator + .listAccesses(request.company, request.identity) + .map(_.length) + .map(count => Ok(Json.toJson(count))) + } def visibleUsersToPro = Act.secured.pros.allowImpersonation.async { implicit request => for { @@ -125,7 +128,7 @@ class CompanyAccessController( } yield () def updateAccess(siret: String, userId: UUID) = - Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).async { implicit request => + Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).forbidImpersonation.async { implicit request => request.body.asJson .map(json => (json \ "level").as[AccessLevel]) .map(level => @@ -140,7 +143,7 @@ class CompanyAccessController( } def removeAccess(siret: String, userId: UUID) = - Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).async { implicit request => + Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).forbidImpersonation.async { implicit request => for { maybeUser <- userRepository.get(userId) user <- maybeUser.liftTo[Future](UserNotFoundById(userId)) @@ -165,28 +168,29 @@ class CompanyAccessController( case class AccessInvitation(email: EmailAddress, level: AccessLevel) def sendInvitation(siret: String) = - Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).async(parse.json) { implicit request => - implicit val reads = Json.reads[AccessInvitation] - request.body - .validate[AccessInvitation] - .fold( - errors => Future.successful(BadRequest(JsError.toJson(errors))), - invitation => - accessesOrchestrator - .addUserOrInvite(request.company, invitation.email, invitation.level, Some(request.identity)) - .map(_ => Ok) - ) + Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).forbidImpersonation.async(parse.json) { + implicit request => + implicit val reads = Json.reads[AccessInvitation] + request.body + .validate[AccessInvitation] + .fold( + errors => Future.successful(BadRequest(JsError.toJson(errors))), + invitation => + accessesOrchestrator + .addUserOrInvite(request.company, invitation.email, invitation.level, Some(request.identity)) + .map(_ => Ok) + ) } def listPendingTokens(siret: String) = - Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).async { implicit request => + Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).allowImpersonation.async { implicit request => accessesOrchestrator .listProPendingToken(request.company, request.identity) .map(tokens => Ok(Json.toJson(tokens))) } def removePendingToken(siret: String, tokenId: UUID) = - Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).async { implicit request => + Act.securedWithCompanyAccessBySiret(siret, adminLevelOnly = true).forbidImpersonation.async { implicit request => for { token <- accessTokenRepository.getToken(request.company, tokenId) _ <- token.map(accessTokenRepository.invalidateToken).getOrElse(Future.unit) diff --git a/app/controllers/CompanyController.scala b/app/controllers/CompanyController.scala index 656b83f4..d9da348c 100644 --- a/app/controllers/CompanyController.scala +++ b/app/controllers/CompanyController.scala @@ -68,7 +68,7 @@ class CompanyController( ) } - def getCompany(companyId: UUID) = Act.securedWithCompanyAccessById(companyId).async { request => + def getCompany(companyId: UUID) = Act.securedWithCompanyAccessById(companyId).allowImpersonation.async { request => implicit val userRole: Option[UserRole] = Some(request.identity.userRole) companyOrchestrator .searchRegisteredById(companyId, request.identity) @@ -82,11 +82,12 @@ class CompanyController( .map(results => Ok(Json.toJson(results))) } - def getResponseRate(companyId: UUID) = Act.securedWithCompanyAccessById(companyId).async { request => - companyOrchestrator - .getCompanyResponseRate(companyId, request.identity) - .map(results => Ok(Json.toJson(results))) - } + def getResponseRate(companyId: UUID) = + Act.securedWithCompanyAccessById(companyId).allowImpersonation.async { request => + companyOrchestrator + .getCompanyResponseRate(companyId, request.identity) + .map(results => Ok(Json.toJson(results))) + } def companiesToActivate() = Act.secured.adminsAndReadonly.async { _ => companyOrchestrator diff --git a/app/controllers/EventsController.scala b/app/controllers/EventsController.scala index 18a5b958..1963a49a 100644 --- a/app/controllers/EventsController.scala +++ b/app/controllers/EventsController.scala @@ -25,7 +25,7 @@ class EventsController( ) extends BaseCompanyController(authenticator, controllerComponents) { def getCompanyEvents(siret: SIRET, eventType: Option[String]): Action[AnyContent] = - Act.securedWithCompanyAccessBySiret(siret.toString).async { implicit request => + Act.securedWithCompanyAccessBySiret(siret.toString).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) diff --git a/app/controllers/StatisticController.scala b/app/controllers/StatisticController.scala index 0f5e6225..58db9111 100644 --- a/app/controllers/StatisticController.scala +++ b/app/controllers/StatisticController.scala @@ -78,28 +78,30 @@ class StatisticController( } def getDelayReportResponseInHours(companyId: UUID) = - Act.securedWithCompanyAccessById(companyId).async { request => + Act.securedWithCompanyAccessById(companyId).allowImpersonation.async { request => statsOrchestrator .getResponseAvgDelay(companyId, request.identity.userRole) .map(count => Ok(Json.toJson(StatsValue(count.map(_.toHours.toInt))))) } - def getReportResponseReviews(companyId: UUID) = Act.securedWithCompanyAccessById(companyId).async { + def getReportResponseReviews(companyId: UUID) = Act.securedWithCompanyAccessById(companyId).allowImpersonation.async { statsOrchestrator.getReportResponseReview(Some(companyId)).map(x => Ok(Json.toJson(x))) } - def getReportEngagementReviews(companyId: UUID) = Act.securedWithCompanyAccessById(companyId).async { - statsOrchestrator.getReportEngagementReview(Some(companyId)).map(x => Ok(Json.toJson(x))) - } + def getReportEngagementReviews(companyId: UUID) = + Act.securedWithCompanyAccessById(companyId).allowImpersonation.async { + statsOrchestrator.getReportEngagementReview(Some(companyId)).map(x => Ok(Json.toJson(x))) + } def getReportsTagsDistribution(companyId: Option[UUID]) = Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { request => statsOrchestrator.getReportsTagsDistribution(companyId, request.identity).map(x => Ok(Json.toJson(x))) } - def getReportsStatusDistribution(companyId: UUID) = Act.securedWithCompanyAccessById(companyId).async { request => - statsOrchestrator.getReportsStatusDistribution(Some(companyId), request.identity).map(x => Ok(Json.toJson(x))) - } + def getReportsStatusDistribution(companyId: UUID) = + Act.securedWithCompanyAccessById(companyId).allowImpersonation.async { request => + statsOrchestrator.getReportsStatusDistribution(Some(companyId), request.identity).map(x => Ok(Json.toJson(x))) + } def getAcceptedResponsesDistribution(companyId: UUID) = Act.secured.adminsAndReadonlyAndAgents.allowImpersonation.async { request =>