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">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]