(tomcat) branch 10.1.x updated: Ensure security constraint with longest matching path is selected

[email protected]
Newsgroups gmane.comp.jakarta.tomcat.devel
Message-ID <178645341931.2052200.11390966519318123802@gitbox3-he-fi.apache.org>
This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new b79752d2a8 Ensure security constraint with longest matching path is selected
b79752d2a8 is described below

commit b79752d2a8578d94743e2a95c50af297f780c0df
Author: Mark Thomas <[email protected]>
AuthorDate: Tue Jul 21 18:52:39 2026 +0100

    Ensure security constraint with longest matching path is selected
---
 java/org/apache/catalina/realm/RealmBase.java      | 17 ++++----
 test/org/apache/catalina/realm/TestRealmBase.java  | 46 ++++++++++++++++++++++
 test/org/apache/tomcat/unittest/TesterRequest.java | 16 +++++++-
 webapps/docs/changelog.xml                         |  4 ++
 4 files changed, 73 insertions(+), 10 deletions(-)

diff --git a/java/org/apache/catalina/realm/RealmBase.java b/java/org/apache/catalina/realm/RealmBase.java
index e57de2ecd1..5f99f3c8c9 100644
--- a/java/org/apache/catalina/realm/RealmBase.java
+++ b/java/org/apache/catalina/realm/RealmBase.java
@@ -68,9 +68,8 @@ import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSName;
 
 /**
- * Abstract base class for Realm implementations.
- * Provides common functionality including credential handling, security constraint evaluation,
- * and GSS-API authentication support.
+ * Abstract base class for Realm implementations. Provides common functionality including credential handling, security
+ * constraint evaluation, and GSS-API authentication support.
  */
 public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
 
@@ -324,8 +323,8 @@ public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
 
 
     /**
-     * Returns the comma separated names of user attributes to additionally query from the realm. These will be
-     * provided to the user through the created Principal's <i>attributes</i> map.
+     * Returns the comma separated names of user attributes to additionally query from the realm. These will be provided
+     * to the user through the created Principal's <i>attributes</i> map.
      *
      * @return The comma separated names of user attributes to additionally query from the realm
      */
@@ -644,7 +643,8 @@ public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
                 boolean matched = false;
                 int length = -1;
                 for (String pattern : patterns) {
-                    if (pattern.startsWith("/") && pattern.endsWith("/*") && pattern.length() >= longest) {
+                    if (pattern.startsWith("/") && pattern.endsWith("/*") && pattern.length() >= longest &&
+                            pattern.length() >= length) {
 
                         if (pattern.length() == 2) {
                             matched = true;
@@ -1138,8 +1138,9 @@ public abstract class RealmBase extends LifecycleMBeanBase implements Realm {
      * Check whether the current credential handler uses the specified message digest algorithm.
      *
      * @param algorithm The name of the message digest algorithm to check
-     * @return {@code true} if the credential handler is a {@link MessageDigestCredentialHandler}
-     *             using the specified algorithm
+     *
+     * @return {@code true} if the credential handler is a {@link MessageDigestCredentialHandler} using the specified
+     *             algorithm
      */
     protected boolean hasMessageDigest(String algorithm) {
         CredentialHandler ch = credentialHandler;
diff --git a/test/org/apache/catalina/realm/TestRealmBase.java b/test/org/apache/catalina/realm/TestRealmBase.java
index 2f1eec3609..6d2479755a 100644
--- a/test/org/apache/catalina/realm/TestRealmBase.java
+++ b/test/org/apache/catalina/realm/TestRealmBase.java
@@ -939,4 +939,50 @@ public class TestRealmBase {
         Assert.assertFalse(mapRealm.hasResourcePermission(
                 request, response, constraints, null));
     }
+
+
+    @Test
+    public void testOverlappingConstraints() throws Exception {
+        // Deny access to levels 1 & 3
+        SecurityConstraint outerConstraint = new SecurityConstraint();
+        SecurityCollection outerCollection = new SecurityCollection();
+        outerCollection.addPattern("/level1/level2/level3/*");
+        outerCollection.addPattern("/level1/*");
+        outerConstraint.addCollection(outerCollection);
+        // Empty auth -> deny
+        outerConstraint.setAuthConstraint(true);
+
+        // Allow access to level 2
+        SecurityConstraint innerConstraint = new SecurityConstraint();
+        SecurityCollection innerCollection = new SecurityCollection();
+        innerCollection.addPattern("/level1/level2/*");
+        innerConstraint.addCollection(innerCollection);
+        // No auth -> allow
+
+        TesterMapRealm mapRealm = new TesterMapRealm();
+
+        // Set up the mock request and response
+        TesterRequest request = new TesterRequest("/level1/index.jsp");
+        Response response = new TesterResponse();
+        Context context = request.getContext();
+        request.getMappingData().context = context;
+
+        // Add the constraints to the context
+        context.addConstraint(outerConstraint);
+        context.addConstraint(innerConstraint);
+
+        // Level 1 should be blocked
+        SecurityConstraint[] constraints = mapRealm.findSecurityConstraints(request, context);
+        Assert.assertFalse(mapRealm.hasResourcePermission(request, response, constraints, null));
+
+        // Level 2 should be blocked
+        request = new TesterRequest("/level1/level2/index.jsp");
+        constraints = mapRealm.findSecurityConstraints(request, context);
+        Assert.assertTrue(mapRealm.hasResourcePermission(request, response, constraints, null));
+
+        // Level 3 should be blocked
+        request = new TesterRequest("/level1/level2/level3/index.jsp");
+        constraints = mapRealm.findSecurityConstraints(request, context);
+        Assert.assertFalse(mapRealm.hasResourcePermission(request, response, constraints, null));
+    }
 }
diff --git a/test/org/apache/tomcat/unittest/TesterRequest.java b/test/org/apache/tomcat/unittest/TesterRequest.java
index 82b0548f3b..594b0aff0c 100644
--- a/test/org/apache/tomcat/unittest/TesterRequest.java
+++ b/test/org/apache/tomcat/unittest/TesterRequest.java
@@ -37,14 +37,25 @@ public class TesterRequest extends Request {
 
     private final TesterContext context;
     private final TesterServletContext servletContext;
+    private final String requestUri;
 
 
     public TesterRequest() {
-        this(false);
+        this(false, "/level1/level2/foo.html");
     }
 
 
     public TesterRequest(boolean withSession) {
+        this(withSession, "/level1/level2/foo.html");
+    }
+
+
+    public TesterRequest(String requestUri) {
+        this(false, requestUri);
+    }
+
+
+    public TesterRequest(boolean withSession, String requestUri) {
         super(null);
         context = new TesterContext();
         servletContext = new TesterServletContext();
@@ -58,6 +69,7 @@ public class TesterRequest extends Request {
             session.setId("1234", false);
             session.setValid(true);
         }
+        this.requestUri = requestUri;
     }
 
 
@@ -79,7 +91,7 @@ public class TesterRequest extends Request {
 
     @Override
     public String getRequestURI() {
-        return "/level1/level2/foo.html";
+        return requestUri;
     }
 
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 3e75b438ee..2b82a93f59 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -189,6 +189,10 @@
         application basis. This attribute will be removed in Tomcat 12 where it
         will effectively be hard-coded to <code>true</code>. (markt)
       </fix>
+      <fix>
+        Ensure the security constraint with the longest matching path is
+        selected when more than one constraint matches the request path. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.