Reputation: 19
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
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