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 installl 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;
    }
}

In a move taken directly from the Secure module, you define the hooks into your application by extending a static inner class of Deadbolt – DeadboltHandler. This class has two key methods -

static RoleHolder getRoleHolder()

getRoleHolder() should return the current user, which must implement the models.deadbolt.RoleHolder interface.

static void onAccessFailure(String controllerClassName)

onAccessFailure(String) should handle the what happens following an unauthorised attempt to access a secured controller method.

A further method, getExternalizedRestrictionsAccessor() can be implemented if you are supported externalised restriction trees.

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({@Restrict("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