Community contributed extensions

Deadbolt

Deadbolt is an authorisation mechanism for defining access rights to certain controller methods or parts of a view using a simple AND/OR/NOT syntax. It is based on the original Secure module that comes with the Play! framework.

Note that Deadbolt doesn’t provide authentication! You can still use the existing Secure module alongside Deadbolt to provide authentication, and in cases where authentication is handled outside your app you can just hook up the authorisation mechanism to whatever auth system is used.

Getting started

To install Deadbolt, you can use the modules repository:

play install deadbolt-<required version>

Examples

Check the samples-and-tests folder for examples of all the code required below.

Identifying the current user

In order to know if access to a view or controller is authorised, you need to know who the current user is. Because various applications will have their own concept of a user, Deadbolt defines the models.deadbolt.RoleHolder interface. This interface should be implemented by any class type that represents an object that will try to access a controller method or view.

package models;
import models.deadbolt.Role;
import models.deadbolt.RoleHolder;
import java.util.Arrays;
import java.util.List;
public class MyRoleHolder implements RoleHolder
{
    public List<? extends Role> getRoles()
    {
        return Arrays.asList(new MyRole("foo"),
                             new MyRole("bar"),
                             new MyRole("restricted"));
    }
}

Once a RoleHolder has been implemented, it can return a list of models.deadbolt.Role objects to indicate which roles a RoleHolder has. Again, Role is an interface and so should not intrude on your application’s model architecture.

package models;
import models.deadbolt.Role;
public class MyRole implements Role
{
    private String name;
    public MyRole(String name)
    {
        this.name = name;
    }
    public String getRoleName()
    {
        return name;
    }
}

Hooking Deadbolt into your application

To provide the information needed for the runtime security, you need to implement the controllers.deadbolt.DeadboltHandler interface; an abstract version – controllers.deadbolt.AbstractDeadboltHandler – is also available for convenience when you aren’t using externalised restrictions.

The DeadboltHandler interface defines four methods:

This method will be invoked prior to checking the authorisation of the current RoleHolder. This forms the basis for hooking into authentication, i.e. in this method you can check if the user is actually logged in, and redirect accordingly if not.

This method will typically return the current RoleHolder.

This method is invoked when a restricted resource is accessed without permission. This method may log something; invoke a controller class and set the current user back on the path of not messing around with the address bar.

If your application externalized restrictions, e.g. in a database or XML file, you will need to provide a way to access them by implementing an ExternalizedRestrictionsAccessor. That implementation can then be passed into Deadbolt by returning it from this method. If you don’t use external restrictions in your app, you can safely return null from this method.

Once you’ve implemented DeadboltHandler, you can declare it in your application.conf file. Don’t forget a no-args constructor is required!

deadbolt.handler=controllers.MyDeadboltHandler

If you want to have different DeadboltHandlers used in different contexts, the standard Play mechanism is used:

%production.deadbolt.handler=controllers.ProductionDeadboltHandler
%test.deadbolt.handler=controllers.TestDeadboltHandler

Securing controllers

Controllers are secured using the @With annotation to ensure method calls are checked against the current user.

@With(Deadbolt.class)
public class MyController extends Controller

Rolenames are matched against result of the Role.getRoleName() method of the roles return from RoleHolder#getRoles().

There are two annotations available to define access to a controller method - @Restrict, and @Restrictions.

@Restrict

The @Restrict annotation defines a set of role names that are ANDed together.

// This restricts access to the list method to any role holder with a role whose name is "foo"
@Restrict("foo")
public static void list()
// This restricts access to the list method to any role holder with both "foo" and a "bar" roles.
@Restrict({"foo", "bar"})
public static void list()

@Restrictions

The @Restrictions annotation defines a set of @Restricts that are ORed together.

// This restricts access to the list method to any role holder with a role whose name is "foo".  This is the equivalent to just @Restrict("foo")
@Restrictions(@Restrict("foo"))
public static void list()
// This restricts access to the list method to any role holder with the "foo" role, or roles whose names are "bar" AND "gee".  Note that a role holder with a "bar" OR a "gee" role will be denied access.
@Restrictions([email protected]("foo"), @Restrict({"bar", "gee"})})
public static void list()

