since Play Framework 2.4 there is the possibility to use dependency injection (with Guice).
Before I used objects (for example AuthenticationService) in my ActionBuilders:
object AuthenticatedAction extends ActionBuilder[AuthenticatedRequest] {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
...
AuthenticationService.authenticate (...)
...
}
}
Now AuthenticationService is not an object anymore, but a class. How can I still use the AuthenticationService in my ActionBuilder?
Define your action builders inside a trait with the authentication service as an abstract field. Then mix them into your controllers, into which you inject the service. For example:
trait MyActionBuilders {
// the abstract dependency
def authService: AuthenticationService
def AuthenticatedAction = new ActionBuilder[AuthenticatedRequest] {
override def invokeBlock[A](request: Request[A], block(AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
authService.authenticate(...)
...
}
}
}
and the controller:
#Singleton
class MyController #Inject()(authService: AuthenticationService) extends Controller with MyActionBuilders {
def myAction(...) = AuthenticatedAction { implicit request =>
Ok("authenticated!")
}
}
I didn't like the way one was required to inherit in the above example. But apparently it's possible to simply wrap a object inside class:
class Authentication #Inject()(authService: AuthenticationService) {
object AuthenticatedAction extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
// Do your thing wit the authService...
block(request)
}
}
}
class YourController #Inject() (val auth: Authentication) extends Controller (
def loggedInUser = auth.AuthenticatedAction(parse.json) { implicit request =>
// ...
}
}
I like accepted answer but for some reason the compiler would not recognize the authService reference. I got around this pretty easily by just sending the service in the method signature, a la...
class Authentication #Inject()(authenticationService: AuthenticationService) extends Controller with ActionBuilders {
def testAuth = AuthenticatedAction(authenticationService).async { implicit request =>
Future.successful(Ok("Authenticated!"))
}
}
Related
I have a code in my Play Scala (2.5x, 2.11.11) app which has been running just fine so far (it is based on the following link: https://fizzylogic.nl/2016/11/27/authorize-access-to-your-play-application-using-action-builders-and-action-functions/). But now I need to pass another class instance to ApplicationAuthorizationHandler class (NOTE: throughout my code I am using Guice DI for injecting parameters into class constructors).
Current code:
class ApplicationAuthorizationHandler
extends AuthorizationHandler {
...
}
trait AuthorizationHandler {
...
}
trait AuthorizationCheck {
def authorizationHandler: AuthorizationHandler = new ApplicationAuthorizationHandler
object AuthenticatedAction extends ActionBuilder[RequestWithPrincipal] {
override def invokeBlock[A](request: Request[A], block: (RequestWithPrincipal[A]) => Future[Result]): Future[Result] = {
def unauthorizedAction = authorizationHandler.unauthorized(RequestWithOptionalPrincipal(None, request))
def authorizedAction(principal: Principal) = block(RequestWithPrincipal(principal, request))
authorizationHandler.principal(request).fold(unauthorizedAction)(authorizedAction)
}
}
}
//Example controller using this trait AuthorizationCheck
class MyController #Inject() extends Controller with AuthorizationCheck {
def myAction = AuthenticatedAction { implicit request =>
...
}
Desired code:
class ApplicationAuthorizationHandler #Inject() (userService: UserService)
extends AuthorizationHandler {
...
// userService is used here
}
But since the instance of ApplicationAuthorizationHandler is instantiated inside trait AuthorizationCheck I can't inject UserService instance into it. I am Mixin this trait with all controllers so would like to keep the same way unless there is a better way (and there must be).
First, is there a way to inject directly into class/trait method ?
Alternatively, is there a way where I don't instantiate ApplicationAuthorizationHandler in trait AuthorizationCheck and pass it during run-time inside the controller ?
Or any other way ?
A trait does not need to provide an implementation, so you can have something like:
trait AuthorizationHandler {
...
}
class ApplicationAuthorizationHandler extends AuthorizationHandler {
...
}
trait AuthorizationCheck {
// just declaring that implementations needs to provide a
def authorizationHandler: AuthorizationHandler
object AuthenticatedAction extends ActionBuilder[RequestWithPrincipal] {
override def invokeBlock[A](request: Request[A], block: (RequestWithPrincipal[A]) => Future[Result]): Future[Result] = {
def unauthorizedAction = authorizationHandler.unauthorized(RequestWithOptionalPrincipal(None, request))
def authorizedAction(principal: Principal) = block(RequestWithPrincipal(principal, request))
authorizationHandler.principal(request).fold(unauthorizedAction)(authorizedAction)
}
}
}
// So, now this controller needs to provide a concrete implementation
// of "authorizationHandler" as declared by "AuthorizationCheck".
// You can do it by injecting a "AuthorizationHandler" as a val with
// name authorizationHandler.
class MyController #Inject()(val authorizationHandler: AuthorizationHandler) extends Controller with AuthorizationCheck {
def myAction = AuthenticatedAction { implicit request =>
...
}
}
And of course, you need to provide a module to bind AuthorizationHandler to ApplicationAuthorizationHandler:
import play.api.inject._
class AuthorizationHandlerModule extends SimpleModule(
bind[AuthorizationHandler].to[ApplicationAuthorizationHandler]
)
Of course, ApplicationAuthorizationHandler can have its own dependencies injected. You can see more details at our docs.
There are many cases when you cannot use the #Inject approach of guice. This is true when dependencies are needed inside of trait and also actors.
The approach I use in these cases is that I put my injector in a object
object Injector {
val injector = Guice.createInjector(new ProjectModule())
}
since the above is inside of an object, you can access it from anywhere. (its like a singleton).
Now inside your trait or an actor when you need the user service do
trait Foo {
lazy val userService = Injector.injector.getInstance(classOf[UserService])
}
Don't forget to make the variable lazy, because you want the instance to be created as late as possible when the injector has already been created.
The documentation on ActionBuilder contains a pipeline of three nodes: the authentication, adding informations, validating step.
I would like to set session values at the authentication step. I mean the .withSession which comes here Ok(_).withSession(_)
import play.api.mvc._
class UserRequest[A](val username: Option[String], request: Request[A]) extends WrappedRequest[A](request)
class UserAction #Inject()(val parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
extends ActionBuilder[UserRequest, AnyContent] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
new UserRequest(request.session.get("username"), request)
}
}
You'll need to do action composition to add values to the request session like so:
object WithSession extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
block(request).map(_.withSession("key" -> "value"))
}
}
in your controller:
def index = WithSession {
Ok("result")
}
I am trying to create my own ActionRefiner to accommodate authentication, but for some reason the compiler won't let me use implicit variables in the refine[A] function... I have the following code:
trait Auth {
object AuthenticatedAction extends ActionBuilder[AuthRequest] with ActionRefiner[Request, AuthRequest] {
def refine[A](request: Request[A])(implicit userCollection: UserCollection, ex: ExecutionContext): Future[Either[Result, AuthRequest[A]]] = {
request.session.get("username") match {
case Some(username) => userCollection.findByEmail(username).map { userOpt =>
userOpt.map(new AuthRequest(_, request)).toRight(Results.Redirect(routes.LoginController.login()))
}
case None => Future.successful(Left(Results.Redirect(routes.LoginController.login())))
}
}
}
}
class AuthRequest[A](val user: User, request: Request[A]) extends WrappedRequest[A](request)
The Scala compiler tells me that the method refine[A](request: R[A]): Future[Either[Result, P[A]]] is not defined. When I remove the implicit variables it registers, but that leaves me with no UserCollection...
So, How do I correctly use the ActionRefiner?
Thanks to this topic I found a way to make it work. Instead of using an implicit, I can define the UserCollection in the trait and pass it from my controller, like so:
trait Auth {
def userCollection: UserCollection
object AuthenticatedAction extends ActionBuilder[AuthRequest] with ActionRefiner[Request, AuthRequest] {
def refine[A](request: Request[A]): Future[Either[Result, AuthRequest[A]]] = {
...
}
}
}
class HomeController #Inject()(val userCollection: UserCollection)(implicit executionContext: ExecutionContext) extends Controller with Auth {
def index = AuthenticatedAction { implicit request =>
Ok(s"Hello ${request.user.name}")
}
}
I just had gotten so used to using implicits that I completely forgot about this.
I am trying to Create customize action for security. I am using Scala Oauth
for handling security in my application and trying to create custom action and wrap the Scala Oauth security in my custom action. According to Play Framework Documentation, i am using two ways for wrapped request object, but unfortunately, i am not getting my custom Request object in custom Action handler. Following are the Ways:
case class AuthRequest[A](user: User, request: Request[A]) extends WrappedRequest[A](request)
First Way
case class CustomSecurityAction[A](action: Action[A]) extends Action[A] with OAuth2Provider{
def apply(request: Request[A]): Future[Result] = {
implicit val executionContext: ExecutionContext = play.api.libs.concurrent.Execution.defaultContext
request.headers.get("Host").map { host =>
authorize(new SecurityDataHandler(host)) { authInfo =>
action(AuthRequest(authInfo.user, request))
}(request, executionContext)
} getOrElse {
Future.successful(Unauthorized("401 No user\n"))
}}
lazy val parser = action.parser
}
object SecurityAction extends ActionBuilder[Request] with OAuth2Provider {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
block(request)
}
override def composeAction[A](action: Action[A]) = new CustomSecurityAction(action)
}
Second Way
object SecurityAction extends ActionBuilder[Request] with OAuth2Provider {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
implicit val executionContext: ExecutionContext = play.api.libs.concurrent.Execution.defaultContext
request.headers.get("Host").map { host =>
authorize(new SecurityDataHandler(host)) { authInfo =>
block(AuthRequest(authInfo.user, request))
}(request, executionContext)
} getOrElse {
Future.successful(Unauthorized("401 No user\n"))
}
}
According to both ways, when i am trying to use user object in my custom handler i am getting following compile time error:
value user is not a member of play.api.mvc.Request[play.api.mvc.AnyContent]
val user = request.user
Following is my handler code:
def testCustomAction = SecurityAction { request =>
val user = request.user
Future.successful(Ok("Apna To Chal Gya"))
}
There is simple problem, in above code. I am using play.api.mvc.Request in invokeBlock method instead of AuthRequest. Please find below code for correction.
object SecurityAction extends ActionBuilder[AuthRequest] {
override def invokeBlock[A](request: Request[A], block: (AuthRequest[A]) => Future[Result]) = {
request match {
case re: AuthRequest[A] => block(re)
case _ => Future.successful(Results.Unauthorized("401 No user\n"))
}
}
override def composeAction[A](action: Action[A]) = CustomSecurityAction(action)
}
How can I use guice when creating a custom Action using ActionBuilder?
It seems to complain with "not found: value MyAction" if I change the ActionBuilder from a object to a class.
I have this but it doesn't work:
case class MyModel(name: String, request: Request[A]) extends WrappedRequest[A](request)
class MyAction #Inject()(userService: UserService) extends ActionBuilder[MyModel] {
def invokeBlock[A](request: Request[A], block: (MyModel[A]) => Future[SimpleResult]) = {
val abc = loadAbc(request)
block(new MyModel(abc, request))
}
def loadAbc(rh: RequestHeader): String {
"abc" // just for testing
}
}
So changing it from an object to a class causes it to fail, and I tried keeping it as an object but then it doesn't compile correctly.
How can I get this to work?
I have it working just fine in my controllers.
With a few minor corrections, what you've got seems to work already. All you've got to do is inject the guice-instantiated instance of MyAction into your controller, and then you can use the instance (rather than trying to use the MyAction class name).
This works with Play 2.3:
import scala.concurrent.Future
import javax.inject.{Inject, Singleton}
import play.api.mvc._
class UserService() {
def loadAbc(rh: RequestHeader) = "abc"
}
class MyModel[A](val name: String, request: Request[A]) extends WrappedRequest[A](request)
class MyAction #Inject()(userService: UserService) extends ActionBuilder[MyModel] {
def invokeBlock[A](request: Request[A], block: (MyModel[A]) => Future[Result]) = {
val abc = userService.loadAbc(request)
block(new MyModel(abc, request))
}
}
#Singleton
class Application #Inject() (myAction: MyAction) extends Controller {
def index = myAction { request =>
Ok(request.name)
}
}
You can't use object because that violates the design of Guice. object is a singleton instantiated by Scala itself and cannot have instance variables, whereas Guice needs to be able to instantiate classes on the fly so that it can inject dependencies.
I think your code should work if you use it like this:
class MyClass #Inject()(myAction: MyAction) {
val echo = myAction { request =>
Ok("Got request [" + request + "]")
}
}