1

I have implemented my own Authenticator from Play Framework and DeadboltHandler from Deadbolt.

Using the methods onUnauthorized respective onAuthFailure I can send users that are not logged in to the "login page" instead of the actual page they are trying to access.

However, instead of sending a user directly to the "login page", I want to specify what page the user should be sent to depending on which page the user tries to reach. For example, if the user tries to access /settings the user should be redirected to the login page. If the user tries to access /player/1 the user should be redirected to another page, say, "create user" page.

I was hoping that there is some smart way to do this with annotations, something like: @someannotation(redirect = route/id) so I can redirect to the relevant route if the user is not logged in, else to the standard "login page".

Any one got any ideas?

Code snippet example for controller and route method:

@Security.Authenticated(Secured.class)
@SubjectPresent(content = "createuser")
@DeferredDeadbolt
public class Settings extends Controller {

    @SubjectPresent(content = "login")
    @CustomRestrict(value = { @RoleGroup({ UserRole.player}), @RoleGroup(UserRole.server_owner) })
    public static Result settings() {

Code snippet example for DeadboltHandler onAuthFailure:

@Override
    public F.Promise<Result> onAuthFailure(Http.Context context, String content) {
        return F.Promise.promise(new F.Function0<Result>() {
            @Override
            public Result apply() throws Throwable {
                System.out.println(content);
Marcus Persson
  • 373
  • 4
  • 20

1 Answers1

2

There are a couple of different ways you can do this.

Approach 1: Repurpose the content value

In this approach, you can use the content value of the constraint annotations to give a hint to the handler. You can use a class-level constraint to define the default redirect, e.g. go to the login page, and method-level constraints to override the default redirect. All constraints have the content value, I'm just using SubjectPresent as an example; you can also mix constraints, e.g. have SubjectPresent at the class level and Restrict at the method level.

@SubjectPresent(content = "login")
public class FooController extends Controller {

    public Result settings() {
        // ...
    }

    public Result somethingElse() {
        // ...
    }

    @SubjectPresent(content = "create-user")
    public Result viewUser() {
        // ...
    }
}

In your DeadboltHandler implementation, you would then need a test on the content:

public CompletionStage<Result> onAuthFailure(final Http.Context context,
                                             final Optional<String> content) {
    return CompletableFuture.supplyAsync(() -> content.map(redirectKey -> {
        final Result result;
        if ("login".equals(redirectKey)) {
            result = [redirect to login action]
        }
        else if ("create-user".equals(redirectKey)) {
            result = [redirect to create user action]
        } else {
            result = [redirect to default authorization failure action]
        }
    }).orElseGet(() -> [redirect to default authorization failure action]), executor);
}

Approach 2: Use the ROUTE_PATTERN tag

Instead of specifying keys in the constraint annotations, you can instead use the route specified in the request to determine the requested action.

public CompletionStage<Result> onAuthFailure(final Http.Context context,
                                             final Optional<String> content) {
    final String route = requestHeader.tags().get(Router.Tags.ROUTE_PATTERN);
    // examine the route and work out what you want to do
}
Steve Chaloner
  • 8,162
  • 1
  • 22
  • 38
  • This looks like what I need, however, it does not quite work for me, at least not now. I'm using an older version of Deadbolt (it will get upgraded at some point) and assume your answer is for a later (if not latest) version of Deadbolt. I also realized that you're the author of Deadbolt, awesome work! :) My AbstractDeadboltHandler's onAuthFailure method looks like this: F.Promise onAuthFailure(Http.Context context, String content). – Marcus Persson Apr 21 '17 at 11:29
  • What I don't quite get is that my "content" parameter always seem to come in empty. Also, can I use both the annotations @CustomRestric(value = ...) and @SubjectPresent(content = ...) at method level, and can I combine @DeferredDeadbolt and @SubjectPresent(content = "createuser") at Controller level? – Marcus Persson Apr 21 '17 at 11:32
  • I will update my post with a code snippet so it's easier to see what I mean. (Please ignore that I'm using syso print :D) – Marcus Persson Apr 21 '17 at 11:35
  • I'll double-check the content issue, but it should be working fine. Rewriting for F.Promise should be straightforward. Combining those annotations is possible. – Steve Chaloner Apr 21 '17 at 13:27
  • Nothing breaks when I enter the test page I've set up. I get redirected to my login page as usual. The content parameter just seems come in empty. However, I also noticed that I'm getting some error messages, namely: `[error] b.o.d.j.u.RequestUtils - Error getting subject: null` `java.lang.NullPointerException: null` `[error] b.o.d.j.a.AbstractDeadboltAction - Access to [/settings] requires a subject, but no subject is present.` – Marcus Persson Apr 21 '17 at 14:03
  • Arh... The content is coming in with a value now. The error message didn't seem to matter. I simply put @SubjectPresent( before @CustomRestrict(. – Marcus Persson Apr 21 '17 at 14:12
  • Regarding the NPE, make sure that if a method returns an Optional, you return an empty option instead of a null if there is no value. – Steve Chaloner Apr 22 '17 at 06:34