From 773e86701edcd6f2179ad66204f55e24c3ae3d51 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 21 Jun 2024 18:49:12 -0600 Subject: [PATCH] Add ParameterRequestMatcher Closes gh-15342 --- .../saml2/Saml2LogoutConfigurer.java | 22 +---- .../http/Saml2LogoutBeanDefinitionParser.java | 22 +---- .../util/matcher/ParameterRequestMatcher.java | 91 +++++++++++++++++++ .../matcher/ParameterRequestMatcherTests.java | 64 +++++++++++++ 4 files changed, 159 insertions(+), 40 deletions(-) create mode 100644 web/src/main/java/org/springframework/security/web/util/matcher/ParameterRequestMatcher.java create mode 100644 web/src/test/java/org/springframework/security/web/util/matcher/ParameterRequestMatcherTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java index 914d46f8ba..960e9e1d71 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -18,8 +18,6 @@ package org.springframework.security.config.annotation.web.configurers.saml2; import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.function.Predicate; import jakarta.servlet.http.HttpServletRequest; @@ -60,6 +58,7 @@ import org.springframework.security.web.csrf.CsrfLogoutHandler; import org.springframework.security.web.csrf.CsrfTokenRepository; import org.springframework.security.web.util.matcher.AndRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.ParameterRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; /** @@ -508,23 +507,6 @@ public final class Saml2LogoutConfigurer> } - private static class ParameterRequestMatcher implements RequestMatcher { - - Predicate test = Objects::nonNull; - - String name; - - ParameterRequestMatcher(String name) { - this.name = name; - } - - @Override - public boolean matches(HttpServletRequest request) { - return this.test.test(request.getParameter(this.name)); - } - - } - private static class Saml2RelyingPartyInitiatedLogoutFilter extends LogoutFilter { Saml2RelyingPartyInitiatedLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) { diff --git a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java index 5f894cf8d8..860ed9fc55 100644 --- a/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -18,8 +18,6 @@ package org.springframework.security.config.http; import java.util.Arrays; import java.util.List; -import java.util.Objects; -import java.util.function.Predicate; import jakarta.servlet.http.HttpServletRequest; import org.w3c.dom.Element; @@ -44,6 +42,7 @@ import org.springframework.security.web.authentication.logout.SecurityContextLog import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.util.matcher.AndRequestMatcher; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.ParameterRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -228,23 +227,6 @@ final class Saml2LogoutBeanDefinitionParser implements BeanDefinitionParser { return this.logoutFilter; } - private static class ParameterRequestMatcher implements RequestMatcher { - - Predicate test = Objects::nonNull; - - String name; - - ParameterRequestMatcher(String name) { - this.name = name; - } - - @Override - public boolean matches(HttpServletRequest request) { - return this.test.test(request.getParameter(this.name)); - } - - } - public static class Saml2RequestMatcher implements RequestMatcher { private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/ParameterRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/ParameterRequestMatcher.java new file mode 100644 index 0000000000..a81976de3a --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/util/matcher/ParameterRequestMatcher.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2024 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.util.matcher; + +import java.util.Map; +import java.util.Objects; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * A {@link RequestMatcher} for matching on a request parameter and its value. + * + *

+ * The value may also be specified as a placeholder in order to match on any value, + * returning the value as part of the {@link MatchResult}. + * + * @author Josh Cummings + * @since 6.4 + */ +public final class ParameterRequestMatcher implements RequestMatcher { + + private static final MatchesValueMatcher NON_NULL = Objects::nonNull; + + private final String name; + + private final ValueMatcher matcher; + + public ParameterRequestMatcher(String name) { + this.name = name; + this.matcher = NON_NULL; + } + + public ParameterRequestMatcher(String name, String value) { + this.name = name; + MatchesValueMatcher matcher = value::equals; + if (value.startsWith("{") && value.endsWith("}")) { + String key = value.substring(1, value.length() - 1); + this.matcher = (v) -> (v != null) ? MatchResult.match(Map.of(key, v)) : MatchResult.notMatch(); + } + else { + this.matcher = matcher; + } + } + + @Override + public boolean matches(HttpServletRequest request) { + return matcher(request).isMatch(); + } + + @Override + public MatchResult matcher(HttpServletRequest request) { + String parameterValue = request.getParameter(this.name); + return this.matcher.matcher(parameterValue); + } + + private interface ValueMatcher { + + MatchResult matcher(String value); + + } + + private interface MatchesValueMatcher extends ValueMatcher { + + default MatchResult matcher(String value) { + if (matches(value)) { + return MatchResult.match(); + } + else { + return MatchResult.notMatch(); + } + } + + boolean matches(String value); + + } + +} diff --git a/web/src/test/java/org/springframework/security/web/util/matcher/ParameterRequestMatcherTests.java b/web/src/test/java/org/springframework/security/web/util/matcher/ParameterRequestMatcherTests.java new file mode 100644 index 0000000000..6da461ed4c --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/util/matcher/ParameterRequestMatcherTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2002-2024 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.util.matcher; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ParameterRequestMatcher} + * + * @author Josh Cummings + */ +@ExtendWith(MockitoExtension.class) +public class ParameterRequestMatcherTests { + + @Test + public void matchesWhenNameThenMatchesOnParameterName() { + ParameterRequestMatcher matcher = new ParameterRequestMatcher("name"); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/bar"); + assertThat(matcher.matches(request)).isFalse(); + request.setParameter("name", "value"); + assertThat(matcher.matches(request)).isTrue(); + } + + @Test + public void matchesWhenNameAndValueThenMatchesOnBoth() { + ParameterRequestMatcher matcher = new ParameterRequestMatcher("name", "value"); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/bar"); + request.setParameter("name", "value"); + assertThat(matcher.matches(request)).isTrue(); + request.setParameter("name", "wrong"); + assertThat(matcher.matches(request)).isFalse(); + } + + @Test + public void matchesWhenValuePlaceholderThenMatchesOnName() { + ParameterRequestMatcher matcher = new ParameterRequestMatcher("name", "{placeholder}"); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/bar"); + request.setParameter("name", "value"); + RequestMatcher.MatchResult result = matcher.matcher(request); + assertThat(result.isMatch()).isTrue(); + assertThat(result.getVariables().get("placeholder")).isEqualTo("value"); + } + +}