play 2.6 add/read request attributes - scala

I am trying to implement authentication for my API. I have created a filter that will load the current user based on the token.
The loading of the user works(Removed it from the example), but now I want to add the user to the request attributes so I can access it in an action or controller. But I can't get it to work:
package filters
import java.util.UUID
import javax.inject.Inject
import akka.stream.Materializer
import play.api.libs.typedmap.TypedKey
import play.api.mvc.{Filter, RequestHeader, Result}
import service.UserService
import scala.concurrent.{ExecutionContext, Future}
class LoadUserFilter #Inject() (implicit val mat: Materializer, ec: ExecutionContext) extends Filter {
override def apply(nextFilter: RequestHeader => Future[Result]) (requestHeader: RequestHeader): Future[Result] = {
val newRequest = requestHeader.addAttr(TypedKey.apply[String]("user"), "test")
System.out.println(newRequest.attrs(TypedKey.apply[String]("user"))) // attribute not found....
nextFilter(newRequest)
}
}
This is the exception I get:
Caused by: java.util.NoSuchElementException: key not found: user
For some reason the attribute is not added... I am using the updated request but it still doesn't work.
How can I fix this?

If I understand the question correctly, you want to extract user id from the request header. I am not sure if Filter is a good place for that purpose. This can be done in different ways, I have chosen ActionBuilder and ActionTransformers for that. See Action Composition on Play Framework website.
First you need a RedirectingAction. Then I extract token through TokenExtractAction, user Id through UserIdExtractAction, and check auth in PermissionCheckAction.
This way it is composable, performant and reuseable. In your controller, instead of Action, use SecureAction. Then you get all the bonus userId, token, etc for free. And if auth fails, it fails silently and without cluttering your controller. This is based on the example I remember seeing on Play's website. But I had to extend it. Among the alternatives presented there, I found this one to be most useful for authenticating every REST request situations.
Also I am using an actorsystem for authentication. You can easily build one for yourself.
case class RedirectingAction #Inject()(parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
extends ActionBuilder[Request, AnyContent] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]): Future[Result] = {
block(request)
}
}
case class TokenExtractAction #Inject()(parser: BodyParsers.Default)(
implicit val executionContext: ExecutionContext)
extends ActionTransformer[Request, TokenExtractRequest] {
def transform[A](request: Request[A]): Future[TokenExtractRequest[A]] = Future.successful {
val token = request.headers.get(Conf.REST_AUTH_HEADER)
new TokenExtractRequest(token, Some(TokenTool.decodeToken(token.getOrElse("invalid_token")).plaintext), request)
}
}
case class UserIdExtractAction #Inject()(parser: BodyParsers.Default)(implicit val executionContext: ExecutionContext)
extends ActionTransformer[TokenExtractRequest, UserRequest] {
def transform[A](request: TokenExtractRequest[A]): Future[UserRequest[A]] = Future.successful {
new UserRequest(
userAuthRole = TokenTool.tokenGetRoleFromToken(request.token),
userId = TokenTool.tokenGetIdFromToken(request.token),
token = request.token,
plainTextToken = request.plainTextToken,
request = request
)
}
}
case class PermissionCheckActionIndividual #Inject()(authSystem: AuthSystem)(implicit val executionContext: ExecutionContext)
extends ActionFilter[UserRequest] {
implicit val timeout: akka.util.Timeout = 5.seconds
def filter[A](input: UserRequest[A]): Future[Option[Result]] = {
val token = input.headers.get(Conf.REST_AUTH_HEADER).orElse(Some("invalid token"))
val authorizer: ActorRef = authSystem.authorizer
// verify sig and check user validity
val res = authorizer ? CheckUserValidityFromToken(token)
res.flatMap {
case true =>
Future(None)
case false =>
Future(Some(Forbidden("Not permitted")))
}
}
}
class SecureActionProvider #Inject()(val authSystem: AuthSystem, val authService: AuthService)(implicit ec: ExecutionContext,
parser: BodyParsers.Default) {
def SecureAction: ActionBuilder[UserRequest, AnyContent] =
RedirectingAction(parser) andThen TokenExtractAction(parser) andThen UserIdExtractAction(parser) andThen PermissionCheckActionIndividual(
authSystem)
}
in your controller:
def someSecureMethod = secureActionProvider.SecureAction.async ... {
...
}
Hope this helps.

How about doing something like this:
nextFilter(requestHeader).map { result =>
result.withHeaders("user" -> "someUser")
}

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.

Play Framework 2.3.x: Wrap Request object using Scala Oauth in Play Framework

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

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