This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/master by this push: new c53dc95 Fix BZ 55559. Add local JNDI support to the UserDatabaseRealm c53dc95 is described below commit c53dc951c8b35590e436b2590431645dc33d9c0b 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 | 117 ++++++++++++++++----- .../apache/catalina/realm/mbeans-descriptors.xml | 4 + webapps/docs/changelog.xml | 11 ++ webapps/docs/config/realm.xml | 7 ++ 4 files changed, 115 insertions(+), 24 deletions(-) diff --git a/java/org/apache/catalina/realm/UserDatabaseRealm.java b/java/org/apache/catalina/realm/UserDatabaseRealm.java index 1eabc15..b8aac01 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) { @@ -190,30 +240,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(); @@ -238,6 +306,7 @@ public class UserDatabaseRealm extends RealmBase { database = null; } + private class UserDatabasePrincipal implements Principal { private final String name; private UserDatabasePrincipal(String name) { diff --git a/java/org/apache/catalina/realm/mbeans-descriptors.xml b/java/org/apache/catalina/realm/mbeans-descriptors.xml index b2aa2c9..9885e74 100644 --- a/java/org/apache/catalina/realm/mbeans-descriptors.xml +++ b/java/org/apache/catalina/realm/mbeans-descriptors.xml @@ -329,6 +329,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 4408522..1794c3f 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 10.0.0-M10 (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 10.0.0-M9 (markt)" rtext="release in progress"> <subsection name="Catalina"> diff --git a/webapps/docs/config/realm.xml b/webapps/docs/config/realm.xml index 444070a..d14fde1 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