Securing views

Views are secured with the deadbolt.restrict tag. This is slightly simpler than the annotations used by controllers, since the syntax is always that of @Restrictions, i.e. ORed role groups in the top level, ANDed roles groups in the inner level:

#{deadbolt.restrict roles:[['foo']]}
    this restricts access to this content to role holders with the "foo" role
#{/deadbolt.restrict}
#{deadbolt.restrict roles:[['foo', 'bar']]}
    this restricts access to this content to role holders with the "foo" AND 'bar' roles
#{/deadbolt.restrict}
#{deadbolt.restrict roles:[['foo'], ['bar']]}
    this restricts access to this content to role holders with the "foo" OR 'bar' role
#{/deadbolt.restrict}

Negating role names

By prefixing a rolename with ! you state the method or view is inaccessible to any role holder with that role.

This restricts access to the list method to any role holder with a "foo" role but no "bar" role.  If a "bar" role is present, access is denied.
@Restrict({"foo", "!bar"})
public static void list()

The same applies in views:

This restricts view rendering for the content to any role holder with a "foo" role but no "bar" role.  If a "bar" role is present, rendering is skipped.
#{deadbolt.restrict roles:[['foo', '!bar']]}
    ...
#{/deadbolt.restrict}

Combining regular and negated role names in views

By nesting regular and negated role names in a view, you can have very fine-grained control over what is displayed. For example,

#{deadbolt.restrict roles:[['foo', 'bar', 'gee']]}
    something
    #{deadbolt.restrict roles:[['!bar']]}
        something only visible to role holders who aren't "bar"s
    #{/deadbolt.restrict}
    something else
#{/deadbolt.restrict}

Externalised restrictions

Deadbolt allows you to annotation controller classes and methods with named restriction trees.

@ExternalRestrictions("admin-only")
public static void edit()
@ExternalRestrictions({"admin", "super-mega-admin"})
public static void edit()

This annotation maps to an externalised restriction – again a combination of ANDs, ORs and NOTs – and so allows you to
define standard security schemes than can be changed at the e.g. database level.

Each name passed to the annotation, e.g. admin and super-mega-admin in the example above, maps to an ExternalizedRestrictions
instance. The results of these instances are OR’d together. Each ExternalizedRestrictions instance has a list of ExternalizedRestriction
instances – each of these instances is also OR’d together. Finally, each ExternalizedRestriction has a list of role names – these are ANDed together, and also support role name
negation.

Below is an example implementation of an ExternalizedRestrictionsAccessor.

import controllers.deadbolt.ExternalizedRestrictionsAccessor;
import models.deadbolt.ExternalizedRestrictions;
import java.util.HashMap;
import java.util.Map;
public class MyExternalizedRestrictionsAccessor implements ExternalizedRestrictionsAccessor
{
    private final Map<String, ExternalizedRestrictions> restrictions = new HashMap<String, ExternalizedRestrictions>();
    public MyExternalizedRestrictionsAccessor()
    {
        restrictions.put("standard",
                         new MyExternalizedRestrictions(new MyExternalizedRestriction("foo"),
                                                        new MyExternalizedRestriction("bar")));
        restrictions.put("one-role-missing",
                         new MyExternalizedRestrictions(new MyExternalizedRestriction("foo"),
                                                        new MyExternalizedRestriction("rab")));
        restrictions.put("admin",
                         new MyExternalizedRestrictions(new MyExternalizedRestriction("admin")));
        restrictions.put("exclude-restricted",
                         new MyExternalizedRestrictions(new MyExternalizedRestriction("!restricted")));
    }
    public ExternalizedRestrictions getExternalizedRestrictions(String name)
    {
        return restrictions.get(name);
    }
}

Mixing annotations

The annotations detailed above can be applied in combination to controller classes and fields. The evaluation is ANDed and the order is

Integrating with authentication systems

For an example of how to integrate Deadbolt with an authentication module, please take a look at the integration-with-secure app in samples-and-tests – this uses the standard Secure module to illustrate the integration.