app/controllers/Janus.scala (252 lines of code) (raw):

package controllers import aws.{AuditTrailDB, Federation} import cats.syntax.all._ import com.gu.googleauth.{AuthAction, UserIdentity} import com.gu.janus.model._ import conf.Config import logic.PlayHelpers.splitQuerystringParam import logic.{AuditTrail, Customisation, Date, Favourites} import play.api.mvc._ import play.api.{Configuration, Logging, Mode} import software.amazon.awssdk.services.dynamodb.DynamoDbClient import software.amazon.awssdk.services.sts.StsClient import software.amazon.awssdk.services.sts.model.Credentials import java.time._ class Janus( janusData: JanusData, controllerComponents: ControllerComponents, authAction: AuthAction[AnyContent], host: String, stsClient: StsClient, configuration: Configuration )(implicit dynamodDB: DynamoDbClient, mode: Mode, assetsFinder: AssetsFinder) extends AbstractController(controllerComponents) with Logging { import logic.AccountOrdering._ import logic.UserAccess._ def index = authAction { implicit request => val displayMode = Date.displayMode(ZonedDateTime.now(ZoneId.of("Europe/London"))) (for { permissions <- userAccess(username(request.user), janusData.access) favourites = Favourites.fromCookie(request.cookies.get("favourites")) awsAccountAccess = orderedAccountAccess(permissions, favourites) } yield { Ok( views.html.index(awsAccountAccess, request.user, janusData, displayMode) ) }) getOrElse Ok(views.html.noPermissions(request.user, janusData)) } def admin = authAction { implicit request => (for { permissions <- userAccess(username(request.user), janusData.admin) awsAccountAccess = orderedAccountAccess(permissions) } yield { Ok(views.html.admin(awsAccountAccess, request.user, janusData)) }) getOrElse Ok( views.html .error("You do not have admin access", Some(request.user), janusData) ) } def support = authAction { implicit request => val now = Instant.now() val currentSupportUsers = activeSupportUsers(now, janusData.support) val supportUsersInNextPeriod = nextSupportUsers(now, janusData.support) val currentUserFutureSupportPeriods = futureRotaSlotsForUser(now, janusData.support, username(request.user)) (for { permissions <- userSupportAccess( username(request.user), now, janusData.support ) awsAccountAccess = orderedAccountAccess(permissions) } yield { Ok( views.html.support.support( awsAccountAccess, currentSupportUsers, supportUsersInNextPeriod, currentUserFutureSupportPeriods, request.user, janusData ) ) }) getOrElse Ok( views.html.support.notSupport( currentSupportUsers, supportUsersInNextPeriod, currentUserFutureSupportPeriods, request.user, janusData ) ) } def consoleLogin(permissionId: String) = authAction { implicit request => (for { (credentials, _) <- assumeRole( request.user, permissionId, JConsole, Customisation.durationParams(request) ) loginUrl = Federation.generateLoginUrl(credentials, host) } yield { SeeOther(loginUrl) .withHeaders(CACHE_CONTROL -> "no-cache") }) getOrElse { logger.warn( s"console login to $permissionId denied for ${username(request.user)}" ) Forbidden(views.html.permissionDenied(request.user, janusData)) } } def consoleUrl(permissionId: String) = authAction { implicit request => (for { (credentials, permission) <- assumeRole( request.user, permissionId, JConsole, Customisation.durationParams(request) ) loginUrl = Federation.generateLoginUrl(credentials, host) } yield { Ok( views.html.consoleUrl( loginUrl, permission.account.name, credentials, request.user, janusData ) ) .withHeaders(CACHE_CONTROL -> "no-cache") }) getOrElse { logger.warn( s"console login to $permissionId denied for ${username(request.user)}" ) Forbidden(views.html.permissionDenied(request.user, janusData)) } } def credentials(permissionId: String) = authAction { implicit request => (for { (credentials, permission) <- assumeRole( request.user, permissionId, JCredentials, Customisation.durationParams(request) ) } yield { Ok( views.html.credentials( credentials.expiration, List((permission.account, credentials)), request.user, janusData ) ) .withHeaders(CACHE_CONTROL -> "no-cache") }) getOrElse { logger.warn( s"denied credentials to $permissionId for ${username(request.user)}" ) Forbidden(views.html.permissionDenied(request.user, janusData)) } } def multiCredentials(rawPermissionIds: String) = authAction { implicit request => val permissionIds = splitQuerystringParam(rawPermissionIds) (for { accountCredentials <- multiAccountAssumption( request.user, permissionIds, Customisation.durationParams(request) ) expiry <- accountCredentials.headOption.map { case (_, creds) => creds.expiration } } yield { Ok( views.html .credentials(expiry, accountCredentials, request.user, janusData) ) .withHeaders(CACHE_CONTROL -> "no-cache") }) getOrElse { logger.warn( s"denied credentials to $rawPermissionIds for ${username(request.user)}" ) Forbidden(views.html.permissionDenied(request.user, janusData)) } } def favourite() = authAction { implicit request => (for { submission <- request.body.asFormUrlEncoded accountSubmission <- submission.get("account") account <- accountSubmission.headOption favourites = Favourites.fromCookie(request.cookies.get("favourites")) newFavourites = Favourites.toggleFavourite(account, favourites) } yield { Redirect(routes.Janus.index) .withCookies(Favourites.toCookie(newFavourites)) }) getOrElse Ok( views.html .error("Invalid favourite submission", Some(request.user), janusData) ) } private def assumeRole( user: UserIdentity, permissionId: String, accessType: JanusAccessType, durationParams: (Option[Duration], Option[ZoneId]) ): Option[(Credentials, Permission)] = { val (requestedDuration, tzOffset) = durationParams for { permission <- checkUserPermission( username(user), permissionId, Instant.now(), janusData.access, janusData.admin, janusData.support ) duration = Federation.duration( permission, requestedDuration, tzOffset.map(Clock.system) ) roleArn = Config.roleArn(permission.account.authConfigKey, configuration) credentials = Federation.assumeRole( username(user), roleArn, permission, stsClient, duration ) auditLog = AuditTrail.createLog( user, permission, accessType, duration, janusData.access ) _ = AuditTrailDB.insert(auditLog) } yield { logger.info( s"$accessType access to $permissionId granted for ${username(user)}" ) (credentials, permission) } } private def multiAccountAssumption( user: UserIdentity, permissionIds: List[String], durationParams: (Option[Duration], Option[ZoneId]) ): Option[List[(AwsAccount, Credentials)]] = { permissionIds .map(assumeRole(user, _, JCredentials, durationParams)) .map(_.map { case (credentials, permission) => permission.account -> credentials }) .sequence } }