This is a simple example of a controller in Play Framework where every action checks the session - if the user is logged in.
object Application extends Controller {
def index = Action { implicit request =>
if (request.session.isEmpty) {
Redirect("/login")
} else {
Ok(views.html.index("index"))
}
}
def about = Action { implicit request =>
if (request.session.isEmpty) {
Redirect("/login")
} else {
Ok(views.html.index("about"))
}
}
}
I'd like to handle the session checking in the constructor instead of every action method, but I just don't know how? It should look something like this:
object Application extends Controller {
//This is where the constructor would check if session exists
//and if not - redirect to login screen
def index = Action {
Ok(views.html.index("index"))
}
def about = Action {
Ok(views.html.index("about"))
}
}
Is this possible and if so then how?
My stack is Play Framework 2.2.1, Scala 2.10.3, Java 1.8.0-ea 64bit
UPDATE - SOLVED Thanks for all your ideas, solution is now found, see my answer.
You could take advantage of Action Composition to achieve this. From the documentation:
import play.api.mvc._
class AuthenticatedRequest[A](val username: String, request: Request[A]) extend WrappedRequest[A](request)
object Authenticated extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) =>Future[SimpleResult]) = {
request.session.get("username").map { username =>
block(new AuthenticatedRequest(username, request))
} getOrElse {
Future.successful(Forbidden)
}
}
}
And then you could simply do:
def index = Authenticated {
Ok(views.html.index("index"))
}
Alternatively you could set up a filter instead (as #Robin Green suggested) like so:
object AuthFilter extends Filter {
override def apply(next: RequestHeader => Result)(rh: RequestHeader): Result = {
rh.session.get("username").map { user =>
next(rh)
}.getOrElse {
Redirect("/login")
}
}
In Global.scala scala, add
override def doFilter(action: EssentialAction) = AuthFilter(action)
For more on Filters, see the official docs
Solution is to use Action Composition and create a custom action.
Auth.scala:
package core
import play.api.mvc._
import scala.concurrent._
import play.api.mvc.Results._
object AuthAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[SimpleResult]) = {
if (request.session.isEmpty) {
//authentication condition not met - redirect to login page
Future.successful(Redirect("/login"))
} else {
//proceed with action as normal
block(request)
}
}
}
Application.scala:
package controllers
import play.api._
import play.api.mvc._
import core._
object Application extends Controller {
def index = AuthAction {
Ok(views.html.index("You are logged in."))
}
}
Take a look at Deadbolt: https://github.com/schaloner/deadbolt-2 . There are exhaustive examples and guides.
Works perfectly in my Play 2 project.
You could use a Filter, which applies to every request in the application. However, then you would need to have some code in that Filter to allow certain URLs to be accessed without a valid session, otherwise then the user would not be able to login in the first place.
Related
I'm trying to integrate Deadbolt2 into my play framework 2.4 application.
A have following piece of code in my controller
import be.objectify.deadbolt.scala.{ActionBuilders, AuthenticatedRequest, DeadboltActions}
import be.objectify.deadbolt.scala.cache.HandlerCache
import play.api.data.Form
import play.api.data.Forms._
import play.api.mvc.{AnyContent, _}
import modules.user.security.{Authenticator, HandlerKeys, MyDeadboltHandler}
class Login #Inject() (deadbolt: DeadboltActions, handlers: HandlerCache, actionBuilder: ActionBuilders) extends Controller {
//...
def login = deadbolt.SubjectNotPresent() {
Action { implicit request =>
Ok(login(loginForm))
}
}
}
And I got error missing parameter type. I'm following deadbolt examples, which helps me a lot, but I can't figure out how to pass implicit request into action.
My template begins like this:
#(loginForm: Form[LoginForm])(implicit flash: Flash)
Using Action directly without deadbolt.SubjectNotPresent() works well.
One persistent question that keeps coming up is "how do I get the subject in an authorized action?". As of Deadbolt 2.5, the request passed into an action has been replaced with an AuthenticatedRequest which contains an Option[Subject]. As users of 2.4 also want this feature, it has been included in 2.4.4 as a breaking change.
The following examples use SubjectPresent as an example, but the same change applies to all authorization constraints.
When using action builders, in place of
def index = actionBuilder.SubjectPresentAction().defaultHandler() { implicit request
Ok(accessOk())
}
we now have
def index = actionBuilder.SubjectPresentAction().defaultHandler() { authRequest =>
Future {
Ok(accessOk())
}
}
When using action composition, in place of
def index = deadbolt.SubjectPresent() { implicit request
Action {
Ok(accessOk())
}
}
we now have
def someFunctionA = deadbolt.SubjectPresent()() { authRequest =>
Future {
Ok("Content accessible")
}
}
The getSubject() function of the DeadboltHandler trait now takes an AuthenticatedRequest instead of a Request.
override def getSubject[A](request: AuthenticatedRequest[A]): Future[Option[Subject]] =
request.subject match {
case Some(user) => Future {request.subject}
case None => // get from database, identity platform, cache, whatever
}
What this means for your app is
def login = deadbolt.SubjectNotPresent() {
Action { implicit request =>
Ok(login(loginForm))
}
}
becomes
def login = deadbolt.SubjectNotPresent()() { authRequest =>
Future {
Ok(login(loginForm))
}
}
I am following this guide -
https://www.playframework.com/documentation/2.1.1/JavaGuide4#Implementing-authenticators
I have implemented the authenticator as below in Scala:
package controllers
import play.mvc.Security
import play.mvc.Http.Context
import play.mvc.Result
import play.mvc.Results
class Secured extends Security.Authenticator {
override def getUsername(ctx: Context) : String = {
return ctx.session().get("username");
}
override def onUnauthorized(ctx: Context) : Result = {
Results.redirect(routes.Application.login())
}
}
I applied it as an annotation to the hello action in the below controller.
package controllers
import services.UserServiceImpl
import models.User._
import play.api.mvc._
import play.mvc.Security.Authenticated
object Application extends Controller {
def index = Action {
Ok(views.html.index("Your new application is ready."))
}
#Authenticated(classOf[Secured])
def hello = Action {
Ok("Hello")
}
def login = Action {
Ok("Login")
}
}
I am using Play 2.4 and play.api.mvc.Controller instead of play.mvc.Controller as in the guide. Is this the reason this is not working? How do I get it to work with play.api.mvc.Controller?
I figured an example would probably help explain things. If you want similar functionality in Play Scala you would do something like the following:
object AuthenticatedAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
request.session.get("username") match {
case Some(user) => block(request)
case None => Redirect(routes.Application.login())
}
}
You would then use this in your controller:
def hello = AuthenticatedAction { implicit request =>
Ok("Hello")
}
I want to write a unit test to test my custom action builder.
Once I set the ActionWithSession as in instance in my test, how do I instantiate it like it would be in a controller?
How can I test for the edge cases if it redirects or returns with the Session?
class SessionAwareRequest[A](val context: MyContext, request: Request[A]) extends WrappedRequest[A](request)
class ActionWithSession #Inject()(siteConfig: SiteConfig, userService: UserService)
extends ActionBuilder[SessionAwareRequest] {
def invokeBlock[A](request: Request[A], block: (SessionAwareRequest[A]) => Future[Result]) = {
val session = loadSession(request)
if(session.isDefined) {
val result: Future[Result] = block(new SessionAwareRequest(session.get, request))
result.map(_.withCookies(CookieHelper.newSession(session.get)(request)))
}
else {
Future.successful(Results.Redirect(routes.MessagesController.show("error!")))
}
def loadSession(requestHeader: RequestHeader): Option[MySession] = {
...
Some(MySession(...))
else
None
}
}
If you're using Play 2.3, you should be able to utilize the PlaySpecification trait, which provides a call method to test Actions. Have a look at the Documentation, especially the section about "Unit Testing EssentialAction
".
Here's an artificial, minimal example based on the code you posted. It basically just checks whether the request headers contains a "foo" field and if so, returns OK together with an additional cookie "baz" that is added via the custom action builder. If the "foo" header is missing it returns BadRequest. Hope it helps.
import play.api.mvc._
import play.api.test.{FakeRequest, PlaySpecification}
import scala.concurrent.Future
class ActionSpec extends PlaySpecification {
case class MySession()
class SessionAwareRequest[A](val context: MySession, request: Request[A]) extends WrappedRequest[A](request)
object ActionWithSession extends ActionBuilder[SessionAwareRequest] {
def loadSession(requestHeader: RequestHeader): Option[MySession] = {
if (requestHeader.headers.get("foo").isDefined)
Some(MySession())
else
None
}
def invokeBlock[A](request: Request[A], block: (SessionAwareRequest[A]) => Future[Result]): Future[Result] = {
val session = loadSession(request)
if (session.isDefined) {
val result: Future[Result] = block(new SessionAwareRequest(session.get, request))
result.map(_.withCookies(Cookie("baz", "quux")))
} else {
Future.successful(Results.BadRequest)
}
}
}
"My custom action" should {
"return HTTP code 400 in case the request header does not contain a 'foo' field" in {
val foo = call(ActionWithSession { request =>
Results.Ok("Yikes")
}, FakeRequest())
status(of = foo) must beEqualTo(BAD_REQUEST)
cookies(of = foo).get("baz") must beNone
}
"return HTTP code 200 in case the request header does contain a 'foo' field" in {
val foo = call(ActionWithSession { request =>
Results.Ok("Yikes")
}, FakeRequest().withHeaders("foo" -> "bar"))
status(of = foo) must beEqualTo(OK)
cookies(of = foo).get("baz") must beSome
}
}
}
Trying to better understand creating custom actions using action builder.
I have a custom action like:
class LogginInRequest[A](val currentUser: User, request: Request[A]) extends WrappedRequest[A](request)
object ActionWithContext extends ActionBuilder[LogginInRequest] {
def invokeBlock[A](request: Request[A], block: (LogginInRequest[A]) => Future[SimpleResult]) = {
val u = /// load User
block(new LogginInRequest(user, request)
}
}
Could I someone create another custom action like the above that inherits from the above one and builds upon it i.e. chain in the action and access the user object
e.g. Say I now create a new section on my website for admins, so I check the user object in this new action for:
user.isAdmin
If isAdmin is false, then I redirect using the Future.successful(Forbidden) call.
So I would create LoggedInAdmin based on LogginInRequest but simply checking for the property isAdmin (this is just an example but I want to know if I can do this).
Is it possible to pass an argument to my custom action that I created using action builder so I can do this?
def someAction(...):= MyCustomActionBuilder(userService) {
// ...
}
i.e. I am passing in a argument to it.
With Play 2.3, action composition using ActionBuilder got a whole lot better. The example below shows how you can define a standard ActionBuilder that creates a wrapped request, and then chain that builder together with an ActionTransformer to create a new wrapped request.
import scala.concurrent.Future
import org.scalatest.{MustMatchers, WordSpec}
import play.api.mvc._
import play.api.test.FakeRequest
import play.api.test.Helpers._
class ChainingTest extends WordSpec with MustMatchers {
class WrappedReq1[A](val str: String, request: Request[A]) extends WrappedRequest[A](request)
object ActionWithReq1 extends ActionBuilder[WrappedReq1] {
def invokeBlock[A](request: Request[A], block: (WrappedReq1[A]) => Future[Result]) = {
block(new WrappedReq1(request.headers("1"), request))
}
}
class WrappedReq2[A](val str1: String, val str2: String, request: Request[A]) extends WrappedRequest[A](request)
val ActionWithReq2 = ActionWithReq1 andThen new ActionTransformer[WrappedReq1, WrappedReq2] {
override protected def transform[A](request: WrappedReq1[A]): Future[WrappedReq2[A]] = {
Future.successful(new WrappedReq2[A](request.str, request.headers("2"), request))
}
}
"chained actions" should {
"work" in {
val request = FakeRequest().withHeaders("1" -> "one", "2" -> "two")
val response = ActionWithReq2 { r => Results.Ok(r.str1 + "-" + r.str2) }(request)
contentAsString(response) must equal("one-two")
}
}
}
There is an ActionTransformer trait for modifying your request wrapper, and an ActionFilter for intercepting certain requests and immediately returning a result.
ScalaActionsComposition describes the traits in more detail.
I am trying to create a custom play.api.mvc.Action which can be used to populate a CustomerAccount based on the request and pass the CustomerAccount into the controller.
Following the documentation for Play 2.2.x I've created an Action and ActionBuilder but I cannot seem to return the CustomerAccount from within the action.
My current code is:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
// Do something to return the account like return a new AccountWrappedRequest?
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
// Or here to pass it to the next request?
block(request) // block(AccountWrappedRequest(account??, request))
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Note: This will not compile because the block(request) function is expecting a type of AccountWrappedRequest which I cannot populate. It will compile when using a straight Request
Additionally...
Ultimately I want to be able to combine this Account action with an Authentication action so that the CustomerAccount can be passed into the Authentication action and user authentication can be provided based on that customer's account. I would then want to pass the customer account and user into the controller.
For example:
Account(Authenticated(Action))) { request => request.account; request.user ... } or better yet as individual objects not requiring a custom request object.
I'm not sure if this is the best way to do it but I have managed to come up with a solution that seems to work pretty well.
The key was to match on the request converting it into an AccountWrappedRequest inside invokeBlock before passing it on to the next request. If another Action in the chain is expecting a value from an earlier action in the chain you can then similarly match the request converting it into the type you need to access the request parameters.
Updating the example from the original question:
case class AccountWrappedRequest[A](account: CustomerAccount, request: Request[A]) extends WrappedRequest[A](request)
case class Account[A](action: Action[A]) extends Action[A] {
lazy val parser = action.parser
def apply(request: Request[A]): Future[SimpleResult] = {
AccountService.getBySubdomain(request.host).map { account =>
action(AccountWrappedRequest(account, request))
} getOrElse {
Future.successful(NotFound(views.html.account_not_found()))
}
}
}
object AccountAction extends ActionBuilder[AccountWrappedRequest] {
def invokeBlock[A](request: Request[A], block: (AccountWrappedRequest[A]) => Future[SimpleResult]) = {
request match {
case req: AccountRequest[A] => block(req)
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
override def composeAction[A](action: Action[A]) = Account(action)
}
Then inside the apply() method of another Action (the Authenticated action in my case) you can similarly do:
def apply(request: Request[A]): Future[SimpleResult] = {
request match {
case req: AccountRequest[A] => {
// Do something that requires req.account
val user = User(1, "New User")
action(AuthenticatedWrappedRequest(req.account, user, request))
}
case _ => Future.successful(BadRequest("400 Invalid Request"))
}
}
And you can chain the actions together in the ActionBuilder
override def composeAction[A](action: Action[A]) = Account(Authenticated(action))
If AuthenticatedWrappedRequest is then passed into the controller you would have access to request.account, request.user and all the usual request parameters.
As you can see there are a couple of cases where the response is unknown which would generate a BadRequest. In reality these should never get called as far as I can tell but they are in there just incase.
I would love to have some feedback on this solution as I'm still fairly new to Scala and I'm not sure if there might be a better way to do it with the same result but I hope this is of use to someone too.
I wrote a standalone small (ish) example that does what you're looking for:
https://github.com/aellerton/play-login-example
I gave up trying to use the Security classes that exist in the play framework proper. I'm sure they're good, but I just couldn't understand them.
Brief guide...
In the example code, a controller is declared as using the AuthenticatedRequests trait:
object UserSpecificController extends Controller with AuthenticatedRequests {
...
}
Forcing any page to require authentication (or redirect to get it) is done with the RequireAuthentication action:
def authenticatedIndex = RequireAuthentication { implicit request: AuthenticatedRequest[AnyContent] =>
Ok("This content will be accessible only after logging in)
}
Signing out is done by using the AbandonAuthentication action:
def signOut = AbandonAuthentication { implicit request =>
Ok("You're logged out.").withNewSession
}
Note that for this to work, you must override methods from the AuthenticatedRequests trait, e.g.:
override def authenticationRequired[A](request: Request[A]): Future[SimpleResult] = {
Future.successful(
Redirect(routes.LoginController.showLoginForm).withSession("goto" -> request.path)
)
}
There's more to it; best to see the code.
HTH
Andrew