Deadbolt 2 for the Play Framework in Scala: Defining a custom Handler

Deadbolt 2 for the Play Framework in Scala: Defining a custom Handler

Last updated:
Deadbolt 2 for the Play Framework in Scala: Defining a custom Handler
Source

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.sbtentry 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.

Dialogue & Discussion