From e86d88d0cf869fce800974eb22e61a9c3dcf5fa2 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:17:15 -0500 Subject: [PATCH 1/4] Support ServerExchangeRejectedHandler @Bean Closes gh-15975 --- .../WebFluxSecurityConfiguration.java | 5 ++- .../WebFluxSecurityConfigurationTests.java | 26 +++++++++++++++ .../pages/reactive/exploits/firewall.adoc | 32 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java index 9796e9349e..a3b9f9aad8 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.java @@ -31,6 +31,7 @@ import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler; import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -67,9 +68,11 @@ class WebFluxSecurityConfiguration { @Bean(SPRING_SECURITY_WEBFILTERCHAINFILTER_BEAN_NAME) @Order(WEB_FILTER_CHAIN_FILTER_ORDER) - WebFilterChainProxy springSecurityWebFilterChainFilter(ObjectProvider firewall) { + WebFilterChainProxy springSecurityWebFilterChainFilter(ObjectProvider firewall, + ObjectProvider rejectedHandler) { WebFilterChainProxy webFilterChainProxy = new WebFilterChainProxy(getSecurityWebFilterChains()); firewall.ifUnique(webFilterChainProxy::setFirewall); + rejectedHandler.ifUnique(webFilterChainProxy::setExchangeRejectedHandler); return webFilterChainProxy; } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java index 9c49fb7a82..2f223e4b34 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java @@ -32,6 +32,8 @@ import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.security.web.server.firewall.HttpStatusExchangeRejectedHandler; +import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler; import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall; import org.springframework.web.server.handler.DefaultWebFilterChain; @@ -66,6 +68,18 @@ public class WebFluxSecurityConfigurationTests { assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); } + @Test + void loadConfigWhenCustomRejectedHandler() throws Exception { + this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, + WebFluxSecurityConfiguration.class, CustomServerExchangeRejectedHandlerConfig.class).autowire(); + WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); + MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build()); + DefaultWebFilterChain chain = emptyChain(); + webFilterChainProxy.filter(exchange, chain).block(); + assertThat(exchange.getResponse().getStatusCode()) + .isEqualTo(CustomServerExchangeRejectedHandlerConfig.EXPECTED_STATUS); + } + @Test void loadConfigWhenFirewallBeanThenCustomized() throws Exception { this.spring.register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, @@ -99,6 +113,18 @@ public class WebFluxSecurityConfigurationTests { } + @Configuration + static class CustomServerExchangeRejectedHandlerConfig { + + static HttpStatus EXPECTED_STATUS = HttpStatus.I_AM_A_TEAPOT; + + @Bean + ServerExchangeRejectedHandler rejectedHandler() { + return new HttpStatusExchangeRejectedHandler(EXPECTED_STATUS); + } + + } + @Configuration static class SubclassConfig extends WebFluxSecurityConfiguration { diff --git a/docs/modules/ROOT/pages/reactive/exploits/firewall.adoc b/docs/modules/ROOT/pages/reactive/exploits/firewall.adoc index dce7fb05f7..f359ebdcf9 100644 --- a/docs/modules/ROOT/pages/reactive/exploits/firewall.adoc +++ b/docs/modules/ROOT/pages/reactive/exploits/firewall.adoc @@ -200,3 +200,35 @@ firewall.setAllowedHeaderValues { } ---- ====== + +The `ServerExchangeRejectedHandler` interface is used to handle `ServerExchangeRejectedException` throw by Spring Security's `ServerWebExchangeFirewall`. +By default `HttpStatusExchangeRejectedHandler` is used to send an HTTP 400 response to clients when a request is rejected. +To customize the behavior, users can expose a `ServerExchangeRejectedHandler` Bean. +For example, the following will send an HTTP 404 when the request is rejected: + + +.Send 404 on Request Rejected +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +@Bean +ServerExchangeRejectedHandler rejectedHandler() { + return new HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND); +} +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +@Bean +fun rejectedHandler(): ServerExchangeRejectedHandler { + return HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND) +} +---- +====== + +Handling can be completely customized by creating a custom `ServerExchangeRejectedHandler` implementation. From 0eaffb37e7f10f30fdbba1d3728e66e26837ff89 Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Mon, 28 Oct 2024 07:38:45 -0400 Subject: [PATCH 2/4] Require Locale argument for toLower/toUpperCase usage --- ...romAssertionAttributesUserDetailsService.java | 4 +++- .../config/http/HttpConfigurationBuilder.java | 5 +++-- ...impleAttributes2GrantedAuthoritiesMapper.java | 6 +++--- .../authority/mapping/SimpleAuthorityMapper.java | 7 ++++--- .../MapReactiveUserDetailsService.java | 5 +++-- .../userdetails/memory/UserAttributeEditor.java | 5 +++-- .../provisioning/InMemoryUserDetailsManager.java | 15 ++++++++------- .../crypto/password/LdapShaPasswordEncoder.java | 7 ++++--- etc/checkstyle/checkstyle-suppressions.xml | 7 +++++++ etc/checkstyle/checkstyle.xml | 16 ++++++++++++++++ .../security/ldap/LdapEncoder.java | 6 ++++-- .../ldap/authentication/LdapEncoder.java | 6 ++++-- ...ctiveDirectoryLdapAuthenticationProvider.java | 11 ++++++----- .../DefaultLdapAuthoritiesPopulator.java | 3 ++- .../ldap/userdetails/LdapUserDetailsManager.java | 7 ++++--- .../ldap/userdetails/LdapUserDetailsMapper.java | 3 ++- .../NestedLdapAuthoritiesPopulator.java | 5 +++-- ...h2AuthorizedClientExchangeFilterFunction.java | 5 +++-- ...h2AuthorizedClientExchangeFilterFunction.java | 5 +++-- .../taglibs/authz/AbstractAuthorizeTag.java | 5 +++-- .../security/web/PortResolverImpl.java | 4 +++- .../security/web/util/UrlUtils.java | 3 ++- .../web/util/matcher/AntPathRequestMatcher.java | 7 ++++--- 23 files changed, 97 insertions(+), 50 deletions(-) diff --git a/cas/src/main/java/org/springframework/security/cas/userdetails/GrantedAuthorityFromAssertionAttributesUserDetailsService.java b/cas/src/main/java/org/springframework/security/cas/userdetails/GrantedAuthorityFromAssertionAttributesUserDetailsService.java index 0e47d1c57f..f32c58d388 100644 --- a/cas/src/main/java/org/springframework/security/cas/userdetails/GrantedAuthorityFromAssertionAttributesUserDetailsService.java +++ b/cas/src/main/java/org/springframework/security/cas/userdetails/GrantedAuthorityFromAssertionAttributesUserDetailsService.java @@ -18,6 +18,7 @@ package org.springframework.security.cas.userdetails; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.jasig.cas.client.validation.Assertion; @@ -73,7 +74,8 @@ public final class GrantedAuthorityFromAssertionAttributesUserDetailsService } private SimpleGrantedAuthority createSimpleGrantedAuthority(Object o) { - return new SimpleGrantedAuthority(this.convertToUpperCase ? o.toString().toUpperCase() : o.toString()); + return new SimpleGrantedAuthority( + this.convertToUpperCase ? o.toString().toUpperCase(Locale.ROOT) : o.toString()); } /** diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index 2e1a4c4a48..35c460aea7 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 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,6 +18,7 @@ package org.springframework.security.config.http; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.servlet.ServletRequest; @@ -286,7 +287,7 @@ class HttpConfigurationBuilder { // Needed to account for placeholders static String createPath(String path, boolean lowerCase) { - return lowerCase ? path.toLowerCase() : path; + return lowerCase ? path.toLowerCase(Locale.ENGLISH) : path; } BeanReference getSecurityContextRepositoryForAuthenticationFilters() { diff --git a/core/src/main/java/org/springframework/security/core/authority/mapping/SimpleAttributes2GrantedAuthoritiesMapper.java b/core/src/main/java/org/springframework/security/core/authority/mapping/SimpleAttributes2GrantedAuthoritiesMapper.java index c07137bb8f..3e907f8920 100755 --- a/core/src/main/java/org/springframework/security/core/authority/mapping/SimpleAttributes2GrantedAuthoritiesMapper.java +++ b/core/src/main/java/org/springframework/security/core/authority/mapping/SimpleAttributes2GrantedAuthoritiesMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 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. @@ -79,10 +79,10 @@ public class SimpleAttributes2GrantedAuthoritiesMapper */ private GrantedAuthority getGrantedAuthority(String attribute) { if (isConvertAttributeToLowerCase()) { - attribute = attribute.toLowerCase(Locale.getDefault()); + attribute = attribute.toLowerCase(Locale.ROOT); } else if (isConvertAttributeToUpperCase()) { - attribute = attribute.toUpperCase(Locale.getDefault()); + attribute = attribute.toUpperCase(Locale.ROOT); } if (isAddPrefixIfAlreadyExisting() || !attribute.startsWith(getAttributePrefix())) { return new SimpleGrantedAuthority(getAttributePrefix() + attribute); diff --git a/core/src/main/java/org/springframework/security/core/authority/mapping/SimpleAuthorityMapper.java b/core/src/main/java/org/springframework/security/core/authority/mapping/SimpleAuthorityMapper.java index 2bb66a73b6..18af306e72 100644 --- a/core/src/main/java/org/springframework/security/core/authority/mapping/SimpleAuthorityMapper.java +++ b/core/src/main/java/org/springframework/security/core/authority/mapping/SimpleAuthorityMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 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,6 +18,7 @@ package org.springframework.security.core.authority.mapping; import java.util.Collection; import java.util.HashSet; +import java.util.Locale; import java.util.Set; import org.springframework.beans.factory.InitializingBean; @@ -71,10 +72,10 @@ public final class SimpleAuthorityMapper implements GrantedAuthoritiesMapper, In private GrantedAuthority mapAuthority(String name) { if (this.convertToUpperCase) { - name = name.toUpperCase(); + name = name.toUpperCase(Locale.ROOT); } else if (this.convertToLowerCase) { - name = name.toLowerCase(); + name = name.toLowerCase(Locale.ROOT); } if (this.prefix.length() > 0 && !name.startsWith(this.prefix)) { name = this.prefix + name; diff --git a/core/src/main/java/org/springframework/security/core/userdetails/MapReactiveUserDetailsService.java b/core/src/main/java/org/springframework/security/core/userdetails/MapReactiveUserDetailsService.java index 10712776a7..ecb459bc3c 100644 --- a/core/src/main/java/org/springframework/security/core/userdetails/MapReactiveUserDetailsService.java +++ b/core/src/main/java/org/springframework/security/core/userdetails/MapReactiveUserDetailsService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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,6 +18,7 @@ package org.springframework.security.core.userdetails; import java.util.Arrays; import java.util.Collection; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -91,7 +92,7 @@ public class MapReactiveUserDetailsService implements ReactiveUserDetailsService } private String getKey(String username) { - return username.toLowerCase(); + return username.toLowerCase(Locale.ROOT); } } diff --git a/core/src/main/java/org/springframework/security/core/userdetails/memory/UserAttributeEditor.java b/core/src/main/java/org/springframework/security/core/userdetails/memory/UserAttributeEditor.java index 09f10c18d0..2b6422e2cb 100644 --- a/core/src/main/java/org/springframework/security/core/userdetails/memory/UserAttributeEditor.java +++ b/core/src/main/java/org/springframework/security/core/userdetails/memory/UserAttributeEditor.java @@ -19,6 +19,7 @@ package org.springframework.security.core.userdetails.memory; import java.beans.PropertyEditorSupport; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.springframework.util.StringUtils; @@ -45,10 +46,10 @@ public class UserAttributeEditor extends PropertyEditorSupport { userAttrib.setPassword(currentToken); } else { - if (currentToken.toLowerCase().equals("enabled")) { + if (currentToken.toLowerCase(Locale.ENGLISH).equals("enabled")) { userAttrib.setEnabled(true); } - else if (currentToken.toLowerCase().equals("disabled")) { + else if (currentToken.toLowerCase(Locale.ENGLISH).equals("disabled")) { userAttrib.setEnabled(false); } else { diff --git a/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java b/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java index 8e7b6b254d..d1b305636d 100644 --- a/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/provisioning/InMemoryUserDetailsManager.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. @@ -19,6 +19,7 @@ package org.springframework.security.provisioning; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Properties; @@ -92,23 +93,23 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai @Override public void createUser(UserDetails user) { Assert.isTrue(!userExists(user.getUsername()), "user should not exist"); - this.users.put(user.getUsername().toLowerCase(), new MutableUser(user)); + this.users.put(user.getUsername().toLowerCase(Locale.ROOT), new MutableUser(user)); } @Override public void deleteUser(String username) { - this.users.remove(username.toLowerCase()); + this.users.remove(username.toLowerCase(Locale.ROOT)); } @Override public void updateUser(UserDetails user) { Assert.isTrue(userExists(user.getUsername()), "user should exist"); - this.users.put(user.getUsername().toLowerCase(), new MutableUser(user)); + this.users.put(user.getUsername().toLowerCase(Locale.ROOT), new MutableUser(user)); } @Override public boolean userExists(String username) { - return this.users.containsKey(username.toLowerCase()); + return this.users.containsKey(username.toLowerCase(Locale.ROOT)); } @Override @@ -139,14 +140,14 @@ public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetai @Override public UserDetails updatePassword(UserDetails user, String newPassword) { String username = user.getUsername(); - MutableUserDetails mutableUser = this.users.get(username.toLowerCase()); + MutableUserDetails mutableUser = this.users.get(username.toLowerCase(Locale.ROOT)); mutableUser.setPassword(newPassword); return mutableUser; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - UserDetails user = this.users.get(username.toLowerCase()); + UserDetails user = this.users.get(username.toLowerCase(Locale.ROOT)); if (user == null) { throw new UsernameNotFoundException(username); } diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java index eb9687c0fc..35f7224a7c 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/LdapShaPasswordEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 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,6 +18,7 @@ package org.springframework.security.crypto.password; import java.security.MessageDigest; import java.util.Base64; +import java.util.Locale; import org.springframework.security.crypto.codec.Utf8; import org.springframework.security.crypto.keygen.BytesKeyGenerator; @@ -50,11 +51,11 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { private static final String SSHA_PREFIX = "{SSHA}"; - private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase(); + private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase(Locale.ENGLISH); private static final String SHA_PREFIX = "{SHA}"; - private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase(); + private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase(Locale.ENGLISH); private BytesKeyGenerator saltGenerator; diff --git a/etc/checkstyle/checkstyle-suppressions.xml b/etc/checkstyle/checkstyle-suppressions.xml index e42d8124ea..94b245f36a 100644 --- a/etc/checkstyle/checkstyle-suppressions.xml +++ b/etc/checkstyle/checkstyle-suppressions.xml @@ -53,4 +53,11 @@ + + + + + + + diff --git a/etc/checkstyle/checkstyle.xml b/etc/checkstyle/checkstyle.xml index e8ea50e0d6..e9ca982d1d 100644 --- a/etc/checkstyle/checkstyle.xml +++ b/etc/checkstyle/checkstyle.xml @@ -23,5 +23,21 @@ + + + + + + + + + + + + + + diff --git a/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java b/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java index a3911aa180..57d45a444c 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java +++ b/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2010 the original author or authors. + * Copyright 2005-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. @@ -16,6 +16,8 @@ package org.springframework.security.ldap; +import java.util.Locale; + import org.springframework.ldap.BadLdapGrammarException; /** @@ -72,7 +74,7 @@ final class LdapEncoder { } protected static String toTwoCharHex(char c) { - String raw = Integer.toHexString(c).toUpperCase(); + String raw = Integer.toHexString(c).toUpperCase(Locale.ENGLISH); return (raw.length() > 1) ? raw : "0" + raw; } diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java index f79f4843ae..e037e3d2ef 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2010 the original author or authors. + * Copyright 2005-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. @@ -16,6 +16,8 @@ package org.springframework.security.ldap.authentication; +import java.util.Locale; + import org.springframework.ldap.BadLdapGrammarException; /** @@ -72,7 +74,7 @@ final class LdapEncoder { } protected static String toTwoCharHex(char c) { - String raw = Integer.toHexString(c).toUpperCase(); + String raw = Integer.toHexString(c).toUpperCase(Locale.ENGLISH); return (raw.length() > 1) ? raw : "0" + raw; } diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java index 381b3c3179..7a8c1deb0e 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 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. @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Hashtable; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -142,9 +143,9 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda */ public ActiveDirectoryLdapAuthenticationProvider(String domain, String url, String rootDn) { Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty"); - this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null; + this.domain = StringUtils.hasText(domain) ? domain.toLowerCase(Locale.ROOT) : null; this.url = url; - this.rootDn = StringUtils.hasText(rootDn) ? rootDn.toLowerCase() : null; + this.rootDn = StringUtils.hasText(rootDn) ? rootDn.toLowerCase(Locale.ROOT) : null; } /** @@ -153,7 +154,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda */ public ActiveDirectoryLdapAuthenticationProvider(String domain, String url) { Assert.isTrue(StringUtils.hasText(url), "Url cannot be empty"); - this.domain = StringUtils.hasText(domain) ? domain.toLowerCase() : null; + this.domain = StringUtils.hasText(domain) ? domain.toLowerCase(Locale.ROOT) : null; this.url = url; this.rootDn = (this.domain != null) ? rootDnFromDomain(this.domain) : null; } @@ -361,7 +362,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda } String createBindPrincipal(String username) { - if (this.domain == null || username.toLowerCase().endsWith(this.domain)) { + if (this.domain == null || username.toLowerCase(Locale.ROOT).endsWith(this.domain)) { return username; } return username + "@" + this.domain; diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java index b69985d2b4..a57fd4ca8e 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -179,7 +180,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator return null; } if (this.convertToUpperCase) { - role = role.toUpperCase(); + role = role.toUpperCase(Locale.ROOT); } return new SimpleGrantedAuthority(this.rolePrefix + role); }; diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java index b2baff26a3..0f89c56514 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 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. @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.Locale; import javax.naming.Context; import javax.naming.NameNotFoundException; @@ -120,7 +121,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { NamingEnumeration ne = roleAttr.getAll(); Object group = ne.next(); String role = group.toString(); - return new SimpleGrantedAuthority(this.rolePrefix + role.toUpperCase()); + return new SimpleGrantedAuthority(this.rolePrefix + role.toUpperCase(Locale.ROOT)); }; private String[] attributesToRetrieve; @@ -283,7 +284,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { */ protected DistinguishedName buildGroupDn(String group) { DistinguishedName dn = new DistinguishedName(this.groupSearchBase); - dn.add(this.groupRoleAttributeName, group.toLowerCase()); + dn.add(this.groupRoleAttributeName, group.toLowerCase(Locale.ROOT)); return dn; } diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsMapper.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsMapper.java index b44e30a0d8..fced0663e1 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsMapper.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsMapper.java @@ -17,6 +17,7 @@ package org.springframework.security.ldap.userdetails; import java.util.Collection; +import java.util.Locale; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -127,7 +128,7 @@ public class LdapUserDetailsMapper implements UserDetailsContextMapper { protected GrantedAuthority createAuthority(Object role) { if (role instanceof String) { if (this.convertToUpperCase) { - role = ((String) role).toUpperCase(); + role = ((String) role).toUpperCase(Locale.ROOT); } return new SimpleGrantedAuthority(this.rolePrefix + role); } diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulator.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulator.java index b61068ec8f..5726513d30 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulator.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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,6 +18,7 @@ package org.springframework.security.ldap.userdetails; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -193,7 +194,7 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula } for (String role : roles) { if (isConvertToUpperCase()) { - role = role.toUpperCase(); + role = role.toUpperCase(Locale.ROOT); } role = getRolePrefix() + role; // if the group already exist, we will not search for it's parents again. diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java index ce48696708..7d2ec93dcd 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -19,6 +19,7 @@ package org.springframework.security.oauth2.client.web.reactive.function.client; import java.time.Duration; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; @@ -749,7 +750,7 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements // @formatter:off return Stream.of(wwwAuthenticateHeader) .filter((header) -> !StringUtils.isEmpty(header)) - .filter((header) -> header.toLowerCase().startsWith("bearer")) + .filter((header) -> header.toLowerCase(Locale.ENGLISH).startsWith("bearer")) .map((header) -> header.substring("bearer".length())) .map((header) -> header.split(",")) .flatMap(Stream::of) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java index c2fcadc1c7..c91d110a1b 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -19,6 +19,7 @@ package org.springframework.security.oauth2.client.web.reactive.function.client; import java.time.Duration; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -720,7 +721,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement private Map parseAuthParameters(String wwwAuthenticateHeader) { // @formatter:off return Stream.of(wwwAuthenticateHeader).filter((header) -> !StringUtils.isEmpty(header)) - .filter((header) -> header.toLowerCase().startsWith("bearer")) + .filter((header) -> header.toLowerCase(Locale.ENGLISH).startsWith("bearer")) .map((header) -> header.substring("bearer".length())) .map((header) -> header.split(",")) .flatMap(Stream::of) diff --git a/taglibs/src/main/java/org/springframework/security/taglibs/authz/AbstractAuthorizeTag.java b/taglibs/src/main/java/org/springframework/security/taglibs/authz/AbstractAuthorizeTag.java index 95fb17928f..0a0b39ca26 100644 --- a/taglibs/src/main/java/org/springframework/security/taglibs/authz/AbstractAuthorizeTag.java +++ b/taglibs/src/main/java/org/springframework/security/taglibs/authz/AbstractAuthorizeTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2004-2010 the original author or authors. + * Copyright 2004-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. @@ -17,6 +17,7 @@ package org.springframework.security.taglibs.authz; import java.io.IOException; +import java.util.Locale; import java.util.Map; import javax.servlet.ServletContext; @@ -167,7 +168,7 @@ public abstract class AbstractAuthorizeTag { } public void setMethod(String method) { - this.method = (method != null) ? method.toUpperCase() : null; + this.method = (method != null) ? method.toUpperCase(Locale.ENGLISH) : null; } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/web/src/main/java/org/springframework/security/web/PortResolverImpl.java b/web/src/main/java/org/springframework/security/web/PortResolverImpl.java index faa01d83c3..58a0ebb421 100644 --- a/web/src/main/java/org/springframework/security/web/PortResolverImpl.java +++ b/web/src/main/java/org/springframework/security/web/PortResolverImpl.java @@ -16,6 +16,8 @@ package org.springframework.security.web; +import java.util.Locale; + import javax.servlet.ServletRequest; import org.springframework.util.Assert; @@ -45,7 +47,7 @@ public class PortResolverImpl implements PortResolver { @Override public int getServerPort(ServletRequest request) { int serverPort = request.getServerPort(); - String scheme = request.getScheme().toLowerCase(); + String scheme = request.getScheme().toLowerCase(Locale.ENGLISH); Integer mappedPort = getMappedPort(serverPort, scheme); return (mappedPort != null) ? mappedPort : serverPort; } diff --git a/web/src/main/java/org/springframework/security/web/util/UrlUtils.java b/web/src/main/java/org/springframework/security/web/util/UrlUtils.java index 98f4bbf5c8..69f7b81d57 100644 --- a/web/src/main/java/org/springframework/security/web/util/UrlUtils.java +++ b/web/src/main/java/org/springframework/security/web/util/UrlUtils.java @@ -16,6 +16,7 @@ package org.springframework.security.web.util; +import java.util.Locale; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; @@ -49,7 +50,7 @@ public final class UrlUtils { */ public static String buildFullRequestUrl(String scheme, String serverName, int serverPort, String requestURI, String queryString) { - scheme = scheme.toLowerCase(); + scheme = scheme.toLowerCase(Locale.ENGLISH); StringBuilder url = new StringBuilder(); url.append(scheme).append("://").append(serverName); // Only add port if not default diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java index 6d9c226b57..5e82f93540 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 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. @@ -17,6 +17,7 @@ package org.springframework.security.web.util.matcher; import java.util.Collections; +import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -267,7 +268,7 @@ public final class AntPathRequestMatcher implements RequestMatcher, RequestVaria private SubpathMatcher(String subpath, boolean caseSensitive) { Assert.isTrue(!subpath.contains("*"), "subpath cannot contain \"*\""); - this.subpath = caseSensitive ? subpath : subpath.toLowerCase(); + this.subpath = caseSensitive ? subpath : subpath.toLowerCase(Locale.ROOT); this.length = subpath.length(); this.caseSensitive = caseSensitive; } @@ -275,7 +276,7 @@ public final class AntPathRequestMatcher implements RequestMatcher, RequestVaria @Override public boolean matches(String path) { if (!this.caseSensitive) { - path = path.toLowerCase(); + path = path.toLowerCase(Locale.ROOT); } return path.startsWith(this.subpath) && (path.length() == this.length || path.charAt(this.length) == '/'); } From d0b2b33dce56cb97b5efd3cdfa9b41fbc09c4586 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 18 Nov 2024 16:36:56 +0000 Subject: [PATCH 3/4] Release 5.7.14 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 6adbe22ca5..93a7defba7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ springJavaformatVersion=0.0.31 springBootVersion=2.4.2 springFrameworkVersion=5.3.29 openSamlVersion=3.4.6 -version=5.7.14-SNAPSHOT +version=5.7.14 kotlinVersion=1.6.21 samplesBranch=5.7.x org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From c62168ca5bdcf3f6a1ef46cc3f856dfdbb3d6d82 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 18 Nov 2024 17:12:34 +0000 Subject: [PATCH 4/4] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 93a7defba7..4a92d6f576 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ springJavaformatVersion=0.0.31 springBootVersion=2.4.2 springFrameworkVersion=5.3.29 openSamlVersion=3.4.6 -version=5.7.14 +version=5.7.15-SNAPSHOT kotlinVersion=1.6.21 samplesBranch=5.7.x org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError