You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2020/10/07 19:32:32 UTC

[tomcat] branch 9.0.x updated: Fix BZ 55559. Add local JNDI support to the UserDatabaseRealm

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

markt 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 9324206  Fix BZ 55559. Add local JNDI support to the UserDatabaseRealm
9324206 is described below

commit 932420649009920337aa8dfb0ebd35f555fa2458
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed Oct 7 20:21:56 2020 +0100

    Fix BZ 55559. Add local JNDI support to the UserDatabaseRealm
---
 .../apache/catalina/realm/UserDatabaseRealm.java   | 116 ++++++++++++++++-----
 .../apache/catalina/realm/mbeans-descriptors.xml   |   4 +
 webapps/docs/changelog.xml                         |  11 ++
 webapps/docs/config/realm.xml                      |   7 ++
 4 files changed, 114 insertions(+), 24 deletions(-)

diff --git a/java/org/apache/catalina/realm/UserDatabaseRealm.java b/java/org/apache/catalina/realm/UserDatabaseRealm.java
index 64957a9..bffe246 100644
--- a/java/org/apache/catalina/realm/UserDatabaseRealm.java
+++ b/java/org/apache/catalina/realm/UserDatabaseRealm.java
@@ -29,13 +29,14 @@ import org.apache.catalina.Role;
 import org.apache.catalina.User;
 import org.apache.catalina.UserDatabase;
 import org.apache.catalina.Wrapper;
+import org.apache.naming.ContextBindings;
 import org.apache.tomcat.util.ExceptionUtils;
 
 /**
  * Implementation of {@link org.apache.catalina.Realm} that is based on an
- * implementation of {@link UserDatabase} made available through the global JNDI
+ * implementation of {@link UserDatabase} made available through the JNDI
  * resources configured for this instance of Catalina. Set the
- * <code>resourceName</code> parameter to the global JNDI resources name for the
+ * <code>resourceName</code> parameter to the JNDI resources name for the
  * configured instance of <code>UserDatabase</code> that we should consult.
  *
  * @author Craig R. McClanahan
@@ -49,7 +50,8 @@ public class UserDatabaseRealm extends RealmBase {
      * The <code>UserDatabase</code> we will use to authenticate users and
      * identify associated roles.
      */
-    protected UserDatabase database = null;
+    protected volatile UserDatabase database = null;
+    private final Object databaseLock = new Object();
 
     /**
      * The global JNDI name of the <code>UserDatabase</code> resource we will be
@@ -57,6 +59,11 @@ public class UserDatabaseRealm extends RealmBase {
      */
     protected String resourceName = "UserDatabase";
 
+    /**
+     * Obtain the UserDatabase from the context (rather than global) JNDI.
+     */
+    private boolean localJndiResource = false;
+
 
     // ------------------------------------------------------------- Properties
 
@@ -80,6 +87,31 @@ public class UserDatabaseRealm extends RealmBase {
     }
 
 
