Refine requestMatcher Validation Rules

Closes gh-13850
This commit is contained in:
Josh Cummings
2023-10-11 14:01:36 -06:00
parent 762319b6be
commit 8f829dd1d7
6 changed files with 232 additions and 22 deletions

View File

@@ -55,6 +55,11 @@ public class MockServletContext extends org.springframework.mock.web.MockServlet
return this.registrations;
}
@Override
public ServletRegistration getServletRegistration(String servletName) {
return this.registrations.get(servletName);
}
private static class MockServletRegistration implements ServletRegistration.Dynamic {
private final String name;

View File

@@ -14,32 +14,32 @@
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
package org.springframework.security.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.MappingMatch;
import org.springframework.mock.web.MockHttpServletMapping;
final class TestMockHttpServletMappings {
public final class TestMockHttpServletMappings {
private TestMockHttpServletMappings() {
}
static MockHttpServletMapping extension(HttpServletRequest request, String extension) {
public static MockHttpServletMapping extension(HttpServletRequest request, String extension) {
String uri = request.getRequestURI();
String matchValue = uri.substring(0, uri.lastIndexOf(extension));
return new MockHttpServletMapping(matchValue, "*" + extension, "extension", MappingMatch.EXTENSION);
}
static MockHttpServletMapping path(HttpServletRequest request, String path) {
public static MockHttpServletMapping path(HttpServletRequest request, String path) {
String uri = request.getRequestURI();
String matchValue = uri.substring(path.length());
return new MockHttpServletMapping(matchValue, path + "/*", "path", MappingMatch.PATH);
}
static MockHttpServletMapping defaultMapping() {
public static MockHttpServletMapping defaultMapping() {
return new MockHttpServletMapping("", "/", "default", MappingMatch.DEFAULT);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
@@ -26,8 +26,11 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.config.MockServletContext;
import org.springframework.security.config.TestMockHttpServletMappings;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.DispatcherServletDelegatingRequestMatcher;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
@@ -40,6 +43,9 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link AbstractRequestMatcherRegistry}.
@@ -159,6 +165,8 @@ public class AbstractRequestMatcherRegistryTests {
public void requestMatchersWhenNoDispatcherServletThenAntPathRequestMatcherType() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/two");
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
@@ -170,7 +178,26 @@ public class AbstractRequestMatcherRegistryTests {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/servlet/**");
servletContext.addServlet("servletTwo", DispatcherServlet.class).addMapping("/servlet/*");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
}
@Test
public void requestMatchersWhenMultipleDispatcherServletMappingsThenException() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/mvc/*");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
}
@Test
public void requestMatchersWhenPathDispatcherServletAndOtherServletsThenException() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
servletContext.addServlet("default", Servlet.class).addMapping("/");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
}
@@ -187,6 +214,67 @@ public class AbstractRequestMatcherRegistryTests {
assertThat(requestMatchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
}
@Test
public void requestMatchersWhenOnlyDispatcherServletThenAllows() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
}
@Test
public void requestMatchersWhenImplicitServletsThenAllows() {
mockMvcIntrospector(true);
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("defaultServlet", Servlet.class);
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx");
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isInstanceOf(DispatcherServletDelegatingRequestMatcher.class);
}
@Test
public void requestMatchersWhenPathBasedNonDispatcherServletThenAllows() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("path", Servlet.class).addMapping("/services/*");
servletContext.addServlet("default", DispatcherServlet.class).addMapping("/");
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/services/*");
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isInstanceOf(DispatcherServletDelegatingRequestMatcher.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint");
request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping());
assertThat(requestMatchers.get(0).matcher(request).isMatch()).isTrue();
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/services"));
request.setServletPath("/services");
request.setPathInfo("/endpoint");
assertThat(requestMatchers.get(0).matcher(request).isMatch()).isTrue();
}
@Test
public void matchesWhenDispatcherServletThenMvc() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", DispatcherServlet.class).addMapping("/");
servletContext.addServlet("path", Servlet.class).addMapping("/services/*");
MvcRequestMatcher mvc = mock(MvcRequestMatcher.class);
AntPathRequestMatcher ant = mock(AntPathRequestMatcher.class);
DispatcherServletDelegatingRequestMatcher requestMatcher = new DispatcherServletDelegatingRequestMatcher(ant,
mvc, servletContext);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint");
request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping());
assertThat(requestMatcher.matches(request)).isFalse();
verify(mvc).matches(request);
verifyNoInteractions(ant);
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/services"));
assertThat(requestMatcher.matches(request)).isFalse();
verify(ant).matches(request);
verifyNoMoreInteractions(mvc);
}
private void mockMvcIntrospector(boolean isPresent) {
ApplicationContext context = this.matcherRegistry.getApplicationContext();
given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);

View File

@@ -36,6 +36,7 @@ import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.MockServletContext;
import org.springframework.security.config.TestMockHttpServletMappings;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;

View File

@@ -19,6 +19,7 @@ package org.springframework.security.config.annotation.web.configurers;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.config.TestMockHttpServletMappings;
import static org.assertj.core.api.Assertions.assertThat;