I am trying to create an ActionBuilder which checks if the user is loggedin and if so, add the user object to the request(AuthenticatedRequest). With MySQL this would be easy because resolving the user would not get a Future object. But in this particular case, we use MongoDB with ReactiveMongo for Play, which does return a future value.
I have made this little snippet here so far. But it gets me a type mismatch:
type mismatch; found : scala.concurrent.Future[Option[models.User]] => scala.concurrent.Future[Object] required: Object => ?
object Authenticated extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[SimpleResult]) = {
import models.User
(for{
sID <- request.session.get("sessionID")
code <- request.session.get("otherCode")
user: Future[Option[User]] <- models.Session.getUserBySessionAndCode(sID, code)
} yield {
(for{
uAbs <- user
} yield {
if(uAbs.isDefined) {
block(AuthenticatedRequest(uAbs.get, request))
}else{
BadRequest
}
})
}).getOrElse(Future.successful(BadRequest))
}
}
Do you have any idea how to move on from here? Maybe this is even the wrong approach.
Thanks!
How about separating the steps into smaller chunks and explicitly typing them the way you expect the types should be, this way it will be clearer and you will find out where your idea and what you have written goes in different directions, for example:
def userFromRequest(request: Request): Future[Option[User]] =
for{
sID <- request.session.get("sessionID")
code <- request.session.get("otherCode")
maybeUser <- models.Session.getUserBySessionAndCode(sID, code)
} yield maybeUser
def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[SimpleResult]) = {
userFromRequest(request).flatMap {
case None => Future.successful(BadRequest)
case Some(user) => block(AuthenticatedRequest(user, request))
}
}
Related
I want to refactor by update action below to look a little more readable and also handle the failure case better
The userService has the following functions:
class UserService {
def getUserByUsername: Future[Option[Int]] // which is the UserId
def getUserById: Future[User]
}
My action looks like:
def update(userId: Int) = Action.async { implicit request =>
request.body.validate[User] match {
case JsSuccess(user, _) => {
userService.getUserByUsername(user.username).map { userId =>
userService.getUserById(userId.get).map { existingUser =>
userService.update(user.username)
Ok
}
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
How do I handle the situation where getUserByUsername returns a None?
Would this look cleaner if it was in a for comprehension, is it better style?
You have some missing data in your questions such as case classes for the User model, userService class.
also better to attach the original function.
Anyways, I will do something as follows:
def update(userId: Int) = Action { implicit request =>
request.body.validate[User] match {
case JsSuccess(user: User, _) => {
val userId = getUserByUsername(user.username)
userId match {
case Some(userId) => {
for {
_ <- userService.getUserById(userId)
_ <- userService.update(user.username)
} yield Ok
}.recover {
case t: Throwable =>
Metrics.errOnUpdate.increment() // Some metric to monitor
logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error
InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
}
case None => Future.successful(NotFound(s"No such user with ${user.username}"))
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
Note: If .update returns Future, you actually not waiting to update before returning Ok to the user, thus, if its fails, its still returns Ok.
To fix that, use flatMap and then map the value of update response.
You can also separate the recovering for the getUserById and update if you prefer.
Edit:
def update(userId: Int) = Action { implicit request =>
request.body.validate[User] match {
case JsSuccess(user: User, _) => {
getUserByUsername(user.username).flatMap {
case Some(userId) => for {
_ <- userService.getUserById(userId)
_ <- userService.update(user.username)
} yield Ok
case None => Future.successful(NotFound(s"No such user with ${user.username}"))
}
}.recover {
case t: Throwable =>
Metrics.errOnUpdate.increment() // Some metric to monitor
logger.error(s"update userId: $userId failed with ex: ${t.getMessage}") // log the error
InternalServerError(Json.toJson(Json.obj("error" -> "Failure occured on update"))) // return custom made exception to the client
}
}
case JsError(err) => Future.sucessful(BadRequest(err))
}
}
First, you probably need to use Option.fold:
#inline final def fold[B](ifEmpty: => B)(f: A => B)
Then you can do something like this:
def update(userId: Int) = Action.async { implicit request =>
def handleJsonErrors(errors: Seq[(JsPath, collection.Seq[JsonValidationError])]): Future[Result] = ???
def updateUser(userWithoutId: User): Future[Result] = {
for {
userId <- userService.getUserByUsername(userWithoutId.username)
_ <- userService.getUserById(userId.get)
_ <- userService.update(userWithoutId.username)
} yield {
Ok
}
}
request.body.asJson.fold {
Future.successful(BadRequest("Bad json"))
} {
_.validate[User].fold(handleJsonErrors, updateUser).recover {
case NonFatal(ex) =>
InternalServerError
}
}
}
This is a really simple use case but I don't find any elegent way to handle it.
Below what I'm trying to do. It's pretty explicit...
Note: users.byEmail return a Future[Option[User]].
override def invokeBlock[A](request: Request[A], block: UserRequest[A] => Future[Result]): Future[Result] = {
val useEmail: Option[String] = request.session.get("userEmail")
if (useEmail.isEmpty) {
return Future.successful(Results.Unauthorized(("No email")))
}
val user: Option[User] = Await.result(users.byEmail(useEmail.get), Duration(1, TimeUnit.MINUTES))
if (user.isEmpty) {
return Future.successful(Results.Unauthorized(("No user")))
}
block(UserRequest(user.get, request))
}
What is the "correct" way to write this ?
Here is a cleaner version:
def invokeBlock[A](request: Request[A], block: UserRequest[A] => Future[Result]): Future[Result] =
request.session.get("userEmail") match {
case None =>
Future.successful(Results.Unauthorized(("No email")))
case Some(useEmail) =>
users.byEmail(useEmail).flatMap {
case None =>
Future.successful(Results.Unauthorized("No user"))
case Some(user) =>
block(UserRequest(user, request))
}
}
The key changes are
Use match rather than if on Option class
Don't use return, this is rarely necessary and may not do what you think it does
Use flatMap on the Future rather than Await.result to avoid blocking
I'm trying to implement an authentication mechanism similar to this example:
def HasToken(action: String => EssentialAction): EssentialAction = EssentialAction { requestHeader =>
val maybeToken = requestHeader.headers.get("X-SECRET-TOKEN")
maybeToken map { token =>
action(token)(requestHeader) // apply requestHeader to EssentialAction produces the Iteratee[Array[Byte], SimpleResult]
} getOrElse {
Done(Unauthorized("401 No Security Token\n")) // 'Done' means the Iteratee has completed its computations
}
}
However, in my case I'm mapping a random token value to a session on the server side stored in Mongodb. The goal was to be able to let a user terminate all his other sessions at will.
However, the data I get from ReactiveMongo is going to be wrapped in a Future.
I would like something like this:
def HasToken(action: String => EssentialAction): EssentialAction = EssentialAction { requestHeader =>
val maybeToken = requestHeader.headers.get("session")
maybeToken map { token =>
//This returns a future..
Session.find(session).map { result =>
result match
case Some(session) => action(session)(requestHeader)
case None => Done(Unauthorized())
}
} getOrElse {
Done(Unauthorized("401 No Security Token\n")) // 'Done' means the Iteratee has completed its computations
}
}
Is this possible with EssentialAction?
Iteratee.flatten goes from Future[Iteratee[A, E]] => Iteratee[A, E] so you could do it like this:
def HasToken(action: String => EssentialAction): EssentialAction = EssentialAction { requestHeader =>
val maybeToken = requestHeader.headers.get("session")
val futureIteratee: Future[Iteratee[Array[Byte], SimpleResult]] = maybeToken map { token =>
//This returns a future..
Session.find(token).map {
case Some(session) => action(session)(requestHeader)
case None => Done[Array[Byte], SimpleResult](Unauthorized("Invalid token"))
}
} getOrElse {
Future.successful(Done[Array[Byte], SimpleResult](Unauthorized("401 No Security Token\n")))
}
Iteratee.flatten(futureIteratee)
}
You can use an ActionBuilder as the invokeBlock method return a Future[SimpleResult] so you can flatMap your future into a call to the underlying block
Something like
object Authenticated extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[SimpleResult]) = {
Session.find(session).map { result =>
result match
case Some(session) => block(new AuthenticatedRequest(session))
case None => Unauthorized()
}
}
}
where AuthenticatedRequest is your request type that wraps your session object
I'm trying to implement authentication in my Play 2.2.1 app, and I can't quite figure out how to make it work with an action that returns a Future[Result].
This post describes pretty close to what I'm trying to do, except without returning Future[Result]:
Play 2.0 Framework, using a BodyParser with an authenticated request
How can I get it to work with Futures? I.e. how would I implement this function:
def IsAuthenticated(f: => String => Request[Any] => Future[Result])
or, better yet, this function:
def IsAuthenticated[A}(b:BodyParser[A])(f: => String => Request[Any] => Future[Result])
which would feed into this function:
def AuthenticatedUser(g: Account => Request[AnyContent] => SimpleResult) = IsAuthenticated {...}
to wrap asynchronous actions in my controllers?
This part I can do:
def IsAuthenticated(f: => String => Request[AnyContent] => Future[SimpleResult]) = {
Security.Authenticated(email, onUnauthorized) {
user => Action.async(request => f(user)(request))
}
}
But if I try to use IsAuthenticated in my wrapper function:
def AuthenticatedUser(g: Account => Request[AnyContent] => Future[SimpleResult]) = IsAuthenticated {
email => implicit request => Account.find(email).map {
opt => opt match {
case Some(account) => g(account)(request)
case None => Future(onUnauthorized(request))
}
}
}
(Account.find returns a Future[Option[Account]] 'cause it's a mongodb call that may take some time. The desire to do the future thing right is what's causing me so much grief now)
I can't get AuthenticatedUser to satisfy the compiler. It says it's getting a Future[Future[SimpleResult]] instead of a Future[SimpleResult].
So, how best to build this whole thing? I need to be able to make authentication wrappers that rely on db calls that are asynchronous.
I'm sure I'm just dense and missing something obvious...
EDIT: Here's what I ended up with. Thank you Jean for pointing me in the right direction.
I found AuthenticatedController while rooting around and it's pretty close to what I'm looking for. I wanted two types of authentication: User (authenticated user) and Administrator (to wrap code for admin tasks).
package controllers
import models.Account
import play.api.mvc._
import scala.concurrent.Future
trait Secured {
class AuthenticatedRequest[A](val account: Account, request: Request[A]) extends WrappedRequest[A](request)
object User extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[SimpleResult]) = {
request.session.get("email") match {
case Some(email) => {
Account.find(email).flatMap {
case Some(account) => {
block(new AuthenticatedRequest(account, request))
}
case _ => Future(Results.Redirect(routes.Index.index()))
}
}
case _ => Future(Results.Redirect(routes.Index.index()))
}
}
}
object Administrator extends ActionBuilder[AuthenticatedRequest] {
def invokeBlock[A](request: Request[A], block: (AuthenticatedRequest[A]) => Future[SimpleResult]) = {
request.session.get("email") match {
case Some(email) => {
Account.find(email).flatMap {
case Some(account) => if (account.admin) {
block(new AuthenticatedRequest(account, request))
} else {
Future(Results.Redirect(routes.Index.index()))
}
case _ => Future(Results.Redirect(routes.Index.index()))
}
}
case _ => Future(Results.Redirect(routes.Index.index()))
}
}
}
}
There have been changes in play 2.2 to make it easier to compose actions. The resource you are referring to is outdated.
Instead you should create a custom action builder by extending ActionBuilder to create your action, this will get you all the fancy apply methods you may need (including async support and all)
For example you may do :
trait MyAction extends Results{
class MyActionBuilder[A] extends ActionBuilder[({ type R[A] = Request[A] })#R] {
def invokeBlock[A](request: Request[A],
block: Request[A] => Future[SimpleResult]) ={
// your authentication code goes here :
request.cookies.get("loggedIn").map { _=>
block(request)
} getOrElse Future.successful(Unauthorized)
}
}
object MyAction extends MyActionBuilder
}
which you can then use as such :
object MyController extends Controller with MyAction{
def authenticatedAction=MyAction {
Ok
}
def asyncAuthenticatedAction=MyAction.async {
Future.successful(Ok)
}
def authenticatedActionWithBodyParser = MyAction(parse.json){ request =>
Ok(request.body)
}
}
For brevity's sake I used a very trivial authentication mechanism you will want to change that :)
Additionally, you can create a custom "request" type to provide additional information. For instance you could define a AuthenticatedRequest as such :
case class AuthenticatedRequest[A](user: User, request: Request[A]) extends WrappedRequest(request)
Provided you have a way to get your user such as
object User{
def find(s:String): Option[User] = ???
}
Then change your builder definition a bit as such
class MyActionBuilder[A] extends
ActionBuilder[({ type R[A] = AuthenticatedRequest[A] })#R] {
def invokeBlock[A](request: Request[A],
block: AuthenticatedRequest[A] => Future[SimpleResult]) ={
// your authentication code goes here :
(for{
userId <- request.cookies.get("userId")
user <- User.find(userId.value)
}yield {
block(AuthenticatedRequest(user,request))
}) getOrElse Future.successful(Unauthorized)
}
}
Your controller now has access to your user in authenticatedActions:
object MyController extends Controller with MyAction{
val logger = Logger("application.controllers.MyController")
def authenticatedAction=MyAction { authenticatedRequest =>
val user = authenticatedRequest.user
logger.info(s"User(${user.id} is accessing the authenticatedAction")
Ok(user.id)
}
def asyncAuthenticatedAction = MyAction.async { authenticatedRequest=>
Future.successful(Ok(authenticatedRequest.user.id))
}
def authenticatedActionWithBodyParser = MyAction(parse.json){ authenticatedRequest =>
Ok(authenticatedRequest.body)
}
}
I have a method, with have a lot of implicit parameters:
def hello(message:String)(implicit a:A,b:B,c:C, ..., user: User) = {...}
Now consider such a class:
object Users extends Controller {
implicit a: A = ...
implicit b: B = ...
...
def index(id:String) = Action {
User.findById(id) match {
case Some(user) => {
implicit val _user = user
hello("implicit")
}
case _ => BadRequest
}
}
}
You can see this line in the above sample:
implicit val _user = user
It exists just to make the object user as an implicit object. Otherwise, I have to call hello as:
hello("implicit")(a,b,c,... user)
I'm thinking if there is any way to improve the code, e.g. we don't need to define that _user variable but make the user is implicit.
Yes, there is a way to eliminate _user variable while making user implicit:
def index(id:String) = Action {
User.findById(id) map (implicit user => hello("implicit")) getOrElse BadRequest
}
UPDATE: Addressing your question about many cases in the comments below.
It all depends what value type is returned by User.findById. If it's Option[User] but you want to match on specific users (assuming User is a case class), then the original solution still applies:
def index(id:String) = Action {
User.findById(id) map { implicit user =>
user match {
case User("bob") => hello("Bob")
case User("alice") => hello("Alice")
case User("john") => hello("John")
case _ => hello("Other user")
}
} getOrElse BadRequest
Or you can match on anything else if you want, as long as User.findById is String => Option[User]
If, on the other hand, User.findById is String => User then you can simply define a helper object like:
object withUser {
def apply[A](user: User)(block: User => A) = block(user)
}
And use it as follows (again assuming User is a case class):
def index(id: String) = Action {
withUser(User findById id) { implicit user =>
user match {
case User("bob") => hello("Bob")
case User("alice") => hello("Alice")
case User("john") => hello("John")
case _ => BadRequest
}
}
}
or matching on some other value, say an Int:
def index(id: String, level: Int) = Action {
withUser(User findById id) { implicit user =>
level match {
case 1 => hello("Number One")
case 2 => hello("Number Two")
case 3 => hello("Number Three")
case _ => BadRequest
}
}
}
I hope this covers all the scenarios you may have.
I know of no trick such as case Some(implicit user) but what about
def hello(message: String, user: User)(implicit a: A, ... z: Z) = ...
def hello(message: String)(implicit a: A, ... z: Z, user: User) = hello(message, user)
case Some(user) => hello("implicit", user)