+    /**
+     * Determines whether this Realm is configured to obtain the associated
+     * {@link UserDatabase} from the global JNDI context or a local (web
+     * application) JNDI context.
+     *
+     * @return {@code true} if a local JNDI context will be used, {@code false}
+     *         if the the global JNDI context will be used
+     */
+    public boolean getLocalJndiResource() {
+        return localJndiResource;
+    }
+
+
+    /**
+     * Configure whether this Realm obtains the associated {@link UserDatabase}
+     * from the global JNDI context or a local (web application) JNDI context.
+     *
+     * @param localJndiResource {@code true} to use a local JNDI context,
+     *                          {@code false} to use the global JNDI context
+     */
+    public void setLocalJndiResource(boolean localJndiResource) {
+        this.localJndiResource = localJndiResource;
+    }
+
+
     // --------------------------------------------------------- Public Methods
 
     /**
@@ -94,6 +126,12 @@ public class UserDatabaseRealm extends RealmBase {
      */
     @Override
     public boolean hasRole(Wrapper wrapper, Principal principal, String role) {
+
+        UserDatabase database = getUserDatabase();
+        if (database == null) {
+            return false;
+        }
+
         // Check for a role alias defined in a <security-role-ref> element
         if (wrapper != null) {
             String realRole = wrapper.findSecurityReference(role);
@@ -140,7 +178,10 @@ public class UserDatabaseRealm extends RealmBase {
 
     @Override
     public void backgroundProcess() {
-        database.backgroundProcess();
+        UserDatabase database = getUserDatabase();
+        if (database != null) {
+            database.backgroundProcess();
+        }
     }
 
 
@@ -149,6 +190,11 @@ public class UserDatabaseRealm extends RealmBase {
      */
     @Override
     protected String getPassword(String username) {
+        UserDatabase database = getUserDatabase();
+        if (database == null) {
+            return null;
+        }
+
         User user = database.findUser(username);
 
         if (user == null) {
@@ -164,6 +210,10 @@ public class UserDatabaseRealm extends RealmBase {
      */
     @Override
     protected Principal getPrincipal(String username) {
+        UserDatabase database = getUserDatabase();
+        if (database == null) {
+            return null;
+        }
 
         User user = database.findUser(username);
         if (user == null) {
@@ -189,30 +239,48 @@ public class UserDatabaseRealm extends RealmBase {
     }
 
 
+    /*
+     * Can't do this in startInternal() with local JNDI as the local JNDI
+     * context won't be initialised at this point.
+     */
+    private UserDatabase getUserDatabase() {
+        // DCL so database MUST be volatile
+        if (database == null) {
+            synchronized (databaseLock) {
+                if (database == null) {
+                    try {
+                        Context context = null;
+                        if (localJndiResource) {
+                            context = ContextBindings.getClassLoader();
+                            context = (Context) context.lookup("comp/env");
+                        } else {
+                            context = getServer().getGlobalNamingContext();
+                        }
+                        database = (UserDatabase) context.lookup(resourceName);
+                    } catch (Throwable e) {
+                        ExceptionUtils.handleThrowable(e);
+                        containerLog.error(sm.getString("userDatabaseRealm.lookup", resourceName), e);
+                        database = null;
+                    }
+                }
+            }
+        }
+        return database;
+    }
+
+
     // ------------------------------------------------------ Lifecycle Methods
 
-    /**
-     * Prepare for the beginning of active use of the public methods of this
-     * component and implement the requirements of
-     * {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
-     *
-     * @exception LifecycleException if this component detects a fatal error
-     *                that prevents this component from being used
-     */
     @Override
     protected void startInternal() throws LifecycleException {
-
-        try {
-            Context context = getServer().getGlobalNamingContext();
-            database = (UserDatabase) context.lookup(resourceName);
-        } catch (Throwable e) {
-            ExceptionUtils.handleThrowable(e);
-            containerLog.error(sm.getString("userDatabaseRealm.lookup", resourceName), e);
-            database = null;
-        }
-        if (database == null) {
-            throw new LifecycleException(
-                    sm.getString("userDatabaseRealm.noDatabase", resourceName));
+        // If the JNDI resource is global, check it here and fail the context
+        // start if it is not valid. Local JNDI resources can't be validated
+        // this way because the JNDI context isn't available at Realm start.
+        if (!localJndiResource) {
+            UserDatabase database = getUserDatabase();
+            if (database == null) {
+                throw new LifecycleException(sm.getString("userDatabaseRealm.noDatabase", resourceName));
+            }
         }
 
         super.startInternal();
diff --git a/java/org/apache/catalina/realm/mbeans-descriptors.xml b/java/org/apache/catalina/realm/mbeans-descriptors.xml
index a75a66d..031afce 100644
--- a/java/org/apache/catalina/realm/mbeans-descriptors.xml
+++ b/java/org/apache/catalina/realm/mbeans-descriptors.xml
@@ -400,6 +400,10 @@
           description="The global JNDI name of the UserDatabase resource to use"
                  type="java.lang.String"/>
 
+    <attribute   name="localJndiResource"
+          description="Configures if the UserDatabase JNDI definition is local to the webapp"
+                 type="boolean"/>
+
     <attribute   name="realmPath"
           description="The realm path"
                  type="java.lang.String"/>
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 8ad8194..4175940 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -45,6 +45,17 @@
   issues do not "pop up" wrt. others).
 -->
 <section name="Tomcat 9.0.40 (markt)" rtext="in development">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>55559</bug>: Add a new attribute, <code>localJndiResource</code>,
+        that allows a UserDatabaseRealm to obtain a UserDatabase instance from
+        the local (web application) JNDI context rather than the global JNDI
+        context. This option is only useful when the Realm is defined on the
+        Context. (markt)
+      </fix>
+    </changelog>
+  </subsection>
 </section>
 <section name="Tomcat 9.0.39 (markt)" rtext="release in progress">
   <subsection name="Catalina">
diff --git a/webapps/docs/config/realm.xml b/webapps/docs/config/realm.xml
index 08e4480..628b186 100644
--- a/webapps/docs/config/realm.xml
+++ b/webapps/docs/config/realm.xml
@@ -647,6 +647,13 @@
         one of those roles.</p>
       </attribute>
 
+      <attribute name="localJndiResource" required="false">
+        <p>When the realm is nested inside a Context element, this allows the
+        realm to use a UserDatabase defined for the Context rather than a global
+        UserDatabase. If not specified, the default is <code>false</code>: use a
+        global UserDatabase.</p>
+      </attribute>
+
       <attribute name="resourceName" required="true">
         <p>The name of the global <code>UserDatabase</code> resource
         that this realm will use for user, password and role information.</p>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org