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) {

Reply via email to