Skip to content
This repository has been archived by the owner on Sep 12, 2021. It is now read-only.

Commit

Permalink
Add MyRequest to show extension on top of Silhouette request
Browse files Browse the repository at this point in the history
  • Loading branch information
wsargent committed Jan 15, 2020
1 parent 9dedd0c commit cef7458
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 27 deletions.
4 changes: 2 additions & 2 deletions app/controllers/ActivateAccountController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ActivateAccountController @Inject() (
* @param email The email address of the user to send the activation mail to.
* @return The result to display.
*/
def send(email: String) = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def send(email: String) = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
val decodedEmail = URLDecoder.decode(email, "UTF-8")
val loginInfo = LoginInfo(CredentialsProvider.ID, decodedEmail)
val result = Redirect(Calls.signin).flashing("info" -> Messages("activation.email.sent", decodedEmail))
Expand Down Expand Up @@ -55,7 +55,7 @@ class ActivateAccountController @Inject() (
* @param token The token to identify a user.
* @return The result to display.
*/
def activate(token: UUID) = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def activate(token: UUID) = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
authTokenService.validate(token).flatMap {
case Some(authToken) => userService.retrieve(authToken.userID).flatMap {
case Some(user) if user.loginInfo.providerID == CredentialsProvider.ID =>
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/ApplicationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ApplicationController @Inject() (
*
* @return The result to display.
*/
def index = SecuredAction.async { implicit request: SecuredRequest[EnvType, AnyContent] =>
def index = SecuredAction.async { implicit request: MySecuredRequest[AnyContent] =>
authInfoRepository.find[GoogleTotpInfo](request.identity.loginInfo).map { totpInfoOpt =>
Ok(home(request.identity, totpInfoOpt))
}
Expand All @@ -33,7 +33,7 @@ class ApplicationController @Inject() (
*
* @return The result to display.
*/
def signOut = SecuredAction.async { implicit request: SecuredRequest[EnvType, AnyContent] =>
def signOut = SecuredAction.async { implicit request: MySecuredRequest[AnyContent] =>
val result = Redirect(Calls.home)
eventBus.publish(LogoutEvent(request.identity, request))
authenticatorService.discard(request.authenticator, result)
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/ChangePasswordController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ChangePasswordController @Inject() (
* @return The result to display.
*/
def view = SecuredAction(WithProvider[AuthType](CredentialsProvider.ID)) {
implicit request: SecuredRequest[DefaultEnv, AnyContent] =>
implicit request =>
Ok(changePassword(ChangePasswordForm.form, request.identity))
}

Expand All @@ -36,7 +36,7 @@ class ChangePasswordController @Inject() (
* @return The result to display.
*/
def submit = SecuredAction(WithProvider[AuthType](CredentialsProvider.ID)).async {
implicit request: SecuredRequest[DefaultEnv, AnyContent] =>
implicit request =>
ChangePasswordForm.form.bindFromRequest.fold(
form => Future.successful(BadRequest(changePassword(form, request.identity))),
password => {
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/ForgotPasswordController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ForgotPasswordController @Inject() (
*
* @return The result to display.
*/
def view = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def view = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
Future.successful(Ok(forgotPassword(ForgotPasswordForm.form)))
}

Expand All @@ -36,7 +36,7 @@ class ForgotPasswordController @Inject() (
*
* @return The result to display.
*/
def submit = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def submit = SecuredAction.async { implicit request: MyRequest[AnyContent] =>
ForgotPasswordForm.form.bindFromRequest.fold(
form => Future.successful(BadRequest(forgotPassword(form))),
email => {
Expand Down
131 changes: 131 additions & 0 deletions app/controllers/MyRequest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package controllers

import com.mohiva.play.silhouette.api.Env
import org.slf4j.Marker
import play.api.MarkerContext
import play.api.i18n.MessagesApi
import play.api.mvc._
import utils.auth.DefaultEnv

import scala.language.higherKinds
import scala.concurrent.{ExecutionContext, Future}

// XXX should be OOTB
trait SecuredRequestHeader[E <: Env] {
def identity: E#I
def authenticator: E#A
}

// XXX should be OOTB
trait UserAwareRequestHeader[E <: Env] {
def identity: Option[E#I]
def authenticator: Option[E#A]
}

/**
* Defines our own request with extended features above and beyond what Silhouette provides.
*/
trait MyRequestHeader extends MessagesRequestHeader
with PreferredMessagesProvider
with MarkerContext

/**
* The request implementation with a parsed body (only relevant for POST requests)
*
* @param request the original request
* @param messagesApi the messages API, needed for producing a Messages instance
* @tparam B the type of the body, if any.
*/
class MyRequest[B](
request: Request[B],
messagesApi: MessagesApi
) extends MessagesRequest[B](request, messagesApi) with MyRequestHeader {
// Stubbed out here, but see marker context docs
// https://www.playframework.com/documentation/2.8.x/ScalaLogging#Using-Markers-and-Marker-Contexts
def marker: Option[Marker] = None
}

/**
* A request with identity and authenticator traits.
*/
trait MySecuredRequestHeader extends MyRequestHeader
with SecuredRequestHeader[DefaultEnv]

/**
* Implementation of secured request.
*/
class MySecuredRequest[B](
request: Request[B],
messagesApi: MessagesApi,
val identity: DefaultEnv#I,
val authenticator: DefaultEnv#A,
) extends MyRequest(request, messagesApi) with MySecuredRequestHeader

/**
* A request with optional identity and authenticator traits.
*/
trait MyUserAwareRequestHeader extends MyRequestHeader
with UserAwareRequestHeader[DefaultEnv]

class MyUserAwareRequest[B](
request: Request[B],
messagesApi: MessagesApi,
val identity: Option[DefaultEnv#I],
val authenticator: Option[DefaultEnv#A]
) extends MyRequest(request, messagesApi) with MyUserAwareRequestHeader

/**
* Abstract class to stop defining executionContext in every subclass
*
* @param cc controller components
*/
protected abstract class AbstractActionTransformer[-R[_], +P[_]](cc: SilhouetteControllerComponents) extends ActionTransformer[R, P] {
override protected def executionContext: ExecutionContext =
cc.executionContext
}

/**
* Transforms from a Request into MyRequest.
*
* @param cc controller components
*/
class MyActionTransformer(cc: SilhouetteControllerComponents) extends AbstractActionTransformer[Request, MyRequest](cc) {
override protected def transform[A](request: Request[A]): Future[MyRequest[A]] = {
Future.successful(new MyRequest[A](
messagesApi = cc.messagesApi,
request = request
))
}
}

/**
* Transforms from a SecuredRequest[DefaultEnv] into MySecuredRequest.
*
* @param cc controller components
*/
class MySecuredActionTransformer(cc: SilhouetteControllerComponents) extends AbstractActionTransformer[SecuredEnvRequest, MySecuredRequest](cc) {
override protected def transform[A](request: SecuredEnvRequest[A]): Future[MySecuredRequest[A]] = {
Future.successful(new MySecuredRequest[A](
messagesApi = cc.messagesApi,
identity = request.identity,
authenticator = request.authenticator,
request = request
))
}
}

/**
* Transforms from a UserAwareRequest[DefaultEnv] into MyUserAwareRequest.
*
* @param cc controller components
*/
class MyUserAwareActionTransformer(cc: SilhouetteControllerComponents) extends AbstractActionTransformer[UserAwareEnvRequest, MyUserAwareRequest](cc) {
override protected def transform[A](request: UserAwareEnvRequest[A]): Future[MyUserAwareRequest[A]] = {
Future.successful(new MyUserAwareRequest[A](
messagesApi = cc.messagesApi,
identity = request.identity,
authenticator = request.authenticator,
request = request
))
}
}
4 changes: 2 additions & 2 deletions app/controllers/ResetPasswordController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ResetPasswordController @Inject() (
* @param token The token to identify a user.
* @return The result to display.
*/
def view(token: UUID) = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def view(token: UUID) = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
authTokenService.validate(token).map {
case Some(_) => Ok(resetPassword(ResetPasswordForm.form, token))
case None => Redirect(Calls.signin).flashing("error" -> Messages("invalid.reset.link"))
Expand All @@ -39,7 +39,7 @@ class ResetPasswordController @Inject() (
* @param token The token to identify a user.
* @return The result to display.
*/
def submit(token: UUID) = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def submit(token: UUID) = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
authTokenService.validate(token).flatMap {
case Some(authToken) =>
ResetPasswordForm.form.bindFromRequest.fold(
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/SignInController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class SignInController @Inject() (
*
* @return The result to display.
*/
def view = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def view = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
Future.successful(Ok(signIn(SignInForm.form, socialProviderRegistry)))
}

Expand All @@ -36,7 +36,7 @@ class SignInController @Inject() (
*
* @return The result to display.
*/
def submit = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def submit = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
SignInForm.form.bindFromRequest.fold(
form => Future.successful(BadRequest(signIn(form, socialProviderRegistry))),
data => {
Expand Down
4 changes: 2 additions & 2 deletions app/controllers/SignUpController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class SignUpController @Inject() (
*
* @return The result to display.
*/
def view = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def view = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
Future.successful(Ok(signUp(SignUpForm.form)))
}

Expand All @@ -36,7 +36,7 @@ class SignUpController @Inject() (
*
* @return The result to display.
*/
def submit = UnsecuredAction.async { implicit request: Request[AnyContent] =>
def submit = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
SignUpForm.form.bindFromRequest.fold(
form => Future.successful(BadRequest(signUp(form))),
data => {
Expand Down
38 changes: 27 additions & 11 deletions app/controllers/SilhouetteController.scala
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
package controllers

import com.mohiva.play.silhouette.api.actions.{ SecuredActionBuilder, UnsecuredActionBuilder }
import com.mohiva.play.silhouette.api.actions._
import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
import com.mohiva.play.silhouette.api.services.{ AuthenticatorService, AvatarService }
import com.mohiva.play.silhouette.api.util.{ Clock, PasswordHasherRegistry }
import com.mohiva.play.silhouette.api.{ EventBus, Silhouette }
import com.mohiva.play.silhouette.api._
import com.mohiva.play.silhouette.impl.providers.{ CredentialsProvider, GoogleTotpProvider, SocialProviderRegistry }
import javax.inject.Inject
import models.services.{ AuthTokenService, UserService }
import play.api.Logging
import play.api.http.FileMimeTypes
import play.api.i18n.{ I18nSupport, Langs, MessagesApi }
import play.api.i18n.{ Langs, MessagesApi }
import play.api.libs.mailer.MailerClient
import play.api.mvc._
import utils.auth.DefaultEnv

import scala.concurrent.duration.FiniteDuration

abstract class SilhouetteController(override protected val controllerComponents: SilhouetteControllerComponents)
extends MessagesAbstractController(controllerComponents) with SilhouetteComponents with I18nSupport with Logging {
extends MessagesAbstractController(controllerComponents) with SilhouetteComponents with Logging {

def SecuredAction: SecuredActionBuilder[EnvType, AnyContent] = controllerComponents.silhouette.SecuredAction
def UnsecuredAction: UnsecuredActionBuilder[EnvType, AnyContent] = controllerComponents.silhouette.UnsecuredAction
private val myActionTransformer = new MyActionTransformer(controllerComponents)
private val mySecuredActionTransformer = new MySecuredActionTransformer(controllerComponents)
private val myUserAwareActionTransformer = new MyUserAwareActionTransformer(controllerComponents)

def UnsecuredAction: ActionBuilder[MyRequest, AnyContent] = controllerComponents.silhouette.UnsecuredAction.andThen(myActionTransformer)

def SecuredAction: ActionBuilder[MySecuredRequest, AnyContent] = {
controllerComponents.silhouette.SecuredAction.andThen(mySecuredActionTransformer)
}

def SecuredAction(errorHandler: SecuredErrorHandler): ActionBuilder[MySecuredRequest, AnyContent] = {
controllerComponents.silhouette.SecuredAction(errorHandler).andThen(mySecuredActionTransformer)
}

def SecuredAction(authorization: Authorization[DefaultEnv#I, DefaultEnv#A]): ActionBuilder[MySecuredRequest, AnyContent] = {
controllerComponents.silhouette.SecuredAction(authorization).andThen(mySecuredActionTransformer)
}

def UserAwareAction: ActionBuilder[MyUserAwareRequest, AnyContent] = controllerComponents.silhouette.UserAwareAction.andThen(myUserAwareActionTransformer)

def userService: UserService = controllerComponents.userService
def authInfoRepository: AuthInfoRepository = controllerComponents.authInfoRepository
Expand All @@ -35,15 +52,14 @@ abstract class SilhouetteController(override protected val controllerComponents:
def totpProvider: GoogleTotpProvider = controllerComponents.totpProvider
def avatarService: AvatarService = controllerComponents.avatarService

def silhouette: Silhouette[EnvType] = controllerComponents.silhouette
def silhouette: Silhouette[DefaultEnv] = controllerComponents.silhouette
def authenticatorService: AuthenticatorService[AuthType] = silhouette.env.authenticatorService
def eventBus: EventBus = silhouette.env.eventBus
}

trait SilhouetteComponents {
type EnvType = DefaultEnv
type AuthType = EnvType#A
type IdentityType = EnvType#I
type AuthType = DefaultEnv#A
type IdentityType = DefaultEnv#I

def userService: UserService
def authInfoRepository: AuthInfoRepository
Expand All @@ -57,7 +73,7 @@ trait SilhouetteComponents {
def totpProvider: GoogleTotpProvider
def avatarService: AvatarService

def silhouette: Silhouette[EnvType]
def silhouette: Silhouette[DefaultEnv]
}

trait SilhouetteControllerComponents extends MessagesControllerComponents with SilhouetteComponents
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/SocialAuthController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class SocialAuthController @Inject() (
* @param provider The ID of the provider to authenticate against.
* @return The result to display.
*/
def authenticate(provider: String) = Action.async { implicit request: Request[AnyContent] =>
def authenticate(provider: String) = UnsecuredAction.async { implicit request: MyRequest[AnyContent] =>
(socialProviderRegistry.get[SocialProvider](provider) match {
case Some(p: SocialProvider with CommonSocialProfileBuilder) =>
p.authenticate().flatMap {
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import com.mohiva.play.silhouette.api.actions.{ SecuredRequest, UserAwareRequest }
import utils.auth.DefaultEnv

package object controllers {
type SecuredEnvRequest[A] = SecuredRequest[DefaultEnv, A]
type UserAwareEnvRequest[A] = UserAwareRequest[DefaultEnv, A]
}
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ lazy val root = (project in file(".")).enablePlugins(PlayScala)
routesImport += "utils.route.Binders._"

// https://github.com/playframework/twirl/issues/105
TwirlKeys.templateImports := Seq()
// TwirlKeys.templateImports := Seq("controllers._")

scalacOptions ++= Seq(
"-deprecation", // Emit warning and location for usages of deprecated APIs.
Expand Down

0 comments on commit cef7458

Please sign in to comment.