Replace method interceptor with request filter

This commit is contained in:
René Pfeuffer
2018-11-13 09:54:28 +01:00
parent 96c2114e53
commit 3e99709035
4 changed files with 108 additions and 48 deletions

View File

@@ -45,7 +45,6 @@ import sonia.scm.api.rest.ObjectMapperProvider;
import sonia.scm.cache.CacheManager; import sonia.scm.cache.CacheManager;
import sonia.scm.cache.GuavaCacheManager; import sonia.scm.cache.GuavaCacheManager;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.debug.DebugResource;
import sonia.scm.event.ScmEventBus; import sonia.scm.event.ScmEventBus;
import sonia.scm.group.DefaultGroupManager; import sonia.scm.group.DefaultGroupManager;
import sonia.scm.group.GroupDAO; import sonia.scm.group.GroupDAO;
@@ -88,7 +87,6 @@ import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.DefaultSecuritySystem; import sonia.scm.security.DefaultSecuritySystem;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;
import sonia.scm.security.LoginAttemptHandler; import sonia.scm.security.LoginAttemptHandler;
import sonia.scm.security.SecurityInterceptor;
import sonia.scm.security.SecuritySystem; import sonia.scm.security.SecuritySystem;
import sonia.scm.store.BlobStoreFactory; import sonia.scm.store.BlobStoreFactory;
import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.store.ConfigurationEntryStoreFactory;
@@ -120,16 +118,7 @@ import sonia.scm.web.security.DefaultAdministrationContext;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import static com.google.inject.matcher.Matchers.annotatedWith;
import static com.google.inject.matcher.Matchers.not;
import static com.google.inject.matcher.Matchers.subclassesOf;
import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH; import static sonia.scm.api.v2.resources.ScmPathInfo.REST_API_PATH;
/** /**
@@ -330,14 +319,6 @@ public class ScmServletModule extends ServletModule
// bind(LastModifiedUpdateListener.class); // bind(LastModifiedUpdateListener.class);
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class); bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
bindInterceptor(not(subclassesOf(DebugResource.class)),
annotatedWith(GET.class)
.or(annotatedWith(POST.class))
.or(annotatedWith(HEAD.class))
.or(annotatedWith(PUT.class))
.or(annotatedWith(DELETE.class))
.or(annotatedWith(OPTIONS.class)),
new SecurityInterceptor());
} }

View File

@@ -1,29 +0,0 @@
package sonia.scm.security;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
public class SecurityInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
if (hasPermission() || anonymousAccessIsAllowed(methodInvocation)) {
return methodInvocation.proceed();
} else {
throw new AuthenticationException();
}
}
private boolean anonymousAccessIsAllowed(MethodInvocation methodInvocation) {
return methodInvocation.getMethod().isAnnotationPresent(AllowAnonymousAccess.class)
|| methodInvocation.getMethod().getDeclaringClass().isAnnotationPresent(AllowAnonymousAccess.class);
}
private boolean hasPermission() {
Subject subject = SecurityUtils.getSubject();
return subject.isAuthenticated() || subject.isRemembered();
}
}

View File

@@ -0,0 +1,46 @@
package sonia.scm.security;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import java.lang.reflect.Method;
@Provider
public class SecurityRequestFilter implements ContainerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(SecurityRequestFilter.class);
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext requestContext) {
Method resourceMethod = resourceInfo.getResourceMethod();
LOG.info("jax-rs method {}", resourceMethod.getName());
if (hasPermission() || anonymousAccessIsAllowed(resourceMethod)) {
LOG.debug("allowed unauthenticated request to method {}", resourceMethod);
// nothing further to do
} else {
LOG.debug("blocked unauthenticated request to method {}", resourceMethod);
throw new AuthenticationException();
}
}
private boolean anonymousAccessIsAllowed(Method method) {
return method.isAnnotationPresent(AllowAnonymousAccess.class)
|| method.getDeclaringClass().isAnnotationPresent(AllowAnonymousAccess.class);
}
private boolean hasPermission() {
Subject subject = SecurityUtils.getSubject();
return subject.isAuthenticated() || subject.isRemembered();
}
}

View File

@@ -0,0 +1,62 @@
package sonia.scm.security;
import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware;
import org.apache.shiro.authc.AuthenticationException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ResourceInfo;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
@SubjectAware(configuration = "classpath:sonia/scm/shiro-001.ini")
public class SecurityRequestFilterTest {
@Rule
public ShiroRule shiroRule = new ShiroRule();
@Mock
private ResourceInfo resourceInfo;
@Mock
private ContainerRequestContext context;
@InjectMocks
private SecurityRequestFilter securityRequestFilter;
@Test
public void shouldAllowUnauthenticatedAccessForAnnotatedMethod() throws NoSuchMethodException {
when(resourceInfo.getResourceMethod()).thenReturn(SecurityTestClass.class.getMethod("anonymousAccessAllowed"));
securityRequestFilter.filter(context);
}
@Test(expected = AuthenticationException.class)
public void shouldRejectUnauthenticatedAccessForAnnotatedMethod() throws NoSuchMethodException {
when(resourceInfo.getResourceMethod()).thenReturn(SecurityTestClass.class.getMethod("loginRequired"));
securityRequestFilter.filter(context);
}
@Test
@SubjectAware(username = "trillian", password = "secret")
public void shouldAllowAuthenticatedAccessForMethodWithoutAnnotation() throws NoSuchMethodException {
when(resourceInfo.getResourceMethod()).thenReturn(SecurityTestClass.class.getMethod("loginRequired"));
securityRequestFilter.filter(context);
}
private static class SecurityTestClass {
@AllowAnonymousAccess
public void anonymousAccessAllowed() {
}
public void loginRequired() {
}
}
}