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 8efd51f061 Various improvements to the DataSourceRealm and JDBCRealm
8efd51f061 is described below

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

    Various improvements to the DataSourceRealm and JDBCRealm
    
    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 | 66 ++++++++++++++++++----
 java/org/apache/catalina/realm/JDBCRealm.java      | 65 ++++++++++++++++++---
 .../apache/catalina/realm/LocalStrings.properties  |  1 +
 .../apache/catalina/realm/TestDataSourceRealm.java |  9 ++-
 webapps/docs/changelog.xml                         |  8 +++
 5 files changed, 129 insertions(+), 20 deletions(-)

diff --git a/java/org/apache/catalina/realm/DataSourceRealm.java 
b/java/org/apache/catalina/realm/DataSourceRealm.java
index bfa4572dc0..198260a41e 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> roles = null;
+
+        if (authenticationSuccess) {
+            // Obtain the roles
+            roles = getRoles(dbConnection, username);
+            if (roles == null) {
+                // Role lookup failed so fail authentication
+                authenticationSuccess = false;
+            }
+        }
+
+        if (authenticationSuccess) {
             if (containerLog.isTraceEnabled()) {
                 
containerLog.trace(sm.getString("dataSourceRealm.authenticateSuccess", 
username));
             }
@@ -330,10 +342,8 @@ 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, credentials, list);
+        return new GenericPrincipal(username, credentials, roles);
     }
 
 
@@ -447,27 +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, null);
+            return null;
         }
         try {
-            return new GenericPrincipal(username, getPassword(dbConnection, 
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, getPassword(dbConnection, 
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) {
 
@@ -491,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/JDBCRealm.java 
b/java/org/apache/catalina/realm/JDBCRealm.java
index df75fd40c8..ffb5a2bae8 100644
--- a/java/org/apache/catalina/realm/JDBCRealm.java
+++ b/java/org/apache/catalina/realm/JDBCRealm.java
@@ -24,6 +24,7 @@ import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Properties;
 
 import org.apache.catalina.LifecycleException;
@@ -373,9 +374,20 @@ public class JDBCRealm extends RealmBase {
         }
 
         // Validate the user's credentials
-        boolean validated = getCredentialHandler().matches(credentials, 
dbCredentials);
+        boolean authenticationSuccess = 
getCredentialHandler().matches(credentials, dbCredentials);
 
-        if (validated) {
+        ArrayList<String> roles = null;
+
+        if (authenticationSuccess) {
+            // Obtain the roles
+            roles = getRoles(username);
+            if (roles == null) {
+                // Role lookup failed so fail authentication
+                authenticationSuccess = false;
+            }
+        }
+
+        if (authenticationSuccess) {
             if (containerLog.isTraceEnabled()) {
                 
containerLog.trace(sm.getString("jdbcRealm.authenticateSuccess", username));
             }
@@ -386,8 +398,6 @@ public class JDBCRealm extends RealmBase {
             return null;
         }
 
-        ArrayList<String> roles = getRoles(username);
-
         // Create and return a suitable Principal for this user
         return new GenericPrincipal(username, credentials, roles);
     }
@@ -536,6 +546,39 @@ public class JDBCRealm extends RealmBase {
         return null;
     }
 
+
+    /**
+     * Confirms if the given user exists in the database.
+     *
+     * @param username     Username to check
+     *
+     * @return {@code true} if the user exists, otherwise {@code false}
+     */
+    protected boolean validateUser(String username) {
+        // Use the credentials lookup as a proxy for whether the user exists
+        try {
+            // Ensure that we have an open database connection
+            open();
+
+            PreparedStatement stmt = credentials(dbConnection, username);
+            try (ResultSet rs = stmt.executeQuery()) {
+                if (rs.next()) {
+                    return true;
+                }
+            }
+        } catch (SQLException e) {
+            containerLog.error(sm.getString("jdbcRealm.exception"), e);
+        }
+
+        // Close the connection so that it gets reopened next time
+        if (dbConnection != null) {
+            close(dbConnection);
+        }
+
+        return false;
+    }
+
+
     /**
      * Get the principal associated with the specified user.
      *
@@ -545,9 +588,17 @@ public class JDBCRealm extends RealmBase {
      */
     @Override
     protected synchronized Principal getPrincipal(String username) {
+        if (!validateUser(username)) {
+            return null;
+        }
+        List<String> roles = getRoles(username);
+        if (roles == null) {
+            return null;
+        }
 
-        return new GenericPrincipal(username, getPassword(username), 
getRoles(username));
+        String password = getPassword(username);
 
+        return new GenericPrincipal(username, password, roles);
     }
 
 
@@ -556,14 +607,14 @@ public class JDBCRealm extends RealmBase {
      *
      * @param username The user name
      *
-     * @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) {
 
         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);
         }
 
         // Number of tries is the number of attempts to connect to the database
diff --git a/java/org/apache/catalina/realm/LocalStrings.properties 
b/java/org/apache/catalina/realm/LocalStrings.properties
index 38d68a1003..df97683981 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 b471d6b599..7b24439277 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