5. Java controller constraints
If you like annotations in Java code, you’re in for a treat. If you don’t, this may be a good time to consider the Scala version. Actually, that’s not strictly true - it also turns out that if you take long enough to write a book, you add new features that allow constraints to be applied during routing; we’ll get to that later, I just didn’t want you to panic at the thought of yet more annotations in your code.
One very important point to bear in mind is the order in which Play evaluates annotations. Annotations applied to a method are applied to annotations applied to a class. This can lead to situations where Deadbolt method constraints deny access because information from a class constraint. See the section on deferring method-level interceptors for a solution to this.
As with the previous chapter, here is a breakdown of all the Java annotation-driven interceptors available in Deadbolt Java, with parameters, usages and tips and tricks. Anywhere you see ec.current(), this is obtaining a java.util.concurrent.Executor from the HttpExecutionContext which has been injected into the controller. To avoid repeating it in every example, assume that each example method exists in a controller similar to the following.
1 import java.util.concurrent.Executor;
2 import javax.inject.Inject;
3 import play.libs.concurrent.HttpExecutionContext;
4
5 public class FooController extends Controller
6 {
7 private final HttpExecutionContext ec;
8
9 @Inject
10 public void FooController(final HttpExecutionContext ec)
11 {
12 this.ec = ec;
13 }
14
15 // ...other methods
16 }
Static constraints
Static constraints, are implemented entirely within Deadbolt because it can finds all the information needed to determine authorization automatically. For example, if a constraint requires two roles, “foo” and “bar” to be present, the logic behind the Restrict constraint knows it just needs to check the roles of the current subject.
The static constraints available are
SubjectPresentSubjectNotPresentRestrictRoleBasedPermissions-
Pattern- when using EQUALITY or REGEX
Dynamic constraints
Dynamic constraints are, as far as Deadbolt is concerned, completely arbitrary; they’re handled by implementations of DynamicResourceHandler.
The dynamic constraints available are
Dynamic-
Pattern- when using CUSTOM
Anything else?
Now you mention it, you can combine all of the above in arbitrary compositions to define constraint trees. These can be applied using the Composite annotation.
5.1 SubjectPresent
SubjectPresent is one of the simplest constraints offered by Deadbolt. It checks if there is a subject present, by invoking DeadboltHandler#getSubject and allows access if the result is an Optional containing a value.
@SubjectPresent can be used at the class or method level.
| Parameter | Type | Default | Notes |
|---|---|---|---|
| content | String | ”” | A hint to indicate the content expected in the response. This value will be passed to DeadboltHandler#onAccessFailure. The value of this parameter is completely arbitrary. |
| handlerKey | String | “defaultHandler” | The name of a handler in the HandlerCache. |
| deferred | boolean | false | If true, the interceptor will not be applied until a DeadboltDeferred annotation is encountered. |
| forceBeforeAuthCheck | false | false | By default, the beforeAuthCheck method of the DeadboltHandler is not invoked before this constraint is applied, because implementations of beforeAuthCheck that return a non-empty Optional when no subject is present can short-cut this constraint. |
1 @SubjectPresent
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> index()
5 {
6 // this method will not be invoked unless there is a subject present
7 ...
8 }
9
10 public CompletionStage<Result> search()
11 {
12 // this method will not be invoked unless there is a subject present
13 ...
14 }
15 }
1 // Deny access to a single method of a controller unless there is a user present
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> index()
5 {
6 // this method is accessible to anyone
7 ...
8 }
9
10 @SubjectPresent
11 public CompletionStage<Result> search()
12 {
13 // this method will not be invoked unless there is a subject present
14 ...
15 }
16 }
5.2 SubjectNotPresent
SubjectNotPresent is the opposite in functionality of SubjectPresent. It checks if there is a subject present, by invoking DeadboltHandler#getSubject and allows access only if the result is an empty Optional.
@SubjectNotPresent can be used at the class or method level.
| Parameter | Type | Default | Notes |
|---|---|---|---|
| content | String | ”” | A hint to indicate the content expected in the response. This value will be passed to DeadboltHandler#onAccessFailure. The value of this parameter is completely arbitrary. |
| handlerKey | String | “defaultHandler” | The name of a handler in the HandlerCache. |
| deferred | boolean | false | If true, the interceptor will not be applied until a DeadboltDeferred annotation is encountered. |
| forceBeforeAuthCheck | false | false | By default, the beforeAuthCheck method of the DeadboltHandler is not invoked before this constraint is applied, because implementations of beforeAuthCheck that return a non-empty Optional when no subject is present can short-cut this constraint. |
1 @SubjectNotPresent
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> index()
5 {
6 // this method will not be invoked if there is a subject present
7 ...
8 }
9
10 public CompletionStage<Result> search()
11 {
12 // this method will not be invoked if there is a subject present
13 ...
14 }
15 }
1 // Deny access to a single method of a controller if there is a user present
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> index()
5 {
6 // this method is accessible to anyone
7 ...
8 }
9
10 @SubjectNotPresent
11 public CompletionStage<Result> search()
12 {
13 // this method will not be invoked unless there is not a subject present
14 ...
15 }
16 }
5.3 Restrict
The Restrict constraint requires that a) there is a subject present, and b) the subject has ALL the roles specified in the at least one of the Groups in the constraint. The key thing to remember about Restrict is that it ANDs together the role names within a group and ORs between groups.
Notation
The role names specified in the annotation can take two forms.
- Exact form - the subject must have a role whose name matches the required role exactly. For example, for a constraint
@Restrict("foo")the Subject must have aRolewhose name is “foo”. - Negated form - if the required role starts with a !, the constraint is negated. For example, for a constraint
@Restrict("!foo")the Subject must not have aRolewhose name is “foo”.
@Restrict can be used at the class or method level.
| Parameter | Type | Default | Notes |
|---|---|---|---|
| value | Group[] | For each Group, the roles that must (or in the case of negation, must not) be held by the Subject. When the restriction is applied, the Group instances are OR’d together. |
|
| content | String | ”” | A hint to indicate the content expected in the response. This value will be passed to DeadboltHandler#onAccessFailure. The value of this parameter is completely arbitrary. |
| handlerKey | String | “defaultHandler” | The name of a handler in the HandlerCache. |
| deferred | boolean | false | If true, the interceptor will not be applied until a DeadboltDeferred annotation is encountered. |
1 @Restrict(@Group{"editor", "viewer"})
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> index()
5 {
6 // this method will not be invoked unless the subject has editor and viewer roles
7 ...
8 }
9
10 public CompletionStage<Result> search()
11 {
12 // this method will not be invoked unless the subject has editor and viewer roles
13 ...
14 }
15 }
Example 2
1 public class MyController extends Controller
2 {
3 @Restrict(@Group("editor"))
4 public CompletionStage<Result> edit()
5 {
6 // this method will not be invoked unless the subject has editor role
7 ...
8 }
9
10 @Restrict(@Group("view"))
11 public CompletionStage<Result> view()
12 {
13 // this method will not be invoked unless the subject has viewer role
14 ...
15 }
16 }
1 @Restrict(@Group({"editor", "!viewer"}))
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> edit()
5 {
6 // this method will not be invoked unless the subject has editor role AND does NOT\
7 have the viewer role
8 ...
9 }
10
11 public CompletionStage<Result> view()
12 {
13 // this method will not be invoked unless the subject has editor role AND does NOT\
14 have the viewer role
15 ...
16 }
17 }
1 @Restrict({@Group("editor"), @Group("viewer")})
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> index()
5 {
6 // this method will not be invoked unless the subject has editor or viewer roles
7 ...
8 }
9
10 public CompletionStage<Result> search()
11 {
12 // this method will not be invoked unless the subject has editor or viewer roles
13 ...
14 }
15 }
1 @Restrict({@Group({"customer", "viewer"}), @Group({"support", "viewer"})})
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> index()
5 {
6 // this method will not be invoked unless the subject has customer and viewer,
7 // or support and viewer roles
8 ...
9 }
10 }
1 @Restrict({@Group("customer", "!viewer"), @Group("support", "!viewer")})
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> index()
5 {
6 // this method will not be invoked unless the subject has customer but not viewer \
7 roles,
8 // or support but not viewer roles
9 ...
10 }
11 }
5.4 RoleBasedPermissions
Roles and permissions are separate concepts. Roles do not imply permissions, and vice versa.
Roles are inherently tied to Restrict , so when @Restrict is used it uses Subject#getRoles and looks if the subject has the required role. If multiple (i.e. AND) or alternative (i.e. OR) relationships are defined - let’s say @Restrict(@Group({"admin", "printer"})), in which a subject must have the (admin AND printer) roles - that is also handled. This is done through a straightforward comparison of the required role name and the name of the roles the subject has, obtained via Role#getName.
Permissions are inherently tied to Pattern, and allow for greater granularity. The Pattern constraint uses Subject#getPermissions and matches e.g. @Pattern("admin.printer") against the subject’s permissions.
Let’s assume a company that has various departments, e.g. IT support, public relations, etc, and take two subjects.
Subject A has been assigned the following:
- Roles
adminpr- Permissions
admin.pr.blog.post.createadmin.pr.blog.post.deleteadmin.pr.blog.post.update
Subject B has been assigned the following:
- Roles
adminit- Permissions
admin.it.printeradmin.it.ldapadmin.it.router
A controller action annotated with @Restrict(@Group("admin")) would be accessible to both subject A and subject B, even though they are admins in different departments. To restrict the action to admins of the IT department, you would need to use @Restrict(@Group({"admin", "it"})).
A controller action annotated with @Pattern("admin.*") would be accessible to both subject A and subject B, even though they are admins in different departments. To restrict the action to admins of the IT department, you would need to use
@Pattern("admin.it.*").
However - and here’s the important bit - removing the admin role from a user would deny them access to actions marked with @Restrict(@Group("admin")) but still allow them access to actions marked with @Pattern("admin.*"). If you only use roles or only use permissions, this isn’t an issue. If you do use both, it can lead to inconsistencies and indeterminate authorizations.
Role-based permissions are a way of combining the two, by associating permissions with roles using DeadboltHandler#getPermissionsForRole.
- an action is constrained to a role specified using e.g.
@RoleBasedPermissions("admin") - the Deadbolt handler is used to get the permissions associated with the admin role, using
DeadboltHandler#getPermissionsForRole - the permissions explicitly obtained from Subject#getPermissions must provide a match.
This is similar to using @Pattern, except that you can only constraint access with a single permission this way, whereas @RoleBasedPermissions allows you to constraint access with any of the permissions linked to a role. Let’s assume that a role foo gives these permissions:
admin.pr.blog.post.createadmin.pr.blog.post.deleteadmin.pr.blog.post.updateadmin.pr.twitter.post
Using @RoleBasedPermissions("foo"), you can constrain access to a subject with any of those permissions. However, if you want to have an access to an action constrained to subjects with admin.pr.twitter permissions, you can target a subset of subjects with the foo role using @Pattern("admin.pr.twitter.*"). In practice, you may not want to mix the different approaches but the possibility is there.
This still doesn’t provide a mechanism for assigning those permissions to a subject when a role is assigned, because this is more properly something that should be handled by the application itself. This could be done by injecting the handler cache into your user management code, which can then be used to obtain the default handler, and when a subject is given a role, use DeadboltHandler#getPermissionsForRole to find out which permissions to assign the subject at the same time. This make a small assumption: even if you have multiple handlers, the result of DeadboltHandler#getPermissionsForRole would always be the same for any given role.
Equally, when removing a role from a subject, DeadboltHandler#getPermissionsForRole can be used to determine the subset of permissions to remove from the subject, e.g. (permissions of removed role) \ (permissions of all other roles held by subject).
| Parameter | Type | Default | Notes |
|---|---|---|---|
| value | String | The role name. | |
| content | String | ”” | A hint to indicate the content expected in the response. This value will be passed to DeadboltHandler#onAccessFailure. The value of this parameter is completely arbitrary. |
| handlerKey | String | “defaultHandler” | The name of a handler in the HandlerCache. |
| deferred | boolean | false | If true, the interceptor will not be applied until a DeadboltDeferred annotation is encountered. |
1 @RoleBasedPermissions("foo")
2 public CompletionStage<Result> someMethod()
3 {
4 // the method will execute if the subject has one or more of the permissions obtained
5 // via DeadboltHandler#getPermissionsForRole("fooa")
6 }
5.5 Dynamic
The most flexible constraint - this is a completely user-defined constraint that uses DynamicResourceHandler#isAllowed to determine access.
@Dynamic can be used at the class or method level.
| Parameter | Type | Default | Notes |
|---|---|---|---|
| value | String | The name of the constraint. | |
| meta | String | Additional information passed into isAllowed. |
|
| content | String | ”” | A hint to indicate the content expected in the response. This value will be passed to DeadboltHandler#onAccessFailure. The value of this parameter is completely arbitrary. |
| handlerKey | String | “defaultHandler” | The name of a handler in the HandlerCache. |
| deferred | boolean | false | If true, the interceptor will not be applied until a DeadboltDeferred annotation is encountered. |
1 @Dynamic(name = "name of the test")
2 public CompletionStage<Result> someMethod()
3 {
4 // the method will execute if the user-defined test returns true
5 }
5.6 Pattern
This uses the Subjects Permissions to perform a variety of checks.
@Pattern uses a Subject’s Permissions to perform a variety of checks. The check depends on the pattern type.
- EQUALITY - the subject must have a permission whose value is exactly the same as the
valueparameter - REGEX - the subject must have a permission which matches the regular expression given in the
valueparameter - CUSTOM - the
DynamicResourceHandler#checkPermissionfunction is used to determine access
It’s possible to invert the constraint by setting the invert parameter to true. This changes the meaning of the constraint in the following way.
- EQUALITY - the subject must NOT have a permission whose value is exactly the same as the
valueparameter - REGEX - the subject must have NO permissions that match the regular expression given in the
valueparameter - CUSTOM - the
DynamicResourceHandler#checkPermissionfunction, where the OPPOSITE of the Boolean resolved from the function is used to determine access
@Pattern can be used at the class or method level.
A note on inverted custom constraints
When using invert and CUSTOM, care must be taken to ensure the desired result is achieved. For example, consider an implementation of checkPermission where a subject is required and that subject must have a certain attribute; access is denied if there is no subject present OR that attribute is not present. When inverting the result, access would be allowed if the subject is not present OR that attribute is not present. This is because when invert is true, the boolean resolved from checkPermission is negated.
If you only mean to allow access if a subject is present but does not have the attribute, you will need to engage in some annoying double negation.
Just before checkPermission is invoked, the value of invert is stored in the arguments of the HTTP context using the ConfigKeys.PATTERN_INVERT key; you can use this to determine what to return.
In the following example, one of four things can happen:
- A subject is present, and it satisfies the arbitrary test.
- A subject is present, and it does not satisfy the arbitrary test
- A subject is not present, and invert is true
- A subject is not present, and invert is false
- There is also a fallback assumption that invert is false if ConfigKeys.PATTERN_INVERT is not in the context; Deadbolt guarantees this will not happen, but it doesn’t hurt to make sure
1 @Override
2 public CompletionStage<Boolean> checkPermission(final String permissionValue,
3 final DeadboltHandler deadboltHandler,
4 final Http.Context ctx)
5 {
6 // just checking for zombies...just like I do every night before I go to bed
7 return deadboltHandler.getSubject(ctx)
8 .thenApplyAsync(option ->
9 option.map(subject -> subject.getPermissions()
10 .stream()
11 .filter(perm -> perm.getValue().contains("zombie"))
12 .count() > 0)
13 .orElseGet(() -> (Boolean) ctx.args.getOrDefault(ConfigKeys.PATTERN_INVERT,
14 false)),
15 ec.current());
16 }
So, in cases where we have a subject we just test like usual; the negation of the result, if required by invert, will be handled by Deadbolt. In cases where there is no subject, we return the value of invert itself - if it’s false, no negation will be internally applied, and if it’s true it will be negated to false and access denied. Thus, the test for perm.getValue().contains("zombie") is separated from the requirement to have a subject.
TL;DR Double negation sucks.
| Parameter | Type | Default | Notes |
|---|---|---|---|
| value | String | The pattern value. Its context depends on the pattern type. | |
| patternType | PatternType | EQUALITY | Additional information passed into isAllowed. |
| content | String | ”” | A hint to indicate the content expected in the response. This value will be passed to DeadboltHandler#onAccessFailure. The value of this parameter is completely arbitrary. |
| handlerKey | String | “defaultHandler” | The name of a handler in the HandlerCache. |
| deferred | boolean | false | If true, the interceptor will not be applied until a DeadboltDeferred annotation is encountered. |
| invert | boolean | false | Invert the result of the test. |
1 @Pattern("admin.printer")
2 public CompletionStage<Result> someMethodA()
3 {
4 // subject must have a permission with the exact value "admin.printer"
5 }
1 @Pattern(value = "(.)*\.printer", patternType = PatternType.REGEX)
2 public CompletionStage<Result> someMethodB()
3 {
4 // subject must have a permission that matches the regular expression (without quotes)\
5 "(.)*\.printer"
6 }
1 @Pattern(value = "something arbitrary", patternType = PatternType.CUSTOM)
2 public CompletionStage<Result> someMethodC()
3 {
4 // the checkPermssion method of the current handler's DynamicResourceHandler will be u\
5 sed. This is a user-defined test
6 }
1 @Pattern(value = "(.)*\.printer", patternType = PatternType.REGEX, invert = true)
2 public CompletionStage<Result> someMethodB()
3 {
4 // subject must have no permissions that end in .printer
5 }
5.7 Unrestricted
Unrestricted allows you to over-ride more general constraints, i.e. if a controller is annotated with @SubjectPresent but you want an action in there to be accessible to everyone.
1 @SubjectPresent
2 public class MyController extends Controller
3 {
4 public CompletionStage<Result> foo()
5 {
6 // a subject must be present for this to be accessible
7 }
8
9 @Unrestricted
10 public CompletionStage<Result> bar()
11 {
12 // anyone can access this action
13 }
14 }
You can also flip this on its head, and use it to show that a controller is explicitly unrestricted - used in this way, it’s a marker of intent rather than something functional. Because method-level constraints are evaluated first, you can have still protected actions within an @Unrestricted controller.
1 @Unrestricted
2 public class MyController extends Controller
3 {
4 @SubjectPresent
5 public CompletionStage<Result> foo()
6 {
7 // a subject must be present for this to be accessible
8 }
9
10 public CompletionStage<Result> bar()
11 {
12 // anyone can access this action
13 }
14 }
@Unrestricted can be used at the class or method level.
| Parameter | Type | Default | Notes |
|---|---|---|---|
| content | String | ”” | A hint to indicate the content expected in the response. This value will be passed to DeadboltHandler#onAccessFailure. The value of this parameter is completely arbitrary. |
| handlerKey | String | “defaultHandler” | The name of a handler in the HandlerCache
|
| deferred | boolean | false | If true, the interceptor will not be applied until a DeadboltDeferred annotation is encountered. |
5.8 Deferring method-level annotation-driven interceptors
Play executes method-level annotations before controller-level annotations. This can cause issues when, for example, you want a particular action to be applied for method and before the method annotations. A good example is Security.Authenticated(Secured.class), which sets a user’s user name for request().username(). Combining this with method-level annotations that require a user would fail, because the user would not be present at the time the method interceptor is invoked.
One way around this is to apply Security.Authenticated on every method, which violates DRY and causes bloat.
1 public class DeferredController extends Controller
2 {
3 @Security.Authenticated(Secured.class)
4 @Restrict(value = "admin")
5 public CompletionStage<Result> someAdminFunction()
6 {
7 // logic
8 }
9
10 @Security.Authenticated(Secured.class)
11 @Restrict(value = "editor")
12 public CompletionStage<Result> someEditorFunction()
13 {
14 // logic
15 }
16 }
A better way is to set the deferred parameter of the Deadbolt annotation to true, and then use @DeferredDeadbolt at the controller level to execute the method-level annotations at controller-annotation time. Since annotations are processed in order of declaration, you can specify @DeferredDeadbolt after @Security.Authenticated and so achieve the desired effect.
1 @Security.Authenticated(Secured.class)
2 @DeferredDeadbolt
3 public class DeferredController extends Controller
4 {
5 @Restrict(value = "admin", deferred = true)
6 public CompletionStage<Result> someAdminFunction()
7 {
8 // logic
9 }
10
11 @Restrict(value = "editor", deferred = true)
12 public CompletionStage<Result> someEditorFunction()
13 {
14 // logic
15 }
16 }
Specifying a controller-level restriction as deferred will work, if the annotation is higher in the annotation list than @DeferredDeadbolt annotation, but this is essentially pointless. If your constraint is already at the class level, there’s no need to defer it. Just ensure it appears below any other annotations you wish to have processed first.
5.9 Invoking DeadboltHandler#beforeAuthCheck independently
DeadboltHandler#beforeAuthCheck is invoked by each interceptor prior to running the actual constraint tests. If the method call returns an empty Optional, the constraint is applied, otherwise the Result contained in the Optional is returned. The same logic can be invoked independently, using the @BeforeAccess annotation, in which case the call to beforeRoleCheck itself becomes the constraint.
1 @BeforeAccess
2 public CompletionStage<Result> someFunction()
3 {
4 // logic
5 }
@BeforeAccess can be used at the class or method level.
| Parameter | Type | Default | Notes |
|---|---|---|---|
| handlerKey | String | “defaultHandler” | The name of a handler in the HandlerCache. |
| alwaysExecute | boolean | true | By default, if another Deadbolt action has already been executed in the same request and has allowed access, beforeAuthCheck will not be executed again. Set this to true if you want it to execute unconditionally. |
| deferred | boolean | false | If true, the interceptor will not be applied until a DeadboltDeferred annotation is encountered. |
5.10 Customising the inputs of annotation-driven actions
One of the problems with Deadbolt’s annotations is they require strings to specify, for example, role names or pattern values. It would be far safer to use enums, but this is not possible for a module - it would completely kill the generic applicability of the annotations. If Deadbolt shipped with an enum containing roles, how would you extend it? You would be stuck with whatever was specified, or forced to fork the codebase and customise it. Similarly, annotations can neither implement interfaces or be extended.
To address this situation, Deadbolt has three constraints whose inputs can be customised to some degree. The trick lies, not with inheritance, but delegation and wrapping. The constraints are
- Restrict
- Dynamic
- Pattern
Here, I’ll explain how to customise the Restrict constraint to use enums as annotation parameters, but the principle is the same for each constraint.
To start, create an enum that represents your roles.
1 public enum MyRoles implements Role
2 {
3 foo,
4 bar,
5 hurdy;
6
7 @Override
8 public String getRoleName()
9 {
10 return name();
11 }
12 }
To allow the AND/OR syntax that Restrict uses, another annotation to group them together is required.
1 @Retention(RetentionPolicy.RUNTIME)
2 @Documented
3 public @interface MyRolesGroup
4 {
5 MyRoles[] value();
6 }
Next, create a new annotation to drive your custom version of Restrict. Note that an array of MyRoles values can be placed in the annotation. The standard Restrict annotation is also present to provide further configuration. This means your customisations are minimised.
1 @With(CustomRestrictAction.class)
2 @Retention(RetentionPolicy.RUNTIME)
3 @Target({ElementType.METHOD, ElementType.TYPE})
4 @Documented
5 @Inherited
6 public @interface CustomRestrict
7 {
8 MyRolesGroup[] value();
9
10 Restrict config();
11 }
The code above contains @With(CustomRestrictAction.class) in order to specify the action that should be triggered by the annotation. This action can be implemented as follows.
1 @Override
2 public CompletionStage<Result> call(Http.Context context) throws Throwable
3 {
4 final CustomRestrict outerConfig = configuration;
5 final RestrictAction restrictAction = new RestrictAction(configuration.config(),
6 this.delegate)
7 {
8 @Override
9 public List<String[]> getRoleGroups()
10 {
11 final List<String[]> roleGroups = new ArrayList<>();
12 for (MyRolesGroup mrg : outerConfig.value())
13 {
14 final List<String> group = new ArrayList<>();
15 for (MyRoles role : mrg.value())
16 {
17 group.add(role.getName());
18 }
19 roleGroups.add(group.toArray(group.size()));
20 }
21 return roleGroups;
22 }
23 };
24 return restrictAction.call(context);
25 }
To use your custom annotation, you apply it as you would any other Deadbolt annotation.
1 @CustomRestrict(value = {MyRoles.foo, MyRoles.bar}, config = @Restrict(""))
2 public static CompletionStage<Result> customRestrictOne()
3 {
4 return CompletableFuture.supplyAsync(accessOk::render,
5 ec.current())
6 .thenApplyAsync(Results::ok,
7 ec.current());
8 }
Each customisable action has one or more extension points. These are
| Action class | Extension points |
|---|---|
| RestrictAction | * List<String[]> getRoleGroups() |
| DynamicAction | * String getValue() |
| * String getMeta() | |
| PatternAction | * String getValue() |