0

In dropwizard auth module, I want to return to the caller of my rest-api that the user is locked.

(the credentials might be correct, but the user is locked/disabled)

I've looked here: http://www.dropwizard.io/1.0.5/docs/manual/auth.html but cannot find anything related to it.

And authenticate method of Authenticator returns Optional of my T object, and the documentation says:

You should only throw an AuthenticationException if the authenticator is unable to check the credentials (e.g., your database is down).

Muhammad Hewedy
  • 29,102
  • 44
  • 127
  • 219
  • couldn't you just create a standard-inactive-user and return this user instead? alternatively, you could create a basic role system as stated in the docs and here: http://stackoverflow.com/questions/27392224/dropwizard-auth-by-example and create a role "disabled", nor? – Dominik Jan 11 '17 at 08:35
  • 1
    Both seems to me as workarounds, thanks for suggestions. does this means that DW doesn't provide a solution for this requirement? As in both cases, the user when tries to access a resource and instead of getting `401`, he will got `403`. because he will be a valid logged-in user with not permissions, and this is not the case. he should be treated as still-not-logged-in user who should always got `401` errors. – Muhammad Hewedy Jan 11 '17 at 08:45
  • I believe DW does not want you to throw any other exception because the auth handler does not handle that. You can certainly throw your own exception and have a handler that returns the correct response for your particular usecase. What they want to avoid is the authentication returning a 500 error on wrong auth. Alternatively, write your own auth filter. – pandaadb Jan 11 '17 at 11:53
  • @pandaadb I don't want to throw an exception here, i just need to tell the client user is locked. Btw can you provide a link in how to customize auth handler? – Muhammad Hewedy Jan 11 '17 at 16:39
  • 1
    If you throw a custom exception and you handle the exception with an exceptionmapper then you could throw ClientUserLockedException() which the handler could translate to a 4XX response code (whichever one you want) – pandaadb Jan 11 '17 at 16:44

1 Answers1

0

so, for some background, the reason you should only throw an AuthenticationException is because the filter provided by the good guys at DW only handles this exception. For all other exceptions, this is undefined.

For detail see: AuthFilter#authenticate

However, there is a really easy way to do what you want.

Looking at the DW code, you get this:

@Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        final BasicCredentials credentials =
                getCredentials(requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
        if (!authenticate(requestContext, credentials, SecurityContext.BASIC_AUTH)) {
            throw new WebApplicationException(unauthorizedHandler.buildResponse(prefix, realm));
        }
    }

What this code does is to call your Authenticator to resolve the user and then check if it is authenticated. If it is not, then it will call the UnauthorizedHandler to check out what response it needs. Sadly, the handler does not get the Principal passed in, so it can only return a static exception.

Now, if you wanted to hand-craft this, this would be your entrypoint. Instead of simply using the BasicCredentialAuthFilter they provide, you would write your own that does the right thing for you.

However, from the code snippet, we can see that all this filter does is to throw a WebApplicationException. So we can shortcut this.

Our Authenticator implementation can do the user-locked check beforehand and populate an exception for us to bypass this behaviour. This way, the downstream logic is preserved (react to the WebapplicationException which I believe is actually a jersey feautere (see: exception mapppers)).

So, consider this example:

public class AuthenticatorTest extends io.dropwizard.Application<Configuration> {
    @Override
    public void run(Configuration configuration, Environment environment) throws Exception {
        environment.jersey().register(new MyHelloResource());
        UserAuth a = new UserAuth();
        environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<Principal>()
                .setAuthenticator(a).setRealm("SUPER SECRET STUFF").buildAuthFilter()));
    }

    public static void main(String[] args) throws Exception {
        new AuthenticatorTest().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/test.yaml");
    }

    @Path("test")
    @Produces(MediaType.APPLICATION_JSON)
    public static class MyHelloResource {

        @GET
        @Path("asd")
        @PermitAll
        public String test(String x) {
            return "Hello";
        }
    }

    public static class UserAuth implements Authenticator<BasicCredentials, Principal> {
        @Override
        public Optional<Principal> authenticate(BasicCredentials credentials) throws AuthenticationException {
            throw new WebApplicationException(Response.status(403).entity("User is blocked").build());
        }
    }

}

This code simply creates a new exception instead of authenticating the username. This results in this curl:

artur@pandaadb:/$ curl "artur:artur@localhost:9085/api/test/asd"
User is blockedartur@pandaadb:/$ curl "artur:artur@localhost:9085/api/test/asd" -v
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9085 (#0)
* Server auth using Basic with user 'artur'
> GET /api/test/asd HTTP/1.1
> Host: localhost:9085
> Authorization: Basic YXJ0dXI6YXJ0dXI=
> User-Agent: curl/7.47.0
> Accept: */*
> 
< HTTP/1.1 403 Forbidden
< Date: Thu, 12 Jan 2017 14:19:27 GMT
< Content-Type: application/json
< Content-Length: 15
< 
* Connection #0 to host localhost left intact
User is blocked

Now, this is likely not the cleanest solution that you can do. If you need this to be quick, then you can just throw the exception out of the Authenticator. However, the correct approach would be:

  1. Implement a new AuthFilter basing it off io.dropwizard.auth.AuthFilter
  2. Overwrite the authenticate method on AuthFilter to check your user and throw the correct exception there.
pandaadb
  • 6,306
  • 2
  • 22
  • 41
  • Thanks for this valuable answer, do you think we should return 401 instead in this case? – Muhammad Hewedy Jan 12 '17 at 14:28
  • That depends on what you want to do. 401 - unauthorized, if "locked user" means that it is unauthorized, then probably 401 is correct here. – pandaadb Jan 12 '17 at 15:02