Dropwizard Request Filters With URI Pattern - java

I am building a RESTful application in Dropwizard. While connecting to a database, I want to set up a UserNotFoundFilter that implements ContainerRequestFilter so that incoming requests go through this filter first.
The idea is that I would like to have this specific filter only mapped to certain URI patterns. For instance, I want the filter to only apply to /users/* and not anything else. Is there a way to do this without resorting to custom annotations and implementation with DynamicFeature ?
#Provider
public class UserNotFoundFilter implements ContainerRequestFilter {
#Context
UriInfo uriInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
MultivaluedMap pathParams = uriInfo.getPathParameters(); // Should contain (uid: 1) pair for /users/1
boolean userExists = // check against the database using above pathparam pair to see if user exists
if (!userExists)
throw new WebApplicationException("User does not exist", Status.NOT_FOUND);
// let the request through as user exists
}
}
My UserResource class
public class UserResource {
#GET
#Path("/users/{uid}")
#Produces(MediaType.APPLICATION_JSON)
public User getUser(#PathParam("uid") String uid) {
// Now I don't need to do the check here !
// boolean userExists = check against database using uid path param
// if (!userExists)
// throw new WebApplicationException("User does not exist", Status.NOT_FOUND);
return database.getUser(uid);
}
}
My ItemResource class
public class ItemResource {
#GET
#Path("/items/{uid}")
#Produces(MediaType.APPLICATION_JSON)
public Item getItem(#PathParam("uid") String uid) {
return database.getItem(uid);
}
}
What I'm trying to do
public class MyApplication extends Application<MyConfiguration> {
// ...
#Override
public void run(MyConfiguration config, Environment environment) throws Exception {
// ... do other things, register resources
// this pseudocode, the UserNotFoundFilter only applies to URIs of the kind /users/*
environment.jersey().register(new UserNotFoundFilter()).forUriPattern("/users/*");
I appreciate any example code snippets.

For Servlet filter -
Probably what you are looking for is addMappingForUrlPatterns from javax.servlet.FilterRegistration interface to be used in your run() as -
environment.servlets().addFilter("FilterName", UserNotFoundFilter.class)
.addMappingForUrlPatterns(EnumSet
.allOf(DispatcherType.class), true, "/users/*");
Signature of the above-used method is -
public void addMappingForUrlPatterns(
EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
String... urlPatterns);
Edit - for binding dynamically :
Try and use DynamicFeature as
#Provider
public class UserNotFoundDynamicFilter implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext featureContext) {
if (resourceInfo.getResourceMethod().getAnnotation(UserRequired.class) != null) {
featureContext.register(UserNotFoundFilter.class);
}
}
}
where you can define the UserRequired annotation as -
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface UserRequired {
}
and within your resources mark all /users/* apis with the same annotation as -
#GET
#Path("/users/{uid}")
#Produces(MediaType.APPLICATION_JSON)
#UserRequired
public User getUser(#PathParam("uid") String uid) {
// Now I don't need to do the check here !
// boolean userExists = check against database using uid path param
// if (!userExists)
// throw new WebApplicationException("User does not exist", Status.NOT_FOUND);
return database.getUser(uid);
}
Source - jersey-filters

You'll get a bunch of useful stuff in ((ContainerRequest) requestContext).getUriInfo(), e.g. for matching /users/*
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String uriTemplate = ((ContainerRequest) requestContext).getUriInfo().getMatchedTemplates().stream().map(o -> o.getTemplate()).reduce("", (acc, template) -> template + acc);
if (uriTemplate == "/users/{id}") {
// matched!
}
String path = ((ContainerRequest) requestContext).getUriInfo().getPath();
if (path.startsWith("users/")) {
// matched!
}
}
Get the actual user id in a similar way for your DB lookup.

Related

Access URITemplate or RequestLine value in Feign RequestInterceptor / RequestTemplate

I'm developing an app against a cloud application that has hard api rate limits in place. In order to have my team get a feeling for how close we are in regards to those limits I want to count all API calls made from our app in a meaningful way.
We use Feign as access layer, and I was hoping to be able to use the RequestInterceptor to count the different API endpoints we call:
RequestInterceptor ri = rq -> addStatistics(rq.url());
Now this does not work, as the resulting URLs almost always count "1" afterwards, as they already contain all resolved path variables, so I get counts for
1 - /something/id1valueverycryptic/get
1 - /something/anothercrypticidkey/get
and so on.
I was hoping to somehow get access to either the #ResuqestLine mapping value (GET /something/{id}/get) or at least the uri template pre-resolve (/somethine/{id}/get)
Is there a way to do this?
Thanks!
Maybe you could try using custom feign InvocationHandlerFactory.
I've managed to log RequestInterceptor using code like this:
change EnableFeignClients and add defaultConfiguration
#EnableFeignClients(defaultConfiguration = FeignConfig.class)
add default feign config
#Configuration
public class FeignConfig {
#Bean
#ConditionalOnMissingBean
public Retryer feignRetryer() {
return Retryer.NEVER_RETRY;
}
#Bean
#Scope("prototype")
#ConditionalOnMissingBean
public Feign.Builder feignBuilder(Retryer retryer) {
return Feign.builder()
.retryer(retryer)
.invocationHandlerFactory((target, dispatch) -> new CountingFeignInvocationHandler(target, dispatch));
}
}
create your invocation handler (code based on feign.ReflectiveFeign.FeignInvocationHandler)
public class CountingFeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
public CountingFeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
RequestLine requestLine = method.getAnnotation(RequestLine.class);
addStatistics(requestLine.value());
return dispatch.get(method).invoke(args);
}
#Override
public boolean equals(Object obj) {
if (obj instanceof CountingFeignInvocationHandler) {
CountingFeignInvocationHandler other = (CountingFeignInvocationHandler) obj;
return target.equals(other.target);
}
return false;
}
#Override
public int hashCode() {
return target.hashCode();
}
#Override
public String toString() {
return target.toString();
}
}
Be careful and check if you feign configuration wasn't more complex and in that case extend classes as needed.
If you are using spring-cloud-starter-openfeign , You could do something like below
add the a primary contract bean
#Bean("YourContract")
#Primary
public Contract springpringContract() {
return (targetType) -> {
List<MethodMetadata> parseAndValidatateMetadata = new SpringMvcContract().parseAndValidatateMetadata(targetType);
parseAndValidatateMetadata.forEach(metadata -> {
RequestTemplate template = metadata.template();
template.header("unresolved_uri", template.path().replace("{", "[").replace("}", "]"));
});
return parseAndValidatateMetadata;
};
}
Add the contract to the feign client builder
#Bean
public <T> T feignBuilder(Class<T> feignInterface, String targetURL) {
return Feign.builder().client(getClient())
.contract(contract)
.
.
}
Once you are done with the above you should be able to access the unresolved path in the RequestTemplate
#component
public class FeignRequestFilter implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
String unresolvedUri = template.headers().getOrDefault("unresolved_uri", Collections.singleton(template.path()))
.iterator().next();
}
}
Maybe you could try overwriting feign Logger.
Suppose we have a feign client,
#FeignClient(name = "demo-client", url = "http://localhost:8080/api", configuration = FeignConfig.class)
public interface DemoClient {
#GetMapping(value = "/test/{id}")
void test(#PathVariable(name = "id") Integer id) {
}
}
import feign.Logger;
import feign.Request;
import feign.Response;
import java.io.IOException;
public class CustomFeignRequestLogging extends Logger {
#Override
protected void logRequest(String configKey, Level logLevel, Request request) {
super.logRequest(configKey, logLevel, request);
// targetUrl = http://localhost:8080/api
String targetUrl = request.requestTemplate().feignTarget().url();
// path = /test/{id}
String path = request.requestTemplate().methodMetadata().template().path();
}
}

Custom error message containing parameter names when validation fails

I would like my API to return errorMessage when the request lacks of required parameters. For example let's say there is a method:
#GET
#Path("/{foo}")
public Response doSth(#PathParam("foo") String foo, #NotNull #QueryParam("bar") String bar, #NotNull #QueryParam("baz") String baz)
where #NotNull is from package javax.validation.constraints.
I wrote an exception mapper which looks like this:
#Provider
public class Mapper extends ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException) {
Iterator<ConstraintViolation<?>> it= exception.getConstraintViolations().iterator();
StringBuilder sb = new StringBuilder();
while(it.hasNext()) {
ConstraintViolation<?> next = it.next();
sb.append(next.getPropertyPath().toString()).append(" is null");
}
// create errorMessage entity and return it with apropriate status
}
but next.getPropertyPath().toString() returns string in format method_name.arg_no, f.e. fooBar.arg1 is null
I'd like to receive output fooBar.baz is null or simply baz is null.
My solution was to include -parameters parameter for javac but to no avail.
Probably I could somehow achieve it with the use of filters:
public class Filter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
UriInfo uriInfo = requestContext.getUriInfo();
UriRoutingContext routingContext = (UriRoutingContext) uriInfo;
Throwable mappedThrowable = routingContext.getMappedThrowable();
if (mappedThrowable != null) {
Method resourceMethod = routingContext.getResourceMethod();
Parameter[] parameters = resourceMethod.getParameters();
// somehow transfer these parameters to exceptionMapper (?)
}
}
}
The only problem with the above idea is that ExeptionMapper is executed first, then the filter is executed. Also I have no idea how could I possibly transfer errorMessage between ExceptionMapper and Filter. Maybe there is another way?
You can inject ResourceInfo into the exception mapper to get the resource method.
#Provider
public class Mapper extends ExceptionMapper<ConstraintViolationException> {
#Context
private ResourceInfo resourceInfo;
#Override
public Response toResponse(ConstraintViolationException ex) {
Method resourceMethod = resourceInfo.getResourceMethod();
Parameter[] parameters = resourceMethod.getParameters();
}
}

How to forbid slash '/' usage in entity attribute using jaxb and jaxrs

I have the following entity:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = EntityConstants.PARTNER)
public class FilePartner
{
#XmlAttribute(name = EntityConstants.IDENTIFIER, required = true)
#XmlJavaTypeAdapter(RestResourceIdJaxbAdapter.class)
private String identifier;
...
}
Here is the jaxb adapter:
public class RestResourceIdJaxbAdapter extends XmlAdapter<String, String>
{
#Override
public String unmarshal(String v) throws Exception
{
if (v != null && v.contains("/"))
{
// throw new ApiException(Status.BAD_REQUEST, RestErrorMessages.BAD_REQUEST_SUFFIX, "Identifier must not contain slashes");
return v.replaceAll("/", "");
}
return v;
}
#Override
public String marshal(String v) throws Exception
{
return v;
}
}
I have a jaxrs service that accepts POST requests with body FilePartner:
#POST
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response createPartner(FilePartner partner,
#Context UriInfo uriInfo,
#HeaderParam(HttpHeaders.ACCEPT) String acceptHeader)
throws ApiException
{
...
}
What I want to achieve is to forbid the usage of slashes '/' in the identifier attribute of the FilePartner entity.
Today I am doing this using some jaxb adapter which simply strips all slashes from the id when unmarshalling.
Instead, what I would like is to return an appropriate BAD_REQUEST exception to the user.
I tried throwing exception in the unmarshal method of the jaxb adapter but seems that jaxrs is swallowing it and simply setting my identifier to null.
If we want to override this behavior I think I must create a new #Provider and register a special ValidationEventHandler in the unmarshaller that the javax.ws.rs.ext.MessageBodyReader creates.
Unfortunately, this is impossible unless I define an explicit dependency to a JAX-RS implementation which I want to avoid.
Are there any other options to restrict the usage of slashes in the identifier attribute, without defining an explicit dependency to jersey/resteasy and without handling the restriction in the #POST method of the service?
To your rescue comes ReaderInterceptor
Don't do any special handling using #XmlJavaTypeAdapter. Register a ReaderInterceptor with your Application class (if in jersey2) or in web.xml if earlier.
import java.io.IOException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
javax.ws.rs.ext.ReaderInterceptor
#Provider
public class ValidationInterceptor implements ReaderInterceptor {
public ValidationInterceptor() {
super();
}
#Override
public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext) throws IOException,
WebApplicationException {
Object o = readerInterceptorContext.proceed();
if (o instanceof FilePartner&& ((FilePartner) o).getIndentifier().contains("/")) {
throw new WebApplicationException(Response.status(400)
.entity("Identifier must not contain a slash")
.build());
}
return o;
}
}
And register the interceptor to your Application in override of public Set<Class<?>> getClasses() method something like classes.add(ValidationInterceptor.class);
Hope that helps.

Jersey filter in Dropwizard to set some global FreeMarker variables

I'm reading https://jersey.github.io/documentation/latest/filters-and-interceptors.html and http://www.dropwizard.io/1.1.4/docs/manual/core.html#jersey-filters to try and make this:
#CookieParam("User-Data") userData: String,
#HeaderParam("User-Agent") userAgent: String,
Not needed in each and every resource GET method of my web app. userData is json data from a cookie with fields like "name" and "id" and userAgent is the full User-Agent string from the header. For each view I pass in:
AppUser.getName(userData), AppUser.isMobile(userAgent)
The getName function parses the json and returns just the name field and the isMobile function returns a true boolean if the string "mobile" is found.
I use this in each view of the app in FreeMarker to display the user's name and to change some layout stuff if mobile is true.
Is there a way to make this less repetitive? I'd rather use a BeforeFilter to just set this automatically each time.
Sounds like something you can just do in a ContainerResponseFilter, which gets called after the return of the view resource/controller. Assuming you are returning a Viewable, you get the Viewable from the ContainerRequestContext#getEntity, get the model from it, and add the extra information to the model.
#Provider
#UserInModel
public class UserInModelFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) throws IOException {
Cookie cookie = request.getCookies().get("User-Data");
String header = request.getHeaderString("User-Agent");
String username = AppUser.getName(cookie.getValue());
boolean isMobile = AppUser.isMobile(header);
Viewable returnViewable = (Viewable) response.getEntity();
Map<String, Object> model = (Map<String, Object>) returnViewable.getModel();
model.put("username", username);
model.put("isMobile", isMobile);
}
}
The #UserInModel annotation is a custom Name Binding annotation, which is used to determine which resource classes or methods should go through this filter. Since you don't want all endpoints to go through this filter, just annotate the methods or classes you want.
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface UserInModel {
}
#Path("/")
public class IndexController {
#GET
#UserInModel
#Produces(MediaType.TEXT_HTML)
public Viewable home() {
Map<String, Object> model = new HashMap<>();
return new Viewable("/index", model);
}
}
With Dropwizard, all you need to do is register the filter.
env.jersey().register(UserInModelFilter.class);
If you want to do some preprocessing of the cookie and header before the resource method is called, you can do that in a ContainerRequestFilter, which can also be name bound. And instead of recalculating the AppUser.xxx method in the response filter, you can also just set a property on the ContainerRequestContext#setProperty that you can later retrieve from the same context (getProperty) in the response filter.
UPDATE
The above answer assumes you are using Jersey's MVC support, hence the use of Viewable. If you are using Dropwizard's view support, then it's not much different. You may want to create an abstract class as a parent for all the view classes, that way you can just cast to the abstract type when retrieving the entity from the filter.
public class AbstractView extends View {
private String userName;
private boolean isMobile;
protected AbstractView(String templateName) {
super(templateName);
}
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public boolean isMobile() { return isMobile; }
public void setIsMobile(boolean mobile) { isMobile = mobile; }
}
public class PersonView extends AbstractView {
private final Person person;
public PersonView(Person person) {
super("person.ftl");
this.person = person;
}
public Person getPerson() {
return this.person;
}
}
In the filter
#Provider
#UserInModel
public class UserInModelFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext request,
ContainerResponseContext response) throws IOException {
Cookie cookie = request.getCookies().get("User-Data");
String header = request.getHeaderString("User-Agent");
String username = AppUser.getName(cookie.getValue());
boolean isMobile = AppUser.isMobile(header);
AbstractView returnViewable = (AbstractView) response.getEntity();
returnViewable.setUserName(username);
returnViewable.setIsMobile(isMobile);
}
}
Tested resource class for completeness
#Path("person")
public class PersonController {
#GET
#UserInModel
#Produces(MediaType.TEXT_HTML)
public PersonView person() {
Person person = new Person("peeskillet#fake.com");
return new PersonView(person);
}
}

Jax RS Authorization

I have an existing code at a class which is extended from javax.ws.rs.core.Application
...
Context childContext = component.getContext().createChildContext();
JaxRsApplication application = new JaxRsApplication(childContext);
application.add(this);
application.setStatusService(new ErrorStatusService());
childContext.getAttributes().put("My Server", this);
...
ChallengeAuthenticator challengeGuard = new ChallengeAuthenticator(null, ChallengeScheme.HTTP_BASIC, "REST API Realm");
//Create in-memory users with roles
MemoryRealm realm = new MemoryRealm();
User user = new User("user", "user");
realm.getUsers().add(user);
realm.map(user, Role.get(null, "user"));
User owner = new User("admin", "admin");
realm.getUsers().add(owner);
realm.map(owner, Role.get(null, "admin"));
//Attach verifier to check authentication and enroler to determine roles
challengeGuard.setVerifier(realm.getVerifier());
challengeGuard.setEnroler(realm.getEnroler());
challengeGuard.setNext(application);
// Attach the application with HTTP basic authentication security
component.getDefaultHost().attach(challengeGuard);
I don't have a web.xml at my code. I would like to add authorization to my code. This: https://restlet.com/technical-resources/restlet-framework/guide/2.3/core/security/authorization does not apply to me since I don't have restlet resources.
How can I implement jax rs authorization into my code?
EDIT 1: Existing code uses restlet JAX-RS extension: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs
I've tried that at my jax-rs resource class:
#GET
#Path("/")
public String getStatus() {
if (!securityContext.isUserInRole("admin")) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
...
}
However, it throws 403 even I log in with admin user.
EDIT 2:
When I check here: https://restlet.com/technical-resources/restlet-framework/guide/2.2/extensions/jaxrs There is a piece of code:
this.setRoleChecker(...); // if needed
This may solve my issue but I don't know how to set a role checker.
PS: I use jersey 1.9 and restlet 2.2.3.
It's not really clear (at least to me :-) ) what you are trying to achieve.
If you have a class which is a subclass of javax.ws.rs.core.Application, you should be able to simply add #RolesAllowed("user") as an annotation to your resource classes, as shown in https://jersey.java.net/documentation/latest/security.html
#Path("/")
#PermitAll
public class Resource {
#RolesAllowed("user")
#GET
public String get() { return "GET"; }
#RolesAllowed("admin")
#POST
public String post(String content) { return content; }
#Path("sub")
public SubResource getSubResource() {
return new SubResource();
}
}
Accessing that resource should prompt you for your credentials. If that doesn't work, then you need to provide a small code sample, which compiles and doesn't do what you want it to do. Then it's easier to see where the problem is and what needs to be done to make it work
I could make it work like that:
Application class:
...
application.setRoles(getRoles(application));
...
public static List<Role> getRoles(JaxRsApplication application) {
List<Role> roles = new ArrayList<>();
for (AuthorizationRoleEnum authorizationRole : AuthorizationRoleEnum.values()) {
roles.add(new Role(application, authorizationRole.toString()));
}
return roles;
}
...
Authorization enum:
public enum AuthorizationRoleEnum {
USER("user"),
ADMIN("admin");
private final String value;
AuthorizationRoleEnum(String value) {
this.value = value;
}
#Override
public String toString() {
return value;
}
}
At my resource classes:
...
#Context
SecurityContext securityContext;
...
allowOnlyAdmin(securityContext);
...
public void allowOnlyAdmin(SecurityContext securityContext) {
if (securityContext.getAuthenticationScheme() != null
&& !securityContext.isUserInRole(AuthorizationRoleEnum.ADMIN.toString())) {
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("User does not have required " + AuthorizationRoleEnum.ADMIN + " role!").build());
}
}
...
You need to implement your RoleChecker using this interface.
As the doc says:
Because the Restlet API does not support its own mechanism for role checks (as e.g. the Servlet API), you must use this inteface if you need role checks in a JAX-RS application.
This interface is used to check, if a user is in a role. Implementations must be thread save.
so as an example of implementation you can do smth like this:
public class MyRoleChecker implements RoleChecker {
public boolean isInRole(Principal principal, String role) {
return principal.getRole().equals(role);
}
}
Edited:
On the other hand as you use the new API, you need to implement SecurityContext and inject it using #Context in your resource methods.
Then you fetch roles list from the storage by username. The storage implementation is up to you. Please refer to this example
#Priority(Priorities.AUTHENTICATION)
public class AuthFilterWithCustomSecurityContext implements ContainerRequestFilter {
#Context
UriInfo uriInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String authHeaderVal = requestContext.getHeaderString("Auth-Token");
String subject = validateToken(authHeaderVal); //execute custom authentication
if (subject!=null) {
final SecurityContext securityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return subject;
}
};
}
#Override
public boolean isUserInRole(String role) {
List<Role> roles = findUserRoles(subject);
return roles.contains(role);
}
#Override
public boolean isSecure() {
return uriInfo.getAbsolutePath().toString().startsWith("https");
}
#Override
public String getAuthenticationScheme() {
return "Token-Based-Auth-Scheme";
}
});
}
}
}

Categories

Resources