diff --git a/app/controllers/AbstractAuthController.scala b/app/controllers/AbstractAuthController.scala index fce96da..0d70e80 100644 --- a/app/controllers/AbstractAuthController.scala +++ b/app/controllers/AbstractAuthController.scala @@ -5,6 +5,7 @@ import com.mohiva.play.silhouette.api._ import com.mohiva.play.silhouette.api.services.AuthenticatorResult import models.User import play.api.mvc._ +import utils.auth.DefaultEnv import utils.route.Calls import scala.concurrent.{ ExecutionContext, Future } @@ -16,7 +17,7 @@ import scala.concurrent.{ ExecutionContext, Future } * @param ex The execution context. */ abstract class AbstractAuthController( - scc: SilhouetteControllerComponents + scc: SilhouetteControllerComponents[DefaultEnv] )(implicit ex: ExecutionContext) extends SilhouetteController(scc) { /** diff --git a/app/controllers/ActivateAccountController.scala b/app/controllers/ActivateAccountController.scala index 63a4580..e8d3440 100644 --- a/app/controllers/ActivateAccountController.scala +++ b/app/controllers/ActivateAccountController.scala @@ -8,7 +8,8 @@ import com.mohiva.play.silhouette.impl.providers.CredentialsProvider import javax.inject.Inject import play.api.i18n.Messages import play.api.libs.mailer.Email -import play.api.mvc.{ AnyContent, Request } +import play.api.mvc.AnyContent +import utils.auth.DefaultEnv import utils.route.Calls import scala.concurrent.{ ExecutionContext, Future } @@ -17,7 +18,7 @@ import scala.concurrent.{ ExecutionContext, Future } * The `Activate Account` controller. */ class ActivateAccountController @Inject() ( - scc: SilhouetteControllerComponents + scc: SilhouetteControllerComponents[DefaultEnv] )(implicit ex: ExecutionContext) extends SilhouetteController(scc) { /** @@ -26,7 +27,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: AppRequest[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)) @@ -55,7 +56,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: AppRequest[AnyContent] => authTokenService.validate(token).flatMap { case Some(authToken) => userService.retrieve(authToken.userID).flatMap { case Some(user) if user.loginInfo.providerID == CredentialsProvider.ID => diff --git a/app/controllers/AppRequest.scala b/app/controllers/AppRequest.scala new file mode 100644 index 0000000..f0474ca --- /dev/null +++ b/app/controllers/AppRequest.scala @@ -0,0 +1,61 @@ +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 scala.language.higherKinds + +/** + * Defines our own request with extended features above and beyond what Silhouette provides. + */ +trait AppRequestHeader 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 AppRequest[B]( + request: Request[B], + messagesApi: MessagesApi +) extends MessagesRequest[B](request, messagesApi) with AppRequestHeader { + // 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 AppSecuredRequestHeader[E <: Env] extends AppRequestHeader + with SecuredRequestHeader[E] + +/** + * Implementation of secured request. + */ +class AppSecuredRequest[E <: Env, B]( + request: Request[B], + messagesApi: MessagesApi, + val identity: E#I, + val authenticator: E#A, +) extends AppRequest(request, messagesApi) with AppSecuredRequestHeader[E] + +/** + * A request with optional identity and authenticator traits. + */ +trait AppUserAwareRequestHeader[E <: Env] extends AppRequestHeader + with UserAwareRequestHeader[E] + +class AppUserAwareRequest[E <: Env, B]( + request: Request[B], + messagesApi: MessagesApi, + val identity: Option[E#I], + val authenticator: Option[E#A] +) extends AppRequest(request, messagesApi) with AppUserAwareRequestHeader[E] diff --git a/app/controllers/ApplicationController.scala b/app/controllers/ApplicationController.scala index 6409b9c..8829a43 100644 --- a/app/controllers/ApplicationController.scala +++ b/app/controllers/ApplicationController.scala @@ -1,10 +1,10 @@ package controllers import com.mohiva.play.silhouette.api.LogoutEvent -import com.mohiva.play.silhouette.api.actions._ import com.mohiva.play.silhouette.impl.providers.GoogleTotpInfo import javax.inject.Inject import play.api.mvc._ +import utils.auth.DefaultEnv import utils.route.Calls import scala.concurrent.ExecutionContext @@ -13,7 +13,7 @@ import scala.concurrent.ExecutionContext * The basic application controller. */ class ApplicationController @Inject() ( - scc: SilhouetteControllerComponents, + scc: SilhouetteControllerComponents[DefaultEnv], home: views.html.home )(implicit ex: ExecutionContext) extends SilhouetteController(scc) { @@ -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: AppSecuredEnvRequest[AnyContent] => authInfoRepository.find[GoogleTotpInfo](request.identity.loginInfo).map { totpInfoOpt => Ok(home(request.identity, totpInfoOpt)) } @@ -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: AppSecuredEnvRequest[AnyContent] => val result = Redirect(Calls.home) eventBus.publish(LogoutEvent(request.identity, request)) authenticatorService.discard(request.authenticator, result) diff --git a/app/controllers/ChangePasswordController.scala b/app/controllers/ChangePasswordController.scala index 9cc8ccb..17eb1bb 100644 --- a/app/controllers/ChangePasswordController.scala +++ b/app/controllers/ChangePasswordController.scala @@ -1,13 +1,11 @@ package controllers -import com.mohiva.play.silhouette.api.actions.SecuredRequest import com.mohiva.play.silhouette.api.exceptions.ProviderException import com.mohiva.play.silhouette.api.util.{ Credentials, PasswordInfo } import com.mohiva.play.silhouette.impl.providers.CredentialsProvider import forms.ChangePasswordForm import javax.inject.Inject import play.api.i18n.Messages -import play.api.mvc._ import utils.auth.{ DefaultEnv, WithProvider } import scala.concurrent.{ ExecutionContext, Future } @@ -16,7 +14,7 @@ import scala.concurrent.{ ExecutionContext, Future } * The `Change Password` controller. */ class ChangePasswordController @Inject() ( - scc: SilhouetteControllerComponents, + scc: SilhouetteControllerComponents[DefaultEnv], changePassword: views.html.changePassword )(implicit ex: ExecutionContext) extends SilhouetteController(scc) { @@ -25,8 +23,8 @@ class ChangePasswordController @Inject() ( * * @return The result to display. */ - def view = SecuredAction(WithProvider[AuthType](CredentialsProvider.ID)) { - implicit request: SecuredRequest[DefaultEnv, AnyContent] => + def view = SecuredAction(WithProvider(CredentialsProvider.ID)) { + implicit request => Ok(changePassword(ChangePasswordForm.form, request.identity)) } @@ -35,8 +33,8 @@ class ChangePasswordController @Inject() ( * * @return The result to display. */ - def submit = SecuredAction(WithProvider[AuthType](CredentialsProvider.ID)).async { - implicit request: SecuredRequest[DefaultEnv, AnyContent] => + def submit = SecuredAction(WithProvider(CredentialsProvider.ID)).async { + implicit request => ChangePasswordForm.form.bindFromRequest.fold( form => Future.successful(BadRequest(changePassword(form, request.identity))), password => { diff --git a/app/controllers/ForgotPasswordController.scala b/app/controllers/ForgotPasswordController.scala index 86a0301..4bca450 100644 --- a/app/controllers/ForgotPasswordController.scala +++ b/app/controllers/ForgotPasswordController.scala @@ -6,7 +6,8 @@ import forms.ForgotPasswordForm import javax.inject.Inject import play.api.i18n.Messages import play.api.libs.mailer.Email -import play.api.mvc.{ AnyContent, Request } +import play.api.mvc.AnyContent +import utils.auth.DefaultEnv import utils.route.Calls import scala.concurrent.{ ExecutionContext, Future } @@ -15,7 +16,7 @@ import scala.concurrent.{ ExecutionContext, Future } * The `Forgot Password` controller. */ class ForgotPasswordController @Inject() ( - components: SilhouetteControllerComponents, + components: SilhouetteControllerComponents[DefaultEnv], forgotPassword: views.html.forgotPassword )(implicit ex: ExecutionContext) extends SilhouetteController(components) { @@ -24,7 +25,7 @@ class ForgotPasswordController @Inject() ( * * @return The result to display. */ - def view = UnsecuredAction.async { implicit request: Request[AnyContent] => + def view = UnsecuredAction.async { implicit request: AppRequest[AnyContent] => Future.successful(Ok(forgotPassword(ForgotPasswordForm.form))) } @@ -36,7 +37,7 @@ class ForgotPasswordController @Inject() ( * * @return The result to display. */ - def submit = UnsecuredAction.async { implicit request: Request[AnyContent] => + def submit = UnsecuredAction.async { implicit request: AppRequest[AnyContent] => ForgotPasswordForm.form.bindFromRequest.fold( form => Future.successful(BadRequest(forgotPassword(form))), email => { diff --git a/app/controllers/ResetPasswordController.scala b/app/controllers/ResetPasswordController.scala index 142a0d4..d71a3d4 100644 --- a/app/controllers/ResetPasswordController.scala +++ b/app/controllers/ResetPasswordController.scala @@ -7,7 +7,8 @@ import com.mohiva.play.silhouette.impl.providers.CredentialsProvider import forms.ResetPasswordForm import javax.inject.Inject import play.api.i18n.Messages -import play.api.mvc.{ AnyContent, Request } +import play.api.mvc.AnyContent +import utils.auth.DefaultEnv import utils.route.Calls import scala.concurrent.{ ExecutionContext, Future } @@ -16,7 +17,7 @@ import scala.concurrent.{ ExecutionContext, Future } * The `Reset Password` controller. */ class ResetPasswordController @Inject() ( - scc: SilhouetteControllerComponents, + scc: SilhouetteControllerComponents[DefaultEnv], resetPassword: views.html.resetPassword )(implicit ex: ExecutionContext) extends SilhouetteController(scc) { @@ -26,7 +27,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: AppRequest[AnyContent] => authTokenService.validate(token).map { case Some(_) => Ok(resetPassword(ResetPasswordForm.form, token)) case None => Redirect(Calls.signin).flashing("error" -> Messages("invalid.reset.link")) @@ -39,7 +40,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: AppRequest[AnyContent] => authTokenService.validate(token).flatMap { case Some(authToken) => ResetPasswordForm.form.bindFromRequest.fold( diff --git a/app/controllers/SignInController.scala b/app/controllers/SignInController.scala index 66e21dd..7938c18 100644 --- a/app/controllers/SignInController.scala +++ b/app/controllers/SignInController.scala @@ -7,7 +7,8 @@ import com.mohiva.play.silhouette.impl.providers._ import forms.{ SignInForm, TotpForm } import javax.inject.Inject import play.api.i18n.Messages -import play.api.mvc.{ AnyContent, Request } +import play.api.mvc.AnyContent +import utils.auth.DefaultEnv import utils.route.Calls import scala.concurrent.{ ExecutionContext, Future } @@ -16,7 +17,7 @@ import scala.concurrent.{ ExecutionContext, Future } * The `Sign In` controller. */ class SignInController @Inject() ( - scc: SilhouetteControllerComponents, + scc: SilhouetteControllerComponents[DefaultEnv], signIn: views.html.signIn, activateAccount: views.html.activateAccount, totp: views.html.totp @@ -27,7 +28,7 @@ class SignInController @Inject() ( * * @return The result to display. */ - def view = UnsecuredAction.async { implicit request: Request[AnyContent] => + def view = UnsecuredAction.async { implicit request: AppRequest[AnyContent] => Future.successful(Ok(signIn(SignInForm.form, socialProviderRegistry))) } @@ -36,7 +37,7 @@ class SignInController @Inject() ( * * @return The result to display. */ - def submit = UnsecuredAction.async { implicit request: Request[AnyContent] => + def submit = UnsecuredAction.async { implicit request: AppRequest[AnyContent] => SignInForm.form.bindFromRequest.fold( form => Future.successful(BadRequest(signIn(form, socialProviderRegistry))), data => { diff --git a/app/controllers/SignUpController.scala b/app/controllers/SignUpController.scala index ca157a2..7f8ef91 100644 --- a/app/controllers/SignUpController.scala +++ b/app/controllers/SignUpController.scala @@ -9,7 +9,8 @@ import javax.inject.Inject import models.User import play.api.i18n.Messages import play.api.libs.mailer.Email -import play.api.mvc.{ AnyContent, Request } +import play.api.mvc.AnyContent +import utils.auth.DefaultEnv import utils.route.Calls import scala.concurrent.{ ExecutionContext, Future } @@ -18,7 +19,7 @@ import scala.concurrent.{ ExecutionContext, Future } * The `Sign Up` controller. */ class SignUpController @Inject() ( - components: SilhouetteControllerComponents, + components: SilhouetteControllerComponents[DefaultEnv], signUp: views.html.signUp )(implicit ex: ExecutionContext) extends SilhouetteController(components) { @@ -27,7 +28,7 @@ class SignUpController @Inject() ( * * @return The result to display. */ - def view = UnsecuredAction.async { implicit request: Request[AnyContent] => + def view = UnsecuredAction.async { implicit request: AppRequest[AnyContent] => Future.successful(Ok(signUp(SignUpForm.form))) } @@ -36,7 +37,7 @@ class SignUpController @Inject() ( * * @return The result to display. */ - def submit = UnsecuredAction.async { implicit request: Request[AnyContent] => + def submit = UnsecuredAction.async { implicit request: AppRequest[AnyContent] => SignUpForm.form.bindFromRequest.fold( form => Future.successful(BadRequest(signUp(form))), data => { diff --git a/app/controllers/SilhouetteController.scala b/app/controllers/SilhouetteController.scala index c4f277c..703e19e 100644 --- a/app/controllers/SilhouetteController.scala +++ b/app/controllers/SilhouetteController.scala @@ -1,27 +1,101 @@ package controllers -import com.mohiva.play.silhouette.api.actions.{ SecuredActionBuilder, UnsecuredActionBuilder } +import com.mohiva.play.silhouette.api._ +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.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.{ ExecutionContext, Future } import scala.concurrent.duration.FiniteDuration -abstract class SilhouetteController(override protected val controllerComponents: SilhouetteControllerComponents) - extends MessagesAbstractController(controllerComponents) with SilhouetteComponents with I18nSupport with Logging { +import scala.language.higherKinds - def SecuredAction: SecuredActionBuilder[EnvType, AnyContent] = controllerComponents.silhouette.SecuredAction - def UnsecuredAction: UnsecuredActionBuilder[EnvType, AnyContent] = controllerComponents.silhouette.UnsecuredAction +abstract class SilhouetteController[E <: Env]( + override protected val controllerComponents: SilhouetteControllerComponents[E]) + extends MessagesAbstractController(controllerComponents) with SilhouetteComponents[E] with Logging { + + type SecuredEnvRequest[A] = SecuredRequest[E, A] + type AppSecuredEnvRequest[A] = AppSecuredRequest[E, A] + type UserAwareEnvRequest[A] = UserAwareRequest[E, A] + type AppUserAwareEnvRequest[A] = AppUserAwareRequest[E, A] + + /* + * Abstract class to stop defining executionContext in every subclass + * + * @param cc controller components + */ + protected abstract class AbstractActionTransformer[-R[_], +P[_]] extends ActionTransformer[R, P] { + override protected def executionContext: ExecutionContext = + controllerComponents.executionContext + } + + /** + * Transforms from a Request into AppRequest. + */ + class AppActionTransformer extends AbstractActionTransformer[Request, AppRequest] { + override protected def transform[A](request: Request[A]): Future[AppRequest[A]] = { + Future.successful(new AppRequest[A]( + messagesApi = controllerComponents.messagesApi, + request = request + )) + } + } + + /** + * Transforms from a SecuredRequest into AppSecuredRequest. + */ + class AppSecuredActionTransformer extends AbstractActionTransformer[SecuredEnvRequest, AppSecuredEnvRequest] { + override protected def transform[A](request: SecuredEnvRequest[A]): Future[AppSecuredEnvRequest[A]] = { + Future.successful(new AppSecuredRequest[E, A]( + messagesApi = controllerComponents.messagesApi, + identity = request.identity, + authenticator = request.authenticator, + request = request + )) + } + } + + /** + * Transforms from a UserAwareRequest into AppUserAwareRequest. + */ + class AppUserAwareActionTransformer extends AbstractActionTransformer[UserAwareEnvRequest, AppUserAwareEnvRequest] { + override protected def transform[A](request: UserAwareEnvRequest[A]): Future[AppUserAwareEnvRequest[A]] = { + Future.successful(new AppUserAwareRequest[E, A]( + messagesApi = controllerComponents.messagesApi, + identity = request.identity, + authenticator = request.authenticator, + request = request + )) + } + } + private val appActionTransformer = new AppActionTransformer + private val appSecuredActionTransformer = new AppSecuredActionTransformer + private val appUserAwareActionTransformer = new AppUserAwareActionTransformer + + def UnsecuredAction: ActionBuilder[AppRequest, AnyContent] = silhouette.UnsecuredAction.andThen(appActionTransformer) + + def SecuredAction: ActionBuilder[AppSecuredEnvRequest, AnyContent] = { + silhouette.SecuredAction.andThen(appSecuredActionTransformer) + } + + def SecuredAction(errorHandler: SecuredErrorHandler): ActionBuilder[AppSecuredEnvRequest, AnyContent] = { + silhouette.SecuredAction(errorHandler).andThen(appSecuredActionTransformer) + } + + def SecuredAction(authorization: Authorization[E#I, E#A]): ActionBuilder[AppSecuredEnvRequest, AnyContent] = { + silhouette.SecuredAction(authorization).andThen(appSecuredActionTransformer) + } + + def UserAwareAction: ActionBuilder[AppUserAwareEnvRequest, AnyContent] = silhouette.UserAwareAction.andThen(appUserAwareActionTransformer) def userService: UserService = controllerComponents.userService def authInfoRepository: AuthInfoRepository = controllerComponents.authInfoRepository @@ -35,16 +109,13 @@ abstract class SilhouetteController(override protected val controllerComponents: def totpProvider: GoogleTotpProvider = controllerComponents.totpProvider def avatarService: AvatarService = controllerComponents.avatarService - def silhouette: Silhouette[EnvType] = controllerComponents.silhouette - def authenticatorService: AuthenticatorService[AuthType] = silhouette.env.authenticatorService + def silhouette: Silhouette[E] = controllerComponents.silhouette + def authenticatorService: AuthenticatorService[E#A] = silhouette.env.authenticatorService def eventBus: EventBus = silhouette.env.eventBus } -trait SilhouetteComponents { - type EnvType = DefaultEnv - type AuthType = EnvType#A - type IdentityType = EnvType#I - +trait SilhouetteComponents[E <: Env] { + def silhouette: Silhouette[E] def userService: UserService def authInfoRepository: AuthInfoRepository def passwordHasherRegistry: PasswordHasherRegistry @@ -56,14 +127,12 @@ trait SilhouetteComponents { def socialProviderRegistry: SocialProviderRegistry def totpProvider: GoogleTotpProvider def avatarService: AvatarService - - def silhouette: Silhouette[EnvType] } -trait SilhouetteControllerComponents extends MessagesControllerComponents with SilhouetteComponents +trait SilhouetteControllerComponents[E <: Env] extends MessagesControllerComponents with SilhouetteComponents[E] -final case class DefaultSilhouetteControllerComponents @Inject() ( - silhouette: Silhouette[DefaultEnv], +final case class DefaultSilhouetteControllerComponents[E <: Env] @Inject() ( + silhouette: Silhouette[E], userService: UserService, authInfoRepository: AuthInfoRepository, passwordHasherRegistry: PasswordHasherRegistry, @@ -82,7 +151,7 @@ final case class DefaultSilhouetteControllerComponents @Inject() ( langs: Langs, fileMimeTypes: FileMimeTypes, executionContext: scala.concurrent.ExecutionContext -) extends SilhouetteControllerComponents +) extends SilhouetteControllerComponents[E] trait RememberMeConfig { def expiry: FiniteDuration diff --git a/app/controllers/SilhouetteRequestHeaders.scala b/app/controllers/SilhouetteRequestHeaders.scala new file mode 100644 index 0000000..ab1b1c1 --- /dev/null +++ b/app/controllers/SilhouetteRequestHeaders.scala @@ -0,0 +1,15 @@ +package controllers + +import com.mohiva.play.silhouette.api._ +import play.api.mvc.RequestHeader +import utils.auth._ + +// XXX should be OOTB +trait SecuredRequestHeader[E <: Env] extends RequestHeader + with IdentityProvider[E#I] + with AuthenticatorProvider[E#A] + +// XXX should be OOTB +trait UserAwareRequestHeader[E <: Env] extends RequestHeader + with IdentityAwareProvider[E#I] + with AuthenticatorAwareProvider[E#A] diff --git a/app/controllers/SocialAuthController.scala b/app/controllers/SocialAuthController.scala index ea09dea..2efe61e 100644 --- a/app/controllers/SocialAuthController.scala +++ b/app/controllers/SocialAuthController.scala @@ -5,7 +5,8 @@ import com.mohiva.play.silhouette.api.exceptions.ProviderException import com.mohiva.play.silhouette.impl.providers._ import javax.inject.Inject import play.api.i18n.Messages -import play.api.mvc.{ AnyContent, Request } +import play.api.mvc.AnyContent +import utils.auth.DefaultEnv import utils.route.Calls import scala.concurrent.{ ExecutionContext, Future } @@ -14,7 +15,7 @@ import scala.concurrent.{ ExecutionContext, Future } * The social auth controller. */ class SocialAuthController @Inject() ( - scc: SilhouetteControllerComponents + scc: SilhouetteControllerComponents[DefaultEnv] )(implicit ex: ExecutionContext) extends SilhouetteController(scc) { /** @@ -23,7 +24,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: AppRequest[AnyContent] => (socialProviderRegistry.get[SocialProvider](provider) match { case Some(p: SocialProvider with CommonSocialProfileBuilder) => p.authenticate().flatMap { diff --git a/app/controllers/TotpController.scala b/app/controllers/TotpController.scala index 758451f..d315910 100644 --- a/app/controllers/TotpController.scala +++ b/app/controllers/TotpController.scala @@ -7,6 +7,7 @@ import com.mohiva.play.silhouette.impl.providers._ import forms.{ TotpForm, TotpSetupForm } import javax.inject.Inject import play.api.i18n.Messages +import utils.auth.DefaultEnv import utils.route.Calls import scala.concurrent.{ ExecutionContext, Future } @@ -15,7 +16,7 @@ import scala.concurrent.{ ExecutionContext, Future } * The `TOTP` controller. */ class TotpController @Inject() ( - scc: SilhouetteControllerComponents, + scc: SilhouetteControllerComponents[DefaultEnv], totp: views.html.totp, home: views.html.home )(implicit ex: ExecutionContext) extends AbstractAuthController(scc) { diff --git a/app/controllers/TotpRecoveryController.scala b/app/controllers/TotpRecoveryController.scala index be57d38..90cf11a 100644 --- a/app/controllers/TotpRecoveryController.scala +++ b/app/controllers/TotpRecoveryController.scala @@ -8,6 +8,7 @@ import com.mohiva.play.silhouette.impl.providers._ import forms.TotpRecoveryForm import javax.inject.Inject import play.api.i18n.Messages +import utils.auth.DefaultEnv import scala.concurrent.{ ExecutionContext, Future } @@ -15,7 +16,7 @@ import scala.concurrent.{ ExecutionContext, Future } * The `TOTP` controller. */ class TotpRecoveryController @Inject() ( - scc: SilhouetteControllerComponents, + scc: SilhouetteControllerComponents[DefaultEnv], totpRecovery: views.html.totpRecovery )(implicit ex: ExecutionContext) extends AbstractAuthController(scc) { diff --git a/app/jobs/AuthTokenCleaner.scala b/app/jobs/AuthTokenCleaner.scala index 5d87cc7..2b5e7e2 100644 --- a/app/jobs/AuthTokenCleaner.scala +++ b/app/jobs/AuthTokenCleaner.scala @@ -1,8 +1,8 @@ package jobs -import javax.inject.Inject import akka.actor._ import com.mohiva.play.silhouette.api.util.Clock +import javax.inject.Inject import jobs.AuthTokenCleaner.Clean import models.services.AuthTokenService import play.api.Logging diff --git a/app/models/services/AuthTokenServiceImpl.scala b/app/models/services/AuthTokenServiceImpl.scala index 11ab9fe..109c8cb 100644 --- a/app/models/services/AuthTokenServiceImpl.scala +++ b/app/models/services/AuthTokenServiceImpl.scala @@ -1,15 +1,15 @@ package models.services import java.util.UUID -import javax.inject.Inject import com.mohiva.play.silhouette.api.util.Clock +import javax.inject.Inject import models.AuthToken import models.daos.AuthTokenDAO import org.joda.time.DateTimeZone -import scala.concurrent.{ ExecutionContext, Future } import scala.concurrent.duration._ +import scala.concurrent.{ ExecutionContext, Future } import scala.language.postfixOps /** diff --git a/app/models/services/UserServiceImpl.scala b/app/models/services/UserServiceImpl.scala index c914451..e0c83bc 100644 --- a/app/models/services/UserServiceImpl.scala +++ b/app/models/services/UserServiceImpl.scala @@ -1,10 +1,10 @@ package models.services import java.util.UUID -import javax.inject.Inject import com.mohiva.play.silhouette.api.LoginInfo import com.mohiva.play.silhouette.impl.providers.CommonSocialProfile +import javax.inject.Inject import models.User import models.daos.UserDAO diff --git a/app/modules/SilhouetteModule.scala b/app/modules/SilhouetteModule.scala index 129852c..0f405a7 100644 --- a/app/modules/SilhouetteModule.scala +++ b/app/modules/SilhouetteModule.scala @@ -492,7 +492,7 @@ class SilhouetteModule extends AbstractModule with ScalaModule { } @Provides - def providesSilhouetteComponents(components: DefaultSilhouetteControllerComponents): SilhouetteControllerComponents = { + def providesSilhouetteComponents(components: DefaultSilhouetteControllerComponents[DefaultEnv]): SilhouetteControllerComponents[DefaultEnv] = { components } } diff --git a/app/utils/auth/CustomSecuredErrorHandler.scala b/app/utils/auth/CustomSecuredErrorHandler.scala index 56bfb13..fe28d9c 100644 --- a/app/utils/auth/CustomSecuredErrorHandler.scala +++ b/app/utils/auth/CustomSecuredErrorHandler.scala @@ -1,9 +1,9 @@ package utils.auth -import javax.inject.Inject import com.mohiva.play.silhouette.api.actions.SecuredErrorHandler +import javax.inject.Inject import play.api.i18n.{ I18nSupport, Messages, MessagesApi } -import play.api.mvc.{ Call, RequestHeader } +import play.api.mvc.RequestHeader import play.api.mvc.Results._ import utils.route.Calls diff --git a/app/utils/auth/Env.scala b/app/utils/auth/Env.scala index a88c872..82dd17b 100644 --- a/app/utils/auth/Env.scala +++ b/app/utils/auth/Env.scala @@ -1,6 +1,6 @@ package utils.auth -import com.mohiva.play.silhouette.api.Env +import com.mohiva.play.silhouette.api.{ Authenticator, Env, Identity } import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator import models.User @@ -11,3 +11,19 @@ trait DefaultEnv extends Env { type I = User type A = CookieAuthenticator } + +trait IdentityProvider[I <: Identity] { + def identity: I +} + +trait IdentityAwareProvider[I <: Identity] { + def identity: Option[I] +} + +trait AuthenticatorProvider[A <: Authenticator] { + def authenticator: A +} + +trait AuthenticatorAwareProvider[A <: Authenticator] { + def authenticator: Option[A] +} diff --git a/app/utils/auth/WithProvider.scala b/app/utils/auth/WithProvider.scala index 329626a..7ffc422 100644 --- a/app/utils/auth/WithProvider.scala +++ b/app/utils/auth/WithProvider.scala @@ -1,6 +1,6 @@ package utils.auth -import com.mohiva.play.silhouette.api.{ Authenticator, Authorization } +import com.mohiva.play.silhouette.api.Authorization import models.User import play.api.mvc.Request @@ -10,9 +10,8 @@ import scala.concurrent.Future * Grants only access if a user has authenticated with the given provider. * * @param provider The provider ID the user must authenticated with. - * @tparam A The type of the authenticator. */ -case class WithProvider[A <: Authenticator](provider: String) extends Authorization[User, A] { +case class WithProvider(provider: String) extends Authorization[User, DefaultEnv#A] { /** * Indicates if a user is authorized to access an action. @@ -23,7 +22,7 @@ case class WithProvider[A <: Authenticator](provider: String) extends Authorizat * @tparam B The type of the request body. * @return True if the user is authorized, false otherwise. */ - override def isAuthorized[B](user: User, authenticator: A)( + override def isAuthorized[B](user: User, authenticator: DefaultEnv#A)( implicit request: Request[B]): Future[Boolean] = { diff --git a/build.sbt b/build.sbt index 39f4f29..6b4892e 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,4 @@ import com.typesafe.sbt.SbtScalariform._ - import scalariform.formatter.preferences._ name := "play-silhouette-seed" @@ -39,7 +38,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. diff --git a/test/controllers/ApplicationControllerSpec.scala b/test/controllers/ApplicationControllerSpec.scala index cba17b7..dfe3c01 100644 --- a/test/controllers/ApplicationControllerSpec.scala +++ b/test/controllers/ApplicationControllerSpec.scala @@ -3,7 +3,7 @@ package controllers import java.util.UUID import com.google.inject.AbstractModule -import com.mohiva.play.silhouette.api.{ Environment, LoginInfo } +import com.mohiva.play.silhouette.api.{Environment, LoginInfo} import com.mohiva.play.silhouette.test._ import models.User import net.codingwell.scalaguice.ScalaModule @@ -11,7 +11,7 @@ import org.specs2.mock.Mockito import org.specs2.specification.Scope import play.api.inject.guice.GuiceApplicationBuilder import play.api.test.CSRFTokenHelper._ -import play.api.test.{ FakeRequest, PlaySpecification, WithApplication } +import play.api.test.{FakeRequest, PlaySpecification, WithApplication} import utils.auth.DefaultEnv import scala.concurrent.ExecutionContext.Implicits.global