app/controllers/Login.scala (70 lines of code) (raw):
package controllers
import cats.data.EitherT
import cats.instances.future._
import cats.syntax.applicativeError._
import com.gu.googleauth.{
GoogleAuthConfig,
GoogleGroupChecker,
LoginSupport,
UserIdentity
}
import play.api.libs.ws.WSClient
import play.api.mvc._
import services.Loggable
import scala.concurrent.{ExecutionContext, Future}
class Login(
val authConfig: GoogleAuthConfig,
override val wsClient: WSClient,
components: ControllerComponents,
googleGroupsToCheck: Set[String],
groupChecker: GoogleGroupChecker
)(implicit executionContext: ExecutionContext)
extends AbstractController(components)
with LoginSupport
with Loggable {
def loginAction = Action.async { implicit request =>
startGoogleLogin()
}
def oauth2Callback = Action.async { implicit request =>
processOauth2Callback()
}
private def processOauth2Callback()(implicit
request: RequestHeader
): Future[Result] = {
(for {
identity <- checkIdentity()
_ <- checkGoogleGroupMembership(identity)
} yield {
log.info(s"User ${identity.email} successfully logged in")
setupSessionWhenSuccessful(identity)
}).merge
}
/*
This is a copy of https://github.com/guardian/play-googleauth with one difference: here we check for membership in at least _one_ group, where as play-googleauth checks membership in _all_ groups.
TODO update https://github.com/guardian/play-googleauth ?
*/
private def checkGoogleGroupMembership(
userIdentity: UserIdentity
): EitherT[Future, Result, Unit] = {
groupChecker
.retrieveGroupsFor(userIdentity.email)
.attemptT
.leftMap({ t =>
val message =
s"Login failure, Could not look up Google groups for ${userIdentity.email}"
logger.warn(message, t)
redirectWithError(failureRedirectTarget, message)
})
.subflatMap { userGroups =>
{
if ((userGroups.intersect(googleGroupsToCheck)).nonEmpty) {
// user is in at least one of the Google groups
Right(())
} else {
val message =
s"Login failure. ${userIdentity.email} does not belong to the required Google groups: ${googleGroupsToCheck
.mkString(", ")}}"
logger.info(message)
Left(redirectWithError(failureRedirectTarget, message))
}
}
}
}
override val failureRedirectTarget: Call = routes.Login.loginAction()
override val defaultRedirectTarget: Call = routes.RootController.index()
}