This is an automated email from the ASF dual-hosted git repository.

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


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

commit 8bafd79a3b54684e80e9cb1bafd4746aede7d3f5
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 55014f1987..a526001c51 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
      */
@@ -636,7 +635,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;
@@ -1127,8 +1127,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 3f920976dd..726225224d 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 793be4495e..fb5a87d0d3 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, 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 1e570849dc..9368dce631 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">


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to