9. Using Deadbolt 2 with Play 2 Scala projects

Deadbolt for Scala provides an idiomatic API for dealing with Scala controllers and templates rendered from Scala controllers in Play applications.

9.1 The Deadbolt Handler

For any module - or framework - to be useable, it must provide a mechanism by which it can be hooked into your application. For Deadbolt, the central hook is the be.objectify.deadbolt.scala.DeadboltHandler trait. The four functions defined by this trait are crucial to Deadbolt - for example, DeadboltHandler#getSubject gets the current user (or subject, to use the correct security terminology), whereas DeadboltHandler#onAccessFailure is used to generate a response when authorization fails.

DeadboltHandler implementations should be stateless.

For each method, a Future is returned. If the future may complete to have an empty value, e.g. calling getSubject when no subject is present, the return type is Future[Option].

Despite the use of the definite article in the section title, you can have as many Deadbolt handlers in your app as you wish.

Performing pre-constraint tests

Before a constraint is applied, the Future[Option[Result]] beforeAuthCheck[A](request: Request[A]) function of the current handler is invoked. If the resulting future completes Some, the target action is not invoked and instead the result of beforeAuthCheck is used for the HTTP response; if the resulting future completes to None the action is invoked with the Deadbolt constraint applied to it.

Obtaining the subject

To get the current subject, the Future[Option[Subject]] getSubject[A](request: Request[A]) function is invoked. Returning a None indicates there is no subject present - this is a valid scenario.

Dealing with authorization failure

When authorization fails, the Future[Result] onAccessFailure[A](request: Request[A]) function is used to obtain a result for the HTTP response. The result required from the Future returned from this method is a regular play.api.mvc.Result, so it can be anything you chose. You might want to return a 403 forbidden, redirect to a location accessible to everyone, etc.

Dealing with dynamic constraints

Dynamic constraints, which are Dynamic and Pattern.CUSTOM constraints, are dealt with by implementations of DynamicResourceHandler; this will be explored in a later chapter. For now, it’s enough to say Future[Optional[DynamicResourceHandler]] getDynamicResourceHandler[A](request: Request[A]) is invoked when a dynamic constraint it used.

9.2 Expose your DeadboltHandlers with a HandlerCache

Deadbolt uses dependency injection to expose handlers in a type-safe manner. Various components of Deadbolt, which will be explored in later chapters, require an instance of be.objectify.deadbolt.scala.cache.HandlerCache - however, no such implementations are provided.

Instead, you need to implement your own version. This trait extends Function[HandlerKey, DeadboltHandler] and Function0[DeadboltHandler] and uses them as follows

  • handlers() will express the default handler
  • handlers(key: HandlerKey) will express a named handler

Here’s one possible implementation, using hard-coded handlers.

An example handler cache
 1 @Singleton
 2 class MyHandlerCache extends HandlerCache {
 3     val defaultHandler: DeadboltHandler = new MyDeadboltHandler
 4 
 5     // HandlerKeys is an user-defined object, containing instances 
 6     // of a case class that extends HandlerKey
 7     val handlers: Map[Any, DeadboltHandler] = 
 8         Map(HandlerKeys.defaultHandler -> defaultHandler,
 9             HandlerKeys.altHandler -> 
10                   new MyDeadboltHandler(Some(MyAlternativeDynamicResourceHandler)),
11             HandlerKeys.userlessHandler -> new MyUserlessDeadboltHandler)
12 
13     // Get the default handler.
14     override def apply(): DeadboltHandler = defaultHandler
15 
16     // Get a named handler
17     override def apply(handlerKey: HandlerKey): DeadboltHandler = handlers(handlerKey)
18 }

Finally, create a small module which binds your implementation.

Binding the handler cache
 1 package com.example.modules
 2 
 3 import be.objectify.deadbolt.scala.cache.HandlerCache
 4 import play.api.inject.{Binding, Module}
 5 import play.api.{Configuration, Environment}
 6 import com.example.security.MyHandlerCache
 7 
 8 class CustomDeadboltHook extends Module {
 9     override def bindings(environment: Environment, 
10                           configuration: Configuration): Seq[Binding[_]] = Seq(
11         bind[HandlerCache].to[MyHandlerCache]
12     )
13 }

9.3 application.conf

