This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new b2c56ec8f2 Ensure security constraint with longest matching path is selected
b2c56ec8f2 is described below
commit b2c56ec8f20c66773a1a813034ffcdb60841f1cf
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 b97e979d07..4ff6d45e0c 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 c71063b270..64a9cce521 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 7635ef6d7d..c21882c102 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 ac3babad90..7d0b14ae5e 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.