I upgraded my Play application from 2.5 to 2.6 today and i got a problem with ActionBuilder. The docs state:
The Scala ActionBuilder trait has been modified to specify the type of the body as a type parameter, and add an abstract parser member as the default body parsers. You will need to modify your ActionBuilders and pass the body parser directly.
documentation
Sadly I haven't found any example and i don't know how to fix that:
class AuthenticatedRequest[A](val token: ProfileTokenData, request: Request[A]) extends WrappedRequest[A](request)
trait Secured {
object SetExtractor {
def unapplySeq[T](s: Set[T]): Option[Seq[T]] = Some(s.toSeq)
}
def Authenticated = new ActionBuilder[AuthenticatedRequest] with JWTTokenProcess {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
request.jwtSession.claimData.asOpt[JWTToken] match {
case Some(token) => block(new AuthenticatedRequest(ProfileTokenData(null, token.sub, AuthRole.None), request)).map(_.refreshJwtSession(request))
case _ => Future.successful(Unauthorized)
}
}
}
def Registered = new ActionBuilder[AuthenticatedRequest] with JWTTokenProcess {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] =
this.processJWTToken(request, block, Seq(AuthRole.Admin, AuthRole.Customer, AuthRole.Registered))
}
def Customer = new ActionBuilder[AuthenticatedRequest] with JWTTokenProcess {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] =
this.processJWTToken(request, block, Seq(AuthRole.Admin, AuthRole.Customer))
}
def Admin = new ActionBuilder[AuthenticatedRequest] with JWTTokenProcess {
override def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] =
this.processJWTToken(request, block, Seq(AuthRole.Admin))
}
}
Does anyone know whic BodyParser i have to pass as second argument?
Had a similar problem. Play 2.6 injects a ControllerComponents, which has a default body parser. Maybe this helps:
class CheckApiKey(apiKeyToCheck: String, cc: ControllerComponents)
extends ActionBuilder[Request, AnyContent] with ActionFilter[Request] {
...
override protected def executionContext: ExecutionContext = cc.executionContext
override def parser: BodyParser[AnyContent] = cc.parsers.defaultBodyParser
}
I got the same problem. In Play 2.6.x you need to inject BodyParser and extend ActionBuilder. You can also try it this way
class SecuredActionBuilder(val parser: BodyParser[AnyContent])(implicit val executionContext: ExecutionContext) extends ActionBuilder[SecuredRequest, AnyContent] {
def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[Result]): Future[Result] = {
request.jwtSession.claimData.asOpt[JWTToken] match {
...
}
}
Related
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 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)
}
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!"))
}
}
I'm trying to wrap my head around Play action composition. My primary reference is: https://www.playframework.com/documentation/2.3.6/ScalaActionsComposition
Here's what I want to achieve (to be able to declare a function in my Controller this way):
def sensitiveOperation() = Protected {request => ...}
So, my action-plan is:
/**
* Now I need to make an Action builder that wraps two things:
* UserAction (to make sure that request is transformed to UserRequest),
* and then... an Action that checks if userId has a value (it proceeds by invoking the actual action)
* , or otherwise return Unauthorized
*/
So... I have these three things:
class UserRequest[A](val userId: Option[String], request: Request[A]) extends WrappedRequest[A](request)
object UserAction extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
new UserRequest(request.session.get("userId"), request)
}
}
object CheckUserId extends ActionFilter[UserRequest] {
def filter[A](userRequest: UserRequest[A]) = Future.successful {
userRequest.userId match {
case Some(userIdVal) => None
case None => Some(Results.Unauthorized("User is not logged in. Log in first."))
}
}
}
All those three things compile.
My question is: how to declare an Action that makes use of UserAction and CheckUserId ?
object Protected extends ActionBuilder[Request] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]) = {
block(request)
}
override def composeAction[A](action: Action[A]) = ... ?? how ?? ....
}
I've tried this one below, but it gives compile error:
override def composeAction[A](action: Action[A]) = UserAction andThen CheckUserId
ADDITIONAL ADDITIONAL
For now I can work around it using this:
def sensitiveOperation() = (UserAction andThen CheckUserId) {request => ...}
But, still, I think it would have been prettier if I can define a companion object named "Protected" that practically does the same (and-ing UserAction and CheckUserId)..., so I can go back with the original idea:
def sensitiveOperation() = Protected {request => ...}
How to achieve it?
Thanks in advance,
Raka
You can't do it with an object because an object can not be the result of an expression. But you can make it a def or val, and you can put that in a package object if you want, eg:
package object security {
val Protected = UserAction andThen CheckUserId
}
I think I've found how....
object Protected extends ActionBuilder[UserRequest] {
def invokeBlock[A](request: Request[A], block: (UserRequest[A]) => Future[Result]) = {
(UserAction andThen CheckUserId).invokeBlock(request, block)
}
}
Given the followoing ActionBuilder implementations:
class SignedRequest[A](request: Request[A]) extends WrappedRequest[A](request) {}
object SignedAction extends ActionBuilder[SignedRequest] {
def invokeBlock[A](request: Request[A], block: SignedRequest[A] => Future[SimpleResult]) = {
block(new SignedRequest(request))
}
}
class SecuredRequest[A](request: Request[A]) extends WrappedRequest[A](request) {}
object SecuredRequest extends ActionBuilder[SecuredRequest] {
def invokeBlock[A](request: Request[A], block: SecuredRequest[A] => Future[SimpleResult]) = {
block(new SecuredRequest(request))
}
}
How do I combine them? I've tried the following...
object MyController extends Controller {
def doSomething = SignedAction.async(parse.json) {
SecuredAction.async(parse.json) { implicit request =>
Future.successful(Ok)
}}
}
... but I always get the following error message:
/home/j3d/test/controllers/MyController.scala:37: type mismatch;
[error] found : play.api.mvc.Action[play.api.libs.json.JsValue]
[error] required: scala.concurrent.Future[play.api.mvc.SimpleResult]
[error] SecuredAction.async(parse.json) {
^
Am I missing something? Tx.
Function async is expecting a Future[SimpleResult], but the nested SecuredAction.async is returning an Action to the top SignedAction.async (notes that in your sample code you omit to declare requests as class and SignedAction is declared twice).
You can compose result of nested SecuredAction within SignedAction by applying it to the signed request.
package controllers
import scala.concurrent.Future
import play.api._
import play.api.mvc._
case class SignedRequest[A](request: Request[A])
extends WrappedRequest[A](request) {}
object SignedAction extends ActionBuilder[SignedRequest] {
def invokeBlock[A](request: Request[A],
block: SignedRequest[A] => Future[Result]) =
block(new SignedRequest(request))
}
case class SecuredRequest[A](request: Request[A])
extends WrappedRequest[A](request) {}
object SecuredAction extends ActionBuilder[SecuredRequest] {
def invokeBlock[A](request: Request[A],
block: SecuredRequest[A] => Future[Result]) =
block(new SecuredRequest(request))
}
object MyController extends Controller {
def doSomething = SignedAction.async(parse.json) { signedReq =>
SecuredAction.async(parse.json) { implicit securedReq =>
Future.successful(Ok)
} apply signedReq
}
}
Such action composition can also be done without ActionBuilder (which can lead to some extra complexity).
package controllers
import scala.concurrent.Future
import play.api._
import play.api.mvc._
case class SignedRequest[A](request: Request[A])
case class SecuredRequest[A](request: Request[A])
object MyController extends Controller {
def Signed[A](bodyParser: BodyParser[A])(signedBlock: SignedRequest[A] => Future[Result]): Action[A] = Action.async(bodyParser) { req =>
signedBlock(SignedRequest(req))
}
def Secured[A](bodyParser: BodyParser[A])(securedBlock: SecuredRequest[A] => Future[Result]): Action[A] = Action.async(bodyParser) { req =>
securedBlock(SecuredRequest(req))
}
def doSomething = Signed(parse.json) { signedReq =>
Secured(parse.json) { implicit securedReq =>
Future.successful(Ok)
} apply signedReq.request
}
}
Composition can be also done around Future[Result]:
package controllers
import scala.concurrent.Future
import play.api._
import play.api.mvc._
import play.api.libs.json.JsValue
case class SignedRequest[A](request: Request[A])
case class SecuredRequest[A](request: Request[A])
object MyController extends Controller {
def Signed[A](signedBlock: SignedRequest[A] => Future[Result])(implicit req: Request[A]): Future[Result] = signedBlock(SignedRequest(req))
def Secured[A](signedBlock: SecuredRequest[A] => Future[Result])(implicit req: Request[A]): Future[Result] = signedBlock(SecuredRequest(req))
def doSomething = Action.async(parse.json) { implicit req =>
Signed[JsValue] { signedReq =>
Secured[JsValue] { securedReq => Future.successful(Ok) }
}
}
}
Using action-zipper you can compose ActionBuilders
import jp.t2v.lab.play2.actzip._
object MyController extends Controller {
val MyAction = SignedAction zip SecuredAction
def doSomething = MyAction.async(parse.json) { case (signedReq, secureReqeq) =>
Future.successful(Ok)
}
}
Json parsing will be executed only once :)
to simplify #applicius answer
I think it can be done without the Future, I think async/Future is a separate concern.
Very simply removing Futures and async, we get this:
def signed[A](signedBlock: SignedRequest[A] => Result)(implicit req: Request[A]) = signedBlock(SignedRequest(req))
def secured[A](securedBlock: SecuredRequest[A] => Result)(implicit req: Request[A]) = securedBlock(SecuredRequest(req))
//the use is the same as with Futures except for no async
def doSomething = Action(parse.json) { implicit req =>
signed[JsValue] { signedReq => secured[JsValue] { securedReq =>
Ok
} } }