user1842754
user1842754

Reputation: 19

Is there a way to get a list of all jersey urls a user, based on their role and @RolesAllowed, has access to?

I am hoping to offer a rest api call to clients(via plain jersey, not spring) to return a list of all endpoints allowed for the specific user based on the JWT they send in the header. I have found on stackoverflow(thanks contributors!) example code to get all endpoints, regardless of role, but not the subset based on role. I have found how to get the annotations per method as well, but would like to avoid re-inventing the wheel of "if @PermitAll and not @DenyAll, or role in RolesAllowed, etc...".

Any chance Jersey 2.0 has a a method I can call that will resolve to true/false given SecurityContext and url endpoint or method?

boolean allowed = isMethodAllowed(SecurityContext ctx, String url);

Or

boolean allowed = isMethodAllowed(SecurityContext ctx, Class method);

Upvotes: 0

Views: 813

Answers (1)

user1842754
user1842754

Reputation: 19

Thanks to this post: Listing all deployed rest endpoints (spring-boot, jersey)

Specifically Johanne Jander's post(thank you!), I've come up with the below code which seems to work in my case, a simple jersey use case. Providing it here in case it's useful to others.

@Path("/v1/userinterface")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public class MyUI extends MyRestApi {

    private final static Logger log = LoggerFactory.getLogger(MyUI.class);

    @Context
    private Configuration configuration;

    @Context
    private SecurityContext security;

    @Path("/allowedendpoints")
    @GET
    @Operation(summary = "List API access points allowed for the currently authenticated user", tags = {
        "ui" }, description = "Returns a list of urls", responses = {})
    public Response showAll(@Context UriInfo ui) {

    log.debug("Get list of all allowed endpoints for user: " + security.getUserPrincipal().getName());

    HashMap<String, ArrayList<String>> map = new HashMap<String, ArrayList<String>>();

    for (Class<?> c : configuration.getClasses()) {
        // Since all of my endpoint classes extend MyRestApi,
        // only scan them, not all classes
        if (MyRestApi.class.isAssignableFrom(c)) {
        scanClass(c, map);
        }
    }

    return Response.ok().entity(map).build();
    }

    public void scanClass(Class<?> baseClass, HashMap<String, ArrayList<String>> map) {
    Builder builder = Resource.builder(baseClass);
    if (null != builder) {
        Resource resource = builder.build();
        String uriPrefix = "";
        process(uriPrefix, resource, map);
    }
    }

    private void process(String uriPrefix, Resource resource, HashMap<String, ArrayList<String>> map) {

    // recursive method

    String pathPrefix = uriPrefix;
    List<Resource> resources = new ArrayList<>();
    resources.addAll(resource.getChildResources());

    if (resource.getPath() != null) {
        pathPrefix = (pathPrefix + "/" + resource.getPath()).replaceAll("//", "/");
    }

    for (ResourceMethod method : resource.getAllMethods()) {
        if (method.getType().equals(ResourceMethod.JaxrsType.SUB_RESOURCE_LOCATOR)) {
        resources.add(Resource
            .from(resource.getResourceLocator().getInvocable().getDefinitionMethod().getReturnType()));
        } else {
        if (isPathAllowed(security, method.getInvocable().getDefinitionMethod())) {
            if (map.containsKey(pathPrefix))
            map.get(pathPrefix).add(method.getHttpMethod());
            else
            map.put(pathPrefix, new ArrayList<String>(Arrays.asList(method.getHttpMethod())));
        }
        }
    }

    for (Resource childResource : resources) {
        process(pathPrefix, childResource, map);
    }
    }

    public boolean isPathAllowed(SecurityContext ctx, Method method) {

    // @DenyAll on the method takes precedence over @RolesAllowed and @PermitAll
    if (method.isAnnotationPresent(DenyAll.class)) {
        return (false);
    }

    // @RolesAllowed on the method takes precedence over @PermitAll
    RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
    if (rolesAllowed != null) {
        return (hasRole(ctx, rolesAllowed.value()));
    }

    // @PermitAll on the method takes precedence over @RolesAllowed on the class
    if (method.isAnnotationPresent(PermitAll.class)) {
        return (true);
    }

    // @DenyAll can't be attached to classes

    // @RolesAllowed on the class takes precedence over @PermitAll on the class
    rolesAllowed = method.getDeclaringClass().getAnnotation(RolesAllowed.class);
    if (rolesAllowed != null) {
        return (hasRole(ctx, rolesAllowed.value()));
    }

    // @PermitAll on the class
    if (method.getDeclaringClass().isAnnotationPresent(PermitAll.class)) {
        return (true);
    }

    return (false); // default
    }

    private boolean hasRole(SecurityContext ctx, String[] rolesAllowed) {

    for (final String role : rolesAllowed) {
        if (ctx.isUserInRole(role)) {
        return (true);
        }
    }

    return (false);
    }

Which returns endpoints the currently authenticated user has access to based on SecurityContext markup with the @DenyAll, @PermitAll and @RolesAllowed annotations.

Full disclosure, my app is simple with just basic class and method annotations at the endpoints. ymmv.

Sample output:

{
   "/v1/resources" : [
      "POST",
      "GET"
   ],
   "/v1/resources/{id}" : [
      "DELETE",
      "GET"
   ]
}

Upvotes: 1

Related Questions