From abae2f3e8786892830e1184c378093df4d164f08 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 7 Mar 2018 17:05:32 -0600 Subject: [PATCH] Allow WithSecurityContextTestExecutionListener to execute after @Before Fixes: gh-2935 --- .../src/docs/asciidoc/_includes/test.adoc | 38 +++++ docs/manual/src/docs/asciidoc/index.adoc | 24 +-- .../context/support/TestExecutionEvent.java | 38 +++++ .../context/support/WithAnonymousUser.java | 11 ++ .../test/context/support/WithMockUser.java | 14 +- .../context/support/WithSecurityContext.java | 13 +- ...hSecurityContextTestExecutionListener.java | 67 ++++++-- .../test/context/support/WithUserDetails.java | 14 +- .../support/WithAnonymousUserTests.java | 65 ++++++++ .../context/support/WithMockUserTests.java | 40 ++++- ...rityContextTestExecutionListenerTests.java | 144 ++++++++++++++++++ .../context/support/WithUserDetailsTests.java | 35 ++++- 12 files changed, 465 insertions(+), 38 deletions(-) create mode 100644 test/src/main/java/org/springframework/security/test/context/support/TestExecutionEvent.java create mode 100644 test/src/test/java/org/springframework/security/test/context/support/WithAnonymousUserTests.java create mode 100644 test/src/test/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListenerTests.java diff --git a/docs/manual/src/docs/asciidoc/_includes/test.adoc b/docs/manual/src/docs/asciidoc/_includes/test.adoc index 7d4b125e09..59bfdbeb50 100644 --- a/docs/manual/src/docs/asciidoc/_includes/test.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/test.adoc @@ -144,6 +144,15 @@ For example, the following would run every test with a user with the username "a public class WithMockUserTests { ---- +By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. +This is the equivalent of happening before JUnit's `@Before`. +You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked. + +[source,java] +---- +@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION) +---- + [[test-method-withanonymoususer]] === @WithAnonymousUser @@ -174,6 +183,15 @@ public class WithUserClassLevelAuthenticationTests { } ---- +By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. +This is the equivalent of happening before JUnit's `@Before`. +You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked. + +[source,java] +---- +@WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_EXECUTION) +---- + [[test-method-withuserdetails]] === @WithUserDetails @@ -227,6 +245,16 @@ public void getMessageWithUserDetailsServiceBeanName() { Like `@WithMockUser` we can also place our annotation at the class level so that every test uses the same user. However unlike `@WithMockUser`, `@WithUserDetails` requires the user to exist. +By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. +This is the equivalent of happening before JUnit's `@Before`. +You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked. + +[source,java] +---- +@WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION) +---- + + [[test-method-withsecuritycontext]] === @WithSecurityContext @@ -301,6 +329,16 @@ final class WithUserDetailsSecurityContextFactory } ---- +By default the `SecurityContext` is set during the `TestExecutionListener.beforeTestMethod` event. +This is the equivalent of happening before JUnit's `@Before`. +You can change this to happen during the `TestExecutionListener.beforeTestExecution` event which is after JUnit's `@Before` but before the test method is invoked. + +[source,java] +---- +@WithSecurityContext(setupBefore = TestExecutionEvent.TEST_EXECUTION) +---- + + [[test-method-meta-annotations]] === Test Meta Annotations diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 6fef2df5f8..3a5776d7e2 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -400,28 +400,16 @@ git clone https://github.com/spring-projects/spring-security.git This will give you access to the entire project history (including all releases and branches) on your local machine. [[new]] -== What's New in Spring Security 5.0 +== What's New in Spring Security 5.1 -Spring Security 5.0 provides a number of new features as well as support for Spring Framework 5. -In total there were 400+ enhancements and bugs resolved. -You can find the change log at -https://github.com/spring-projects/spring-security/milestone/90?closed=1[5.0.0.M1] -https://github.com/spring-projects/spring-security/milestone/97?closed=1[5.0.0.M2] -https://github.com/spring-projects/spring-security/milestone/100?closed=1[5.0.0.M3] -https://github.com/spring-projects/spring-security/milestone/101?closed=1[5.0.0.M4] -https://github.com/spring-projects/spring-security/milestone/102?closed=1[5.0.0.M5] -https://github.com/spring-projects/spring-security/milestone/103?closed=1[5.0.0.RC1] -https://github.com/spring-projects/spring-security/milestone/98?closed=1[5.0.0.RELEASE]. -Below are the highlights of this milestone release. +Spring Security 5.1 provides a number of new features. +Below are the highlights of the release. === New Features -* <> -* Reactive Support -** <> -** <> -** <> -* Modernized <> +* <> - Support for customizing when the `SecurityContext` is setup in the test. +For example, `@WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)` will setup a user after JUnit's `@Before` and before the test executes. + [[samples]] == Samples and Guides (Start Here) diff --git a/test/src/main/java/org/springframework/security/test/context/support/TestExecutionEvent.java b/test/src/main/java/org/springframework/security/test/context/support/TestExecutionEvent.java new file mode 100644 index 0000000000..7e349eb96a --- /dev/null +++ b/test/src/main/java/org/springframework/security/test/context/support/TestExecutionEvent.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.test.context.support; + +import org.springframework.test.context.TestContext; + +/** + * Represents the events on the methods of {@link org.springframework.test.context.TestExecutionListener} + * + * @author Rob Winch + * @since 5.1 + */ +public enum TestExecutionEvent { + /** + * Associated to {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)} + * event. + */ + TEST_METHOD, + /** + * Associated to {@link org.springframework.test.context.TestExecutionListener#beforeTestExecution(TestContext)} + * event. + */ + TEST_EXECUTION +} diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUser.java b/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUser.java index d9db9c1204..43471aa47a 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUser.java +++ b/test/src/main/java/org/springframework/security/test/context/support/WithAnonymousUser.java @@ -22,8 +22,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.context.SecurityContext; +import org.springframework.test.context.TestContext; /** * When used with {@link WithSecurityContextTestExecutionListener} this @@ -58,4 +60,13 @@ import org.springframework.security.core.context.SecurityContext; @WithSecurityContext(factory = WithAnonymousUserSecurityContextFactory.class) public @interface WithAnonymousUser { + /** + * Determines when the {@link SecurityContext} is setup. The default is before + * {@link TestExecutionEvent#TEST_METHOD} which occurs during + * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)} + * @return the {@link TestExecutionEvent} to initialize before + * @since 5.1 + */ + @AliasFor(annotation = WithSecurityContext.class) + TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD; } diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithMockUser.java b/test/src/main/java/org/springframework/security/test/context/support/WithMockUser.java index 9144915ada..8f90e077ea 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/WithMockUser.java +++ b/test/src/main/java/org/springframework/security/test/context/support/WithMockUser.java @@ -22,10 +22,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.TestContext; import org.springframework.test.web.servlet.MockMvc; /** @@ -102,4 +104,14 @@ public @interface WithMockUser { * @return */ String password() default "password"; -} \ No newline at end of file + + /** + * Determines when the {@link SecurityContext} is setup. The default is before + * {@link TestExecutionEvent#TEST_METHOD} which occurs during + * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)} + * @return the {@link TestExecutionEvent} to initialize before + * @since 5.1 + */ + @AliasFor(annotation = WithSecurityContext.class) + TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD; +} diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContext.java b/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContext.java index b1e9b54d54..ce8d36253d 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContext.java +++ b/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContext.java @@ -25,6 +25,7 @@ import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContext; +import org.springframework.test.context.TestContext; /** *

