Integrating Deadbolt

Implementations of the be.objectify.deadbolt.java.DeadboltHandler interface are used to provide Deadbolt with what it needs to satisfy your project's authorization constraints. This interface 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

You only need to implement be.objectify.deadbolt.java.DynamicResourceHandler if you're planning to use Dynamic or Pattern.CUSTOM constraints. Dynamic constraints are tests implemented entirely by your code. This interface 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.java.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
public class MyHandlerCache implements HandlerCache
{
    private final DeadboltHandler defaultHandler = new MyDeadboltHandler();
    private final Map<String, DeadboltHandler> handlers = new HashMap<>();

    public MyHandlerCache()
    {
        handlers.put(HandlerKeys.DEFAULT.key, defaultHandler);
        handlers.put(HandlerKeys.ALT.key, new MyAlternativeDeadboltHandler());
    }

    @Override
    public DeadboltHandler apply(final String key)
    {
        return handlers.get(key);
    }

    @Override
    public DeadboltHandler get()
    {
        return defaultHandler;
    }
}

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.java.cache.HandlerCache;
import play.api.Configuration;
import play.api.Environment;
import play.api.inject.Binding;
import play.api.inject.Module;
import scala.collection.Seq;
import security.MyHandlerCache;

import javax.inject.Singleton;

public class CustomDeadboltHook extends Module
{
    @Override
    public Seq<Binding<?>> bindings(final Environment environment,
                                    final Configuration configuration)
    {
        return seq(bind(HandlerCache.class).to(MyHandlerCache.class).in(Singleton.class));
    }
}

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

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

Injecting handlers into the HandlerCache

In the example handler cache above, the DeadboltHandler instance is created directly within the object. What happens if you want to inject it? Just use the existing dependency injection mechanism.

public class CustomDeadboltHook extends Module
{
    @Override
    public Seq<Binding<?>> bindings(final Environment environment,
                                    final Configuration configuration)
    {
        return seq(bind(DeadboltHandler.class).to(MyDeadboltHandler.class).in(Singleton.class),
                   bind(HandlerCache.class).to(MyHandlerCache.class).in(Singleton.class));
    }
}

Your handler cache can now be injected with that instance.

@Singleton
public class MyHandlerCache implements HandlerCache
{
    private final DeadboltHandler defaultHandler;
    private final Map<String, DeadboltHandler> handlers = new HashMap<>();

    @Inject
    public MyHandlerCache(final DeadboltHandler handler)
    {
        this.defaultHandler = handler;
        handlers.put(HandlerKeys.DEFAULT.key, defaultHandler);
        handlers.put(HandlerKeys.ALT.key, new MyAlternativeDeadboltHandler());
    }

    // ...
}

But, what happens if you want to have multiple handlers defined and injected? The most flexible mechanism is to qualify the bindings through the use of custom annotations. For the following example, we will have two handler implementations; scaling up to more handlers just follows the same logical process.

This example uses Guice, which is the default DI framework of Play; if you're using a different DI framework, you will need to check how it supports multiple bindings of an interface.

First, create one annotation per handler implementation. These annotations will be annotated with com.google.inject.BindingAnnotation.

import com.google.inject.BindingAnnotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class HandlerQualifiers
{
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.PARAMETER})
    @BindingAnnotation
    public @interface MainHandler
    {
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.PARAMETER})
    @BindingAnnotation
    public @interface SomeOtherHandler
    {
    }
}

You can now use these annotations on their respective handlers.

@HandlerQualifiers.MainHandler
public class MyDeadboltHandler extends AbstractDeadboltHandler

Create a constructor in your handler cache which accepts the handlers. The parameters use the same annotations to identify which instance will be passed as an argument for that parameter.

public class MyHandlerCache implements HandlerCache
{
    private final DeadboltHandler handler;

    private final Map<String, DeadboltHandler> handlers = new HashMap<>();

    @Inject
    public MyHandlerCache(@HandlerQualifiers.MainHandler final DeadboltHandler handler,
                          @HandlerQualifiers.SomeOtherHandler final DeadboltHandler otherHandler)
    {
        this.handler = handler;
        this.handlers.put(handler.handlerName(),
                          handler);
        this.handlers.put(otherHandler.handlerName(),
                          otherHandler);
    }

    @Override
    public DeadboltHandler apply(final String name)
    {
        return handlers.get(name);
    }

    @Override
    public DeadboltHandler get()
    {
        return handler;
    }
}

The final step is to update your bindings to use the qualifiers.

public class CustomDeadboltHook extends Module
{
    @Override
    public Seq<Binding<?>> bindings(final Environment environment,
                                    final Configuration configuration)
    {
        return seq(bind(DeadboltHandler.class).qualifiedWith(HandlerQualifiers.MainHandler.class).to(MyDeadboltHandler.class).in(Singleton.class),
                   bind(DeadboltHandler.class).qualifiedWith(HandlerQualifiers.SomeOtherHandler.class).to(SomeOtherDeadboltHandler.class).in(Singleton.class),
                   bind(HandlerCache.class).to(MyHandlerCache.class).in(Singleton.class));
    }
}