From 42b8b794a890cc1a04e10d9bcf0c73a2d5987a8d Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 16 Nov 2018 15:40:12 -0700 Subject: [PATCH] RequestCacheConfigurerTests groovy->java Issue: gh-4939 --- .../RequestCacheConfigurerTests.groovy | 209 -------------- .../RequestCacheConfigurerTests.java | 273 ++++++++++++++++++ 2 files changed, 273 insertions(+), 209 deletions(-) delete mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.groovy create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.groovy deleted file mode 100644 index a5cf5fde08..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.groovy +++ /dev/null @@ -1,209 +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 - * - * 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.config.annotation.web.configurers - -import org.springframework.security.core.userdetails.PasswordEncodedUser - -import javax.servlet.http.HttpServletResponse - -import org.springframework.context.annotation.Configuration -import org.springframework.http.MediaType -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.web.savedrequest.RequestCache -import org.springframework.security.web.savedrequest.RequestCacheAwareFilter - -import spock.lang.Unroll; - -/** - * - * @author Rob Winch - */ -class RequestCacheConfigurerTests extends BaseSpringSpec { - - def "requestCache ObjectPostProcessor"() { - setup: - AnyObjectPostProcessor opp = Mock() - HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:]) - when: - http - .requestCache() - .and() - .build() - - then: "RequestCacheAwareFilter is registered with LifecycleManager" - 1 * opp.postProcess(_ as RequestCacheAwareFilter) >> {RequestCacheAwareFilter o -> o} - } - - def "invoke requestCache twice does not reset"() { - setup: - RequestCache RC = Mock() - AnyObjectPostProcessor opp = Mock() - HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:]) - when: - http - .requestCache() - .requestCache(RC) - .and() - .requestCache() - - then: - http.getSharedObject(RequestCache) == RC - } - - def "RequestCache disables faviocon.ico"() { - setup: - loadConfig(RequestCacheDefautlsConfig) - request.servletPath = "/favicon.ico" - request.requestURI = "/favicon.ico" - request.method = "GET" - when: "request favicon.ico" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to the login page" - response.status == HttpServletResponse.SC_MOVED_TEMPORARILY - response.redirectedUrl == "http://localhost/login" - when: "authenticate successfully" - super.setupWeb(request.session) - request.servletPath = "/login" - request.setParameter("username","user") - request.setParameter("password","password") - request.method = "POST" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to default URL since it was favicon.ico" - response.status == HttpServletResponse.SC_MOVED_TEMPORARILY - response.redirectedUrl == "/" - } - - def "RequestCache disables faviocon.png"() { - setup: - loadConfig(RequestCacheDefautlsConfig) - request.servletPath = "/favicon.png" - request.requestURI = "/favicon.png" - request.method = "GET" - when: "request favicon.ico" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to the login page" - response.status == HttpServletResponse.SC_MOVED_TEMPORARILY - response.redirectedUrl == "http://localhost/login" - when: "authenticate successfully" - super.setupWeb(request.session) - request.servletPath = "/login" - request.setParameter("username","user") - request.setParameter("password","password") - request.method = "POST" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to default URL since it was favicon.ico" - response.status == HttpServletResponse.SC_MOVED_TEMPORARILY - response.redirectedUrl == "/" - } - - def "SEC-2321: RequestCache disables application/json"() { - setup: - loadConfig(RequestCacheDefautlsConfig) - request.addHeader("Accept", MediaType.APPLICATION_JSON_VALUE) - request.method = "GET" - request.servletPath = "/messages" - request.requestURI = "/messages" - when: "request application/json" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to the login page" - response.status == HttpServletResponse.SC_MOVED_TEMPORARILY - response.redirectedUrl == "http://localhost/login" - when: "authenticate successfully" - super.setupWeb(request.session) - request.servletPath = "/login" - request.setParameter("username","user") - request.setParameter("password","password") - request.method = "POST" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to default URL since it was application/json. This is desirable since JSON requests are typically not invoked directly from the browser and we don't want the browser to replay them" - response.status == HttpServletResponse.SC_MOVED_TEMPORARILY - response.redirectedUrl == "/" - } - - def "SEC-2321: RequestCache disables X-Requested-With"() { - setup: - loadConfig(RequestCacheDefautlsConfig) - request.addHeader("X-Requested-With", "XMLHttpRequest") - request.method = "GET" - request.servletPath = "/messages" - request.requestURI = "/messages" - when: "request X-Requested-With" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to the login page" - response.status == HttpServletResponse.SC_MOVED_TEMPORARILY - response.redirectedUrl == "http://localhost/login" - when: "authenticate successfully" - super.setupWeb(request.session) - request.servletPath = "/login" - request.setParameter("username","user") - request.setParameter("password","password") - request.method = "POST" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to default URL since it was X-Requested-With" - response.status == HttpServletResponse.SC_MOVED_TEMPORARILY - response.redirectedUrl == "/" - } - - @Unroll - def "RequestCache saves #headerName: #headerValue"() { - setup: - loadConfig(RequestCacheDefautlsConfig) - request.addHeader(headerName, headerValue) - request.method = "GET" - request.servletPath = "/messages" - request.requestURI = "/messages" - when: "request content type" - springSecurityFilterChain.doFilter(request,response,chain) - super.setupWeb(request.session) - request.servletPath = "/login" - request.setParameter("username","user") - request.setParameter("password","password") - request.method = "POST" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to saved URL" - response.status == HttpServletResponse.SC_MOVED_TEMPORARILY - response.redirectedUrl == "http://localhost/messages" - where: - headerName << ["Accept", "Accept", "Accept", "X-Requested-With"] - headerValue << [MediaType.ALL_VALUE, MediaType.TEXT_HTML, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","com.android"] - - } - - @EnableWebSecurity - static class RequestCacheDefautlsConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().authenticated() - .and() - .formLogin() - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .inMemoryAuthentication() - .withUser(PasswordEncodedUser.user()); - } - } -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java new file mode 100644 index 0000000000..bd74a02eae --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java @@ -0,0 +1,273 @@ +/* + * 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.config.annotation.web.configurers; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.config.annotation.ObjectPostProcessor; +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.userdetails.User; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.RequestBuilder; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; + +/** + * Tests for {@link RequestCacheConfigurer} + * + * @author Rob Winch + * @author Josh Cummings + */ +public class RequestCacheConfigurerTests { + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired + MockMvc mvc; + + @Test + public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() { + this.spring.register(ObjectPostProcessorConfig.class, DefaultSecurityConfig.class).autowire(); + + verify(ObjectPostProcessorConfig.objectPostProcessor) + .postProcess(any(RequestCacheAwareFilter.class)); + } + + @EnableWebSecurity + static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter { + static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requestCache(); + // @formatter:on + } + + @Bean + static ObjectPostProcessor objectPostProcessor() { + return objectPostProcessor; + } + } + + static class ReflectingObjectPostProcessor implements ObjectPostProcessor { + @Override + public O postProcess(O object) { + return object; + } + } + + @Test + public void getWhenInvokingExceptionHandlingTwiceThenOriginalEntryPointUsed() throws Exception { + this.spring.register(InvokeTwiceDoesNotOverrideConfig.class).autowire(); + + this.mvc.perform(get("/")); + + verify(InvokeTwiceDoesNotOverrideConfig.requestCache) + .getMatchingRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); + } + + @EnableWebSecurity + static class InvokeTwiceDoesNotOverrideConfig extends WebSecurityConfigurerAdapter { + static RequestCache requestCache = mock(RequestCache.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requestCache() + .requestCache(requestCache) + .and() + .requestCache(); + // @formatter:on + } + } + + @Test + public void getWhenBookmarkedUrlIsFaviconIcoThenPostAuthenticationRedirectsToRoot() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/favicon.ico")) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("/")); // ignores favicon.ico + } + + @Test + public void getWhenBookmarkedUrlIsFaviconPngThenPostAuthenticationRedirectsToRoot() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/favicon.png")) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("/")); // ignores favicon.png + } + + // SEC-2321 + @Test + public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedirectsToRoot() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/messages") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("/")); // ignores application/json + + // This is desirable since JSON requests are typically not invoked directly from the browser and we don't want the browser to replay them + } + + // SEC-2321 + @Test + public void getWhenBookmarkedRequestIsXRequestedWithThenPostAuthenticationRedirectsToRoot() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/messages") + .header("X-Requested-With", "XMLHttpRequest")) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("/")); + + // This is desirable since XHR requests are typically not invoked directly from the browser and we don't want the browser to replay them + } + + @Test + public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/messages") + .header(HttpHeaders.ACCEPT, MediaType.ALL)) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("http://localhost/messages")); + } + + @Test + public void getWhenBookmarkedRequestIsTextHtmlThenPostAuthenticationRemembers() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/messages") + .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML)) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("http://localhost/messages")); + } + + @Test + public void getWhenBookmarkedRequestIsChromeThenPostAuthenticationRemembers() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/messages") + .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("http://localhost/messages")); + } + + @Test + public void getWhenBookmarkedRequestIsRequestedWithAndroidThenPostAuthenticationRemembers() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/messages") + .header("X-Requested-With", "com.android")) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("http://localhost/messages")); + } + + @EnableWebSecurity + static class RequestCacheDefaultsConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin(); + } + } + + @EnableWebSecurity + static class DefaultSecurityConfig { + + @Bean + public InMemoryUserDetailsManager userDetailsManager() { + return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .roles("USER") + .build() + ); + } + } + + private static RequestBuilder formLogin(MockHttpSession session) { + return post("/login") + .param("username", "user") + .param("password", "password") + .session(session) + .with(csrf()); + } +}