@@ -61,4 +62,14 @@ public @interface WithSecurityContext { * @return */ Class> factory(); -} \ No newline at end of file + + /** + * Determines when the {@link SecurityContext} is setup. The default is before + * {@link TestExecutionEvent#TEST_METHOD} which occurs during + * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)} + * @return the {@link TestExecutionEvent} to initialize before + * @since 5.1 + */ + TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD; + +} diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java b/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java index 50304b39da..f9fd4ecd08 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java +++ b/test/src/main/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListener.java @@ -20,6 +20,7 @@ import java.lang.reflect.AnnotatedElement; import org.springframework.beans.BeanUtils; import org.springframework.core.GenericTypeResolver; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -46,6 +47,8 @@ import org.springframework.test.web.servlet.MockMvc; public class WithSecurityContextTestExecutionListener extends AbstractTestExecutionListener { + static final String SECURITY_CONTEXT_ATTR_NAME = WithSecurityContextTestExecutionListener.class.getName().concat(".SECURITY_CONTEXT"); + /** * Sets up the {@link SecurityContext} for each test method. First the specific method * is inspected for a {@link WithSecurityContext} or {@link Annotation} that has @@ -54,46 +57,68 @@ public class WithSecurityContextTestExecutionListener */ @Override public void beforeTestMethod(TestContext testContext) throws Exception { - SecurityContext securityContext = createSecurityContext( + TestSecurityContext testSecurityContext = createTestSecurityContext( testContext.getTestMethod(), testContext); - if (securityContext == null) { - securityContext = createSecurityContext(testContext.getTestClass(), + if (testSecurityContext == null) { + testSecurityContext = createTestSecurityContext(testContext.getTestClass(), testContext); } - if (securityContext != null) { + if (testSecurityContext == null) { + return; + } + + SecurityContext securityContext = testSecurityContext.securityContext; + if (testSecurityContext.getTestExecutionEvent() == TestExecutionEvent.TEST_METHOD) { + TestSecurityContextHolder.setContext(securityContext); + } else { + testContext.setAttribute(SECURITY_CONTEXT_ATTR_NAME, securityContext); + } + } + + /** + * If configured before test execution sets the SecurityContext + * @since 5.1 + */ + @Override + public void beforeTestExecution(TestContext testContext) { + SecurityContext securityContext = (SecurityContext) testContext.removeAttribute(SECURITY_CONTEXT_ATTR_NAME); + if(securityContext != null) { TestSecurityContextHolder.setContext(securityContext); } } - private SecurityContext createSecurityContext(AnnotatedElement annotated, + private TestSecurityContext createTestSecurityContext(AnnotatedElement annotated, TestContext context) { - WithSecurityContext withSecurityContext = AnnotationUtils - .findAnnotation(annotated, WithSecurityContext.class); - return createSecurityContext(annotated, withSecurityContext, context); + WithSecurityContext withSecurityContext = AnnotatedElementUtils + .findMergedAnnotation(annotated, WithSecurityContext.class); + return createTestSecurityContext(annotated, withSecurityContext, context); } - private SecurityContext createSecurityContext(Class annotated, + private TestSecurityContext createTestSecurityContext(Class annotated, TestContext context) { MetaAnnotationUtils.AnnotationDescriptor withSecurityContextDescriptor = MetaAnnotationUtils .findAnnotationDescriptor(annotated, WithSecurityContext.class); WithSecurityContext withSecurityContext = withSecurityContextDescriptor == null ? null : withSecurityContextDescriptor.getAnnotation(); - return createSecurityContext(annotated, withSecurityContext, context); + return createTestSecurityContext(annotated, withSecurityContext, context); } @SuppressWarnings({ "rawtypes", "unchecked" }) - private SecurityContext createSecurityContext(AnnotatedElement annotated, + private TestSecurityContext createTestSecurityContext(AnnotatedElement annotated, WithSecurityContext withSecurityContext, TestContext context) { if (withSecurityContext == null) { return null; } + withSecurityContext = AnnotationUtils + .synthesizeAnnotation(withSecurityContext, annotated); WithSecurityContextFactory factory = createFactory(withSecurityContext, context); Class type = (Class) GenericTypeResolver .resolveTypeArgument(factory.getClass(), WithSecurityContextFactory.class); Annotation annotation = findAnnotation(annotated, type); + TestExecutionEvent initialize = withSecurityContext.setupBefore(); try { - return factory.createSecurityContext(annotation); + return new TestSecurityContext(factory.createSecurityContext(annotation), initialize); } catch (RuntimeException e) { throw new IllegalStateException( @@ -150,4 +175,22 @@ public class WithSecurityContextTestExecutionListener public int getOrder() { return 10000; } + + static class TestSecurityContext { + private final SecurityContext securityContext; + private final TestExecutionEvent testExecutionEvent; + + TestSecurityContext(SecurityContext securityContext, TestExecutionEvent testExecutionEvent) { + this.securityContext = securityContext; + this.testExecutionEvent = testExecutionEvent; + } + + public SecurityContext getSecurityContext() { + return this.securityContext; + } + + public TestExecutionEvent getTestExecutionEvent() { + return this.testExecutionEvent; + } + } } diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithUserDetails.java b/test/src/main/java/org/springframework/security/test/context/support/WithUserDetails.java index 3bef071355..173ccc5a78 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/WithUserDetails.java +++ b/test/src/main/java/org/springframework/security/test/context/support/WithUserDetails.java @@ -22,11 +22,13 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.annotation.AliasFor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.test.context.TestContext; import org.springframework.test.web.servlet.MockMvc; /** @@ -69,4 +71,14 @@ public @interface WithUserDetails { * @since 4.1 */ String userDetailsServiceBeanName() default ""; -} \ No newline at end of file + + /** + * Determines when the {@link SecurityContext} is setup. The default is before + * {@link TestExecutionEvent#TEST_METHOD} which occurs during + * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)} + * @return the {@link TestExecutionEvent} to initialize before + * @since 5.1 + */ + @AliasFor(annotation = WithSecurityContext.class) + TestExecutionEvent setupBefore() default TestExecutionEvent.TEST_METHOD; +} diff --git a/test/src/test/java/org/springframework/security/test/context/support/WithAnonymousUserTests.java b/test/src/test/java/org/springframework/security/test/context/support/WithAnonymousUserTests.java new file mode 100644 index 0000000000..fe123665f4 --- /dev/null +++ b/test/src/test/java/org/springframework/security/test/context/support/WithAnonymousUserTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.test.context.support; + +import org.junit.Test; +import org.springframework.core.annotation.AnnotatedElementUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Rob Winch + * @since 5.0 + */ +public class WithAnonymousUserTests { + @Test + public void defaults() { + WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(Annotated.class, + WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); + } + + @WithAnonymousUser + private class Annotated { + } + + @Test + public void findMergedAnnotationWhenSetupExplicitThenOverridden() { + WithSecurityContext context = AnnotatedElementUtils + .findMergedAnnotation(SetupExplicit.class, + WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); + } + + @WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_METHOD) + private class SetupExplicit { + } + + @Test + public void findMergedAnnotationWhenSetupOverriddenThenOverridden() { + WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(SetupOverridden.class, + WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION); + } + + @WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_EXECUTION) + private class SetupOverridden { + } +} diff --git a/test/src/test/java/org/springframework/security/test/context/support/WithMockUserTests.java b/test/src/test/java/org/springframework/security/test/context/support/WithMockUserTests.java index 0ada5b8948..028edfa178 100644 --- a/test/src/test/java/org/springframework/security/test/context/support/WithMockUserTests.java +++ b/test/src/test/java/org/springframework/security/test/context/support/WithMockUserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,26 +13,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.security.test.context.support; import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; -import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.AnnotatedElementUtils; public class WithMockUserTests { @Test public void defaults() { - WithMockUser mockUser = AnnotationUtils.findAnnotation(Annotated.class, + WithMockUser mockUser = AnnotatedElementUtils.findMergedAnnotation(Annotated.class, WithMockUser.class); assertThat(mockUser.value()).isEqualTo("user"); assertThat(mockUser.username()).isEmpty(); assertThat(mockUser.password()).isEqualTo("password"); assertThat(mockUser.roles()).containsOnly("USER"); + assertThat(mockUser.setupBefore()).isEqualByComparingTo(TestExecutionEvent.TEST_METHOD); + + WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(Annotated.class, + WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); } @WithMockUser private class Annotated { } -} \ No newline at end of file + + @Test + public void findMergedAnnotationWhenSetupExplicitThenOverridden() { + WithSecurityContext context = AnnotatedElementUtils + .findMergedAnnotation(SetupExplicit.class, + WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); + } + + @WithMockUser(setupBefore = TestExecutionEvent.TEST_METHOD) + private class SetupExplicit { + } + + @Test + public void findMergedAnnotationWhenSetupOverriddenThenOverridden() { + WithSecurityContext context = AnnotatedElementUtils.findMergedAnnotation(SetupOverridden.class, + WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION); + } + + @WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION) + private class SetupOverridden { + } +} diff --git a/test/src/test/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListenerTests.java b/test/src/test/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListenerTests.java new file mode 100644 index 0000000000..02ff68a409 --- /dev/null +++ b/test/src/test/java/org/springframework/security/test/context/support/WithSecurityContextTestExecutionListenerTests.java @@ -0,0 +1,144 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.test.context.support; + +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.test.context.TestSecurityContextHolder; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; + +import java.lang.reflect.Method; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Rob Winch + * @since 5.0 + */ +@RunWith(MockitoJUnitRunner.class) +@ContextConfiguration(classes = WithSecurityContextTestExecutionListenerTests.NoOpConfiguration.class) +public class WithSecurityContextTestExecutionListenerTests { + @ClassRule + public static final SpringClassRule spring = new SpringClassRule(); + @Rule + public final SpringMethodRule springMethod = new SpringMethodRule(); + + @Autowired + private ApplicationContext applicationContext; + + @Mock + private TestContext testContext; + + private WithSecurityContextTestExecutionListener listener = new WithSecurityContextTestExecutionListener(); + + @After + public void cleanup() { + TestSecurityContextHolder.clearContext(); + } + + @Test + public void beforeTestMethodWhenWithMockUserTestExecutionDefaultThenSecurityContextSet() throws Exception { + Method testMethod = TheTest.class.getMethod("withMockUserDefault"); + when(this.testContext.getApplicationContext()).thenReturn(this.applicationContext); + when(this.testContext.getTestMethod()).thenReturn(testMethod); + + this.listener.beforeTestMethod(this.testContext); + + assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNotNull(); + verify(this.testContext, never()).setAttribute(eq(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME), any(SecurityContext.class)); + } + + @Test + public void beforeTestMethodWhenWithMockUserTestMethodThenSecurityContextSet() throws Exception { + Method testMethod = TheTest.class.getMethod("withMockUserTestMethod"); + when(this.testContext.getApplicationContext()).thenReturn(this.applicationContext); + when(this.testContext.getTestMethod()).thenReturn(testMethod); + + this.listener.beforeTestMethod(this.testContext); + + assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNotNull(); + verify(this.testContext, never()).setAttribute(eq(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME), any(SecurityContext.class)); + } + + @Test + public void beforeTestMethodWhenWithMockUserTestExecutionThenTestContextSet() throws Exception { + Method testMethod = TheTest.class.getMethod("withMockUserTestExecution"); + when(this.testContext.getApplicationContext()).thenReturn(this.applicationContext); + when(this.testContext.getTestMethod()).thenReturn(testMethod); + + this.listener.beforeTestMethod(this.testContext); + + assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNull(); + verify(this.testContext).setAttribute(eq(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME), any(SecurityContext.class)); + } + + @Test + public void beforeTestExecutionWhenTestContextNullThenSecurityContextNotSet() throws Exception { + this.listener.beforeTestExecution(this.testContext); + + assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isNull(); + } + + @Test + public void beforeTestExecutionWhenTestContextNotNullThenSecurityContextSet() throws Exception { + SecurityContextImpl securityContext = new SecurityContextImpl(); + securityContext.setAuthentication(new TestingAuthenticationToken("user", "passsword", "ROLE_USER")); + when(this.testContext.removeAttribute(WithSecurityContextTestExecutionListener.SECURITY_CONTEXT_ATTR_NAME)).thenReturn(securityContext); + + this.listener.beforeTestExecution(this.testContext); + + assertThat(TestSecurityContextHolder.getContext().getAuthentication()).isEqualTo(securityContext.getAuthentication()); + } + + @Configuration + static class NoOpConfiguration {} + + static class TheTest { + @WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION) + public void withMockUserTestExecution() { + } + + @WithMockUser(setupBefore = TestExecutionEvent.TEST_METHOD) + public void withMockUserTestMethod() { + } + + @WithMockUser + public void withMockUserDefault() { + } + } + + +} diff --git a/test/src/test/java/org/springframework/security/test/context/support/WithUserDetailsTests.java b/test/src/test/java/org/springframework/security/test/context/support/WithUserDetailsTests.java index d4726c35ca..d8ff3eda92 100644 --- a/test/src/test/java/org/springframework/security/test/context/support/WithUserDetailsTests.java +++ b/test/src/test/java/org/springframework/security/test/context/support/WithUserDetailsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.security.test.context.support; import static org.assertj.core.api.Assertions.assertThat; import org.junit.Test; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; public class WithUserDetailsTests { @@ -27,9 +28,41 @@ public class WithUserDetailsTests { WithUserDetails userDetails = AnnotationUtils.findAnnotation(Annotated.class, WithUserDetails.class); assertThat(userDetails.value()).isEqualTo("user"); + + WithSecurityContext context = AnnotatedElementUtils + .findMergedAnnotation(Annotated.class, + WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); } @WithUserDetails private static class Annotated { } + + @Test + public void findMergedAnnotationWhenSetupExplicitThenOverridden() { + WithSecurityContext context = AnnotatedElementUtils + .findMergedAnnotation(SetupExplicit.class, + WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_METHOD); + } + + @WithUserDetails(setupBefore = TestExecutionEvent.TEST_METHOD) + private class SetupExplicit { + } + + @Test + public void findMergedAnnotationWhenSetupOverriddenThenOverridden() { + WithSecurityContext context = AnnotatedElementUtils + .findMergedAnnotation(SetupOverridden.class, + WithSecurityContext.class); + + assertThat(context.setupBefore()).isEqualTo(TestExecutionEvent.TEST_EXECUTION); + } + + @WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION) + private class SetupOverridden { + } }