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 3ff06ceb98 Various improvements to the DataSourceRealm
3ff06ceb98 is described below

commit 3ff06ceb984edc2a3c9e0161b01e833c5e50ed4f
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Jul 31 08:49:10 2026 +0100

    Various improvements to the DataSourceRealm
    
    A failure to connect to the database or an exception during either user
    or role lookup will now result in an authentication failure rather than
    a partially populated Principal.
    
    For CLIENT-CERT and SPNEGO authentication, the user must exist in the
    database for authentication to succeed.
---
 .../org/apache/catalina/realm/DataSourceRealm.java | 63 ++++++++++++++++++----
 .../apache/catalina/realm/LocalStrings.properties  |  1 +
 .../apache/catalina/realm/TestDataSourceRealm.java |  9 +++-
 webapps/docs/changelog.xml                         |  8 +++
 4 files changed, 70 insertions(+), 11 deletions(-)

diff --git a/java/org/apache/catalina/realm/DataSourceRealm.java 
b/java/org/apache/catalina/realm/DataSourceRealm.java
index 845cecd545..249e1d70ff 100644
--- a/java/org/apache/catalina/realm/DataSourceRealm.java
+++ b/java/org/apache/catalina/realm/DataSourceRealm.java
@@ -23,6 +23,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.List;
 
 import javax.naming.Context;
 import javax.sql.DataSource;
@@ -317,9 +318,20 @@ public class DataSourceRealm extends RealmBase {
         }
 
         // Validate the user's credentials
-        boolean validated = getCredentialHandler().matches(credentials, 
dbCredentials);
+        boolean authenticationSuccess = 
getCredentialHandler().matches(credentials, dbCredentials);
 
-        if (validated) {
+        ArrayList<String> list = null;
+
+        if (authenticationSuccess) {
+            // Obtain the roles
+            list = getRoles(dbConnection, username);
+            if (list == null) {
+                // Role lookup failed so fail authentication
+                authenticationSuccess = false;
+            }
+        }
+
+        if (authenticationSuccess) {
             if (containerLog.isTraceEnabled()) {
                 
containerLog.trace(sm.getString("dataSourceRealm.authenticateSuccess", 
username));
             }
@@ -330,8 +342,6 @@ public class DataSourceRealm extends RealmBase {
             return null;
         }
 
-        ArrayList<String> list = getRoles(dbConnection, username);
-
         // Create and return a suitable Principal for this user
         return new GenericPrincipal(username, list);
     }
@@ -447,26 +457,59 @@ public class DataSourceRealm extends RealmBase {
     }
 
 
+    /**
+     * Confirms if the given user exists in the database.
+     *
+     * @param dbConnection The database connection to be used
+     * @param username     Username to check
+     *
+     * @return {@code true} if the user exists, otherwise {@code false}
+     */
+    protected boolean validateUser(Connection dbConnection, String username) {
+        // Use the credentials lookup as a proxy for whether the user exists
+        try (PreparedStatement stmt = 
dbConnection.prepareStatement(preparedCredentials)) {
+            stmt.setString(1, username);
+
+            try (ResultSet rs = stmt.executeQuery()) {
+                if (rs.next()) {
+                    return true;
+                }
+            }
+        } catch (SQLException e) {
+            
containerLog.error(sm.getString("dataSourceRealm.validateUser.exception", 
username), e);
+        }
+
+        return false;
+    }
+
+
     @Override
     protected Principal getPrincipal(String username) {
         Connection dbConnection = open();
         if (dbConnection == null) {
-            return new GenericPrincipal(username, null);
+            return null;
         }
         try {
-            return new GenericPrincipal(username, getRoles(dbConnection, 
username));
+            if (!validateUser(dbConnection, username)) {
+                return null;
+            }
+            List<String> roles = getRoles(dbConnection, username);
+            if (roles == null) {
+                return null;
+            }
+            return new GenericPrincipal(username, roles);
         } finally {
             close(dbConnection);
         }
-
     }
 
+
     /**
      * Return the roles associated with the given username.
      *
      * @param username Username for which roles should be retrieved
      *
-     * @return an array list of the role names
+     * @return an array list of the role names or {@code null} if the lookup 
fails
      */
     protected ArrayList<String> getRoles(String username) {
 
@@ -490,14 +533,14 @@ public class DataSourceRealm extends RealmBase {
      * @param dbConnection The database connection to be used
      * @param username     Username for which roles should be retrieved
      *
-     * @return an array list of the role names
+     * @return an array list of the role names or {@code null} if the lookup 
fails
      */
     protected ArrayList<String> getRoles(Connection dbConnection, String 
username) {
 
         if (allRolesMode != AllRolesMode.STRICT_MODE && !isRoleStoreDefined()) 
{
             // Using an authentication only configuration and no role store has
             // been defined so don't spend cycles looking
-            return null;
+            return new ArrayList<>(0);
         }
 
         try (PreparedStatement stmt = 
dbConnection.prepareStatement(preparedRoles)) {
diff --git a/java/org/apache/catalina/realm/LocalStrings.properties 
b/java/org/apache/catalina/realm/LocalStrings.properties
index 36abdca052..4e0cf9e312 100644
--- a/java/org/apache/catalina/realm/LocalStrings.properties
+++ b/java/org/apache/catalina/realm/LocalStrings.properties
@@ -40,6 +40,7 @@ dataSourceRealm.noUserCredCol=No column was specified for 
user credentials
 dataSourceRealm.noUserNameCol=No column was specified for user names
 dataSourceRealm.noUserTable=No user table was specified
 dataSourceRealm.roleConfigMismatch=Role configuration is incomplete: both 
userRoleTable and roleNameCol must be specified together
+dataSourceRealm.validateUser.exception=Exception validating that user [{0}] 
exists
 
 jaasCallback.username=Returned username [{0}]
 
diff --git a/test/org/apache/catalina/realm/TestDataSourceRealm.java 
b/test/org/apache/catalina/realm/TestDataSourceRealm.java
index 6c09624e1f..cd1471a799 100644
--- a/test/org/apache/catalina/realm/TestDataSourceRealm.java
+++ b/test/org/apache/catalina/realm/TestDataSourceRealm.java
@@ -152,7 +152,14 @@ public class TestDataSourceRealm extends LoggingBaseTest {
         List<String> roles = db.getRoles("tomcat");
         Assert.assertEquals(2, roles.size());
 
-        db.stop();
+        p = db.getPrincipal("blabla");
+        Assert.assertNull(p);
+
+        p = db.getPrincipal("random");
+        Assert.assertTrue(p instanceof GenericPrincipal);
+        gp = (GenericPrincipal) p;
+        Assert.assertEquals(0, gp.getRoles().length);
 
+        db.stop();
     }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index ecde97da20..514c82220b 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -202,6 +202,14 @@
         <code>doAuthenticate()</code> and/or <code>restoreRequest()</code> will
         require modification. (markt)
       </fix>
+      <fix>
+        Various improvements to the <code>DataSourceRealm</code>. A failure to
+        connect to the database or an exception during either user or role
+        lookup will now result in an authentication failure rather than a
+        partially populated Principal. For <code>CLIENT-CERT</code> and
+        <code>SPNEGO</code> authentication, the user must exist in the database
+        for authentication to succeed. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">


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

Reply via email to