Add support for optional authentication
This commit is contained in:
parent
ec37a3c67a
commit
80ac3cf7e6
4
pom.xml
4
pom.xml
@ -79,8 +79,8 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@ -6,6 +6,9 @@ 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.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
|
||||
@ -19,22 +22,30 @@ public class AuthDynamicFeature implements DynamicFeature {
|
||||
|
||||
@Override
|
||||
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
|
||||
AnnotatedMethod annotatedMethod = new AnnotatedMethod(resourceInfo.getResourceMethod());
|
||||
Annotation[][] parameterAnnotations = annotatedMethod.getParameterAnnotations();
|
||||
Class<?>[] parameterTypes = annotatedMethod.getParameterTypes();
|
||||
AnnotatedMethod annotatedMethod = new AnnotatedMethod(resourceInfo.getResourceMethod());
|
||||
Annotation[][] parameterAnnotations = annotatedMethod.getParameterAnnotations();
|
||||
Class<?>[] parameterTypes = annotatedMethod.getParameterTypes ();
|
||||
Type[] parameterGenericTypes = annotatedMethod.getGenericParameterTypes();
|
||||
|
||||
verifyAuthAnnotations(parameterAnnotations);
|
||||
|
||||
for (int i=0;i<parameterAnnotations.length;i++) {
|
||||
for (Annotation annotation : parameterAnnotations[i]) {
|
||||
if (annotation instanceof Auth) {
|
||||
context.register(getFilterFor(parameterTypes[i]));
|
||||
Type parameterType = parameterTypes[i];
|
||||
|
||||
if (parameterType == Optional.class) {
|
||||
parameterType = ((ParameterizedType)parameterGenericTypes[i]).getActualTypeArguments()[0];
|
||||
context.register(new WebApplicationExceptionCatchingFilter(getFilterFor(parameterType)));
|
||||
} else {
|
||||
context.register(getFilterFor(parameterType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AuthFilter getFilterFor(Class<?> parameterType) {
|
||||
private AuthFilter getFilterFor(Type parameterType) {
|
||||
for (AuthFilter filter : authFilters) {
|
||||
if (filter.supports(parameterType)) return filter;
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@ import javax.annotation.Priority;
|
||||
import javax.ws.rs.Priorities;
|
||||
import javax.ws.rs.container.ContainerRequestFilter;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.DefaultUnauthorizedHandler;
|
||||
import io.dropwizard.auth.UnauthorizedHandler;
|
||||
|
||||
@ -19,7 +22,7 @@ public abstract class AuthFilter<C, P> implements ContainerRequestFilter {
|
||||
protected UnauthorizedHandler unauthorizedHandler = new DefaultUnauthorizedHandler();
|
||||
|
||||
|
||||
public boolean supports(Class<?> clazz) {
|
||||
public boolean supports(Type clazz) {
|
||||
return clazz.equals(principalType);
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@ import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.security.Principal;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
|
||||
@ -33,29 +34,62 @@ public class AuthValueFactoryProvider extends AbstractValueFactoryProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AbstractContainerRequestValueFactory() {
|
||||
if (parameter.getRawType() == Optional.class) {
|
||||
return new OptionalContainerRequestValueFactory(parameter);
|
||||
} else {
|
||||
return new StandardContainerReqeustValueFactory(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Principal} stored on the request, or {@code null} if no object was found.
|
||||
*/
|
||||
public Object provide() {
|
||||
Principal principal = getContainerRequest().getSecurityContext().getUserPrincipal();
|
||||
private static class StandardContainerReqeustValueFactory extends AbstractContainerRequestValueFactory {
|
||||
private final Parameter parameter;
|
||||
|
||||
if (principal == null) {
|
||||
throw new IllegalStateException("Cannot inject a custom principal into unauthenticated request");
|
||||
}
|
||||
StandardContainerReqeustValueFactory(Parameter parameter) {
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
if (!(principal instanceof AuthPrincipal)) {
|
||||
throw new IllegalArgumentException("Cannot inject a non-AuthPrincipal into request");
|
||||
}
|
||||
/**
|
||||
* @return {@link Principal} stored on the request, or {@code null} if no object was found.
|
||||
*/
|
||||
public Object provide() {
|
||||
Principal principal = getContainerRequest().getSecurityContext().getUserPrincipal();
|
||||
|
||||
if (!parameter.getRawType().isAssignableFrom(((AuthPrincipal)principal).getAuthenticated().getClass())) {
|
||||
throw new IllegalArgumentException("Authenticated principal is of the wrong type!");
|
||||
}
|
||||
|
||||
return parameter.getRawType().cast(((AuthPrincipal) principal).getAuthenticated());
|
||||
if (principal == null) {
|
||||
throw new IllegalStateException("Cannot inject a custom principal into unauthenticated request");
|
||||
}
|
||||
};
|
||||
|
||||
if (!(principal instanceof AuthPrincipal)) {
|
||||
throw new IllegalArgumentException("Cannot inject a non-AuthPrincipal into request");
|
||||
}
|
||||
|
||||
if (!parameter.getRawType().isAssignableFrom(((AuthPrincipal)principal).getAuthenticated().getClass())) {
|
||||
throw new IllegalArgumentException("Authenticated principal is of the wrong type!");
|
||||
}
|
||||
|
||||
return parameter.getRawType().cast(((AuthPrincipal) principal).getAuthenticated());
|
||||
}
|
||||
}
|
||||
|
||||
private static class OptionalContainerRequestValueFactory extends AbstractContainerRequestValueFactory {
|
||||
private final Parameter parameter;
|
||||
|
||||
OptionalContainerRequestValueFactory(Parameter parameter) {
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link Principal} stored on the request, or {@code null} if no object was found.
|
||||
*/
|
||||
public Object provide() {
|
||||
Principal principal = getContainerRequest().getSecurityContext().getUserPrincipal();
|
||||
|
||||
if (principal != null && !(principal instanceof AuthPrincipal)) {
|
||||
throw new IllegalArgumentException("Cannot inject a non-AuthPrincipal into request");
|
||||
}
|
||||
|
||||
if (principal == null) return Optional.empty();
|
||||
else return Optional.of(((AuthPrincipal)principal).getAuthenticated());
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package org.whispersystems.dropwizard.simpleauth;
|
||||
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.AuthenticationException;
|
||||
|
||||
@ -17,7 +17,7 @@ public interface Authenticator<C, P> {
|
||||
* Given a set of user-provided credentials, return an optional principal.
|
||||
*
|
||||
* If the credentials are valid and map to a principal, returns an {@code Optional.of(p)}.
|
||||
*
|
||||
*
|
||||
* If the credentials are invalid, returns an {@code Optional.absent()}.
|
||||
*
|
||||
* @param credentials a set of user-provided credentials
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package org.whispersystems.dropwizard.simpleauth;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.io.BaseEncoding;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -13,6 +12,7 @@ import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.AuthenticationException;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
package org.whispersystems.dropwizard.simpleauth;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
import javax.ws.rs.Priorities;
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.container.ContainerRequestFilter;
|
||||
|
||||
/**
|
||||
* A {@link ContainerRequestFilter} decorator which catches any {@link
|
||||
* WebApplicationException WebApplicationExceptions} thrown by an
|
||||
* underlying {@code ContextRequestFilter}.
|
||||
*/
|
||||
@Priority(Priorities.AUTHENTICATION)
|
||||
class WebApplicationExceptionCatchingFilter implements ContainerRequestFilter {
|
||||
private final ContainerRequestFilter underlying;
|
||||
|
||||
public WebApplicationExceptionCatchingFilter(ContainerRequestFilter underlying) {
|
||||
Preconditions.checkNotNull(underlying, "Underlying ContainerRequestFilter is not set");
|
||||
this.underlying = underlying;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void filter(ContainerRequestContext requestContext) throws IOException {
|
||||
try {
|
||||
underlying.filter(requestContext);
|
||||
} catch (WebApplicationException err) {
|
||||
// Pass through.
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ContainerRequestFilter getUnderlying() {
|
||||
return underlying;
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,20 @@
|
||||
package org.whispersystems.dropwizard.simpleauth;
|
||||
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import javax.ws.rs.container.ContainerRequestFilter;
|
||||
import javax.ws.rs.container.ResourceInfo;
|
||||
import javax.ws.rs.core.FeatureContext;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.Auth;
|
||||
import io.dropwizard.auth.AuthenticationException;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class AuthDynamicFeatureTest {
|
||||
@ -34,11 +39,33 @@ public class AuthDynamicFeatureTest {
|
||||
|
||||
dynamicFeature.configure(resourceInfo, featureContext);
|
||||
verify(featureContext).register(eq(stringPrincipal));
|
||||
reset(featureContext);
|
||||
|
||||
when(resourceInfo.getResourceMethod()).thenReturn(MockMethod.class.getDeclaredMethod("integerAuthParam", Integer.class));
|
||||
|
||||
dynamicFeature.configure(resourceInfo, featureContext);
|
||||
verify(featureContext).register(eq(integerPrincipal));
|
||||
reset(featureContext);
|
||||
|
||||
when(resourceInfo.getResourceMethod()).thenReturn(MockMethod.class.getDeclaredMethod("optionalStringAuthParam", Optional.of("test").getClass()));
|
||||
|
||||
dynamicFeature.configure(resourceInfo, featureContext);
|
||||
|
||||
ArgumentCaptor<ContainerRequestFilter> stringOptionalCaptor = ArgumentCaptor.forClass(ContainerRequestFilter.class);
|
||||
verify(featureContext).register(stringOptionalCaptor.capture());
|
||||
assertTrue(stringOptionalCaptor.getValue() instanceof WebApplicationExceptionCatchingFilter);
|
||||
assertEquals(((WebApplicationExceptionCatchingFilter)stringOptionalCaptor.getValue()).getUnderlying(), stringPrincipal);
|
||||
|
||||
reset(featureContext);
|
||||
|
||||
when(resourceInfo.getResourceMethod()).thenReturn(MockMethod.class.getDeclaredMethod("optionalIntegerAuthParam", Optional.of(1).getClass()));
|
||||
|
||||
dynamicFeature.configure(resourceInfo, featureContext);
|
||||
|
||||
ArgumentCaptor<ContainerRequestFilter> integerOptionalCaptor = ArgumentCaptor.forClass(ContainerRequestFilter.class);
|
||||
verify(featureContext, times(1)).register(integerOptionalCaptor.capture());
|
||||
assertTrue(integerOptionalCaptor.getValue() instanceof WebApplicationExceptionCatchingFilter);
|
||||
assertEquals(((WebApplicationExceptionCatchingFilter)integerOptionalCaptor.getValue()).getUnderlying(), integerPrincipal);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -55,12 +82,26 @@ public class AuthDynamicFeatureTest {
|
||||
} catch (Exception e) {
|
||||
// Good
|
||||
}
|
||||
|
||||
when(resourceInfo.getResourceMethod()).thenReturn(MockMethod.class.getDeclaredMethod("multipleOptionalAuthParams", Optional.of("foo").getClass(), Optional.of("bar").getClass()));
|
||||
|
||||
try {
|
||||
dynamicFeature.configure(resourceInfo, featureContext);
|
||||
throw new AssertionError("Shouldn't support multiple auth tags!");
|
||||
} catch (Exception e) {
|
||||
// Good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
private static class MockMethod {
|
||||
public void multipleAuthParams(@Auth String foo, @Auth String bar) {}
|
||||
public void stringAuthParam(@Auth String foo) {}
|
||||
public void integerAuthParam(@Auth Integer bar) {}
|
||||
public void optionalStringAuthParam(@Auth Optional<String> foo) {}
|
||||
public void optionalIntegerAuthParam(@Auth Optional<Integer> bar) {}
|
||||
public void multipleOptionalAuthParams(@Auth Optional<String> foo, @Auth Optional<String> bar) {}
|
||||
}
|
||||
|
||||
private static class StringAuthenticator implements Authenticator<BasicCredentials, String> {
|
||||
@ -72,7 +113,7 @@ public class AuthDynamicFeatureTest {
|
||||
credentials.getPassword().equals("password"))
|
||||
return Optional.of("user");
|
||||
|
||||
return Optional.absent();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package org.whispersystems.dropwizard.simpleauth;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
@ -12,6 +11,7 @@ import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.AuthenticationException;
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
@ -76,7 +76,7 @@ public class BasicCredentialAuthFilterTest {
|
||||
return Optional.of("user");
|
||||
}
|
||||
|
||||
return Optional.absent();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user