diff --git a/core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextCallable.java b/core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextCallable.java index 3b4d428665..834732f99c 100644 --- a/core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextCallable.java +++ b/core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextCallable.java @@ -19,8 +19,17 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.Assert; /** - * Wraps a delegate {@link Callable} with logic for setting up a {@link SecurityContext} before invoking the delegate - * {@link Callable} and then removing the {@link SecurityContext} after the delegate has completed. + *

+ * Wraps a delegate {@link Callable} with logic for setting up a + * {@link SecurityContext} before invoking the delegate {@link Callable} and + * then removing the {@link SecurityContext} after the delegate has completed. + *

+ *

+ * By default the {@link SecurityContext} is only setup if {@link #call()} is + * invoked on a separate {@link Thread} than the + * {@link DelegatingSecurityContextCallable} was created on. This can be + * overridden by setting {@link #setEnableOnOriginalThread(boolean)} to true. + *

* * @author Rob Winch * @since 3.2 @@ -31,6 +40,10 @@ public final class DelegatingSecurityContextCallable implements Callable { private final SecurityContext securityContext; + private final Thread originalThread; + + private boolean enableOnOriginalThread; + /** * Creates a new {@link DelegatingSecurityContextCallable} with a specific {@link SecurityContext}. * @param delegate the delegate {@link DelegatingSecurityContextCallable} to run with the specified @@ -43,6 +56,7 @@ public final class DelegatingSecurityContextCallable implements Callable { Assert.notNull(securityContext, "securityContext cannot be null"); this.delegate = delegate; this.securityContext = securityContext; + this.originalThread = Thread.currentThread(); } /** @@ -54,7 +68,27 @@ public final class DelegatingSecurityContextCallable implements Callable { this(delegate, SecurityContextHolder.getContext()); } + /** + * Determines if the SecurityContext should be transfered if {@link #call()} + * is invoked on the same {@link Thread} the + * {@link DelegatingSecurityContextCallable} was created on. + * + * @param enableOnOriginalThread + * if false (default), will only transfer the + * {@link SecurityContext} if {@link #call()} is invoked on a + * different {@link Thread} than the + * {@link DelegatingSecurityContextCallable} was created on. + * + * @since 4.0.2 + */ + public void setEnableOnOriginalThread(boolean enableOnOriginalThread) { + this.enableOnOriginalThread = enableOnOriginalThread; + } + public V call() throws Exception { + if(!enableOnOriginalThread && originalThread == Thread.currentThread()) { + return delegate.call(); + } try { SecurityContextHolder.setContext(securityContext); return delegate.call(); diff --git a/core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnable.java b/core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnable.java index 4bae805dfb..28d018c9d1 100644 --- a/core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnable.java +++ b/core/src/main/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnable.java @@ -17,8 +17,17 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.Assert; /** - * Wraps a delegate {@link Runnable} with logic for setting up a {@link SecurityContext} before invoking the delegate - * {@link Runnable} and then removing the {@link SecurityContext} after the delegate has completed. + *

+ * Wraps a delegate {@link Runnable} with logic for setting up a {@link SecurityContext} + * before invoking the delegate {@link Runnable} and then removing the + * {@link SecurityContext} after the delegate has completed. + *

+ *

+ * By default the {@link SecurityContext} is only setup if {@link #run()} is + * invoked on a separate {@link Thread} than the + * {@link DelegatingSecurityContextRunnable} was created on. This can be + * overridden by setting {@link #setEnableOnOriginalThread(boolean)} to true. + *

* * @author Rob Winch * @since 3.2 @@ -29,6 +38,10 @@ public final class DelegatingSecurityContextRunnable implements Runnable { private final SecurityContext securityContext; + private final Thread originalThread; + + private boolean enableOnOriginalThread; + /** * Creates a new {@link DelegatingSecurityContextRunnable} with a specific {@link SecurityContext}. * @param delegate the delegate {@link Runnable} to run with the specified {@link SecurityContext}. Cannot be null. @@ -40,6 +53,7 @@ public final class DelegatingSecurityContextRunnable implements Runnable { Assert.notNull(securityContext, "securityContext cannot be null"); this.delegate = delegate; this.securityContext = securityContext; + this.originalThread = Thread.currentThread(); } /** @@ -51,7 +65,27 @@ public final class DelegatingSecurityContextRunnable implements Runnable { this(delegate, SecurityContextHolder.getContext()); } + /** + * Determines if the SecurityContext should be transfered if {@link #call()} + * is invoked on the same {@link Thread} the + * {@link DelegatingSecurityContextCallable} was created on. + * + * @param enableOnOriginalThread + * if false (default), will only transfer the + * {@link SecurityContext} if {@link #call()} is invoked on a + * different {@link Thread} than the + * {@link DelegatingSecurityContextCallable} was created on. + * @since 4.0.2 + */ + public void setEnableOnOriginalThread(boolean enableOnOriginalThread) { + this.enableOnOriginalThread = enableOnOriginalThread; + } + public void run() { + if(!enableOnOriginalThread && originalThread == Thread.currentThread()) { + delegate.run(); + return; + } try { SecurityContextHolder.setContext(securityContext); delegate.run(); diff --git a/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextCallableTests.java b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextCallableTests.java index 17bb536a0b..48527482bc 100644 --- a/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextCallableTests.java +++ b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextCallableTests.java @@ -17,6 +17,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import org.junit.After; import org.junit.Before; @@ -45,6 +48,8 @@ public class DelegatingSecurityContextCallableTests { private Callable callable; + private ExecutorService executor; + @Before @SuppressWarnings("serial") public void setUp() throws Exception { @@ -55,6 +60,7 @@ public class DelegatingSecurityContextCallableTests { return super.answer(invocation); } }); + executor = Executors.newFixedThreadPool(1); } @After @@ -88,15 +94,34 @@ public class DelegatingSecurityContextCallableTests { @Test public void call() throws Exception { - callable = new DelegatingSecurityContextCallable(delegate, securityContext); - assertWrapped(callable.call()); + callable = new DelegatingSecurityContextCallable(delegate, + securityContext); + assertWrapped(callable); } @Test public void callDefaultSecurityContext() throws Exception { SecurityContextHolder.setContext(securityContext); callable = new DelegatingSecurityContextCallable(delegate); - SecurityContextHolder.clearContext(); // ensure callable is what sets up the SecurityContextHolder + SecurityContextHolder.clearContext(); // ensure callable is what sets up the + // SecurityContextHolder + assertWrapped(callable); + } + + // SEC-3031 + @Test + public void callOnSameThread() throws Exception { + callable = new DelegatingSecurityContextCallable(delegate, + securityContext); + securityContext = SecurityContextHolder.createEmptyContext(); + assertWrapped(callable.call()); + } + + @Test + public void callOnSameThreadExplicitlyEnabled() throws Exception { + DelegatingSecurityContextCallable callable = new DelegatingSecurityContextCallable(delegate, + securityContext); + callable.setEnableOnOriginalThread(true); assertWrapped(callable.call()); } @@ -116,14 +141,15 @@ public class DelegatingSecurityContextCallableTests { public void createNullSecurityContext() throws Exception { SecurityContextHolder.setContext(securityContext); callable = DelegatingSecurityContextCallable.create(delegate, null); - SecurityContextHolder.clearContext(); // ensure callable is what sets up the SecurityContextHolder - assertWrapped(callable.call()); + SecurityContextHolder.clearContext(); // ensure callable is what sets up the + // SecurityContextHolder + assertWrapped(callable); } @Test public void create() throws Exception { callable = DelegatingSecurityContextCallable.create(delegate, securityContext); - assertWrapped(callable.call()); + assertWrapped(callable); } // --- toString @@ -131,13 +157,19 @@ public class DelegatingSecurityContextCallableTests { // SEC-2682 @Test public void toStringDelegates() { - callable = new DelegatingSecurityContextCallable(delegate, securityContext); + callable = new DelegatingSecurityContextCallable(delegate, + securityContext); assertThat(callable.toString()).isEqualTo(delegate.toString()); } - private void assertWrapped(Object actualResult) throws Exception { - assertThat(actualResult).isEqualTo(callableResult); + private void assertWrapped(Callable callable) throws Exception { + Future submit = executor.submit(callable); + assertWrapped(submit.get()); + } + + private void assertWrapped(Object callableResult) throws Exception { verify(delegate).call(); - assertThat(SecurityContextHolder.getContext()).isEqualTo(SecurityContextHolder.createEmptyContext()); + assertThat(SecurityContextHolder.getContext()).isEqualTo( + SecurityContextHolder.createEmptyContext()); } } \ No newline at end of file diff --git a/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnableTests.java b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnableTests.java index c5aaf3c4c9..cbbae4e642 100644 --- a/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnableTests.java +++ b/core/src/test/java/org/springframework/security/concurrent/DelegatingSecurityContextRunnableTests.java @@ -16,6 +16,10 @@ import static org.fest.assertions.Assertions.assertThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -24,6 +28,8 @@ import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.core.task.support.ExecutorServiceAdapter; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -43,6 +49,8 @@ public class DelegatingSecurityContextRunnableTests { private Runnable runnable; + private ExecutorService executor; + @Before public void setUp() throws Exception { doAnswer(new Answer() { @@ -50,8 +58,9 @@ public class DelegatingSecurityContextRunnableTests { assertThat(SecurityContextHolder.getContext()).isEqualTo(securityContext); return null; } - }) - .when(delegate).run(); + }).when(delegate).run(); + + executor = Executors.newFixedThreadPool(1); } @After @@ -86,17 +95,35 @@ public class DelegatingSecurityContextRunnableTests { @Test public void call() throws Exception { runnable = new DelegatingSecurityContextRunnable(delegate, securityContext); - runnable.run(); - assertWrapped(); + assertWrapped(runnable); } @Test public void callDefaultSecurityContext() throws Exception { SecurityContextHolder.setContext(securityContext); runnable = new DelegatingSecurityContextRunnable(delegate); - SecurityContextHolder.clearContext(); // ensure runnable is what sets up the SecurityContextHolder - runnable.run(); - assertWrapped(); + SecurityContextHolder.clearContext(); // ensure runnable is what sets up the + // SecurityContextHolder + assertWrapped(runnable); + } + + // SEC-3031 + @Test + public void callOnSameThread() throws Exception { + executor = synchronousExecutor(); + runnable = new DelegatingSecurityContextRunnable(delegate, + securityContext); + securityContext = SecurityContextHolder.createEmptyContext(); + assertWrapped(runnable); + } + + @Test + public void callOnSameThreadExplicitlyEnabled() throws Exception { + executor = synchronousExecutor(); + DelegatingSecurityContextRunnable runnable = new DelegatingSecurityContextRunnable(delegate, + securityContext); + runnable.setEnableOnOriginalThread(true); + assertWrapped(runnable); } // --- create --- @@ -112,19 +139,18 @@ public class DelegatingSecurityContextRunnableTests { } @Test - public void createNullSecurityContext() { + public void createNullSecurityContext() throws Exception { SecurityContextHolder.setContext(securityContext); runnable = DelegatingSecurityContextRunnable.create(delegate, null); - SecurityContextHolder.clearContext(); // ensure runnable is what sets up the SecurityContextHolder - runnable.run(); - assertWrapped(); + SecurityContextHolder.clearContext(); // ensure runnable is what sets up the + // SecurityContextHolder + assertWrapped(runnable); } @Test - public void create() { + public void create() throws Exception { runnable = DelegatingSecurityContextRunnable.create(delegate, securityContext); - runnable.run(); - assertWrapped(); + assertWrapped(runnable); } // --- toString @@ -136,8 +162,15 @@ public class DelegatingSecurityContextRunnableTests { assertThat(runnable.toString()).isEqualTo(delegate.toString()); } - private void assertWrapped() { + private void assertWrapped(Runnable runnable) throws Exception { + Future submit = executor.submit(runnable); + submit.get(); verify(delegate).run(); - assertThat(SecurityContextHolder.getContext()).isEqualTo(SecurityContextHolder.createEmptyContext()); + assertThat(SecurityContextHolder.getContext()).isEqualTo( + SecurityContextHolder.createEmptyContext()); + } + + private static ExecutorService synchronousExecutor() { + return new ExecutorServiceAdapter(new SyncTaskExecutor()); } } \ No newline at end of file diff --git a/web/src/test/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilterTests.java b/web/src/test/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilterTests.java index cf0a51bd5f..054ce5437f 100644 --- a/web/src/test/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/servletapi/SecurityContextHolderAwareRequestFilterTests.java @@ -63,7 +63,6 @@ import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.util.ClassUtils; - /** * Tests {@link SecurityContextHolderAwareRequestFilter}. * @@ -107,18 +106,23 @@ public class SecurityContextHolderAwareRequestFilterTests { SecurityContextHolder.clearContext(); } - //~ Methods ======================================================================================================== + // ~ Methods + // ======================================================================================================== @Test public void expectedRequestWrapperClassIsUsed() throws Exception { filter.setRolePrefix("ROLE_"); - filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), filterChain); + filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), + filterChain); // Now re-execute the filter, ensuring our replacement wrapper is still used - filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), filterChain); + filter.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse(), + filterChain); - verify(filterChain, times(2)).doFilter(any(SecurityContextHolderAwareRequestWrapper.class), any(HttpServletResponse.class)); + verify(filterChain, times(2)).doFilter( + any(SecurityContextHolderAwareRequestWrapper.class), + any(HttpServletResponse.class)); filter.destroy(); } @@ -126,17 +130,20 @@ public class SecurityContextHolderAwareRequestFilterTests { @Test public void authenticateFalse() throws Exception { assertThat(wrappedRequest().authenticate(response)).isFalse(); - verify(authenticationEntryPoint).commence(eq(requestCaptor.getValue()), eq(response), any(AuthenticationException.class)); + verify(authenticationEntryPoint).commence(eq(requestCaptor.getValue()), + eq(response), any(AuthenticationException.class)); verifyZeroInteractions(authenticationManager, logoutHandler); verify(request, times(0)).authenticate(any(HttpServletResponse.class)); } @Test public void authenticateTrue() throws Exception { - SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("test","password","ROLE_USER")); + SecurityContextHolder.getContext().setAuthentication( + new TestingAuthenticationToken("test", "password", "ROLE_USER")); assertThat(wrappedRequest().authenticate(response)).isTrue(); - verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler); + verifyZeroInteractions(authenticationEntryPoint, authenticationManager, + logoutHandler); verify(request, times(0)).authenticate(any(HttpServletResponse.class)); } @@ -147,7 +154,8 @@ public class SecurityContextHolderAwareRequestFilterTests { assertThat(wrappedRequest().authenticate(response)).isFalse(); verify(request).authenticate(response); - verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler); + verifyZeroInteractions(authenticationEntryPoint, authenticationManager, + logoutHandler); } @Test @@ -158,53 +166,73 @@ public class SecurityContextHolderAwareRequestFilterTests { assertThat(wrappedRequest().authenticate(response)).isTrue(); verify(request).authenticate(response); - verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler); + verifyZeroInteractions(authenticationEntryPoint, authenticationManager, + logoutHandler); } @Test public void login() throws Exception { - TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER"); - when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenReturn(expectedAuth); + TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", + "password", "ROLE_USER"); + when( + authenticationManager + .authenticate(any(UsernamePasswordAuthenticationToken.class))) + .thenReturn(expectedAuth); - wrappedRequest().login(expectedAuth.getName(),String.valueOf(expectedAuth.getCredentials())); + wrappedRequest().login(expectedAuth.getName(), + String.valueOf(expectedAuth.getCredentials())); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(expectedAuth); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs( + expectedAuth); verifyZeroInteractions(authenticationEntryPoint, logoutHandler); - verify(request, times(0)).login(anyString(),anyString()); + verify(request, times(0)).login(anyString(), anyString()); } // SEC-2296 @Test public void loginWithExstingUser() throws Exception { - TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER"); - when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenReturn(new TestingAuthenticationToken("newuser","not be found","ROLE_USER")); + TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", + "password", "ROLE_USER"); + when( + authenticationManager + .authenticate(any(UsernamePasswordAuthenticationToken.class))) + .thenReturn( + new TestingAuthenticationToken("newuser", "not be found", + "ROLE_USER")); SecurityContextHolder.getContext().setAuthentication(expectedAuth); try { - wrappedRequest().login(expectedAuth.getName(),String.valueOf(expectedAuth.getCredentials())); + wrappedRequest().login(expectedAuth.getName(), + String.valueOf(expectedAuth.getCredentials())); fail("Expected Exception"); - } catch(ServletException success) { - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(expectedAuth); + } + catch (ServletException success) { + assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs( + expectedAuth); verifyZeroInteractions(authenticationEntryPoint, logoutHandler); - verify(request, times(0)).login(anyString(),anyString()); + verify(request, times(0)).login(anyString(), anyString()); } } @Test public void loginFail() throws Exception { AuthenticationException authException = new BadCredentialsException("Invalid"); - when(authenticationManager.authenticate(any(UsernamePasswordAuthenticationToken.class))).thenThrow(authException); + when( + authenticationManager + .authenticate(any(UsernamePasswordAuthenticationToken.class))) + .thenThrow(authException); try { - wrappedRequest().login("invalid","credentials"); + wrappedRequest().login("invalid", "credentials"); Assert.fail("Expected Exception"); - } catch(ServletException success) { + } + catch (ServletException success) { assertThat(success.getCause()).isEqualTo(authException); } assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); verifyZeroInteractions(authenticationEntryPoint, logoutHandler); - verify(request, times(0)).login(anyString(),anyString()); + verify(request, times(0)).login(anyString(), anyString()); } @Test @@ -218,7 +246,8 @@ public class SecurityContextHolderAwareRequestFilterTests { wrappedRequest().login(username, password); verify(request).login(username, password); - verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler); + verifyZeroInteractions(authenticationEntryPoint, authenticationManager, + logoutHandler); } @Test @@ -234,16 +263,19 @@ public class SecurityContextHolderAwareRequestFilterTests { try { wrappedRequest().login(username, password); Assert.fail("Expected Exception"); - } catch(ServletException success) { + } + catch (ServletException success) { assertThat(success).isEqualTo(authException); } - verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler); + verifyZeroInteractions(authenticationEntryPoint, authenticationManager, + logoutHandler); } @Test public void logout() throws Exception { - TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER"); + TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", + "password", "ROLE_USER"); SecurityContextHolder.getContext().setAuthentication(expectedAuth); HttpServletRequest wrappedRequest = wrappedRequest(); @@ -262,78 +294,105 @@ public class SecurityContextHolderAwareRequestFilterTests { wrappedRequest().logout(); verify(request).logout(); - verifyZeroInteractions(authenticationEntryPoint, authenticationManager, logoutHandler); + verifyZeroInteractions(authenticationEntryPoint, authenticationManager, + logoutHandler); } @Test public void getAsyncContextStart() throws Exception { ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); SecurityContext context = SecurityContextHolder.createEmptyContext(); - TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER"); + TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", + "password", "ROLE_USER"); context.setAuthentication(expectedAuth); SecurityContextHolder.setContext(context); AsyncContext asyncContext = mock(AsyncContext.class); when(request.getAsyncContext()).thenReturn(asyncContext); Runnable runnable = new Runnable() { - public void run() {} + public void run() { + } }; wrappedRequest().getAsyncContext().start(runnable); verifyZeroInteractions(authenticationManager, logoutHandler); verify(asyncContext).start(runnableCaptor.capture()); - DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor.getValue(); - assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class)).isEqualTo(context); - assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, Runnable.class)).isEqualTo(runnable); + DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor + .getValue(); + assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class)) + .isEqualTo(context); + assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, "delegate")) + .isEqualTo(runnable); } @Test public void startAsyncStart() throws Exception { ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); SecurityContext context = SecurityContextHolder.createEmptyContext(); - TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER"); + TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", + "password", "ROLE_USER"); context.setAuthentication(expectedAuth); SecurityContextHolder.setContext(context); AsyncContext asyncContext = mock(AsyncContext.class); when(request.startAsync()).thenReturn(asyncContext); Runnable runnable = new Runnable() { - public void run() {} + public void run() { + } }; wrappedRequest().startAsync().start(runnable); verifyZeroInteractions(authenticationManager, logoutHandler); verify(asyncContext).start(runnableCaptor.capture()); - DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor.getValue(); - assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class)).isEqualTo(context); - assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, Runnable.class)).isEqualTo(runnable); + DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor + .getValue(); + assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class)) + .isEqualTo(context); + assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, "delegate")) + .isEqualTo(runnable); } @Test public void startAsyncWithRequestResponseStart() throws Exception { ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); SecurityContext context = SecurityContextHolder.createEmptyContext(); - TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", "password","ROLE_USER"); + TestingAuthenticationToken expectedAuth = new TestingAuthenticationToken("user", + "password", "ROLE_USER"); context.setAuthentication(expectedAuth); SecurityContextHolder.setContext(context); AsyncContext asyncContext = mock(AsyncContext.class); - when(request.startAsync(request,response)).thenReturn(asyncContext); + when(request.startAsync(request, response)).thenReturn(asyncContext); Runnable runnable = new Runnable() { - public void run() {} + public void run() { + } }; wrappedRequest().startAsync(request, response).start(runnable); verifyZeroInteractions(authenticationManager, logoutHandler); verify(asyncContext).start(runnableCaptor.capture()); - DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor.getValue(); - assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class)).isEqualTo(context); - assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, Runnable.class)).isEqualTo(runnable); + DelegatingSecurityContextRunnable wrappedRunnable = (DelegatingSecurityContextRunnable) runnableCaptor + .getValue(); + assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, SecurityContext.class)) + .isEqualTo(context); + assertThat(WhiteboxImpl.getInternalState(wrappedRunnable, "delegate")) + .isEqualTo(runnable); + } + + // SEC-3047 + @Test + public void updateRequestFactory() throws Exception { + SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("user", + "password", "PREFIX_USER")); + filter.setRolePrefix("PREFIX_"); + + assertThat(wrappedRequest().isUserInRole("PREFIX_USER")).isTrue();; } private HttpServletRequest wrappedRequest() throws Exception { filter.doFilter(request, response, filterChain); - verify(filterChain).doFilter(requestCaptor.capture(), any(HttpServletResponse.class)); + verify(filterChain).doFilter(requestCaptor.capture(), + any(HttpServletResponse.class)); return requestCaptor.getValue(); }