Play 2.3: How to pass parameter to custom action - scala

My current custom action is this
class UserRequest[A](val user: Option[models.UserProfile],
request: Request[A]) extends WrappedRequest[A](request)
object UserAction extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
val user = ... get user from session
new UserRequest(user, request)
}
}
Additionally, I want to pass a parameter to UserAction (e.g. role to validate). So in controller I can use it in this way:
def admin = UserAction("admin") { Ok("granted") }

I don't know why you want to do such thing, but you can probably achieve it this way :
class UserRequest[A](val user: Option[models.UserProfile],
request: Request[A]) extends WrappedRequest[A](request)
object UserActionInner extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
val user = ... get user from session
new UserRequest(user, request)
}
}
object UserAction {
def apply[ A ]( str: String )( block: Request[ A ] => Future[ Result ] ) {
// do something with your str
// Now let UserActionInner do the job
UserActionInner( block )
}
}
// Now you can use it like this.
def admin = UserAction("admin") { Ok("granted") }

Using a case class with a role parameter, instead of an object, should give you the desired effect:
case class UserAction(role: String) extends ActionBuilder[UserRequest] with ActionTransformer[Request, UserRequest] {
override def transform[A](request: Request[A]) = ...
}

Related

Extract Option from Future in ActionBuilder

I am implementing authentication based on this Scala Play Authentication example.
Therefore I use the following ActionBuilder to build an UserAction.
UserAction.scala
class UserRequest[A](val user: Option[Admin], request: Request[A]) extends WrappedRequest[A](request)
class UserAction #Inject()(adminService: AdminService, parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
extends ActionBuilder[UserRequest, AnyContent]
with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) = Future.successful {
val sessionTokenOpt = request.session.get("sessionToken")
val user = sessionTokenOpt
.flatMap(token => Sessions.getSession(token))
.filter(_.expiration.isAfter(LocalDateTime.now(ZoneOffset.UTC)))
.map(_.email)
// Signature: getAdminByEmail(email: String): Future[Option[Admin]]
.flatMap(adminService.getAdminByEmail) // <- Extract Future here
// The user has to be Option[Admin] but is Future[Option[Admin]]
// because of adminService.getAdminByEmail
new UserRequest(user, request)
}
}
Since the example implementation in line 25 does not return a Future because there is no database setup included, I get the following error:
type mismatch;
found : scala.concurrent.Future[Option[models.Tables.Admin]]
required: Option[?]
Because I am new to scala I have no clue how to extract the Future at this point. How to handle the Future result to make sure we return a Option[Admin]?
Fixed it on my own. I just divided the email and fetching into two different parts and moved the Future inside the email match.
class UserAction #Inject()(adminService: AdminService, val parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
extends ActionBuilder[UserRequest, AnyContent]
with ActionTransformer[Request, UserRequest] {
def transform[A](request: Request[A]) : Future[UserRequest[A]] = {
val sessionTokenOpt = request.session.get("sessionToken")
val email = sessionTokenOpt
.flatMap(token => Sessions.getSession(token))
.filter(_.expiration.isAfter(LocalDateTime.now(ZoneOffset.UTC)))
.map(_.email)
email match {
case Some(value) =>
adminService.getAdminByEmail(value).map(a => new UserRequest(a, request))
case None =>
Future { new UserRequest(None, request) }
}
}
}

How to set a session in ActionBuilder in scala play 2.6?

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")
}

Use of implicits in Play 2.5 ActionRefiner

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.

PlayFramework 2.4 Scala ActionRefiner with parameter from url usage

I've made ActionRefiner to read language of current request from parameter in url:
class LangRequest[A](val lang: Lang, request: Request[A]) extends WrappedRequest[A](request)
def LangAction(lang: String) = new ActionRefiner[Request, LangRequest] {
def refine[A](input: Request[A]) = Future.successful {
val availLangs: List[String] = Play.current.configuration.getStringList("play.i18n.langs").get.toList
if (!availLangs.contains(lang))
Left {
input.acceptLanguages.head match {
case Lang(value, _) if availLangs.contains(value) => Redirect(controllers.routes.Application.index(value))
case _ => Redirect(controllers.routes.Application.index(availLangs.head))
}
}
else Right {
new LangRequest(Lang(lang), input)
}
}
}
and try to use it in action like this:
def login(lng: String) = LangAction(lng) { implicit request: Request[AnyContent] =>
Ok("Ok")
}
And I've got
"play.api.mvc.ActionRefiner[play.api.mvc.Request,controllers.actionbuilders.LangRequest]
does not take parameters"
error at compilation time. How can I use this ActionRefiner? In PlayFramework documentation https://www.playframework.com/documentation/2.4.x/ScalaActionsComposition they show ActionRefiner usages with Filter, Transformer like this:
(UserAction andThen ItemAction(itemId) andThen PermissionCheckAction)
and this does work. But how to use one ActionRefiner?
I'm sorry; this is pretty old, but I am facing a similar problem and wanted to post the solution so others could find it. I suspect it is because the first item in the chain of actions should be an ActionBuilder, in addition to whatever else. ActionTransformer is not a subtype of ActionBuilder, though it can be an argument to ActionBuilder#andThen.
It is correct, the ActionBuilder has to be the first element in the chain based on https://www.playframework.com/documentation/2.4.x/ScalaActionsComposition#Putting-it-all-together
On the other hand, there are scenarios where we need to pass arguments to the ActionRefiner due to interactions with the database (e.g. retrieve products using id). At this point, we need to #Inject dependencies like services to get access to DB and the function is a partial solution. So, we can wrap the function in class to inject the dependencies and also inherent from ActionBuilder due to we can have only one element in the chain and we need to start with an ActionBuilder.
class UserActionBuilder #Inject()(
val defaultBodyParsers: BodyParsers.Default,
userService: UserService
)(implicit val ec: ExecutionContext) {
def refine(userEmail: String): ActionRefiner[Request, UserRequest] with ActionBuilder[UserRequest, AnyContent] =
new ActionRefiner[Request, UserRequest] with ActionBuilder[UserRequest, AnyContent] {
def executionContext: ExecutionContext = ec
override def refine[A](request: Request[A]): Future[Either[Result, UserRequest[A]]] = {
userService.retrieveUserByEmail(userEmail)
.map {
case Some(user) if user.isActive => Right(UserRequest(user, request))
case _ => Left(Results.Forbidden("User is inactive"))
}
.recover { case ex: Throwable =>
Left(Results.InternalServerError(ex.getMessage))
}
}
override def parser: BodyParser[AnyContent] = defaultBodyParsers
}
}
case class UserRequest[A](user: User, request: Request[A])
extends WrappedRequest[A](request)
And then
class UserController #Inject()(
arguments...,
userAction: UserActionBuilder
)(implicit executionContext: ExecutionContext) {
def updateUserPurchase(emailUser: String): Action[UserPurchaseRequest] = {
userAction.refine(emailUser)).async(apiParser.parserOf[UserPurchaseRequest]) { implicit request =>
...
doUpdate(request.body, request.user)
...
}
}
}

Scala Play Action Composition : Declare ActionBuilder that produces Action by combining several ActionFunction

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)
}
}