package io.dropwizard.auth;

import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.glassfish.jersey.server.model.AnnotatedMethod;

import javax.annotation.Nullable;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.FeatureContext;
import java.lang.annotation.Annotation;
import java.util.Optional;

A DynamicFeature that registers the provided auth filter to resource methods annotated with the RolesAllowed, PermitAll and DenyAll annotations.

In conjunction with RolesAllowedDynamicFeature it enables authorization AND authentication of requests on the annotated methods.

If authorization is not a concern, then RolesAllowedDynamicFeature could be omitted. But to enable authentication, the PermitAll annotation should be placed on the corresponding resource methods.

Note that registration of the filter will follow the semantics of Configurable<FeatureContext>.register(Class<?>) and Configurable<FeatureContext>.register(Object): passing the filter as a Class to the AuthDynamicFeature(Class<? extends ContainerRequestFilter>) constructor will result in dependency injection, while objects passed to the AuthDynamicFeature(ContainerRequestFilter) will be used directly.

/** * A {@link DynamicFeature} that registers the provided auth filter * to resource methods annotated with the {@link RolesAllowed}, {@link PermitAll} * and {@link DenyAll} annotations. * <p>In conjunction with {@link RolesAllowedDynamicFeature} it enables * authorization <i>AND</i> authentication of requests on the annotated methods.</p> * <p>If authorization is not a concern, then {@link RolesAllowedDynamicFeature} * could be omitted. But to enable authentication, the {@link PermitAll} annotation * should be placed on the corresponding resource methods.</p> * <p>Note that registration of the filter will follow the semantics of * {@link FeatureContext#register(Class)} and {@link FeatureContext#register(Object)}: * passing the filter as a {@link Class} to the {@link #AuthDynamicFeature(Class)} * constructor will result in dependency injection, while objects passed to * the {@link #AuthDynamicFeature(ContainerRequestFilter)} will be used directly.</p> */
public class AuthDynamicFeature implements DynamicFeature { private final ContainerRequestFilter authFilter; private final Class<? extends ContainerRequestFilter> authFilterClass; // We suppress the null away checks, as adding `@Nullable` to the auth // filter fields, causes Jersey to try and resolve the fields to a concrete // type (which subsequently fails). @SuppressWarnings("NullAway") public AuthDynamicFeature(ContainerRequestFilter authFilter) { this.authFilter = authFilter; this.authFilterClass = null; } @SuppressWarnings("NullAway") public AuthDynamicFeature(Class<? extends ContainerRequestFilter> authFilterClass) { this.authFilter = null; this.authFilterClass = authFilterClass; } @Override public void configure(ResourceInfo resourceInfo, FeatureContext context) { final AnnotatedMethod am = new AnnotatedMethod(resourceInfo.getResourceMethod()); final Annotation[][] parameterAnnotations = am.getParameterAnnotations(); final Class<?>[] parameterTypes = am.getParameterTypes(); // First, check for any @Auth annotations on the method. for (int i = 0; i < parameterAnnotations.length; i++) { for (final Annotation annotation : parameterAnnotations[i]) { if (annotation instanceof Auth) { // Optional auth requires that a concrete AuthFilter be provided. if (parameterTypes[i].equals(Optional.class) && authFilter != null) { context.register(new WebApplicationExceptionCatchingFilter(authFilter)); return; } else { registerAuthFilter(context); return; } } } } // Second, check for any authorization annotations on the class or method. // Note that @DenyAll shouldn't be attached to classes. final boolean annotationOnClass = (resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class) != null) || (resourceInfo.getResourceClass().getAnnotation(PermitAll.class) != null); final boolean annotationOnMethod = am.isAnnotationPresent(RolesAllowed.class) || am.isAnnotationPresent(DenyAll.class) || am.isAnnotationPresent(PermitAll.class); if (annotationOnClass || annotationOnMethod) { registerAuthFilter(context); } } private void registerAuthFilter(FeatureContext context) { if (authFilter != null) { context.register(authFilter); } else if (authFilterClass != null) { context.register(authFilterClass); } } }