Add PathPatterRequestMatcher

Closes gh-16429
Clsoes gh-16430
This commit is contained in:
Josh Cummings
2025-02-21 13:24:55 -07:00
parent 4f25f0b90f
commit 588220a020
6 changed files with 596 additions and 44 deletions

View File

@@ -48,6 +48,7 @@ import org.springframework.security.web.firewall.FirewalledRequest;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.RequestRejectedException;
import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.servlet.TestMockHttpServletMappings;
import org.springframework.security.web.util.matcher.RequestMatcher;
import static org.assertj.core.api.Assertions.assertThat;
@@ -166,6 +167,7 @@ public class FilterChainProxyTests {
FirewalledRequest fwr = mock(FirewalledRequest.class);
given(fwr.getRequestURI()).willReturn("/");
given(fwr.getContextPath()).willReturn("");
given(fwr.getHttpServletMapping()).willReturn(TestMockHttpServletMappings.defaultMapping());
this.fcp.setFirewall(fw);
given(fw.getFirewalledRequest(this.request)).willReturn(fwr);
given(this.matcher.matches(any(HttpServletRequest.class))).willReturn(false);
@@ -183,9 +185,11 @@ public class FilterChainProxyTests {
FirewalledRequest firstFwr = mock(FirewalledRequest.class, "firstFwr");
given(firstFwr.getRequestURI()).willReturn("/");
given(firstFwr.getContextPath()).willReturn("");
given(firstFwr.getHttpServletMapping()).willReturn(TestMockHttpServletMappings.defaultMapping());
FirewalledRequest fwr = mock(FirewalledRequest.class, "fwr");
given(fwr.getRequestURI()).willReturn("/");
given(fwr.getContextPath()).willReturn("");
given(fwr.getHttpServletMapping()).willReturn(TestMockHttpServletMappings.defaultMapping());
given(fw.getFirewalledRequest(this.request)).willReturn(firstFwr);
given(fw.getFirewalledRequest(firstFwr)).willReturn(fwr);
given(fwr.getRequest()).willReturn(firstFwr);

View File

@@ -0,0 +1,156 @@
/*
* Copyright 2002-2025 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.web.servlet.util.matcher;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.web.servlet.MockServletContext;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.util.ServletRequestPathUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
/**
* Tests for {@link PathPatternRequestMatcher}
*/
public class PathPatternRequestMatcherTests {
@Test
void matcherWhenPatternMatchesRequestThenMatchResult() {
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher("/uri");
assertThat(matcher.matches(request("/uri"))).isTrue();
}
@Test
void matcherWhenPatternContainsPlaceholdersThenMatchResult() {
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher("/uri/{username}");
assertThat(matcher.matcher(request("/uri/bob")).getVariables()).containsEntry("username", "bob");
}
@Test
void matcherWhenOnlyPathInfoMatchesThenMatches() {
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher("/uri");
assertThat(matcher.matches(request("GET", "/mvc/uri", "/mvc"))).isTrue();
}
@Test
void matcherWhenUriContainsServletPathThenNoMatch() {
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher("/mvc/uri");
assertThat(matcher.matches(request("GET", "/mvc/uri", "/mvc"))).isFalse();
}
@Test
void matcherWhenSameMethodThenMatchResult() {
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, "/uri");
assertThat(matcher.matches(request("/uri"))).isTrue();
}
@Test
void matcherWhenDifferentPathThenNoMatch() {
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, "/uri");
assertThat(matcher.matches(request("GET", "/urj", ""))).isFalse();
}
@Test
void matcherWhenDifferentMethodThenNoMatch() {
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher(HttpMethod.GET, "/uri");
assertThat(matcher.matches(request("POST", "/mvc/uri", "/mvc"))).isFalse();
}
@Test
void matcherWhenNoMethodThenMatches() {
RequestMatcher matcher = PathPatternRequestMatcher.withDefaults().matcher("/uri");
assertThat(matcher.matches(request("POST", "/uri", ""))).isTrue();
assertThat(matcher.matches(request("GET", "/uri", ""))).isTrue();
}
@Test
void matcherWhenServletPathThenMatchesOnlyServletPath() {
PathPatternRequestMatcher.Builder servlet = PathPatternRequestMatcher.withDefaults()
.servletPath("/servlet/path");
RequestMatcher matcher = servlet.matcher(HttpMethod.GET, "/endpoint");
ServletContext servletContext = servletContext("/servlet/path");
MockHttpServletRequest mock = get("/servlet/path/endpoint").servletPath("/servlet/path")
.buildRequest(servletContext);
ServletRequestPathUtils.parseAndCache(mock);
assertThat(matcher.matches(mock)).isTrue();
mock = get("/endpoint").servletPath("/endpoint").buildRequest(servletContext);
ServletRequestPathUtils.parseAndCache(mock);
assertThat(matcher.matches(mock)).isFalse();
}
@Test
void matcherWhenRequestPathThenIgnoresServletPath() {
PathPatternRequestMatcher.Builder request = PathPatternRequestMatcher.withDefaults();
RequestMatcher matcher = request.matcher(HttpMethod.GET, "/endpoint");
MockHttpServletRequest mock = get("/servlet/path/endpoint").servletPath("/servlet/path").buildRequest(null);
ServletRequestPathUtils.parseAndCache(mock);
assertThat(matcher.matches(mock)).isTrue();
mock = get("/endpoint").servletPath("/endpoint").buildRequest(null);
ServletRequestPathUtils.parseAndCache(mock);
assertThat(matcher.matches(mock)).isTrue();
}
@Test
void matcherWhenServletPathThenRequiresServletPathToExist() {
PathPatternRequestMatcher.Builder servlet = PathPatternRequestMatcher.withDefaults()
.servletPath("/servlet/path");
RequestMatcher matcher = servlet.matcher(HttpMethod.GET, "/endpoint");
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(
() -> matcher.matches(get("/servlet/path/endpoint").servletPath("/servlet/path").buildRequest(null)));
}
@Test
void servletPathWhenEndsWithSlashOrStarThenIllegalArgument() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> PathPatternRequestMatcher.withDefaults().servletPath("/path/**"));
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> PathPatternRequestMatcher.withDefaults().servletPath("/path/*"));
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> PathPatternRequestMatcher.withDefaults().servletPath("/path/"));
}
MockHttpServletRequest request(String uri) {
MockHttpServletRequest request = new MockHttpServletRequest("GET", uri);
ServletRequestPathUtils.parseAndCache(request);
return request;
}
MockHttpServletRequest request(String method, String uri, String servletPath) {
MockHttpServletRequest request = new MockHttpServletRequest(method, uri);
request.setServletPath(servletPath);
ServletRequestPathUtils.parseAndCache(request);
return request;
}
MockServletContext servletContext(String... servletPath) {
MockServletContext servletContext = new MockServletContext();
ServletRegistration.Dynamic registration = servletContext.addServlet("servlet", Servlet.class);
for (String s : servletPath) {
registration.addMapping(s + "/*");
}
return servletContext;
}
}