Declare the necessary modules

Both be.objectify.deadbolt.scala.DeadboltModule and your custom bindings module must be declared in the configuration.

Enable your module
1 play {
2   modules {
3     enabled += be.objectify.deadbolt.scala.DeadboltModule
4     enabled += com.example.modules.CustomDeadboltHook
5   }
6 }

9.4 Using compile-time dependency injection

If you prefer to wire everything together with compile-time dependency, you don’t need to create a custom module or add DeadboltModule to play.modules.

Instead, dependencies are handled using a custom ApplicationLoader. To make things easier, various Deadbolt components are made available via the be.objectify.deadbolt.scala.DeadboltComponents trait. You will still need to provide a couple of things, such as your HandlerCache implementation, and you’ll then have access to all the usual pieces of Deadbolt.

An example ApplicationLoader for compile-time DI
 1 class CompileTimeDiApplicationLoader extends ApplicationLoader  {
 2   override def load(context: Context): Application 
 3              = new ApplicationComponents(context).application
 4 }
 5 
 6 class ApplicationComponents(context: Context) 
 7                             extends BuiltInComponentsFromContext(context) 
 8                             with DeadboltComponents
 9                             with EhCacheComponents {
10 
11   // Define a pattern cache implementation
12   // defaultCacheApi is a component from EhCacheComponents
13   override lazy val patternCache: PatternCache = new DefaultPatternCache(defaultCacheApi)
14 
15   // Declare something required by MyHandlerCache
16   lazy val subjectDao: SubjectDao = new TestSubjectDao
17 
18   // Specify the DeadboltHandler implementation to use
19   override lazy val handlers: HandlerCache = new MyHandlerCache(subjectDao) 
20 
21   // everything from here down is application-level
22   // configuration, unrelated to Deadbolt, such as controllers, routers, etc
23   // ...
24 }

The components provided by Deadbolt are

  • scalaAnalyzer - constraint logic
  • deadboltActions - for composing actions
  • actionBuilders - for building actions
  • viewSupport - for template constraints
  • patternCache - for caching regular expressions. You need to define this yourself in the application loader, but as in the example above it’s easy to use the default implementation
  • handlers - the implementation of HandlerCache that you provide
  • configuration - the application configuration
  • ecContextProvider - the execution context for concurrent operations. Defaults to scala.concurrent.ExecutionContext.global
  • templateFailureListenerProvider - for listening to Deadbolt-related errors that occur when rendering templates. Defaults to a no-operation implementation

Once you’ve defined your ApplicationLoader, you need to add it to your application.conf.

Specify the application loader to use
1 play {
2   application {
3     loader=com.example.myapp.CompileTimeDiApplicationLoader
4   }
5 }

9.5 Tweaking Deadbolt

Deadbolt Scala-specific configuration lives in the deadbolt.scala namespace.

There is one setting, deadbolt.scala.view-timeout, which is millisecond timeout applied to blocking calls when rendering templates. This defaults to 1000ms.

Personally, I prefer the HOCON (Human-Optimized Config Object Notation) syntax supported by Play, so I would recommend the following:

Example configuration
1 deadbolt {
2   scala {
3     view-timeout=500
4   }
5 }

Execution context

By default, all futures are executed in the scala.concurrent.ExecutionContext.global context. If you want to provide a separate execution context, you can plug it into Deadbolt by implementing the DeadboltExecutionContextProvider trait.

Providing a custom execution context
1 import be.objectify.deadbolt.scala.DeadboltExecutionContextProvider
2 
3 class CustomDeadboltExecutionContextProvider extends DeadboltExecutionContextProvider {
4     override def get(): ExecutionContext = ???
5 }

NB: This provider is invoked twice, once in DeadboltActions and once in ViewSupport. Make sure you take this into account when you implement the get() function.

Once you’ve implemented the provider, you need to declare it in your custom module (see CustomDeadboltHook above for further information).

Binding the custom execution context provider
1 class CustomDeadboltHook extends Module {
2     override def bindings(environment: Environment, 
3                           configuration: Configuration): Seq[Binding[_]] = Seq(
4         bind[HandlerCache].to[MyHandlerCache],
5         bind[DeadboltExecutionContextProvider].to[CustomDeadboltExecutionContextProvider]
6     )
7 }