From e15922322ea7de1c1087f8a8e97c37b9724dd04f Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 27 May 2019 10:21:12 -0400 Subject: [PATCH] Migrate LogoutConfigurerTests groovy->java Issue: gh-4939 --- .../configurers/LogoutConfigurerTests.groovy | 270 ------------- .../configurers/LogoutConfigurerTests.java | 375 ++++++++++++++++++ 2 files changed, 375 insertions(+), 270 deletions(-) delete mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy deleted file mode 100644 index aab6184150..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.groovy +++ /dev/null @@ -1,270 +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 org.springframework.http.HttpStatus -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.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.authentication.RememberMeServices -import org.springframework.security.web.authentication.logout.LogoutFilter -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler -import org.springframework.security.web.util.matcher.RequestMatcher - -/** - * - * @author Rob Winch - */ -class LogoutConfigurerTests extends BaseSpringSpec { - - def defaultLogoutSuccessHandlerForNullLogoutHandler() { - setup: - LogoutConfigurer config = new LogoutConfigurer(); - when: - config.defaultLogoutSuccessHandlerFor(null, Mock(RequestMatcher)) - then: - thrown(IllegalArgumentException) - } - - def defaultLogoutSuccessHandlerForNullMatcher() { - setup: - LogoutConfigurer config = new LogoutConfigurer(); - when: - config.defaultLogoutSuccessHandlerFor(Mock(LogoutSuccessHandler), null) - then: - thrown(IllegalArgumentException) - } - - def "logout ObjectPostProcessor"() { - setup: - AnyObjectPostProcessor opp = Mock() - HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:]) - when: - http - .logout() - .and() - .build() - - then: "LogoutFilter is registered with LifecycleManager" - 1 * opp.postProcess(_ as LogoutFilter) >> {LogoutFilter o -> o} - } - - def "invoke logout twice does not override"() { - when: - loadConfig(InvokeTwiceDoesNotOverride) - request.method = "POST" - request.servletPath = "/custom/logout" - findFilter(LogoutFilter).doFilter(request,response,chain) - then: - response.redirectedUrl == "/login?logout" - } - - @EnableWebSecurity - static class InvokeTwiceDoesNotOverride extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .logout() - .logoutUrl("/custom/logout") - .and() - .logout() - } - } - - def "Logout allows other methods if CSRF is disabled"() { - when: - loadConfig(CsrfDisabledConfig) - request.method = method - request.servletPath = "/logout" - findFilter(LogoutFilter).doFilter(request,response,chain) - then: - response.status == httpStatus.value() - response.redirectedUrl == url - where: - method | httpStatus | url - "GET" | HttpStatus.FOUND | "/login?logout" - "POST" | HttpStatus.FOUND | "/login?logout" - "PUT" | HttpStatus.FOUND | "/login?logout" - "DELETE" | HttpStatus.FOUND | "/login?logout" - "OPTIONS" | HttpStatus.OK | null - "HEAD" | HttpStatus.OK | null - "TRACE" | HttpStatus.OK | null - - } - - @EnableWebSecurity - static class CsrfDisabledConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .csrf().disable() - .logout() - } - } - - - def "Logout allows other methods if CSRF is disabled with custom logout URL"() { - when: - loadConfig(CsrfDisabledCustomLogoutUrlConfig) - request.method = method - request.servletPath = "/custom/logout" - findFilter(LogoutFilter).doFilter(request,response,chain) - then: - response.status == httpStatus.value() - response.redirectedUrl == url - where: - method | httpStatus | url - "GET" | HttpStatus.FOUND | "/login?logout" - "POST" | HttpStatus.FOUND | "/login?logout" - "PUT" | HttpStatus.FOUND | "/login?logout" - "DELETE" | HttpStatus.FOUND | "/login?logout" - "OPTIONS" | HttpStatus.OK | null - "HEAD" | HttpStatus.OK | null - "TRACE" | HttpStatus.OK | null - - } - - @EnableWebSecurity - static class CsrfDisabledCustomLogoutUrlConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .logout() - .logoutUrl("/custom/logout") - .and() - .csrf().disable() - } - } - - def "SEC-3170: LogoutConfigurer RememberMeService not LogoutHandler"() { - setup: - RememberMeNoLogoutHandler.REMEMBER_ME = Mock(RememberMeServices) - loadConfig(RememberMeNoLogoutHandler) - request.method = "POST" - request.servletPath = "/logout" - when: - findFilter(LogoutFilter).doFilter(request,response,chain) - then: - response.redirectedUrl == "/login?logout" - } - - def "SEC-3170: LogoutConfigurer prevents null LogoutHandler"() { - when: - new LogoutConfigurer().addLogoutHandler(null) - then: - thrown(IllegalArgumentException) - } - - @EnableWebSecurity - static class RememberMeNoLogoutHandler extends WebSecurityConfigurerAdapter { - static RememberMeServices REMEMBER_ME - - @Override - protected void configure(HttpSecurity http) throws Exception { - http - .rememberMe() - .rememberMeServices(REMEMBER_ME) - } - } - - def "LogoutConfigurer content negotiation text/html redirects"() { - setup: - loadConfig(LogoutHandlerContentNegotiation) - when: - login() - request.method = 'POST' - request.servletPath = '/logout' - request.addHeader('Accept', 'text/html') - springSecurityFilterChain.doFilter(request,response,chain) - then: - response.status == 302 - response.redirectedUrl == '/login?logout' - } - - // gh-3282 - def "LogoutConfigurer content negotiation json 201"() { - setup: - loadConfig(LogoutHandlerContentNegotiation) - when: - login() - request.method = 'POST' - request.servletPath = '/logout' - request.addHeader('Accept', 'application/json') - springSecurityFilterChain.doFilter(request,response,chain) - then: - response.status == 204 - } - - // gh-4831 - def "LogoutConfigurer content negotiation all 201"() { - setup: - loadConfig(LogoutHandlerContentNegotiation) - when: - login() - request.method = 'POST' - request.servletPath = '/logout' - request.addHeader('Accept', MediaType.ALL_VALUE) - springSecurityFilterChain.doFilter(request,response,chain) - then: - response.status == 204 - } - - @EnableWebSecurity - static class LogoutHandlerContentNegotiation extends WebSecurityConfigurerAdapter { - } - // gh-3902 - def "logout in chrome is 302"() { - setup: - loadConfig(LogoutHandlerContentNegotiationForChrome) - when: - login() - request.method = 'POST' - request.servletPath = '/logout' - request.addHeader('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8') - springSecurityFilterChain.doFilter(request,response,chain) - then: - response.status == 302 - } - - @EnableWebSecurity - static class LogoutHandlerContentNegotiationForChrome extends WebSecurityConfigurerAdapter { - } - - // gh-3997 - def "LogoutConfigurer for XMLHttpRequest is 204"() { - setup: - loadConfig(LogoutXMLHttpRequestConfig) - when: - login() - request.method = 'POST' - request.servletPath = '/logout' - request.addHeader('Accept', 'text/html,application/json') - request.addHeader('X-Requested-With', 'XMLHttpRequest') - springSecurityFilterChain.doFilter(request,response,chain) - then: - response.status == 204 - } - - @EnableWebSecurity - static class LogoutXMLHttpRequestConfig extends WebSecurityConfigurerAdapter { - } -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java new file mode 100644 index 0000000000..3ef62bf4a8 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java @@ -0,0 +1,375 @@ +/* + * 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.apache.http.HttpHeaders; +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.http.MediaType; +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.web.authentication.RememberMeServices; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.test.web.servlet.MockMvc; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for {@link LogoutConfigurer} + * + * @author Rob Winch + * @author Eleftheria Stein + */ +public class LogoutConfigurerTests { + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired + MockMvc mvc; + + @Test + public void configureWhenDefaultLogoutSuccessHandlerForHasNullLogoutHandlerThenException() { + assertThatThrownBy(() -> this.spring.register(NullLogoutSuccessHandlerConfig.class).autowire()) + .isInstanceOf(BeanCreationException.class) + .hasRootCauseInstanceOf(IllegalArgumentException.class); + } + + @EnableWebSecurity + static class NullLogoutSuccessHandlerConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout() + .defaultLogoutSuccessHandlerFor(null, mock(RequestMatcher.class)); + // @formatter:on + } + } + + @Test + public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherThenException() { + assertThatThrownBy(() -> this.spring.register(NullMatcherConfig.class).autowire()) + .isInstanceOf(BeanCreationException.class) + .hasRootCauseInstanceOf(IllegalArgumentException.class); + } + + @EnableWebSecurity + static class NullMatcherConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout() + .defaultLogoutSuccessHandlerFor(mock(LogoutSuccessHandler.class), null); + // @formatter:on + } + } + + @Test + public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLogoutFilter() { + this.spring.register(ObjectPostProcessorConfig.class).autowire(); + + verify(ObjectPostProcessorConfig.objectPostProcessor) + .postProcess(any(LogoutFilter.class)); + } + + @EnableWebSecurity + static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter { + static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout(); + // @formatter:on + } + + @Bean + static ObjectPostProcessor objectPostProcessor() { + return objectPostProcessor; + } + } + + static class ReflectingObjectPostProcessor implements ObjectPostProcessor { + @Override + public O postProcess(O object) { + return object; + } + } + + @Test + public void logoutWhenInvokedTwiceThenUsesOriginalLogoutUrl() throws Exception { + this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); + + this.mvc.perform(post("/custom/logout") + .with(csrf())) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @EnableWebSecurity + static class DuplicateDoesNotOverrideConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout() + .logoutUrl("/custom/logout") + .and() + .logout(); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication(); + // @formatter:on + } + } + + // SEC-2311 + @Test + public void logoutWhenGetRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { + this.spring.register(CsrfDisabledConfig.class).autowire(); + + this.mvc.perform(get("/logout")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @Test + public void logoutWhenPostRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { + this.spring.register(CsrfDisabledConfig.class).autowire(); + + this.mvc.perform(post("/logout")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @Test + public void logoutWhenPutRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { + this.spring.register(CsrfDisabledConfig.class).autowire(); + + this.mvc.perform(put("/logout")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @Test + public void logoutWhenDeleteRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { + this.spring.register(CsrfDisabledConfig.class).autowire(); + + this.mvc.perform(delete("/logout")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @EnableWebSecurity + static class CsrfDisabledConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf() + .disable() + .logout(); + // @formatter:on + } + } + + @Test + public void logoutWhenGetRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { + this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); + + this.mvc.perform(get("/custom/logout")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @Test + public void logoutWhenPostRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { + this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); + + this.mvc.perform(post("/custom/logout")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @Test + public void logoutWhenPutRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { + this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); + + this.mvc.perform(put("/custom/logout")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @Test + public void logoutWhenDeleteRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { + this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); + + this.mvc.perform(delete("/custom/logout")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @EnableWebSecurity + static class CsrfDisabledAndCustomLogoutConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf() + .disable() + .logout() + .logoutUrl("/custom/logout"); + // @formatter:on + } + } + + // SEC-3170 + @Test + public void configureWhenLogoutHandlerNullThenException() { + assertThatThrownBy(() -> this.spring.register(NullLogoutHandlerConfig.class).autowire()) + .isInstanceOf(BeanCreationException.class) + .hasRootCauseInstanceOf(IllegalArgumentException.class); + } + + @EnableWebSecurity + static class NullLogoutHandlerConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout() + .addLogoutHandler(null); + // @formatter:on + } + } + + // SEC-3170 + @Test + public void rememberMeWhenRememberMeServicesNotLogoutHandlerThenRedirectsToLogin() throws Exception { + this.spring.register(RememberMeNoLogoutHandler.class).autowire(); + + this.mvc.perform(post("/logout") + .with(csrf())) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + @EnableWebSecurity + static class RememberMeNoLogoutHandler extends WebSecurityConfigurerAdapter { + static RememberMeServices REMEMBER_ME = mock(RememberMeServices.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .rememberMe() + .rememberMeServices(REMEMBER_ME); + // @formatter:on + } + } + + @Test + public void logoutWhenAcceptTextHtmlThenRedirectsToLogin() throws Exception { + this.spring.register(BasicSecurityConfig.class).autowire(); + + this.mvc.perform(post("/logout") + .with(csrf()) + .with(user("user")) + .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + // gh-3282 + @Test + public void logoutWhenAcceptApplicationJsonThenReturnsStatusNoContent() throws Exception { + this.spring.register(BasicSecurityConfig.class).autowire(); + + this.mvc.perform(post("/logout") + .with(csrf()) + .with(user("user")) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)) + .andExpect(status().isNoContent()); + } + + // gh-4831 + @Test + public void logoutWhenAcceptAllThenReturnsStatusNoContent() throws Exception { + this.spring.register(BasicSecurityConfig.class).autowire(); + + this.mvc.perform(post("/logout") + .with(csrf()) + .with(user("user")) + .header(HttpHeaders.ACCEPT, MediaType.ALL_VALUE)) + .andExpect(status().isNoContent()); + } + + // gh-3902 + @Test + public void logoutWhenAcceptFromChromeThenRedirectsToLogin() throws Exception { + this.spring.register(BasicSecurityConfig.class).autowire(); + + this.mvc.perform(post("/logout") + .with(csrf()).with(user("user")) + .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("/login?logout")); + } + + // gh-3997 + @Test + public void logoutWhenXMLHttpRequestThenReturnsStatusNoContent() throws Exception { + this.spring.register(BasicSecurityConfig.class).autowire(); + + this.mvc.perform(post("/logout") + .with(csrf()) + .with(user("user")) + .header(HttpHeaders.ACCEPT, "text/html,application/json") + .header("X-Requested-With", "XMLHttpRequest")) + .andExpect(status().isNoContent()); + } + + @EnableWebSecurity + static class BasicSecurityConfig extends WebSecurityConfigurerAdapter { + } +}