Skip to content

Commit

Permalink
Merge pull request #1888 from betagouv/master
Browse files Browse the repository at this point in the history
MEP handle impersonation on withCompanyAccess endpoints
  • Loading branch information
eletallbetagouv authored Feb 13, 2025
2 parents 26067fd + f579802 commit 381c369
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 53 deletions.
8 changes: 8 additions & 0 deletions app/authentication/actions/ImpersonationAction.scala
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 {
Expand Down
26 changes: 19 additions & 7 deletions app/controllers/BaseController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -167,18 +168,18 @@ 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
) extends BaseController(authenticator, controllerComponents) {
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 =
Expand Down Expand Up @@ -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 {}

Expand Down
66 changes: 35 additions & 31 deletions app/controllers/CompanyAccessController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 =>
Expand All @@ -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))
Expand All @@ -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)
Expand Down
13 changes: 7 additions & 6 deletions app/controllers/CompanyController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/EventsController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
18 changes: 10 additions & 8 deletions app/controllers/StatisticController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down

0 comments on commit 381c369

Please sign in to comment.