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-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org