Use modified classpath test support for tests that depend on the classpath

Issue gh-11347
This commit is contained in:
Marcus Da Coregio 2022-10-04 14:39:56 -03:00
parent 77dcc691b3
commit 76d7a85bc0
4 changed files with 234 additions and 122 deletions

View File

@ -0,0 +1,89 @@
/*
* Copyright 2002-2022 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;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.security.test.support.ClassPathExclusions;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AbstractRequestMatcherRegistry} with no Spring MVC in the classpath
*
* @author Marcus Da Coregio
*/
@ClassPathExclusions("spring-webmvc-*.jar")
public class AbstractRequestMatcherRegistryNoMvcTests {
private TestRequestMatcherRegistry matcherRegistry;
@BeforeEach
public void setUp() {
this.matcherRegistry = new TestRequestMatcherRegistry();
}
@Test
public void requestMatchersWhenPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@Test
public void requestMatchersWhenHttpMethodAndPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@Test
public void requestMatchersWhenHttpMethodAndMvcNotPresentThenReturnAntPathMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
private static class TestRequestMatcherRegistry extends AbstractRequestMatcherRegistry<List<RequestMatcher>> {
@Override
public List<RequestMatcher> mvcMatchers(String... mvcPatterns) {
return null;
}
@Override
public List<RequestMatcher> mvcMatchers(HttpMethod method, String... mvcPatterns) {
return null;
}
@Override
protected List<RequestMatcher> chainRequestMatchers(List<RequestMatcher> requestMatchers) {
return requestMatchers;
}
}
}

View File

