diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy deleted file mode 100644 index 1751b61b46..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.groovy +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2002-2013 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 - * - * https://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.config.annotation.web.configurers - -import groovy.transform.CompileStatic - -import javax.servlet.ServletException -import javax.servlet.ServletRequest -import javax.servlet.ServletResponse - -import org.springframework.mock.web.MockFilterChain -import org.springframework.mock.web.MockHttpServletRequest -import org.springframework.mock.web.MockHttpServletResponse -import org.springframework.security.authentication.AuthenticationTrustResolver -import org.springframework.security.authentication.TestingAuthenticationToken -import org.springframework.security.config.annotation.AnyObjectPostProcessor -import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.web.AuthenticationEntryPoint -import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler -import org.springframework.security.web.context.HttpSessionSecurityContextRepository -import org.springframework.security.web.csrf.CsrfLogoutHandler -import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter - -/** - * - * @author Rob Winch - */ -class ServletApiConfigurerTests extends BaseSpringSpec { - - def "servletApi ObjectPostProcessor"() { - setup: - AnyObjectPostProcessor opp = Mock() - HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:]) - when: - http - .servletApi() - .and() - .build() - - then: "SecurityContextHolderAwareRequestFilter is registered with LifecycleManager" - 1 * opp.postProcess(_ as SecurityContextHolderAwareRequestFilter) >> {SecurityContextHolderAwareRequestFilter o -> o} - } - - def "SecurityContextHolderAwareRequestFilter properties set"() { - when: - loadConfig(ServletApiConfig) - SecurityContextHolderAwareRequestFilter filter = findFilter(SecurityContextHolderAwareRequestFilter) - then: "SEC-2215: authenticationManager != null" - filter.authenticationManager != null - and: "authenticationEntryPoint != null" - filter.authenticationEntryPoint != null - and: "requestFactory != null" - filter.requestFactory != null - and: "logoutHandlers populated" - filter.logoutHandlers.collect { it.class } == [CsrfLogoutHandler, SecurityContextLogoutHandler] - } - - - def 'SEC-2926: Role Prefix is set'() { - setup: - loadConfig(ServletApiConfig) - MockFilterChain chain = new MockFilterChain() { - public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { - assert request.isUserInRole("USER") - - super.doFilter(request,response) - } - } - MockHttpServletRequest request = new MockHttpServletRequest(method:'GET') - SecurityContext context = SecurityContextHolder.createEmptyContext() - context.setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_USER")) - request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context) - - when: - springSecurityFilterChain.doFilter(request, new MockHttpServletResponse(), chain) - then: - chain.request != null - } - - @CompileStatic - @EnableWebSecurity - static class ServletApiConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER") - } - } - - def "SecurityContextHolderAwareRequestFilter.authenticationEntryPoint = customEntryPoint"() { - setup: - CustomEntryPointConfig.ENTRYPOINT = Mock(AuthenticationEntryPoint) - when: "load config with customEntryPoint" - loadConfig(CustomEntryPointConfig) - then: "SecurityContextHolderAwareRequestFilter.authenticationEntryPoint == customEntryPoint" - findFilter(SecurityContextHolderAwareRequestFilter).authenticationEntryPoint == CustomEntryPointConfig.ENTRYPOINT - } - - @EnableWebSecurity - static class CustomEntryPointConfig extends WebSecurityConfigurerAdapter { - static AuthenticationEntryPoint ENTRYPOINT - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .exceptionHandling() - .authenticationEntryPoint(ENTRYPOINT) - .and() - .formLogin() - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER") - } - } - - def "invoke servletApi twice does not override"() { - setup: - InvokeTwiceDoesNotOverrideConfig.ENTRYPOINT = Mock(AuthenticationEntryPoint) - when: - loadConfig(InvokeTwiceDoesNotOverrideConfig) - then: - findFilter(SecurityContextHolderAwareRequestFilter).authenticationEntryPoint == InvokeTwiceDoesNotOverrideConfig.ENTRYPOINT - } - - @EnableWebSecurity - static class InvokeTwiceDoesNotOverrideConfig extends WebSecurityConfigurerAdapter { - static AuthenticationEntryPoint ENTRYPOINT - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .exceptionHandling() - .authenticationEntryPoint(ENTRYPOINT) - .and() - .exceptionHandling() - } - } - - def "use sharedObject trustResolver"() { - setup: - SharedTrustResolverConfig.TR = Mock(AuthenticationTrustResolver) - when: - loadConfig(SharedTrustResolverConfig) - then: - findFilter(SecurityContextHolderAwareRequestFilter).trustResolver == SharedTrustResolverConfig.TR - } - - @EnableWebSecurity - static class SharedTrustResolverConfig extends WebSecurityConfigurerAdapter { - static AuthenticationTrustResolver TR - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .setSharedObject(AuthenticationTrustResolver, TR) - } - } -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java new file mode 100644 index 0000000000..dcf96fa2d8 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java @@ -0,0 +1,242 @@ +/* + * Copyright 2002-2019 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 + * + * https://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.config.annotation.web.configurers; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.userdetails.PasswordEncodedUser; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for {@link ServletApiConfigurer} + * + * @author Rob Winch + * @author Eleftheria Stein + */ +public class ServletApiConfigurerTests { + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired + MockMvc mvc; + + @Test + public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSecurityContextHolderAwareRequestFilter() { + this.spring.register(ObjectPostProcessorConfig.class).autowire(); + + verify(ObjectPostProcessorConfig.objectPostProcessor) + .postProcess(any(SecurityContextHolderAwareRequestFilter.class)); + } + + @EnableWebSecurity + static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter { + static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .servletApi(); + // @formatter:on + } + + @Bean + static ObjectPostProcessor objectPostProcessor() { + return objectPostProcessor; + } + } + + static class ReflectingObjectPostProcessor implements ObjectPostProcessor { + @Override + public O postProcess(O object) { + return object; + } + } + + // SEC-2215 + @Test + public void configureWhenUsingDefaultsThenAuthenticationManagerIsNotNull() { + this.spring.register(ServletApiConfig.class).autowire(); + + assertThat(this.spring.getContext().getBean("customAuthenticationManager")).isNotNull(); + } + + @Test + public void configureWhenUsingDefaultsThenAuthenticationEntryPointIsLogin() throws Exception { + this.spring.register(ServletApiConfig.class).autowire(); + + this.mvc.perform(formLogin()) + .andExpect(status().isFound()); + } + + // SEC-2926 + @Test + public void configureWhenUsingDefaultsThenRolePrefixIsSet() throws Exception { + this.spring.register(ServletApiConfig.class, AdminController.class).autowire(); + + this.mvc.perform(get("/admin") + .with(authentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN")))) + .andExpect(status().isOk()); + } + + @EnableWebSecurity + static class ServletApiConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + + @Bean + public AuthenticationManager customAuthenticationManager() throws Exception { + return super.authenticationManagerBean(); + } + } + + @Test + public void requestWhenCustomAuthenticationEntryPointThenEntryPointUsed() throws Exception { + this.spring.register(CustomEntryPointConfig.class).autowire(); + + this.mvc.perform(get("/")); + + verify(CustomEntryPointConfig.ENTRYPOINT) + .commence(any(HttpServletRequest.class), + any(HttpServletResponse.class), any(AuthenticationException.class)); + } + + @EnableWebSecurity + static class CustomEntryPointConfig extends WebSecurityConfigurerAdapter { + static AuthenticationEntryPoint ENTRYPOINT = spy(AuthenticationEntryPoint.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .exceptionHandling() + .authenticationEntryPoint(ENTRYPOINT) + .and() + .formLogin(); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser("user").password("password").roles("USER"); + // @formatter:on + } + } + + @Test + public void servletApiWhenInvokedTwiceThenUsesOriginalRole() throws Exception { + this.spring.register(DuplicateInvocationsDoesNotOverrideConfig.class, AdminController.class).autowire(); + + this.mvc.perform(get("/admin") + .with(user("user").authorities(AuthorityUtils.createAuthorityList("PERMISSION_ADMIN")))) + .andExpect(status().isOk()); + + this.mvc.perform(get("/admin") + .with(user("user").authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN")))) + .andExpect(status().isForbidden()); + } + + @EnableWebSecurity + static class DuplicateInvocationsDoesNotOverrideConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .servletApi() + .rolePrefix("PERMISSION_") + .and() + .servletApi(); + // @formatter:on + } + } + + @Test + public void configureWhenSharedObjectTrustResolverThenTrustResolverUsed() throws Exception { + this.spring.register(SharedTrustResolverConfig.class).autowire(); + + this.mvc.perform(get("/")); + + verify(SharedTrustResolverConfig.TR, atLeastOnce()).isAnonymous(any()); + } + + @EnableWebSecurity + static class SharedTrustResolverConfig extends WebSecurityConfigurerAdapter { + static AuthenticationTrustResolver TR = spy(AuthenticationTrustResolver.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .setSharedObject(AuthenticationTrustResolver.class, TR); + // @formatter:on + } + } + + @RestController + static class AdminController { + @GetMapping("/admin") + public void admin(HttpServletRequest request) { + if (!request.isUserInRole("ADMIN")) { + throw new AccessDeniedException("This resource is only available to admins"); + } + } + } +}