Integrating Deadbolt

Implementations of the be.objectify.deadbolt.scala.DeadboltHandler trait are used to provide Deadbolt with what it needs to satisfy your project's authorization constraints. This trait has 4 functions.

  • getSubject gets the current user from, for example, the cache or database
  • beforeAuthCheck run a pre-authorization task that can block further execution
  • onAuthFailure defines behaviour for when authorization requirements are not met
  • getDynamicResourceHandler provides a hook into the dynamic constraint types
import be.objectify.deadbolt.scala.models.Subject
import be.objectify.deadbolt.scala.{AuthenticatedRequest, DynamicResourceHandler, DeadboltHandler}
import views.html.security.{login, denied}
import play.api.mvc.{Results, Result, Request}
import play.twirl.api.HtmlFormat

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

class MyDeadboltHandler extends DeadboltHandler {

  override def beforeAuthCheck[A](request: Request[A]): Future[Option[Result]] = Future {None}

  override def getDynamicResourceHandler[A](request: Request[A]): Future[Option[DynamicResourceHandler]] = Future {None}

  override def getSubject[A](request: AuthenticatedRequest[A]): Future[Option[Subject]] = 
    Future {
      request.subject.orElse {
        // replace request.session.get("userId") with how you identify the user
        request.session.get("userId") match {
          case Some(userId) => 
            // get from database, identity platform, cache, etc, if some
            // identifier is present in the request
          case _ => None
        }
      }}

  override def onAuthFailure[A](request: AuthenticatedRequest[A]): Future[Result] = {
    def toContent(maybeSubject: Option[Subject]): (Boolean, HtmlFormat.Appendable) =
      maybeSubject.map(subject => (true, denied(Some(subject))))
                  .getOrElse {(false, login(clientId, domain, redirectUri))}

    getSubject(request).map(maybeSubject => toContent(maybeSubject))
    .map(subjectPresentAndContent =>
      if (subjectPresentAndContent._1) Results.Forbidden(subjectPresentAndContent._2)
      else Results.Unauthorized(subjectPresentAndContent._2))
  }
}

You only need to implement be.objectify.deadbolt.scala.DynamicResourceHandler if you're planning to use Dynamic or Pattern.CUSTOM constraints. Dynamic constraints are tests implemented entirely by your code. This trait has two functions:

  • isAllowed is used by the Dynamic constraint
  • checkPermission is used by the Pattern constraint when the pattern type is CUSTOM

In order to expose your handler (or handlers - you can have more than one) to Deadbolt, you will need to implement the be.objectify.deadbolt.scala.cache.HandlerCache trait. This implementation needs to

  • Provide a default handler; you can always use a specific handler in a template or controller, but if nothing is specified a well-known instance will be used.
  • Provide a named instance

Here's an example of a HandlerCache implementation.

@Singleton
class MyHandlerCache extends HandlerCache {
    val defaultHandler: DeadboltHandler = new MyDeadboltHandler

    // HandlerKeys is an user-defined object, containing instances 
    // of a case class that extends HandlerKey  
    val handlers: Map[Any, DeadboltHandler] = Map(HandlerKeys.defaultHandler -> defaultHandler,
                                                  HandlerKeys.altHandler -> new MyDeadboltHandler(Some(MyAlternativeDynamicResourceHandler)),
                                                  HandlerKeys.userlessHandler -> new MyUserlessDeadboltHandler)

    // Get the default handler.
    override def apply(): DeadboltHandler = defaultHandler

    // Get a named handler
    override def apply(handlerKey: HandlerKey): DeadboltHandler = handlers(handlerKey)
}

Finally, expose your handlers to Deadbolt. To do this, you will need to create a small module that binds your handler cache by type...

package com.example.modules

import be.objectify.deadbolt.scala.cache.HandlerCache
import play.api.inject.{Binding, Module}
import play.api.{Configuration, Environment}
import com.example.security.MyHandlerCache

class CustomDeadboltHook extends Module {
    override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = Seq(
        bind[HandlerCache].to[MyHandlerCache]
    )
}

...and add it to your application.conf.

play {
    modules {
        enabled += be.objectify.deadbolt.scala.DeadboltModule
        enabled += com.example.modules.CustomDeadboltHook
    }
}