public
Last active

Securing play 2.0 controllers sans boilerplate.

  • Download Gist
Admin.scala
Scala
1 2 3 4 5 6 7 8 9
object Admin extends SecureableController {
implicit val requiredRight: Option[Right] = Some(Right("admin"))
def categories = authorized[AnyContent] { implicit request =>
Ok(html.adminCategories(Messages("admin.categories.title")))
}
 
}
SecureableController.scala
Scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
package controllers
 
import models.User
import models.Right
import play.api.mvc._
import play.api.mvc.Results.Unauthorized
import play.api.mvc.BodyParsers._
import play.mvc.Http
 
trait SecureableController extends Controller {
 
case class AuthenticatedRequest[A](val user: User, val request: Request[A]) extends WrappedRequest(request)
 
implicit def user(implicit request : RequestHeader) : Option[User] = {
// substitute in your own user lookup mechanism here
session.get("email").flatMap(User.findOne(_))
}
 
/**
* A utility function that produces different actions by applying either of two request handling
* functions depending on whether or not a user is authenticated
*
* @param allowed the function used where an authenticated user is found
* @param notAllowed the function used where no user is found
* @param parser a BodyParser
*
*/
def authenticated[A](allowed: AuthenticatedRequest[A] => Result,
notAllowed: Request[A] => Result = ((r : Request[A]) => Unauthorized))
(implicit parser: BodyParser[A] = parse.anyContent) = {
Action(parser) { implicit request =>
user.map { user =>
allowed(AuthenticatedRequest(user, request))
}.getOrElse(notAllowed(request))
}
}
/**
* A utility function that produces different actions by applying either of two request handling
* functions depending on whether or not a user is authenticated and has the required rights
*
* @param allowed the function used where an authenticated user is found
* @param requiredRight the right required for this to be allowed
* @param notAllowed the function used where no user is found
* @param parser a BodyParser
*
*/
def authorized[A](allowed: AuthenticatedRequest[A] => Result,
notAllowed: Request[A] => Result = ((r : Request[A]) => Unauthorized))
(implicit parser: BodyParser[A] = parse.anyContent, requiredRight: Option[Right] = None) = {
authenticated ({ implicit request: AuthenticatedRequest[A] =>
requiredRight match {
case None => allowed(request)
case Some(right) => if (request.user.permitted(right)) allowed(request) else notAllowed(request)
}
},notAllowed)(parser)
}
}

The idea here was to create a low boilerplate way of securing Play 2.0 controllers depending on the user's rights.

The user model is simple. User is a case class which has a companion which extends salat ModelCompanion (since I'm using Mongo). User has Roles, Role has Rights, simple case classes again. User.permitted checks that User.roles contains a Right which permits the right passed in (at the moment just simple string ==). All this pipework is easily switchable.

The SecureableController trait can be extended by any Controller and used as in the Admin example. It will default to a simple 401 empty response if the user isn't present, or is present but isn't permitted, otherwise it'll evaluate the "allowed" function. Also, your User will be in scope for any implicits in resulting views.

I've posted it as an example you're welcome to use, I'd love to hear of any ways to make this simpler, cleaner or more useful. I used info from Julien Richard-Foy's excellent answer to this question on Stack Overflow and the Play 2.0 documentation on Action composition to put this together.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.