Deadbolt 2 for the Play Framework in Scala: Defining a custom Handler
Last updated:Following is the bare basic you need to have a functioning custom handler for requests on the Play 2 Framework for Scala.
Controller code
// for the call to @inject
import javax.inject.Inject
// play stuff
import play.api.Play.current
import play.api.mvc._
// deadbolt stuff
import be.objectify.deadbolt.core.PatternType.CUSTOM
// import the handler you will define next
import path.to.your.handlers.MyDefaultHandler
// we need to inject DeadboltActions here
class UserController @Inject()(deadbolt: DeadboltActions) extends Controller {
// a normal action method, but you need to wrap it with a call
// to deadbolt.Pattern
// then define a permission string for it
// set CUSTOM pattern type
// and choose a handler
def add() = deadbolt.Pattern("user.add", CUSTOM, new MyDefaultHandler) {
Action.async { implicit request =>
// ... controller code
Handler code
You need two classes here. One is the regular handler (extends be.objectify.deadbolt.scala.DeadboltHandler
) and a dynamic handler (extends be.objectify.deadbolt.scala.DynamicResourceHandler
).
The regular handler only supports checking for equality and regexp matching. If you need more custom code to check for permissions, you need to implement a dynamic handler.
- Regular handler
import play.api.mvc._
import play.api.mvc.Results.Forbidden
import be.objectify.deadbolt.scala.DeadboltHandler
import be.objectify.deadbolt.scala.DynamicResourceHandler
import be.objectify.deadbolt.core.models.Subject
// for the subject, you need a class that implements
// be.objectify.deadbolt.core.models.Subject
// in my case, it's the following class
import models.domain.User
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
class MyDefaultHandler() extends DeadboltHandler {
// you need to define how to get a subject from a request
// this is generally an object representing a user
// you must return a future of an instance of a class that implements Subject
// in this example, I've checked users must have option "login" in their cookies
// if you don't need a Subject right now, you can just return Future(None)
override def getSubject[A](request: Request[A]): Future[Option[Subject]] =
request.session.get("login") match {
case Some(login) => Future(new User(login))
case _ => Future(None)
}
// this is where you define what dynamic handler for this handler
// this is the class we'll create in the next section
override def getDynamicResourceHandler[A](request: Request[A]): Future[Option[DynamicResourceHandler]] =
Future(Some(new MyDynamicHandler))
// we need this too
def onAuthFailure[A](request: Request[A]): Future[Result] = Future(Forbidden)
// and this
def beforeAuthCheck[A](request: Request[A]): Future[Option[Result]] = Future(None)
}
- Dynamic handler
import play.api.mvc.Request
import be.objectify.deadbolt.scala.DynamicResourceHandler
import be.objectify.deadbolt.scala.DeadboltHandler
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
class MyDynamicHandler extends DynamicResourceHandler {
// edit this to return Future(true) or Future(false)
// depending upon your business rules
def checkPermission[A](
permissionValue: String,
deadboltHandler: DeadboltHandler,
request: Request[A]): Future[Boolean] = Future(false) //no users are allowed
// we won't use this but we need it to be here.
def isAllowed[A](
name: String,
meta: String,
deadboltHandler: DeadboltHandler,
request: Request[A]): Future[Boolean] = Future(true)
}
UPDATE - Accessing the Subject from the Controller
It's possible (and very usual) to access the current Subject
from within the Controller.
Subject
is a Trait so you need to create a Class that extends this Trait, as in the previous example (in my project, I have a class User
that extends that Trait).
One way to do it is to create a Trait called UserMethods
with a method called user
that takes the current request and returns the user.
// CHANGE THIS TO REFLECT THE PLACE WHERE YOUR Handler IS
import path.to.MyDefaultHandler
// this is where my user class is located
import models.domain.User
// play stuff
import play.api.mvc.{Request, Controller}
// these imports are required for using Futures
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
// tell your trait that `self` is a controller
trait UserMethods {self: Controller =>
// create a method that takes the current request that is being handled,
// extracts the subject from it and calls .get() on it because
// the method getSubject() returns a Future[Option[User]]
def user(req: Request[_]):Future[User] =
((new DefaultHandler()).getSubject(req).asInstanceOf[Future[Option[User]]]).map(maybeUser => maybeUser.get)
}
Now you make your controller extend this Trait you've just created and then call the method you just created (remember that it returns a Future
so act accordingly).
Modify the Controller form the previous example as follows:
class UserController @Inject()(deadbolt: DeadboltActions) extends Controller
with UserMethods {
// you don't need to add the default handler in such calls
def sayMyName() = deadbolt.Pattern("user.saymyname", CUSTOM) {
Action.async { implicit request =>
// retrieve the current user as a future
user(request).flatMap{ u=>
// print user's name
println(u.name)
// other code...
}
}
}
Extras
build.sbt
entry for deadbolt 2"be.objectify" %% "deadbolt-scala" % "2.4.0.2",
Play version 2.4.0 was used
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.4.0")
Credits
I just wrote this guide, Steve Chaloner is the guy to thank for the library.