1

I have built an EmployeeEndpoint which holds different methods like create, update, remove, and many more. To simplify this question I only used the create method.

Because I want a scalable application I’ve built an interface that holds the base methods. Within the interface I can now annotate the methods with the JAX-RS-Annotations. Because they will be inherited I only have to override the interface method within EmployeeEndpoint.

Interface

public interface RESTCollection<T> {
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public T create(T entity) throws Exception;
}

Endpoint

@Stateless
@Path(“employee“)
public class EmployeeEndpoint implements RESTCollection<Employee> {
    @Override
    public Employee create(Employee employee) throws Exception {
        return this.createEmployee(employee);
    }
}

The example above works fine. If I want to add a custom annotation I can do:

Solution 1

public interface RESTCollection<T> {
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Permissions(Role.Admin)
    public T create(T entity) throws Exception;
}

or

Solution 2

@Stateless
@Path(“employee“)
public class EmployeeEndpoint implements RESTCollection<Employee> {
    @Override
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @Permissions(Role.Admin)
    public Employee create(Employee employee) throws Exception {
        return this.createEmployee(employee);
    }
}

But solution 1 isn’t a good idea, because not every entity can be created only by an administrator. And with solution 2 I am loosing the advantage of scalability and less code for the annotations. So the best way would be:

Solution 3

@Stateless
@Path(“employee“)
public class EmployeeEndpoint implements RESTCollection<Employee> {
    @Override
    @Permissions(Role.Admin)
    public Employee create(Employee employee) throws Exception {
        return this.createEmployee(employee);
    }
}

But now when I catch the Permissions-Annotation within the JAX-RS' ContainerRequestFilter interface method called filter I get the value of null which I don’t understand.

@Context
private ResourceInfo resourceInfo;

resourceInfo.getResourceMethod().getAnnotation(Permissions.class) // is null

Annotation

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Permissions {
    Role[] value() default {};
}

Enum

public enum Role {
    Admin,
    User
}

Is it possible in any way to go with solution 3 or a different approach where I have the same advantage?

UPDATE

Because the reason doesn't seem to be the code I posted I will show you my AuthorizationFilter. Therefore I used this post.

AuthorizationFilter

@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter {

    @Inject
    @AuthenticatedUser
    private User authenticatedUser;

    @Context
    private ResourceInfo resourceInfo;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {

        Class<?> resourceClass = resourceInfo.getResourceClass();
        List<Role> classRoles = extractRoles(resourceClass);

        Method resourceMethod = resourceInfo.getResourceMethod();
        List<Role> methodRoles = extractRoles(resourceMethod);

        try {

            if (methodRoles.isEmpty()) checkPermissions(classRoles, requestContext.getHeaderString(HttpHeaders.AUTHORIZATION));
            else checkPermissions(methodRoles, requestContext.getHeaderString(HttpHeaders.AUTHORIZATION));

        } catch (NotAuthorizedException e) {
            requestContext.abortWith(
                    Response.status(Response.Status.UNAUTHORIZED).build());
        } catch (Exception e) {
            requestContext.abortWith(
                    Response.status(Response.Status.FORBIDDEN).build());
        }
    }

    private List<Role> extractRoles(AnnotatedElement annotatedElement) {
        if (annotatedElement == null) return new ArrayList<Role>();
        else {
            Permissions perms = annotatedElement.getAnnotation(Permissions.class);
            if (perms == null) return new ArrayList<Role>();
            else {
                Role[] allowedRoles = perms.value();
                return Arrays.asList(allowedRoles);
            }
        }
    }

    private void checkPermissions(List<Role> allowedRoles, String authorizationHeader) throws NotAuthorizedException, Exception {
        if (!allowedRoles.isEmpty()) {
            if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer "))
                throw new NotAuthorizedException("Authorization header must be provided");
            else if (!allowedRoles.contains(this.authenticatedUser.getRole()))
                throw new Exception("User has no permissions");
        }
    }
}
Nadine
  • 21
  • 6

1 Answers1

0

Your code looks good.

I have run a few tests and the only reason I can think of is that you are using 2 different Permission types on your Employee resource than the one on your filter. (check your imports)

Not sure about your Filter code, but this is mine which is working (see the imports):

package com.app.filters; // TODO change this with yours

import java.io.IOException;
import java.util.Arrays;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;

import com.app.services.Permissions; // TODO change this with yours

public class AuthorizationFilter implements ContainerRequestFilter {

    @Context
    ResourceInfo resourceInfo;

    @Override
    public void filter (ContainerRequestContext requestContext) throws IOException {
        Permissions perms   = resourceInfo.getResourceMethod ().getAnnotation (Permissions.class);

        System.out.println (getClass ().getSimpleName () + " --> Permissions: " + Arrays.toString (perms.value ())); // prints [Admin]
    }

}

Bonus, if you want to test the actual value of the annotation on your Employee resource:

....
import com.app.services.Permissions; // TODO change this with yours (the one on the filter being the same as this one)
....

@Permissions (Role.Admin)
@Override
public Employee create (Employee employee) throws Exception {
    Class<?> [] cArg    = new Class [1];
    cArg [0]            = Employee.class;

    Method method       = getClass ().getMethod ("create", cArg);
    Permissions perms   = method.getAnnotation (Permissions.class);

    System.out.println (EmployeeService.class.getSimpleName () + " --> Permissions: " + Arrays.toString (perms.value ()));

    return null;
}
  • I thought the same thing, but that doesn't explain why solution 1 and 2 work. This is assuming the OP actually tested 1 and 2. – Paul Samsotha Aug 19 '17 at 21:54
  • Thank you for your answer! It isn't an import issue and my AuthorizationFilter is pretty similar to yours. But I updated my question and posted the AuthorizationFilter, just in case. – Nadine Aug 21 '17 at 08:03