This is an automated email from the ASF dual-hosted git repository.
lprimak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shiro.git
The following commit(s) were added to refs/heads/main by this push:
new f839b7835 [#2924] - WildcardPermission and DomainPermission lower-case
with the JVM default locale (#2925)
f839b7835 is described below
commit f839b783506ae75e44d70385107fa0bd51cc1426
Author: Sharang Gupta <[email protected]>
AuthorDate: Thu Oct 22 21:06:08 2026 +0100
[#2924] - WildcardPermission and DomainPermission lower-case with the JVM
default locale (#2925)
---
.../shiro/authz/permission/DomainPermission.java | 4 +-
.../shiro/authz/permission/WildcardPermission.java | 4 +-
.../permission/PermissionDefaultLocaleTest.java | 62 ++++++++++++++++++++++
.../crypto/hash/format/ProvidedHashFormat.java | 2 +-
.../crypto/support/hashes/argon2/Argon2Hash.java | 2 +-
.../org/apache/shiro/lang/util/StringUtils.java | 5 +-
.../web/filter/authc/HttpAuthenticationFilter.java | 8 +--
.../shiro/web/filter/authc/LogoutFilter.java | 2 +-
.../filter/authz/HttpMethodPermissionFilter.java | 5 +-
.../shiro/web/mgt/DefaultWebSecurityManager.java | 3 +-
.../org/apache/shiro/web/servlet/SimpleCookie.java | 2 +-
.../web/session/mgt/DefaultWebSessionManager.java | 5 +-
12 files changed, 87 insertions(+), 17 deletions(-)
diff --git
a/core/src/main/java/org/apache/shiro/authz/permission/DomainPermission.java
b/core/src/main/java/org/apache/shiro/authz/permission/DomainPermission.java
index 765468e5f..a3259842f 100644
--- a/core/src/main/java/org/apache/shiro/authz/permission/DomainPermission.java
+++ b/core/src/main/java/org/apache/shiro/authz/permission/DomainPermission.java
@@ -21,6 +21,7 @@ package org.apache.shiro.authz.permission;
import org.apache.shiro.lang.util.StringUtils;
import java.io.Serial;
+import java.util.Locale;
import java.util.Set;
/**
@@ -95,7 +96,8 @@ public class DomainPermission extends WildcardPermission {
}
protected String getDomain(Class<? extends DomainPermission> clazz) {
- String domain = clazz.getSimpleName().toLowerCase();
+ // Locale.ROOT: under certain non-English default locales
"InvoicePermission" would become "ınvoicepermission"
+ String domain = clazz.getSimpleName().toLowerCase(Locale.ROOT);
//strip any trailing 'permission' text from the name (as all
subclasses should have been named):
int index = domain.lastIndexOf("permission");
if (index != -1) {
diff --git
a/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermission.java
b/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermission.java
index a3f17166a..08ab55f8d 100644
---
a/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermission.java
+++
b/core/src/main/java/org/apache/shiro/authz/permission/WildcardPermission.java
@@ -26,6 +26,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
/**
@@ -155,7 +156,8 @@ public class WildcardPermission implements Permission,
Serializable {
}
if (!caseSensitive) {
- wildcardString = wildcardString.toLowerCase();
+ // Locale.ROOT (as in SHIRO-415/SHIRO-483): under certain
non-English default locales "ADMIN" would become "admın"
+ wildcardString = wildcardString.toLowerCase(Locale.ROOT);
}
List<String> parts =
CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));
diff --git
a/core/src/test/java/org/apache/shiro/authz/permission/PermissionDefaultLocaleTest.java
b/core/src/test/java/org/apache/shiro/authz/permission/PermissionDefaultLocaleTest.java
new file mode 100644
index 000000000..484b16960
--- /dev/null
+++
b/core/src/test/java/org/apache/shiro/authz/permission/PermissionDefaultLocaleTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.apache.shiro.authz.permission;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.parallel.Isolated;
+
+import java.util.Locale;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Permission matching must not depend on the JVM default locale: under a
Turkish default locale
+ * {@code "ADMIN".toLowerCase()} is {@code "admın"} (dotless i) while {@code
"admin"} stays {@code "admin"}.
+ * The class changes the default locale, so it runs isolated from the
(parallel) test execution.
+ */
+@Isolated
+public class PermissionDefaultLocaleTest {
+
+ @Test
+ void wildcardPermissionIsCaseInsensitiveRegardlessOfDefaultLocale() {
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.forLanguageTag("tr-TR"));
+ try {
+ assertThat(new WildcardPermission("ADMIN:*").implies(new
WildcardPermission("admin:read"))).isTrue();
+ assertThat(new WildcardPermission("admin:*").implies(new
WildcardPermission("ADMIN:read"))).isTrue();
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ @Test
+ void domainPermissionDerivesDomainRegardlessOfDefaultLocale() {
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.forLanguageTag("tr-TR"));
+ try {
+ assertThat(new
InvoicePermission().getDomain()).isEqualTo("invoice");
+ assertThat(new WildcardPermission("invoice:*").implies(new
InvoicePermission())).isTrue();
+ } finally {
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ private static final class InvoicePermission extends DomainPermission {
+ }
+}
diff --git
a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/format/ProvidedHashFormat.java
b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/format/ProvidedHashFormat.java
index 746f165ba..12e5cde7d 100644
---
a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/format/ProvidedHashFormat.java
+++
b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/format/ProvidedHashFormat.java
@@ -66,7 +66,7 @@ public enum ProvidedHashFormat {
try {
// Use English Locale, some Locales handle uppercase/lower
differently. i.e. Turkish and upper case 'i'
// is not 'I'. And 'SHIRO1' would be 'SHİRO1'
- return valueOf(id.toUpperCase(Locale.ENGLISH));
+ return valueOf(id.toUpperCase(Locale.ROOT));
} catch (final IllegalArgumentException ignored) {
return null;
}
diff --git
a/crypto/support/hashes/argon2/src/main/java/org/apache/shiro/crypto/support/hashes/argon2/Argon2Hash.java
b/crypto/support/hashes/argon2/src/main/java/org/apache/shiro/crypto/support/hashes/argon2/Argon2Hash.java
index 010845039..58c2a8aa9 100644
---
a/crypto/support/hashes/argon2/src/main/java/org/apache/shiro/crypto/support/hashes/argon2/Argon2Hash.java
+++
b/crypto/support/hashes/argon2/src/main/java/org/apache/shiro/crypto/support/hashes/argon2/Argon2Hash.java
@@ -332,7 +332,7 @@ class Argon2Hash extends AbstractCryptHash {
private CharSequence formatParameters() {
return String.format(
- Locale.ENGLISH,
+ Locale.ROOT,
"t=%d,m=%d,p=%d",
getIterations(),
getMemoryKiB(),
diff --git a/lang/src/main/java/org/apache/shiro/lang/util/StringUtils.java
b/lang/src/main/java/org/apache/shiro/lang/util/StringUtils.java
index 1412ae02a..013387772 100644
--- a/lang/src/main/java/org/apache/shiro/lang/util/StringUtils.java
+++ b/lang/src/main/java/org/apache/shiro/lang/util/StringUtils.java
@@ -25,6 +25,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Set;
import java.util.StringTokenizer;
@@ -130,8 +131,8 @@ public final class StringUtils {
if (str.length() < prefix.length()) {
return false;
}
- String lcStr = str.substring(0, prefix.length()).toLowerCase();
- String lcPrefix = prefix.toLowerCase();
+ String lcStr = str.substring(0,
prefix.length()).toLowerCase(Locale.ROOT);
+ String lcPrefix = prefix.toLowerCase(Locale.ROOT);
return lcStr.equals(lcPrefix);
}
diff --git
a/web/src/main/java/org/apache/shiro/web/filter/authc/HttpAuthenticationFilter.java
b/web/src/main/java/org/apache/shiro/web/filter/authc/HttpAuthenticationFilter.java
index 8fbe068a5..3779f763b 100644
---
a/web/src/main/java/org/apache/shiro/web/filter/authc/HttpAuthenticationFilter.java
+++
b/web/src/main/java/org/apache/shiro/web/filter/authc/HttpAuthenticationFilter.java
@@ -208,7 +208,7 @@ abstract class HttpAuthenticationFilter extends
AuthenticatingFilter {
boolean authcRequired = methods.isEmpty();
for (String m : methods) {
// list of methods is in upper case
- if (httpMethod.toUpperCase(Locale.ENGLISH).equals(m)) {
+ if (httpMethod.toUpperCase(Locale.ROOT).equals(m)) {
authcRequired = true;
break;
}
@@ -229,7 +229,7 @@ abstract class HttpAuthenticationFilter extends
AuthenticatingFilter {
// to be backwards compatible with 1.3, we can ONLY check for
known args
// ideally we would just validate HTTP methods, but someone
could already be using this for webdav
if (!option.equalsIgnoreCase(PERMISSIVE)) {
- methods.add(option.toUpperCase(Locale.ENGLISH));
+ methods.add(option.toUpperCase(Locale.ROOT));
}
}
}
@@ -314,8 +314,8 @@ abstract class HttpAuthenticationFilter extends
AuthenticatingFilter {
*/
protected boolean isLoginAttempt(String authzHeader) {
//SHIRO-415: use English Locale:
- String authzScheme = getAuthzScheme().toLowerCase(Locale.ENGLISH);
- return authzHeader.toLowerCase(Locale.ENGLISH).startsWith(authzScheme);
+ String authzScheme = getAuthzScheme().toLowerCase(Locale.ROOT);
+ return authzHeader.toLowerCase(Locale.ROOT).startsWith(authzScheme);
}
/**
diff --git
a/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
b/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
index 7c3a06d24..3977b1906 100644
--- a/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
+++ b/web/src/main/java/org/apache/shiro/web/filter/authc/LogoutFilter.java
@@ -81,7 +81,7 @@ public class LogoutFilter extends AdviceFilter {
if (isPostOnlyLogout()) {
// check if the current request's method is a POST, if not redirect
- if
(!WebUtils.toHttp(request).getMethod().toUpperCase(Locale.ENGLISH).equals("POST"))
{
+ if
(!WebUtils.toHttp(request).getMethod().toUpperCase(Locale.ROOT).equals("POST"))
{
return onLogoutRequestNotAPost(request, response);
}
}
diff --git
a/web/src/main/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilter.java
b/web/src/main/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilter.java
index 82a2e28cf..31765e70a 100644
---
a/web/src/main/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilter.java
+++
b/web/src/main/java/org/apache/shiro/web/filter/authz/HttpMethodPermissionFilter.java
@@ -27,6 +27,7 @@ import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
/**
@@ -150,7 +151,7 @@ public class HttpMethodPermissionFilter extends
PermissionsAuthorizationFilter {
*/
public HttpMethodPermissionFilter() {
for (HttpMethodAction methodAction : HttpMethodAction.values()) {
- httpMethodActions.put(methodAction.name().toLowerCase(),
methodAction.getAction());
+
httpMethodActions.put(methodAction.name().toLowerCase(Locale.ROOT),
methodAction.getAction());
}
}
@@ -187,7 +188,7 @@ public class HttpMethodPermissionFilter extends
PermissionsAuthorizationFilter {
* @return The string equivalent verb of the method.
*/
protected String getHttpMethodAction(String method) {
- String lc = method.toLowerCase();
+ String lc = method.toLowerCase(Locale.ROOT);
String resolved = getHttpMethodActions().get(lc);
return resolved != null ? resolved : method;
}
diff --git
a/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java
b/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java
index f9b12d7cd..75ae6dd10 100644
--- a/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java
+++ b/web/src/main/java/org/apache/shiro/web/mgt/DefaultWebSecurityManager.java
@@ -48,6 +48,7 @@ import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.Serializable;
import java.util.Collection;
+import java.util.Locale;
import java.util.function.Supplier;
@@ -163,7 +164,7 @@ public class DefaultWebSecurityManager extends
DefaultSecurityManager implements
if (mode == null) {
throw new IllegalArgumentException("sessionMode argument cannot be
null.");
}
- mode = sessionMode.toLowerCase();
+ mode = sessionMode.toLowerCase(Locale.ROOT);
if (!HTTP_SESSION_MODE.equals(mode) &&
!NATIVE_SESSION_MODE.equals(mode)) {
String msg = "Invalid sessionMode [" + sessionMode + "]. Allowed
values are "
+ "public static final String constants in the " +
getClass().getName() + " class: '"
diff --git a/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java
b/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java
index 16a73fc33..fb1624636 100644
--- a/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java
+++ b/web/src/main/java/org/apache/shiro/web/servlet/SimpleCookie.java
@@ -385,7 +385,7 @@ public class SimpleCookie implements Cookie {
sb.append(ATTRIBUTE_DELIMITER);
sb.append(SAME_SITE_ATTRIBUTE_NAME)
.append(NAME_VALUE_DELIMITER)
- .append(sameSite.toString().toLowerCase(Locale.ENGLISH));
+ .append(sameSite.toString().toLowerCase(Locale.ROOT));
}
}
diff --git
a/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
index 0d2bf1e4c..ca52d528e 100644
---
a/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
+++
b/web/src/main/java/org/apache/shiro/web/session/mgt/DefaultWebSessionManager.java
@@ -19,6 +19,7 @@
package org.apache.shiro.web.session.mgt;
import org.apache.shiro.session.ExpiredSessionException;
+import java.util.Locale;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionManager;
@@ -141,9 +142,9 @@ public class DefaultWebSessionManager extends
DefaultSessionManager implements W
if (queryString != null && queryString.contains(name)) {
id = request.getParameter(name);
}
- if (id == null && queryString != null &&
queryString.contains(name.toLowerCase())) {
+ if (id == null && queryString != null &&
queryString.contains(name.toLowerCase(Locale.ROOT))) {
//try lowercase:
- id = request.getParameter(name.toLowerCase());
+ id = request.getParameter(name.toLowerCase(Locale.ROOT));
}
}
if (id != null) {