@ -16,8 +16,6 @@
package org.springframework.security.config.annotation.web;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import jakarta.servlet.DispatcherType;
@ -43,6 +41,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link AbstractRequestMatcherRegistry}.
*
* @author Joe Grandja
* @author Marcus Da Coregio
*/
public class AbstractRequestMatcherRegistryTests {
@ -61,6 +60,7 @@ public class AbstractRequestMatcherRegistryTests {
ApplicationContext context = mock(ApplicationContext.class);
given(context.getBean(ObjectPostProcessor.class)).willReturn(NO_OP_OBJECT_POST_PROCESSOR);
this.matcherRegistry.setApplicationContext(context);
mockMvcIntrospector(true);
}
@Test
@ -113,9 +113,7 @@ public class AbstractRequestMatcherRegistryTests {
}
@Test
public void requestMatchersWhenPatternAndMvcPresentThenReturnMvcRequestMatcherType() throws Exception {
mockMvcPresentClasspath(true);
mockMvcIntrospector(true);
public void requestMatchersWhenPatternAndMvcPresentThenReturnMvcRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
@ -123,9 +121,7 @@ public class AbstractRequestMatcherRegistryTests {
}
@Test
public void requestMatchersWhenHttpMethodAndPatternAndMvcPresentThenReturnMvcRequestMatcherType() throws Exception {
mockMvcPresentClasspath(true);
mockMvcIntrospector(true);
public void requestMatchersWhenHttpMethodAndPatternAndMvcPresentThenReturnMvcRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
@ -133,9 +129,7 @@ public class AbstractRequestMatcherRegistryTests {
}
@Test
public void requestMatchersWhenHttpMethodAndMvcPresentThenReturnMvcRequestMatcherType() throws Exception {
mockMvcPresentClasspath(true);
mockMvcIntrospector(true);
public void requestMatchersWhenHttpMethodAndMvcPresentThenReturnMvcRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
@ -143,40 +137,7 @@ public class AbstractRequestMatcherRegistryTests {
}
@Test
public void requestMatchersWhenPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType() throws Exception {
mockMvcPresentClasspath(false);
mockMvcIntrospector(false);
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@Test
public void requestMatchersWhenHttpMethodAndPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType()
throws Exception {
mockMvcPresentClasspath(false);
mockMvcIntrospector(false);
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@Test
public void requestMatchersWhenHttpMethodAndMvcNotPresentThenReturnAntPathMatcherType() throws Exception {
mockMvcPresentClasspath(false);
mockMvcIntrospector(false);
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@Test
public void requestMatchersWhenMvcPresentInClassPathAndMvcIntrospectorBeanNotAvailableThenException()
throws Exception {
mockMvcPresentClasspath(true);
public void requestMatchersWhenMvcPresentInClassPathAndMvcIntrospectorBeanNotAvailableThenException() {
mockMvcIntrospector(false);
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/path")).withMessageContaining(
@ -188,15 +149,6 @@ public class AbstractRequestMatcherRegistryTests {
given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);
}
private void mockMvcPresentClasspath(Object newValue) throws Exception {
Field mvcPresentField = AbstractRequestMatcherRegistry.class.getDeclaredField("mvcPresent");
mvcPresentField.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(mvcPresentField, mvcPresentField.getModifiers() & ~Modifier.FINAL);
mvcPresentField.set(null, newValue);
}
private static class TestRequestMatcherRegistry extends AbstractRequestMatcherRegistry<List<RequestMatcher>> {
@Override

View File

@ -0,0 +1,134 @@
/*
* Copyright 2002-2022 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 jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.test.support.ClassPathExclusions;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Marcus Da Coregio
*
*/
@ClassPathExclusions("spring-webmvc-*.jar")
public class HttpSecuritySecurityMatchersNoMvcTests {
AnnotationConfigWebApplicationContext context;
MockHttpServletRequest request;
MockHttpServletResponse response;
MockFilterChain chain;
@Autowired
FilterChainProxy springSecurityFilterChain;
@BeforeEach
public void setup() throws Exception {
this.request = new MockHttpServletRequest("GET", "");
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
}
@AfterEach
public void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void securityMatcherWhenNoMvcThenAntMatcher() throws Exception {
loadConfig(SecurityMatcherNoMvcConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
assertThat(this.springSecurityFilterChain.getFilterChains())
.extracting((c) -> ((DefaultSecurityFilterChain) c).getRequestMatcher())
.hasOnlyElementsOfType(AntPathRequestMatcher.class);
}
public void loadConfig(Class<?>... configs) {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(configs);
this.context.setServletContext(new MockServletContext());
this.context.refresh();
this.context.getAutowireCapableBeanFactory().autowireBean(this);
}
@EnableWebSecurity
@Configuration
@Import(HttpSecuritySecurityMatchersTests.UsersConfig.class)
static class SecurityMatcherNoMvcConfig {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatcher("/path")
.httpBasic().and()
.authorizeHttpRequests()
.anyRequest().denyAll();
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
}

View File

@ -16,9 +16,6 @@
package org.springframework.security.config.annotation.web.configurers;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -34,13 +31,13 @@ import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
@ -78,7 +75,6 @@ public class HttpSecuritySecurityMatchersTests {
this.request.setMethod("GET");
this.response = new MockHttpServletResponse();
this.chain = new MockFilterChain();
mockMvcPresentClasspath(true);
}
@AfterEach
@ -104,23 +100,6 @@ public class HttpSecuritySecurityMatchersTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
}
@Test
public void securityMatcherWhenNoMvcThenAntMatcher() throws Exception {
mockMvcPresentClasspath(false);
loadConfig(SecurityMatcherNoMvcConfig.class, LegacyMvcMatchingConfig.class);
this.request.setServletPath("/path");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
setup();
this.request.setServletPath("/path.html");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
setup();
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
}
@Test
public void securityMatcherWhenMvcMatcherAndGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() {
loadConfig(SecurityMatcherMvcConfig.class);
@ -141,6 +120,9 @@ public class HttpSecuritySecurityMatchersTests {
this.request.setServletPath("/path/");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
assertThat(this.springSecurityFilterChain.getFilterChains())
.extracting((c) -> ((DefaultSecurityFilterChain) c).getRequestMatcher())
.hasOnlyElementsOfType(MvcRequestMatcher.class);
}
@Test
@ -237,20 +219,6 @@ public class HttpSecuritySecurityMatchersTests {
this.context.getAutowireCapableBeanFactory().autowireBean(this);
}
private void mockMvcPresentClasspath(Object newValue) throws Exception {
mockMvcPresentClasspath(HttpSecurity.class, newValue);
mockMvcPresentClasspath(AbstractRequestMatcherRegistry.class, newValue);
}
private void mockMvcPresentClasspath(Class<?> clazz, Object newValue) throws Exception {
Field mvcPresentField = clazz.getDeclaredField("mvcPresent");
mvcPresentField.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(mvcPresentField, mvcPresentField.getModifiers() & ~Modifier.FINAL);
mvcPresentField.set(null, newValue);
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@ -376,35 +344,6 @@ public class HttpSecuritySecurityMatchersTests {
}
@EnableWebSecurity
@Configuration
@Import(UsersConfig.class)
static class SecurityMatcherNoMvcConfig {
@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatcher("/path")
.httpBasic().and()
.authorizeHttpRequests()
.anyRequest().denyAll();
// @formatter:on
return http.build();
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@EnableWebSecurity
@Configuration
@EnableWebMvc
@ -415,9 +354,7 @@ public class HttpSecuritySecurityMatchersTests {
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.securityMatchers()
.requestMatchers("/path")
.and()
.securityMatcher("/path")
.httpBasic().and()
.authorizeHttpRequests()
.anyRequest().denyAll();