File tomcat-9.0-CVE-2021-30640.patch of Package tomcat.21644
Index: apache-tomcat-9.0.36-src/build.properties.default
===================================================================
--- apache-tomcat-9.0.36-src.orig/build.properties.default
+++ apache-tomcat-9.0.36-src/build.properties.default
@@ -248,6 +248,15 @@ objenesis.home=${base.path}/objenesis-${
objenesis.jar=${objenesis.home}/objenesis-${objenesis.version}.jar
objenesis.loc=${base-maven.loc}/org/objenesis/objenesis/${objenesis.version}/objenesis-${objenesis.version}.jar
+# ----- UnboundID, used by unit tests, version 5.1.4 or later -----
+unboundid.version=5.1.4
+unboundid.checksum.enabled=true
+unboundid.checksum.algorithm=SHA-512
+unboundid.checksum.value=04cf7f59eddebdd5b51e5be55021f9d9c667cca6101eac954e7a8d5b51f4c23372cd8f041640157f082435a166b75d85e79252b516130ede7d966dae6d3eae67
+unboundid.home=${base.path}/unboundid-${unboundid.version}
+unboundid.jar=${unboundid.home}/unboundid-ldapsdk-${unboundid.version}.jar
+unboundid.loc=${base-maven.loc}/com/unboundid/unboundid-ldapsdk/${unboundid.version}/unboundid-ldapsdk-${unboundid.version}.jar
+
# ----- Checkstyle, version 6.16 or later -----
checkstyle.version=8.22
checkstyle.checksum.enabled=true
Index: apache-tomcat-9.0.36-src/build.xml
===================================================================
--- apache-tomcat-9.0.36-src.orig/build.xml
+++ apache-tomcat-9.0.36-src/build.xml
@@ -2862,6 +2862,15 @@ skip.installer property in build.propert
<param name="checksum.value" value="${objenesis.checksum.value}"/>
</antcall>
+ <antcall target="downloadfile">
+ <param name="sourcefile" value="${unboundid.loc}"/>
+ <param name="destfile" value="${unboundid.jar}"/>
+ <param name="destdir" value="${unboundid.home}"/>
+ <param name="checksum.enabled" value="${unboundid.checksum.enabled}"/>
+ <param name="checksum.algorithm" value="${unboundid.checksum.algorithm}"/>
+ <param name="checksum.value" value="${unboundid.checksum.value}"/>
+ </antcall>
+
</target>
<target name="download-cobertura"
Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/realm/JNDIRealm.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/catalina/realm/JNDIRealm.java
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/realm/JNDIRealm.java
@@ -32,6 +32,8 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import javax.naming.AuthenticationException;
import javax.naming.CommunicationException;
@@ -61,6 +63,7 @@ import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import org.apache.catalina.LifecycleException;
+import org.apache.tomcat.util.collections.SynchronizedStack;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSName;
@@ -166,10 +169,6 @@ import org.ietf.jgss.GSSName;
* directory server itself.</li>
* </ul>
*
- * <p><strong>TODO</strong> - Support connection pooling (including message
- * format objects) so that <code>authenticate()</code> does not have to be
- * synchronized.</p>
- *
* <p><strong>WARNING</strong> - There is a reported bug against the Netscape
* provider code (com.netscape.jndi.ldap.LdapContextFactory) with respect to
* successfully authenticated a non-existing user. The
@@ -182,7 +181,6 @@ import org.ietf.jgss.GSSName;
*/
public class JNDIRealm extends RealmBase {
-
// ----------------------------------------------------- Instance Variables
/**
@@ -195,25 +193,16 @@ public class JNDIRealm extends RealmBase
*/
protected String connectionName = null;
-
/**
* The connection password for the server we will contact.
*/
protected String connectionPassword = null;
-
/**
* The connection URL for the server we will contact.
*/
protected String connectionURL = null;
-
- /**
- * The directory context linking us to our directory server.
- */
- protected DirContext context = null;
-
-
/**
* The JNDI context factory used to acquire our InitialContext. By
* default, assumes use of an LDAP server using the standard JNDI LDAP
@@ -221,7 +210,6 @@ public class JNDIRealm extends RealmBase
*/
protected String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
-
/**
* How aliases should be dereferenced during search operations.
*/
@@ -233,14 +221,12 @@ public class JNDIRealm extends RealmBase
*/
public static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
-
/**
* The protocol that will be used in the communication with the
* directory server.
*/
protected String protocol = null;
-
/**
* Should we ignore PartialResultExceptions when iterating over NamingEnumerations?
* Microsoft Active Directory often returns referrals, which lead
@@ -250,7 +236,6 @@ public class JNDIRealm extends RealmBase
*/
protected boolean adCompat = false;
-
/**
* How should we handle referrals? Microsoft Active Directory often returns
* referrals. If you need to follow them set referrals to "follow".
@@ -259,20 +244,17 @@ public class JNDIRealm extends RealmBase
*/
protected String referrals = null;
-
/**
* The base element for user searches.
*/
protected String userBase = "";
-
/**
* The message format used to search for a user, with "{0}" marking
* the spot where the username goes.
*/
protected String userSearch = null;
-
/**
* When searching for users, should the search be performed as the user
* currently being authenticated? If false, {@link #connectionName} and
@@ -281,20 +263,11 @@ public class JNDIRealm extends RealmBase
*/
private boolean userSearchAsUser = false;
-
- /**
- * The MessageFormat object associated with the current
- * <code>userSearch</code>.
- */
- protected MessageFormat userSearchFormat = null;
-
-
/**
* Should we search the entire subtree for matching users?
*/
protected boolean userSubtree = false;
-
/**
* The attribute name used to retrieve the user password.
*/
@@ -308,7 +281,6 @@ public class JNDIRealm extends RealmBase
*/
protected String userRoleAttribute = null;
-
/**
* A string of LDAP user patterns or paths, ":"-separated
* These will be used to form the distinguished name of a
@@ -319,7 +291,6 @@ public class JNDIRealm extends RealmBase
*/
protected String[] userPatternArray = null;
-
/**
* The message format used to form the distinguished name of a
* user, with "{0}" marking the spot where the specified username
@@ -327,46 +298,22 @@ public class JNDIRealm extends RealmBase
*/
protected String userPattern = null;
-
- /**
- * An array of MessageFormat objects associated with the current
- * <code>userPatternArray</code>.
- */
- protected MessageFormat[] userPatternFormatArray = null;
-
/**
* The base element for role searches.
*/
protected String roleBase = "";
-
- /**
- * The MessageFormat object associated with the current
- * <code>roleBase</code>.
- */
- protected MessageFormat roleBaseFormat = null;
-
-
- /**
- * The MessageFormat object associated with the current
- * <code>roleSearch</code>.
- */
- protected MessageFormat roleFormat = null;
-
-
/**
* The name of an attribute in the user's entry containing
* roles for that user
*/
protected String userRoleName = null;
-
/**
* The name of the attribute containing roles held elsewhere
*/
protected String roleName = null;
-
/**
* The message format used to select roles for a user, with "{0}" marking
* the spot where the distinguished name of the user goes. The "{1}"
@@ -374,7 +321,6 @@ public class JNDIRealm extends RealmBase
*/
protected String roleSearch = null;
-
/**
* Should we search the entire subtree for matching memberships?
*/
@@ -409,7 +355,6 @@ public class JNDIRealm extends RealmBase
*/
protected String commonRole = null;
-
/**
* The timeout, in milliseconds, to use when trying to create a connection
* to the directory. The default is 5000 (5 seconds).
@@ -434,14 +379,12 @@ public class JNDIRealm extends RealmBase
*/
protected int timeLimit = 0;
-
/**
* Should delegated credentials from the SPNEGO authenticator be used if
* available
*/
protected boolean useDelegatedCredential = true;
-
/**
* The QOP that should be used for the connection to the LDAP server after
* authentication. This value is used to set the
@@ -499,6 +442,33 @@ public class JNDIRealm extends RealmBase
private boolean forceDnHexEscape = false;
+ /**
+ * Non pooled connection to our directory server.
+ */
+ protected JNDIConnection singleConnection = new JNDIConnection();
+
+ /**
+ * The lock to ensure single connection thread safety.
+ */
+ protected final Lock singleConnectionLock = new ReentrantLock();
+
+ /**
+ * Connection pool.
+ */
+ protected SynchronizedStack<JNDIConnection> connectionPool = null;
+
+ /**
+ * The pool size limit. If 1, pooling is not used.
+ */
+ protected int connectionPoolSize = 1;
+
+ /**
+ * Whether to use context ClassLoader or default ClassLoader.
+ * True means use context ClassLoader, and True is the default
+ * value.
+ */
+ protected boolean useContextClassLoader = true;
+
// ------------------------------------------------------------- Properties
@@ -506,37 +476,35 @@ public class JNDIRealm extends RealmBase
return forceDnHexEscape;
}
+
public void setForceDnHexEscape(boolean forceDnHexEscape) {
this.forceDnHexEscape = forceDnHexEscape;
}
+
/**
* @return the type of authentication to use.
*/
public String getAuthentication() {
-
return authentication;
-
}
+
/**
* Set the type of authentication to use.
*
* @param authentication The authentication
*/
public void setAuthentication(String authentication) {
-
this.authentication = authentication;
-
}
+
/**
* @return the connection username for this Realm.
*/
public String getConnectionName() {
-
return this.connectionName;
-
}
@@ -546,9 +514,7 @@ public class JNDIRealm extends RealmBase
* @param connectionName The new connection username
*/
public void setConnectionName(String connectionName) {
-
this.connectionName = connectionName;
-
}
@@ -556,9 +522,7 @@ public class JNDIRealm extends RealmBase
* @return the connection password for this Realm.
*/
public String getConnectionPassword() {
-
return this.connectionPassword;
-
}
@@ -568,9 +532,7 @@ public class JNDIRealm extends RealmBase
* @param connectionPassword The new connection password
*/
public void setConnectionPassword(String connectionPassword) {
-
this.connectionPassword = connectionPassword;
-
}
@@ -578,9 +540,7 @@ public class JNDIRealm extends RealmBase
* @return the connection URL for this Realm.
*/
public String getConnectionURL() {
-
return this.connectionURL;
-
}
@@ -590,9 +550,7 @@ public class JNDIRealm extends RealmBase
* @param connectionURL The new connection URL
*/
public void setConnectionURL(String connectionURL) {
-
this.connectionURL = connectionURL;
-
}
@@ -600,9 +558,7 @@ public class JNDIRealm extends RealmBase
* @return the JNDI context factory for this Realm.
*/
public String getContextFactory() {
-
return this.contextFactory;
-
}
@@ -612,11 +568,10 @@ public class JNDIRealm extends RealmBase
* @param contextFactory The new context factory
*/
public void setContextFactory(String contextFactory) {
-
this.contextFactory = contextFactory;
-
}
+
/**
* @return the derefAliases setting to be used.
*/
@@ -624,33 +579,32 @@ public class JNDIRealm extends RealmBase
return derefAliases;
}
+
/**
* Set the value for derefAliases to be used when searching the directory.
*
* @param derefAliases New value of property derefAliases.
*/
public void setDerefAliases(java.lang.String derefAliases) {
- this.derefAliases = derefAliases;
+ this.derefAliases = derefAliases;
}
+
/**
* @return the protocol to be used.
*/
public String getProtocol() {
-
return protocol;
-
}
+
/**
* Set the protocol for this Realm.
*
* @param protocol The new protocol.
*/
public void setProtocol(String protocol) {
-
this.protocol = protocol;
-
}
@@ -694,9 +648,7 @@ public class JNDIRealm extends RealmBase
* @return the base element for user searches.
*/
public String getUserBase() {
-
return this.userBase;
-
}
@@ -706,9 +658,7 @@ public class JNDIRealm extends RealmBase
* @param userBase The new base element
*/
public void setUserBase(String userBase) {
-
this.userBase = userBase;
-
}
@@ -716,9 +666,7 @@ public class JNDIRealm extends RealmBase
* @return the message format pattern for selecting users in this Realm.
*/
public String getUserSearch() {
-
return this.userSearch;
-
}
@@ -728,13 +676,8 @@ public class JNDIRealm extends RealmBase
* @param userSearch The new user search pattern
*/
public void setUserSearch(String userSearch) {
-
this.userSearch = userSearch;
- if (userSearch == null)
- userSearchFormat = null;
- else
- userSearchFormat = new MessageFormat(userSearch);
-
+ singleConnection = create();
}
@@ -752,9 +695,7 @@ public class JNDIRealm extends RealmBase
* @return the "search subtree for users" flag.
*/
public boolean getUserSubtree() {
-
return this.userSubtree;
-
}
@@ -764,9 +705,7 @@ public class JNDIRealm extends RealmBase
* @param userSubtree The new search flag
*/
public void setUserSubtree(boolean userSubtree) {
-
this.userSubtree = userSubtree;
-
}
@@ -774,7 +713,6 @@ public class JNDIRealm extends RealmBase
* @return the user role name attribute name for this Realm.
*/
public String getUserRoleName() {
-
return userRoleName;
}
@@ -785,9 +723,7 @@ public class JNDIRealm extends RealmBase
* @param userRoleName The new userRole name attribute name
*/
public void setUserRoleName(String userRoleName) {
-
this.userRoleName = userRoleName;
-
}
@@ -795,9 +731,7 @@ public class JNDIRealm extends RealmBase
* @return the base element for role searches.
*/
public String getRoleBase() {
-
return this.roleBase;
-
}
@@ -807,13 +741,8 @@ public class JNDIRealm extends RealmBase
* @param roleBase The new base element
*/
public void setRoleBase(String roleBase) {
-
this.roleBase = roleBase;
- if (roleBase == null)
- roleBaseFormat = null;
- else
- roleBaseFormat = new MessageFormat(roleBase);
-
+ singleConnection = create();
}
@@ -821,9 +750,7 @@ public class JNDIRealm extends RealmBase
* @return the role name attribute name for this Realm.
*/
public String getRoleName() {
-
return this.roleName;
-
}
@@ -833,9 +760,7 @@ public class JNDIRealm extends RealmBase
* @param roleName The new role name attribute name
*/
public void setRoleName(String roleName) {
-
this.roleName = roleName;
-
}
@@ -843,9 +768,7 @@ public class JNDIRealm extends RealmBase
* @return the message format pattern for selecting roles in this Realm.
*/
public String getRoleSearch() {
-
return this.roleSearch;
-
}
@@ -855,13 +778,8 @@ public class JNDIRealm extends RealmBase
* @param roleSearch The new role search pattern
*/
public void setRoleSearch(String roleSearch) {
-
this.roleSearch = roleSearch;
- if (roleSearch == null)
- roleFormat = null;
- else
- roleFormat = new MessageFormat(roleSearch);
-
+ singleConnection = create();
}
@@ -879,9 +797,7 @@ public class JNDIRealm extends RealmBase
* @return the "search subtree for roles" flag.
*/
public boolean getRoleSubtree() {
-
return this.roleSubtree;
-
}
@@ -891,18 +807,15 @@ public class JNDIRealm extends RealmBase
* @param roleSubtree The new search flag
*/
public void setRoleSubtree(boolean roleSubtree) {
-
this.roleSubtree = roleSubtree;
-
}
+
/**
* @return the "The nested group search flag" flag.
*/
public boolean getRoleNested() {
-
return this.roleNested;
-
}
@@ -912,9 +825,7 @@ public class JNDIRealm extends RealmBase
* @param roleNested The nested group search flag
*/
public void setRoleNested(boolean roleNested) {
-
this.roleNested = roleNested;
-
}
@@ -922,9 +833,7 @@ public class JNDIRealm extends RealmBase
* @return the password attribute used to retrieve the user password.
*/
public String getUserPassword() {
-
return this.userPassword;
-
}
@@ -934,9 +843,7 @@ public class JNDIRealm extends RealmBase
* @param userPassword The new password attribute
*/
public void setUserPassword(String userPassword) {
-
this.userPassword = userPassword;
-
}
@@ -944,6 +851,7 @@ public class JNDIRealm extends RealmBase
return userRoleAttribute;
}
+
public void setUserRoleAttribute(String userRoleAttribute) {
this.userRoleAttribute = userRoleAttribute;
}
@@ -952,14 +860,10 @@ public class JNDIRealm extends RealmBase
* @return the message format pattern for selecting users in this Realm.
*/
public String getUserPattern() {
-
return this.userPattern;
-
}
-
-
/**
* Set the message format pattern for selecting users in this Realm.
* This may be one simple pattern, or multiple patterns to be tried,
@@ -971,18 +875,12 @@ public class JNDIRealm extends RealmBase
* @param userPattern The new user pattern
*/
public void setUserPattern(String userPattern) {
-
this.userPattern = userPattern;
- if (userPattern == null)
+ if (userPattern == null) {
userPatternArray = null;
- else {
+ } else {
userPatternArray = parseUserPatternString(userPattern);
- int len = this.userPatternArray.length;
- userPatternFormatArray = new MessageFormat[len];
- for (int i=0; i < len; i++) {
- userPatternFormatArray[i] =
- new MessageFormat(userPatternArray[i]);
- }
+ singleConnection = create();
}
}
@@ -993,9 +891,7 @@ public class JNDIRealm extends RealmBase
* @return Value of property alternateURL.
*/
public String getAlternateURL() {
-
return this.alternateURL;
-
}
@@ -1005,9 +901,7 @@ public class JNDIRealm extends RealmBase
* @param alternateURL New value of property alternateURL.
*/
public void setAlternateURL(String alternateURL) {
-
this.alternateURL = alternateURL;
-
}
@@ -1015,9 +909,7 @@ public class JNDIRealm extends RealmBase
* @return the common role
*/
public String getCommonRole() {
-
return commonRole;
-
}
@@ -1027,9 +919,7 @@ public class JNDIRealm extends RealmBase
* @param commonRole The common role
*/
public void setCommonRole(String commonRole) {
-
this.commonRole = commonRole;
-
}
@@ -1037,9 +927,7 @@ public class JNDIRealm extends RealmBase
* @return the connection timeout.
*/
public String getConnectionTimeout() {
-
return connectionTimeout;
-
}
@@ -1049,18 +937,15 @@ public class JNDIRealm extends RealmBase
* @param timeout The new connection timeout
*/
public void setConnectionTimeout(String timeout) {
-
this.connectionTimeout = timeout;
-
}
+
/**
* @return the read timeout.
*/
public String getReadTimeout() {
-
return readTimeout;
-
}
@@ -1070,9 +955,7 @@ public class JNDIRealm extends RealmBase
* @param timeout The new read timeout
*/
public void setReadTimeout(String timeout) {
-
this.readTimeout = timeout;
-
}
@@ -1100,6 +983,7 @@ public class JNDIRealm extends RealmBase
return useDelegatedCredential;
}
+
public void setUseDelegatedCredential(boolean useDelegatedCredential) {
this.useDelegatedCredential = useDelegatedCredential;
}
@@ -1109,6 +993,7 @@ public class JNDIRealm extends RealmBase
return spnegoDelegationQop;
}
+
public void setSpnegoDelegationQop(String spnegoDelegationQop) {
this.spnegoDelegationQop = spnegoDelegationQop;
}
@@ -1121,6 +1006,7 @@ public class JNDIRealm extends RealmBase
return useStartTls;
}
+
/**
* Flag whether StartTLS should be used when connecting to the ldap server
*
@@ -1132,6 +1018,7 @@ public class JNDIRealm extends RealmBase
this.useStartTls = useStartTls;
}
+
/**
* @return list of the allowed cipher suites when connections are made using
* StartTLS
@@ -1151,6 +1038,7 @@ public class JNDIRealm extends RealmBase
return this.cipherSuitesArray;
}
+
/**
* Set the allowed cipher suites when opening a connection using StartTLS.
* The cipher suites are expected as a comma separated list.
@@ -1162,6 +1050,25 @@ public class JNDIRealm extends RealmBase
this.cipherSuites = suites;
}
+
+ /**
+ * @return the connection pool size, or the default value 1 if pooling
+ * is disabled
+ */
+ public int getConnectionPoolSize() {
+ return connectionPoolSize;
+ }
+
+
+ /**
+ * Set the connection pool size
+ * @param connectionPoolSize the new pool size
+ */
+ public void setConnectionPoolSize(int connectionPoolSize) {
+ this.connectionPoolSize = connectionPoolSize;
+ }
+
+
/**
* @return name of the {@link HostnameVerifier} class used for connections
* using StartTLS, or the empty string, if the default verifier
@@ -1174,6 +1081,7 @@ public class JNDIRealm extends RealmBase
return this.hostnameVerifier.getClass().getCanonicalName();
}
+
/**
* Set the {@link HostnameVerifier} to be used when opening connections
* using StartTLS. An instance of the given class name will be constructed
@@ -1190,6 +1098,7 @@ public class JNDIRealm extends RealmBase
}
}
+
/**
* @return the {@link HostnameVerifier} to use for peer certificate
* verification when opening connections using StartTLS.
@@ -1198,8 +1107,7 @@ public class JNDIRealm extends RealmBase
if (this.hostnameVerifier != null) {
return this.hostnameVerifier;
}
- if (this.hostNameVerifierClassName == null
- || hostNameVerifierClassName.equals("")) {
+ if (this.hostNameVerifierClassName == null || hostNameVerifierClassName.equals("")) {
return null;
}
try {
@@ -1219,6 +1127,7 @@ public class JNDIRealm extends RealmBase
}
}
+
/**
* Set the {@link SSLSocketFactory} to be used when opening connections
* using StartTLS. An instance of the factory with the given name will be
@@ -1232,6 +1141,7 @@ public class JNDIRealm extends RealmBase
this.sslSocketFactoryClassName = factoryClassName;
}
+
/**
* Set the ssl protocol to be used for connections using StartTLS.
*
@@ -1242,6 +1152,7 @@ public class JNDIRealm extends RealmBase
this.sslProtocol = protocol;
}
+
/**
* @return the list of supported ssl protocols by the default
* {@link SSLContext}
@@ -1255,12 +1166,36 @@ public class JNDIRealm extends RealmBase
}
}
+
private Object constructInstance(String className)
throws ReflectiveOperationException {
Class<?> clazz = Class.forName(className);
return clazz.getConstructor().newInstance();
}
+
+ /**
+ * Sets whether to use the context or default ClassLoader.
+ * True means use context ClassLoader.
+ *
+ * @param useContext True means use context ClassLoader
+ */
+ public void setUseContextClassLoader(boolean useContext) {
+ useContextClassLoader = useContext;
+ }
+
+
+ /**
+ * Returns whether to use the context or default ClassLoader.
+ * True means to use the context ClassLoader.
+ *
+ * @return The value of useContextClassLoader
+ */
+ public boolean isUseContextClassLoader() {
+ return useContextClassLoader;
+ }
+
+
// ---------------------------------------------------------- Realm Methods
/**
@@ -1280,20 +1215,21 @@ public class JNDIRealm extends RealmBase
@Override
public Principal authenticate(String username, String credentials) {
- DirContext context = null;
+ JNDIConnection connection = null;
Principal principal = null;
try {
// Ensure that we have a directory context available
- context = open();
+ connection = get();
- // Occasionally the directory context will timeout. Try one more
- // time before giving up.
try {
+ // Occasionally the directory context will timeout. Try one more
+ // time before giving up.
+
// Authenticate the specified username if possible
- principal = authenticate(context, username, credentials);
+ principal = authenticate(connection, username, credentials);
} catch (NullPointerException | NamingException e) {
/*
@@ -1315,19 +1251,19 @@ public class JNDIRealm extends RealmBase
containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
// close the connection so we know it will be reopened.
- if (context != null)
- close(context);
+ close(connection);
+ closePooledConnections();
// open a new directory context.
- context = open();
+ connection = get();
// Try the authentication again.
- principal = authenticate(context, username, credentials);
+ principal = authenticate(connection, username, credentials);
}
// Release this context
- release(context);
+ release(connection);
// Return the authenticated Principal (if any)
return principal;
@@ -1337,31 +1273,24 @@ public class JNDIRealm extends RealmBase
// Log the problem for posterity
containerLog.error(sm.getString("jndiRealm.exception"), e);
- // Close the connection so that it gets reopened next time
- if (context != null)
- close(context);
+ // close the connection so we know it will be reopened.
+ close(connection);
+ closePooledConnections();
// Return "not authenticated" for this request
- if (containerLog.isDebugEnabled())
+ if (containerLog.isDebugEnabled()) {
containerLog.debug("Returning null principal.");
+ }
return null;
-
}
-
}
- // -------------------------------------------------------- Package Methods
-
-
- // ------------------------------------------------------ Protected Methods
-
-
/**
* Return the Principal associated with the specified username and
* credentials, if there is one; otherwise return <code>null</code>.
*
- * @param context The directory context
+ * @param connection The directory context
* @param username Username of the Principal to look up
* @param credentials Password or other credentials to use in
* authenticating this username
@@ -1369,30 +1298,26 @@ public class JNDIRealm extends RealmBase
*
* @exception NamingException if a directory server error occurs
*/
- public synchronized Principal authenticate(DirContext context,
- String username,
- String credentials)
- throws NamingException {
-
- if (username == null || username.equals("")
- || credentials == null || credentials.equals("")) {
- if (containerLog.isDebugEnabled())
+ public Principal authenticate(JNDIConnection connection, String username, String credentials)
+ throws NamingException {
+
+ if (username == null || username.equals("") || credentials == null || credentials.equals("")) {
+ if (containerLog.isDebugEnabled()) {
containerLog.debug("username null or empty: returning null principal.");
+ }
return null;
}
if (userPatternArray != null) {
- for (int curUserPattern = 0;
- curUserPattern < userPatternFormatArray.length;
- curUserPattern++) {
+ for (int curUserPattern = 0; curUserPattern < userPatternArray.length; curUserPattern++) {
// Retrieve user information
- User user = getUser(context, username, credentials, curUserPattern);
+ User user = getUser(connection, username, credentials, curUserPattern);
if (user != null) {
try {
// Check the user's credentials
- if (checkCredentials(context, user, credentials)) {
+ if (checkCredentials(connection.context, user, credentials)) {
// Search for additional roles
- List<String> roles = getRoles(context, user);
+ List<String> roles = getRoles(connection, user);
if (containerLog.isDebugEnabled()) {
containerLog.debug("Found roles: " + roles.toString());
}
@@ -1411,16 +1336,18 @@ public class JNDIRealm extends RealmBase
return null;
} else {
// Retrieve user information
- User user = getUser(context, username, credentials);
- if (user == null)
+ User user = getUser(connection, username, credentials);
+ if (user == null) {
return null;
+ }
// Check the user's credentials
- if (!checkCredentials(context, user, credentials))
+ if (!checkCredentials(connection.context, user, credentials)) {
return null;
+ }
// Search for additional roles
- List<String> roles = getRoles(context, user);
+ List<String> roles = getRoles(connection, user);
if (containerLog.isDebugEnabled()) {
containerLog.debug("Found roles: " + roles.toString());
}
@@ -1431,22 +1358,22 @@ public class JNDIRealm extends RealmBase
}
+ // ------------------------------------------------------ Protected Methods
+
/**
* Return a User object containing information about the user
* with the specified username, if found in the directory;
* otherwise return <code>null</code>.
*
- * @param context The directory context
+ * @param connection The directory context
* @param username Username to be looked up
* @return the User object
* @exception NamingException if a directory server error occurs
*
- * @see #getUser(DirContext, String, String, int)
+ * @see #getUser(JNDIConnection, String, String, int)
*/
- protected User getUser(DirContext context, String username)
- throws NamingException {
-
- return getUser(context, username, null, -1);
+ protected User getUser(JNDIConnection connection, String username) throws NamingException {
+ return getUser(connection, username, null, -1);
}
@@ -1455,18 +1382,16 @@ public class JNDIRealm extends RealmBase
* with the specified username, if found in the directory;
* otherwise return <code>null</code>.
*
- * @param context The directory context
+ * @param connection The directory context
* @param username Username to be looked up
* @param credentials User credentials (optional)
* @return the User object
* @exception NamingException if a directory server error occurs
*
- * @see #getUser(DirContext, String, String, int)
+ * @see #getUser(JNDIConnection, String, String, int)
*/
- protected User getUser(DirContext context, String username, String credentials)
- throws NamingException {
-
- return getUser(context, username, credentials, -1);
+ protected User getUser(JNDIConnection connection, String username, String credentials) throws NamingException {
+ return getUser(connection, username, credentials, -1);
}
@@ -1481,25 +1406,26 @@ public class JNDIRealm extends RealmBase
* configuration attribute is specified, all values of that
* attribute are retrieved from the directory entry.
*
- * @param context The directory context
+ * @param connection The directory context
* @param username Username to be looked up
* @param credentials User credentials (optional)
* @param curUserPattern Index into userPatternFormatArray
* @return the User object
* @exception NamingException if a directory server error occurs
*/
- protected User getUser(DirContext context, String username,
- String credentials, int curUserPattern)
- throws NamingException {
+ protected User getUser(JNDIConnection connection, String username, String credentials, int curUserPattern)
+ throws NamingException {
User user = null;
// Get attributes to retrieve from user entry
List<String> list = new ArrayList<>();
- if (userPassword != null)
+ if (userPassword != null) {
list.add(userPassword);
- if (userRoleName != null)
+ }
+ if (userRoleName != null) {
list.add(userRoleName);
+ }
if (userRoleAttribute != null) {
list.add(userRoleAttribute);
}
@@ -1507,8 +1433,8 @@ public class JNDIRealm extends RealmBase
list.toArray(attrIds);
// Use pattern or search for user entry
- if (userPatternFormatArray != null && curUserPattern >= 0) {
- user = getUserByPattern(context, username, credentials, attrIds, curUserPattern);
+ if (userPatternArray != null && curUserPattern >= 0) {
+ user = getUserByPattern(connection, username, credentials, attrIds, curUserPattern);
if (containerLog.isDebugEnabled()) {
containerLog.debug("Found user by pattern [" + user + "]");
}
@@ -1516,12 +1442,12 @@ public class JNDIRealm extends RealmBase
boolean thisUserSearchAsUser = isUserSearchAsUser();
try {
if (thisUserSearchAsUser) {
- userCredentialsAdd(context, username, credentials);
+ userCredentialsAdd(connection.context, username, credentials);
}
- user = getUserBySearch(context, username, attrIds);
+ user = getUserBySearch(connection, username, attrIds);
} finally {
if (thisUserSearchAsUser) {
- userCredentialsRemove(context);
+ userCredentialsRemove(connection.context);
}
}
if (containerLog.isDebugEnabled()) {
@@ -1531,8 +1457,7 @@ public class JNDIRealm extends RealmBase
if (userPassword == null && credentials != null && user != null) {
// The password is available. Insert it since it may be required for
// role searches.
- return new User(user.getUserName(), user.getDN(), credentials,
- user.getRoles(), user.getUserRoleId());
+ return new User(user.getUserName(), user.getDN(), credentials, user.getRoles(), user.getUserRoleId());
}
return user;
@@ -1552,11 +1477,8 @@ public class JNDIRealm extends RealmBase
* @return the User object
* @exception NamingException if a directory server error occurs
*/
- protected User getUserByPattern(DirContext context,
- String username,
- String[] attrIds,
- String dn)
- throws NamingException {
+ protected User getUserByPattern(DirContext context, String username, String[] attrIds, String dn)
+ throws NamingException {
// If no attributes are requested, no need to look for them
if (attrIds == null || attrIds.length == 0) {
@@ -1570,13 +1492,15 @@ public class JNDIRealm extends RealmBase
} catch (NameNotFoundException e) {
return null;
}
- if (attrs == null)
+ if (attrs == null) {
return null;
+ }
// Retrieve value of userPassword
String password = null;
- if (userPassword != null)
+ if (userPassword != null) {
password = getAttributeValue(userPassword, attrs);
+ }
String userRoleAttrValue = null;
if (userRoleAttribute != null) {
@@ -1585,8 +1509,9 @@ public class JNDIRealm extends RealmBase
// Retrieve values of userRoleName attribute
ArrayList<String> roles = null;
- if (userRoleName != null)
+ if (userRoleName != null) {
roles = addAttributeValues(userRoleName, attrs, roles);
+ }
return new User(username, dn, password, roles, userRoleAttrValue);
}
@@ -1598,7 +1523,7 @@ public class JNDIRealm extends RealmBase
* username and return a User object; otherwise return
* <code>null</code>.
*
- * @param context The directory context
+ * @param connection The directory context
* @param username The username
* @param credentials User credentials (optional)
* @param attrIds String[]containing names of attributes to
@@ -1607,34 +1532,34 @@ public class JNDIRealm extends RealmBase
* @exception NamingException if a directory server error occurs
* @see #getUserByPattern(DirContext, String, String[], String)
*/
- protected User getUserByPattern(DirContext context,
- String username,
- String credentials,
- String[] attrIds,
- int curUserPattern)
- throws NamingException {
+ protected User getUserByPattern(JNDIConnection connection, String username, String credentials, String[] attrIds,
+ int curUserPattern) throws NamingException {
User user = null;
- if (username == null || userPatternFormatArray[curUserPattern] == null)
+ if (username == null || userPatternArray[curUserPattern] == null) {
return null;
+ }
- // Form the dn from the user pattern
- String dn = userPatternFormatArray[curUserPattern].format(new String[] { username });
+ // Form the DistinguishedName from the user pattern.
+ // Escape in case username contains a character with special meaning in
+ // an attribute value.
+ String dn = connection.userPatternFormatArray[curUserPattern].format(
+ new String[] { doAttributeValueEscaping(username) });
try {
- user = getUserByPattern(context, username, attrIds, dn);
+ user = getUserByPattern(connection.context, username, attrIds, dn);
} catch (NameNotFoundException e) {
return null;
} catch (NamingException e) {
// If the getUserByPattern() call fails, try it again with the
// credentials of the user that we're searching for
try {
- userCredentialsAdd(context, dn, credentials);
+ userCredentialsAdd(connection.context, dn, credentials);
- user = getUserByPattern(context, username, attrIds, dn);
+ user = getUserByPattern(connection.context, username, attrIds, dn);
} finally {
- userCredentialsRemove(context);
+ userCredentialsRemove(connection.context);
}
}
return user;
@@ -1646,22 +1571,23 @@ public class JNDIRealm extends RealmBase
* information about the user with the specified username, if
* found in the directory; otherwise return <code>null</code>.
*
- * @param context The directory context
+ * @param connection The directory context
* @param username The username
* @param attrIds String[]containing names of attributes to retrieve.
* @return the User object
* @exception NamingException if a directory server error occurs
*/
- protected User getUserBySearch(DirContext context,
- String username,
- String[] attrIds)
- throws NamingException {
+ protected User getUserBySearch(JNDIConnection connection, String username, String[] attrIds)
+ throws NamingException {
- if (username == null || userSearchFormat == null)
+ if (username == null || connection.userSearchFormat == null) {
return null;
+ }
// Form the search filter
- String filter = userSearchFormat.format(new String[] { username });
+ // Escape in case username contains a character with special meaning in
+ // a search filter.
+ String filter = connection.userSearchFormat.format(new String[] { doFilterEscaping(username) });
// Set up the search controls
SearchControls constraints = new SearchControls();
@@ -1676,12 +1602,12 @@ public class JNDIRealm extends RealmBase
constraints.setTimeLimit(timeLimit);
// Specify the attributes to be retrieved
- if (attrIds == null)
+ if (attrIds == null) {
attrIds = new String[0];
+ }
constraints.setReturningAttributes(attrIds);
- NamingEnumeration<SearchResult> results =
- context.search(userBase, filter, constraints);
+ NamingEnumeration<SearchResult> results = connection.context.search(userBase, filter, constraints);
try {
// Fail if no entries found
@@ -1690,10 +1616,11 @@ public class JNDIRealm extends RealmBase
return null;
}
} catch (PartialResultException ex) {
- if (!adCompat)
+ if (!adCompat) {
throw ex;
- else
+ } else {
return null;
+ }
}
// Get result for the first entry found
@@ -1702,29 +1629,34 @@ public class JNDIRealm extends RealmBase
// Check no further entries were found
try {
if (results.hasMore()) {
- if(containerLog.isInfoEnabled())
- containerLog.info("username " + username + " has multiple entries");
+ if (containerLog.isInfoEnabled()) {
+ containerLog.info(sm.getString("jndiRealm.multipleEntries", username));
+ }
return null;
}
} catch (PartialResultException ex) {
- if (!adCompat)
+ if (!adCompat) {
throw ex;
+ }
}
- String dn = getDistinguishedName(context, userBase, result);
+ String dn = getDistinguishedName(connection.context, userBase, result);
- if (containerLog.isTraceEnabled())
+ if (containerLog.isTraceEnabled()) {
containerLog.trace(" entry found for " + username + " with dn " + dn);
+ }
// Get the entry's attributes
Attributes attrs = result.getAttributes();
- if (attrs == null)
+ if (attrs == null) {
return null;
+ }
// Retrieve value of userPassword
String password = null;
- if (userPassword != null)
+ if (userPassword != null) {
password = getAttributeValue(userPassword, attrs);
+ }
String userRoleAttrValue = null;
if (userRoleAttribute != null) {
@@ -1733,8 +1665,9 @@ public class JNDIRealm extends RealmBase
// Retrieve values of userRoleName attribute
ArrayList<String> roles = null;
- if (userRoleName != null)
+ if (userRoleName != null) {
roles = addAttributeValues(userRoleName, attrs, roles);
+ }
return new User(username, dn, password, roles, userRoleAttrValue);
} finally {
@@ -1760,30 +1693,25 @@ public class JNDIRealm extends RealmBase
* @return <code>true</code> if the credentials are validated
* @exception NamingException if a directory server error occurs
*/
- protected boolean checkCredentials(DirContext context,
- User user,
- String credentials)
- throws NamingException {
-
- boolean validated = false;
-
- if (userPassword == null) {
- validated = bindAsUser(context, user, credentials);
- } else {
- validated = compareCredentials(context, user, credentials);
- }
-
- if (containerLog.isTraceEnabled()) {
- if (validated) {
- containerLog.trace(sm.getString("jndiRealm.authenticateSuccess",
- user.getUserName()));
- } else {
- containerLog.trace(sm.getString("jndiRealm.authenticateFailure",
- user.getUserName()));
- }
- }
- return validated;
- }
+ protected boolean checkCredentials(DirContext context, User user, String credentials) throws NamingException {
+
+ boolean validated = false;
+
+ if (userPassword == null) {
+ validated = bindAsUser(context, user, credentials);
+ } else {
+ validated = compareCredentials(context, user, credentials);
+ }
+
+ if (containerLog.isTraceEnabled()) {
+ if (validated) {
+ containerLog.trace(sm.getString("jndiRealm.authenticateSuccess", user.getUserName()));
+ } else {
+ containerLog.trace(sm.getString("jndiRealm.authenticateFailure", user.getUserName()));
+ }
+ }
+ return validated;
+ }
/**
@@ -1796,17 +1724,15 @@ public class JNDIRealm extends RealmBase
* @return <code>true</code> if the credentials are validated
* @exception NamingException if a directory server error occurs
*/
- protected boolean compareCredentials(DirContext context,
- User info,
- String credentials)
- throws NamingException {
-
+ protected boolean compareCredentials(DirContext context, User info, String credentials) throws NamingException {
// Validate the credentials specified by the user
- if (containerLog.isTraceEnabled())
+ if (containerLog.isTraceEnabled()) {
containerLog.trace(" validating credentials");
+ }
- if (info == null || credentials == null)
+ if (info == null || credentials == null) {
return false;
+ }
String password = info.getPassword();
@@ -1823,21 +1749,22 @@ public class JNDIRealm extends RealmBase
* @return <code>true</code> if the credentials are validated
* @exception NamingException if a directory server error occurs
*/
- protected boolean bindAsUser(DirContext context,
- User user,
- String credentials)
- throws NamingException {
-
- if (credentials == null || user == null)
- return false;
-
- String dn = user.getDN();
- if (dn == null)
- return false;
-
- // Validate the credentials specified by the user
- if (containerLog.isTraceEnabled()) {
- containerLog.trace(" validating credentials by binding as the user");
+ protected boolean bindAsUser(DirContext context, User user, String credentials) throws NamingException {
+
+ if (credentials == null || user == null) {
+ return false;
+ }
+
+ // This is returned from the directory so will be attribute value
+ // escaped if required
+ String dn = user.getDN();
+ if (dn == null) {
+ return false;
+ }
+
+ // Validate the credentials specified by the user
+ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" validating credentials by binding as the user");
}
userCredentialsAdd(context, dn, credentials);
@@ -1862,74 +1789,79 @@ public class JNDIRealm extends RealmBase
return validated;
}
- /**
- * Configure the context to use the provided credentials for
- * authentication.
- *
- * @param context DirContext to configure
- * @param dn Distinguished name of user
- * @param credentials Credentials of user
- * @exception NamingException if a directory server error occurs
- */
- private void userCredentialsAdd(DirContext context, String dn,
- String credentials) throws NamingException {
+
+ /**
+ * Configure the context to use the provided credentials for
+ * authentication.
+ *
+ * @param context DirContext to configure
+ * @param dn Distinguished name of user
+ * @param credentials Credentials of user
+ * @exception NamingException if a directory server error occurs
+ */
+ private void userCredentialsAdd(DirContext context, String dn, String credentials) throws NamingException {
// Set up security environment to bind as the user
context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
}
+
/**
* Configure the context to use {@link #connectionName} and
* {@link #connectionPassword} if specified or an anonymous connection if
* those attributes are not specified.
*
- * @param context DirContext to configure
- * @exception NamingException if a directory server error occurs
+ * @param context DirContext to configure
+ * @exception NamingException if a directory server error occurs
*/
- private void userCredentialsRemove(DirContext context)
- throws NamingException {
+ private void userCredentialsRemove(DirContext context) throws NamingException {
// Restore the original security environment
if (connectionName != null) {
- context.addToEnvironment(Context.SECURITY_PRINCIPAL,
- connectionName);
+ context.addToEnvironment(Context.SECURITY_PRINCIPAL, connectionName);
} else {
context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
}
if (connectionPassword != null) {
- context.addToEnvironment(Context.SECURITY_CREDENTIALS,
- connectionPassword);
+ context.addToEnvironment(Context.SECURITY_CREDENTIALS, connectionPassword);
} else {
context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
}
}
+
/**
* Return a List of roles associated with the given User. Any
* roles present in the user's directory entry are supplemented by
* a directory search. If no roles are associated with this user,
* a zero-length List is returned.
*
- * @param context The directory context we are searching
+ * @param connection The directory context we are searching
* @param user The User to be checked
* @return the list of role names
* @exception NamingException if a directory server error occurs
*/
- protected List<String> getRoles(DirContext context, User user)
- throws NamingException {
+ protected List<String> getRoles(JNDIConnection connection, User user) throws NamingException {
- if (user == null)
+ if (user == null) {
return null;
+ }
+ // This is returned from the directory so will be attribute value
+ // escaped if required
String dn = user.getDN();
+ // This is the name the user provided to the authentication process so
+ // it will not be escaped
String username = user.getUserName();
String userRoleId = user.getUserRoleId();
- if (dn == null || username == null)
+ if (dn == null || username == null) {
return null;
+ }
- if (containerLog.isTraceEnabled())
+ if (containerLog.isTraceEnabled()) {
containerLog.trace(" getRoles(" + dn + ")");
+ }
// Start with roles retrieved from the user entry
List<String> list = new ArrayList<>();
@@ -1937,8 +1869,9 @@ public class JNDIRealm extends RealmBase
if (userRoles != null) {
list.addAll(userRoles);
}
- if (commonRole != null)
+ if (commonRole != null) {
list.add(commonRole);
+ }
if (containerLog.isTraceEnabled()) {
containerLog.trace(" Found " + list.size() + " user internal roles");
@@ -1946,54 +1879,66 @@ public class JNDIRealm extends RealmBase
}
// Are we configured to do role searches?
- if ((roleFormat == null) || (roleName == null))
+ if ((connection.roleFormat == null) || (roleName == null)) {
return list;
+ }
- // Set up parameters for an appropriate search
- String filter = roleFormat.format(new String[] { doRFC2254Encoding(dn), username, userRoleId });
+ // Set up parameters for an appropriate search filter
+ // The dn is already attribute value escaped but the others are not
+ // This is a filter so all input will require filter escaping
+ String filter = connection.roleFormat.format(new String[] {
+ doFilterEscaping(dn),
+ doFilterEscaping(doAttributeValueEscaping(username)),
+ doFilterEscaping(doAttributeValueEscaping(userRoleId)) });
SearchControls controls = new SearchControls();
- if (roleSubtree)
+ if (roleSubtree) {
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
- else
+ } else {
controls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
+ }
controls.setReturningAttributes(new String[] {roleName});
String base = null;
- if (roleBaseFormat != null) {
- NameParser np = context.getNameParser("");
+ if (connection.roleBaseFormat != null) {
+ NameParser np = connection.context.getNameParser("");
Name name = np.parse(dn);
String nameParts[] = new String[name.size()];
for (int i = 0; i < name.size(); i++) {
- nameParts[i] = name.get(i);
+ // May have been returned with \<char> escaping rather than
+ // \<hex><hex>. Make sure it is \<hex><hex>.
+ nameParts[i] = convertToHexEscape(name.get(i));
}
- base = roleBaseFormat.format(nameParts);
+ base = connection.roleBaseFormat.format(nameParts);
} else {
base = "";
}
// Perform the configured search and process the results
- NamingEnumeration<SearchResult> results = searchAsUser(context, user, base, filter, controls,
+ NamingEnumeration<SearchResult> results = searchAsUser(connection.context, user, base, filter, controls,
isRoleSearchAsUser());
- if (results == null)
+ if (results == null) {
return list; // Should never happen, but just in case ...
+ }
Map<String, String> groupMap = new HashMap<>();
try {
while (results.hasMore()) {
SearchResult result = results.next();
Attributes attrs = result.getAttributes();
- if (attrs == null)
+ if (attrs == null) {
continue;
- String dname = getDistinguishedName(context, roleBase, result);
+ }
+ String dname = getDistinguishedName(connection.context, base, result);
String name = getAttributeValue(roleName, attrs);
if (name != null && dname != null) {
groupMap.put(dname, name);
}
}
} catch (PartialResultException ex) {
- if (!adCompat)
+ if (!adCompat) {
throw ex;
+ }
} finally {
results.close();
}
@@ -2018,23 +1963,29 @@ public class JNDIRealm extends RealmBase
Map<String, String> newThisRound = new HashMap<>(); // Stores the groups we find in this iteration
for (Entry<String, String> group : newGroups.entrySet()) {
- filter = roleFormat.format(new String[] { doRFC2254Encoding(group.getKey()),
- group.getValue(), group.getValue() });
+ // Group key is already value escaped if required
+ // Group value is not value escaped
+ // Everything needs to be filter escaped
+ filter = connection.roleFormat.format(new String[] {
+ doFilterEscaping(group.getKey()),
+ doFilterEscaping(doAttributeValueEscaping(group.getValue())),
+ doFilterEscaping(doAttributeValueEscaping(group.getValue())) });
if (containerLog.isTraceEnabled()) {
- containerLog.trace("Perform a nested group search with base "+ roleBase + " and filter " + filter);
+ containerLog.trace("Perform a nested group search with base "+ roleBase +
+ " and filter " + filter);
}
- results = searchAsUser(context, user, roleBase, filter, controls,
- isRoleSearchAsUser());
+ results = searchAsUser(connection.context, user, base, filter, controls, isRoleSearchAsUser());
try {
while (results.hasMore()) {
SearchResult result = results.next();
Attributes attrs = result.getAttributes();
- if (attrs == null)
+ if (attrs == null) {
continue;
- String dname = getDistinguishedName(context, roleBase, result);
+ }
+ String dname = getDistinguishedName(connection.context, roleBase, result);
String name = getAttributeValue(roleName, attrs);
if (name != null && dname != null && !groupMap.keySet().contains(dname)) {
groupMap.put(dname, name);
@@ -2043,12 +1994,12 @@ public class JNDIRealm extends RealmBase
if (containerLog.isTraceEnabled()) {
containerLog.trace(" Found nested role " + dname + " -> " + name);
}
-
}
- }
+ }
} catch (PartialResultException ex) {
- if (!adCompat)
+ if (!adCompat) {
throw ex;
+ }
} finally {
results.close();
}
@@ -2062,6 +2013,7 @@ public class JNDIRealm extends RealmBase
return list;
}
+
/**
* Perform the search on the context as the {@code dn}, when
* {@code searchAsUser} is {@code true}, otherwise search the context with
@@ -2084,8 +2036,7 @@ public class JNDIRealm extends RealmBase
* @throws NamingException
* if a directory server error occurs
*/
- private NamingEnumeration<SearchResult> searchAsUser(DirContext context,
- User user, String base, String filter,
+ private NamingEnumeration<SearchResult> searchAsUser(DirContext context, User user, String base, String filter,
SearchControls controls, boolean searchAsUser) throws NamingException {
NamingEnumeration<SearchResult> results;
try {
@@ -2110,26 +2061,30 @@ public class JNDIRealm extends RealmBase
* @return the attribute value
* @exception NamingException if a directory server error occurs
*/
- private String getAttributeValue(String attrId, Attributes attrs)
- throws NamingException {
+ private String getAttributeValue(String attrId, Attributes attrs) throws NamingException {
- if (containerLog.isTraceEnabled())
+ if (containerLog.isTraceEnabled()) {
containerLog.trace(" retrieving attribute " + attrId);
+ }
- if (attrId == null || attrs == null)
+ if (attrId == null || attrs == null) {
return null;
+ }
Attribute attr = attrs.get(attrId);
- if (attr == null)
+ if (attr == null) {
return null;
+ }
Object value = attr.get();
- if (value == null)
+ if (value == null) {
return null;
+ }
String valueString = null;
- if (value instanceof byte[])
+ if (value instanceof byte[]) {
valueString = new String((byte[]) value);
- else
+ } else {
valueString = value.toString();
+ }
return valueString;
}
@@ -2144,20 +2099,22 @@ public class JNDIRealm extends RealmBase
* @return the list of attribute values
* @exception NamingException if a directory server error occurs
*/
- private ArrayList<String> addAttributeValues(String attrId,
- Attributes attrs,
- ArrayList<String> values)
- throws NamingException{
+ private ArrayList<String> addAttributeValues(String attrId, Attributes attrs, ArrayList<String> values)
+ throws NamingException {
- if (containerLog.isTraceEnabled())
+ if (containerLog.isTraceEnabled()) {
containerLog.trace(" retrieving values for attribute " + attrId);
- if (attrId == null || attrs == null)
+ }
+ if (attrId == null || attrs == null) {
return values;
- if (values == null)
+ }
+ if (values == null) {
values = new ArrayList<>();
+ }
Attribute attr = attrs.get(attrId);
- if (attr == null)
+ if (attr == null) {
return values;
+ }
NamingEnumeration<?> e = attr.getAll();
try {
while(e.hasMore()) {
@@ -2165,8 +2122,9 @@ public class JNDIRealm extends RealmBase
values.add(value);
}
} catch (PartialResultException ex) {
- if (!adCompat)
+ if (!adCompat) {
throw ex;
+ }
} finally {
e.close();
}
@@ -2177,13 +2135,17 @@ public class JNDIRealm extends RealmBase
/**
* Close any open connection to the directory server for this Realm.
*
- * @param context The directory context to be closed
+ * @param connection The directory context to be closed
*/
- protected void close(DirContext context) {
+ protected void close(JNDIConnection connection) {
// Do nothing if there is no opened connection
- if (context == null)
+ if (connection == null || connection.context == null) {
+ if (connectionPool == null) {
+ singleConnectionLock.unlock();
+ }
return;
+ }
// Close tls startResponse if used
if (tls != null) {
@@ -2195,14 +2157,34 @@ public class JNDIRealm extends RealmBase
}
// Close our opened connection
try {
- if (containerLog.isDebugEnabled())
+ if (containerLog.isDebugEnabled()) {
containerLog.debug("Closing directory context");
- context.close();
+ }
+ connection.context.close();
} catch (NamingException e) {
containerLog.error(sm.getString("jndiRealm.close"), e);
}
- this.context = null;
+ connection.context = null;
+ // The lock will be reacquired before any manipulation of the connection
+ if (connectionPool == null) {
+ singleConnectionLock.unlock();
+ }
+ }
+
+ /**
+ * Close all pooled connections.
+ */
+ protected void closePooledConnections() {
+ if (connectionPool != null) {
+ // Close any pooled connections as they might be bad as well
+ synchronized (connectionPool) {
+ JNDIConnection connection = null;
+ while ((connection = connectionPool.pop()) != null) {
+ close(connection);
+ }
+ }
+ }
}
@@ -2218,8 +2200,34 @@ public class JNDIRealm extends RealmBase
return null;
}
+ JNDIConnection connection = null;
+ User user = null;
try {
- User user = getUser(open(), username, null);
+ // Ensure that we have a directory context available
+ connection = get();
+
+ // Occasionally the directory context will timeout. Try one more
+ // time before giving up.
+ try {
+ user = getUser(connection, username, null);
+ } catch (NullPointerException | NamingException e) {
+ // log the exception so we know it's there.
+ containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
+
+ // close the connection so we know it will be reopened.
+ close(connection);
+ closePooledConnections();
+
+ // open a new directory context.
+ connection = get();
+
+ // Try the authentication again.
+ user = getUser(connection, username, null);
+ }
+
+ // Release this context
+ release(connection);
+
if (user == null) {
// User should be found...
return null;
@@ -2228,11 +2236,13 @@ public class JNDIRealm extends RealmBase
return user.getPassword();
}
} catch (NamingException e) {
+ // Log the problem for posterity
+ containerLog.error(sm.getString("jndiRealm.exception"), e);
return null;
}
-
}
+
/**
* Get the principal associated with the specified certificate.
* @param username The user name
@@ -2243,9 +2253,9 @@ public class JNDIRealm extends RealmBase
return getPrincipal(username, null);
}
+
@Override
- protected Principal getPrincipal(GSSName gssName,
- GSSCredential gssCredential) {
+ protected Principal getPrincipal(GSSName gssName, GSSCredential gssCredential) {
String name = gssName.toString();
if (isStripRealmForGss()) {
@@ -2259,120 +2269,102 @@ public class JNDIRealm extends RealmBase
return getPrincipal(name, gssCredential);
}
+
@Override
- protected Principal getPrincipal(String username,
- GSSCredential gssCredential) {
+ protected Principal getPrincipal(String username, GSSCredential gssCredential) {
- DirContext context = null;
+ JNDIConnection connection = null;
Principal principal = null;
try {
-
// Ensure that we have a directory context available
- context = open();
+ connection = get();
// Occasionally the directory context will timeout. Try one more
// time before giving up.
try {
// Authenticate the specified username if possible
- principal = getPrincipal(context, username, gssCredential);
+ principal = getPrincipal(connection, username, gssCredential);
} catch (CommunicationException | ServiceUnavailableException e) {
-
// log the exception so we know it's there.
containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
// close the connection so we know it will be reopened.
- if (context != null)
- close(context);
+ close(connection);
+ closePooledConnections();
// open a new directory context.
- context = open();
+ connection = get();
// Try the authentication again.
- principal = getPrincipal(context, username, gssCredential);
-
+ principal = getPrincipal(connection, username, gssCredential);
}
-
// Release this context
- release(context);
+ release(connection);
// Return the authenticated Principal (if any)
return principal;
} catch (NamingException e) {
-
// Log the problem for posterity
containerLog.error(sm.getString("jndiRealm.exception"), e);
- // Close the connection so that it gets reopened next time
- if (context != null)
- close(context);
-
// Return "not authenticated" for this request
return null;
-
}
-
-
}
/**
* Get the principal associated with the specified certificate.
- * @param context The directory context
+ * @param connection The directory context
* @param username The user name
* @param gssCredential The credentials
* @return the Principal associated with the given certificate.
* @exception NamingException if a directory server error occurs
*/
- protected synchronized Principal getPrincipal(DirContext context,
- String username, GSSCredential gssCredential)
- throws NamingException {
+ protected Principal getPrincipal(JNDIConnection connection, String username, GSSCredential gssCredential)
+ throws NamingException {
User user = null;
List<String> roles = null;
Hashtable<?, ?> preservedEnvironment = null;
+ DirContext context = connection.context;
try {
if (gssCredential != null && isUseDelegatedCredential()) {
// Preserve the current context environment parameters
preservedEnvironment = context.getEnvironment();
// Set up context
- context.addToEnvironment(
- Context.SECURITY_AUTHENTICATION, "GSSAPI");
- context.addToEnvironment(
- "javax.security.sasl.server.authentication", "true");
- context.addToEnvironment(
- "javax.security.sasl.qop", spnegoDelegationQop);
+ context.addToEnvironment(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+ context.addToEnvironment("javax.security.sasl.server.authentication", "true");
+ context.addToEnvironment("javax.security.sasl.qop", spnegoDelegationQop);
// Note: Subject already set in SPNEGO authenticator so no need
// for Subject.doAs() here
}
- user = getUser(context, username);
+ user = getUser(connection, username);
if (user != null) {
- roles = getRoles(context, user);
+ roles = getRoles(connection, user);
}
} finally {
if (gssCredential != null && isUseDelegatedCredential()) {
- restoreEnvironmentParameter(context,
- Context.SECURITY_AUTHENTICATION, preservedEnvironment);
- restoreEnvironmentParameter(context,
- "javax.security.sasl.server.authentication", preservedEnvironment);
- restoreEnvironmentParameter(context, "javax.security.sasl.qop",
- preservedEnvironment);
+ restoreEnvironmentParameter(context, Context.SECURITY_AUTHENTICATION, preservedEnvironment);
+ restoreEnvironmentParameter(context, "javax.security.sasl.server.authentication", preservedEnvironment);
+ restoreEnvironmentParameter(context, "javax.security.sasl.qop", preservedEnvironment);
}
}
if (user != null) {
- return new GenericPrincipal(user.getUserName(), user.getPassword(),
- roles, null, null, gssCredential);
+ return new GenericPrincipal(user.getUserName(), user.getPassword(), roles, null, null, gssCredential);
}
return null;
}
+
private void restoreEnvironmentParameter(DirContext context,
String parameterName, Hashtable<?, ?> preservedEnvironment) {
try {
@@ -2386,55 +2378,118 @@ public class JNDIRealm extends RealmBase
}
}
+
/**
* Open (if necessary) and return a connection to the configured
* directory server for this Realm.
- * @return the directory context
+ * @return the connection
* @exception NamingException if a directory server error occurs
*/
- protected DirContext open() throws NamingException {
+ protected JNDIConnection get() throws NamingException {
+ JNDIConnection connection = null;
+ // Use the pool if available, otherwise use the single connection
+ if (connectionPool != null) {
+ connection = connectionPool.pop();
+ if (connection == null) {
+ connection = create();
+ }
+ } else {
+ singleConnectionLock.lock();
+ connection = singleConnection;
+ }
+ if (connection.context == null) {
+ open(connection);
+ }
+ return connection;
+ }
- // Do nothing if there is a directory server connection already open
- if (context != null)
- return context;
- try {
+ /**
+ * Release our use of this connection so that it can be recycled.
+ *
+ * @param connection The directory context to release
+ */
+ protected void release(JNDIConnection connection) {
+ if (connectionPool != null) {
+ if (!connectionPool.push(connection)) {
+ // Any connection that doesn't end back to the pool must be closed
+ close(connection);
+ }
+ } else {
+ singleConnectionLock.unlock();
+ }
+ }
- // Ensure that we have a directory context available
- context = createDirContext(getDirectoryContextEnvironment());
+ /**
+ * Create a new connection wrapper, along with the
+ * message formats.
+ * @return the new connection
+ */
+ protected JNDIConnection create() {
+ JNDIConnection connection = new JNDIConnection();
+ if (userSearch != null) {
+ connection.userSearchFormat = new MessageFormat(userSearch);
+ }
+ if (userPattern != null) {
+ int len = userPatternArray.length;
+ connection.userPatternFormatArray = new MessageFormat[len];
+ for (int i = 0; i < len; i++) {
+ connection.userPatternFormatArray[i] = new MessageFormat(userPatternArray[i]);
+ }
+ }
+ if (roleBase != null) {
+ connection.roleBaseFormat = new MessageFormat(roleBase);
+ }
+ if (roleSearch != null) {
+ connection.roleFormat = new MessageFormat(roleSearch);
+ }
+ return connection;
+ }
+
+
+ /**
+ * Create a new connection to the directory server.
+ * @param connection The directory server connection wrapper
+ * @throws NamingException if a directory server error occurs
+ */
+ protected void open(JNDIConnection connection) throws NamingException {
+ ClassLoader ocl = null;
+ try {
+ if (!isUseContextClassLoader()) {
+ ocl = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
+ }
+ // Ensure that we have a directory context available
+ connection.context = createDirContext(getDirectoryContextEnvironment());
} catch (Exception e) {
if (alternateURL == null || alternateURL.length() == 0) {
// No alternate URL. Re-throw the exception.
throw e;
}
-
connectionAttempt = 1;
-
// log the first exception.
containerLog.info(sm.getString("jndiRealm.exception.retry"), e);
-
// Try connecting to the alternate url.
- context = createDirContext(getDirectoryContextEnvironment());
-
+ connection.context = createDirContext(getDirectoryContextEnvironment());
} finally {
-
// reset it in case the connection times out.
// the primary may come back.
connectionAttempt = 0;
-
+ if (!isUseContextClassLoader()) {
+ Thread.currentThread().setContextClassLoader(ocl);
+ }
}
-
- return context;
-
}
+
@Override
public boolean isAvailable() {
// Simple best effort check
- return (context != null);
+ return (connectionPool != null || singleConnection.context != null);
}
+
private DirContext createDirContext(Hashtable<String, String> env) throws NamingException {
if (useStartTls) {
return createTlsDirContext(env);
@@ -2443,13 +2498,13 @@ public class JNDIRealm extends RealmBase
}
}
+
private SSLSocketFactory getSSLSocketFactory() {
if (sslSocketFactory != null) {
return sslSocketFactory;
}
final SSLSocketFactory result;
- if (this.sslSocketFactoryClassName != null
- && !sslSocketFactoryClassName.trim().equals("")) {
+ if (this.sslSocketFactoryClassName != null && !sslSocketFactoryClassName.trim().equals("")) {
result = createSSLSocketFactoryFromClassName(this.sslSocketFactoryClassName);
} else {
result = createSSLContextFactoryFromProtocol(sslProtocol);
@@ -2458,6 +2513,7 @@ public class JNDIRealm extends RealmBase
return result;
}
+
private SSLSocketFactory createSSLSocketFactoryFromClassName(String className) {
try {
Object o = constructInstance(className);
@@ -2475,6 +2531,7 @@ public class JNDIRealm extends RealmBase
}
}
+
private SSLSocketFactory createSSLContextFactoryFromProtocol(String protocol) {
try {
SSLContext sslContext;
@@ -2486,14 +2543,13 @@ public class JNDIRealm extends RealmBase
}
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
- List<String> allowedProtocols = Arrays
- .asList(getSupportedSslProtocols());
- throw new IllegalArgumentException(
- sm.getString("jndiRealm.invalidSslProtocol", protocol,
- allowedProtocols), e);
+ List<String> allowedProtocols = Arrays.asList(getSupportedSslProtocols());
+ throw new IllegalArgumentException(sm.getString("jndiRealm.invalidSslProtocol",
+ protocol, allowedProtocols), e);
}
}
+
/**
* Create a tls enabled LdapContext and set the StartTlsResponse tls
* instance variable.
@@ -2504,12 +2560,10 @@ public class JNDIRealm extends RealmBase
* @throws NamingException
* when something goes wrong while negotiating the connection
*/
- private DirContext createTlsDirContext(
- Hashtable<String, String> env) throws NamingException {
+ private DirContext createTlsDirContext(Hashtable<String, String> env) throws NamingException {
Map<String, Object> savedEnv = new HashMap<>();
- for (String key : Arrays.asList(Context.SECURITY_AUTHENTICATION,
- Context.SECURITY_CREDENTIALS, Context.SECURITY_PRINCIPAL,
- Context.SECURITY_PROTOCOL)) {
+ for (String key : Arrays.asList(Context.SECURITY_AUTHENTICATION, Context.SECURITY_CREDENTIALS,
+ Context.SECURITY_PRINCIPAL, Context.SECURITY_PROTOCOL)) {
Object entry = env.remove(key);
if (entry != null) {
savedEnv.put(key, entry);
@@ -2518,8 +2572,7 @@ public class JNDIRealm extends RealmBase
LdapContext result = null;
try {
result = new InitialLdapContext(env, null);
- tls = (StartTlsResponse) result
- .extendedOperation(new StartTlsRequest());
+ tls = (StartTlsResponse) result.extendedOperation(new StartTlsRequest());
if (getHostnameVerifier() != null) {
tls.setHostnameVerifier(getHostnameVerifier());
}
@@ -2528,22 +2581,21 @@ public class JNDIRealm extends RealmBase
}
try {
SSLSession negotiate = tls.negotiate(getSSLSocketFactory());
- containerLog.debug(sm.getString("jndiRealm.negotiatedTls",
- negotiate.getProtocol()));
+ containerLog.debug(sm.getString("jndiRealm.negotiatedTls", negotiate.getProtocol()));
} catch (IOException e) {
throw new NamingException(e.getMessage());
}
} finally {
if (result != null) {
for (Map.Entry<String, Object> savedEntry : savedEnv.entrySet()) {
- result.addToEnvironment(savedEntry.getKey(),
- savedEntry.getValue());
+ result.addToEnvironment(savedEntry.getKey(), savedEntry.getValue());
}
}
}
return result;
}
+
/**
* Create our directory context configuration.
*
@@ -2554,52 +2606,48 @@ public class JNDIRealm extends RealmBase
Hashtable<String,String> env = new Hashtable<>();
// Configure our directory context environment.
- if (containerLog.isDebugEnabled() && connectionAttempt == 0)
+ if (containerLog.isDebugEnabled() && connectionAttempt == 0) {
containerLog.debug("Connecting to URL " + connectionURL);
- else if (containerLog.isDebugEnabled() && connectionAttempt > 0)
+ } else if (containerLog.isDebugEnabled() && connectionAttempt > 0) {
containerLog.debug("Connecting to URL " + alternateURL);
+ }
env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
- if (connectionName != null)
+ if (connectionName != null) {
env.put(Context.SECURITY_PRINCIPAL, connectionName);
- if (connectionPassword != null)
+ }
+ if (connectionPassword != null) {
env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
- if (connectionURL != null && connectionAttempt == 0)
+ }
+ if (connectionURL != null && connectionAttempt == 0) {
env.put(Context.PROVIDER_URL, connectionURL);
- else if (alternateURL != null && connectionAttempt > 0)
+ } else if (alternateURL != null && connectionAttempt > 0) {
env.put(Context.PROVIDER_URL, alternateURL);
- if (authentication != null)
+ }
+ if (authentication != null) {
env.put(Context.SECURITY_AUTHENTICATION, authentication);
- if (protocol != null)
+ }
+ if (protocol != null) {
env.put(Context.SECURITY_PROTOCOL, protocol);
- if (referrals != null)
+ }
+ if (referrals != null) {
env.put(Context.REFERRAL, referrals);
- if (derefAliases != null)
+ }
+ if (derefAliases != null) {
env.put(JNDIRealm.DEREF_ALIASES, derefAliases);
- if (connectionTimeout != null)
+ }
+ if (connectionTimeout != null) {
env.put("com.sun.jndi.ldap.connect.timeout", connectionTimeout);
- if (readTimeout != null)
+ }
+ if (readTimeout != null) {
env.put("com.sun.jndi.ldap.read.timeout", readTimeout);
+ }
return env;
-
- }
-
-
- /**
- * Release our use of this connection so that it can be recycled.
- *
- * @param context The directory context to release
- */
- protected void release(DirContext context) {
-
- // NO-OP since we are not pooling anything
-
}
// ------------------------------------------------------ Lifecycle Methods
-
/**
* Prepare for the beginning of active use of the public methods of this
* component and implement the requirements of
@@ -2611,15 +2659,22 @@ public class JNDIRealm extends RealmBase
@Override
protected void startInternal() throws LifecycleException {
+ if (connectionPoolSize != 1) {
+ connectionPool = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, connectionPoolSize);
+ }
+
// Check to see if the connection to the directory can be opened
+ JNDIConnection connection = null;
try {
- open();
+ connection = get();
} catch (NamingException e) {
// A failure here is not fatal as the directory may be unavailable
// now but available later. Unavailability of the directory is not
// fatal once the Realm has started so there is no reason for it to
// be fatal when the Realm starts.
containerLog.error(sm.getString("jndiRealm.open"), e);
+ } finally {
+ release(connection);
}
super.startInternal();
@@ -2634,16 +2689,20 @@ public class JNDIRealm extends RealmBase
* @exception LifecycleException if this component detects a fatal error
* that needs to be reported
*/
- @Override
+ @Override
protected void stopInternal() throws LifecycleException {
-
super.stopInternal();
-
// Close any open directory server connection
- close(this.context);
-
+ if (connectionPool == null) {
+ singleConnectionLock.lock();
+ close(singleConnection);
+ } else {
+ closePooledConnections();
+ connectionPool = null;
+ }
}
+
/**
* Given a string containing LDAP patterns for user locations (separated by
* parentheses in a pseudo-LDAP search string format -
@@ -2678,8 +2737,7 @@ public class JNDIRealm extends RealmBase
while (userPatternString.charAt(endParenLoc - 1) == '\\') {
endParenLoc = userPatternString.indexOf(')', endParenLoc+1);
}
- String nextPathPart = userPatternString.substring
- (startParenLoc+1, endParenLoc);
+ String nextPathPart = userPatternString.substring(startParenLoc+1, endParenLoc);
pathList.add(nextPathPart);
startingPoint = endParenLoc+1;
startParenLoc = userPatternString.indexOf('(', startingPoint);
@@ -2687,7 +2745,6 @@ public class JNDIRealm extends RealmBase
return pathList.toArray(new String[] {});
}
return null;
-
}
@@ -2702,10 +2759,36 @@ public class JNDIRealm extends RealmBase
* ) -> \29
* \ -> \5c
* \0 -> \00
+ *
* @param inString string to escape according to RFC 2254 guidelines
+ *
* @return String the escaped/encoded result
+ *
+ * @deprecated Will be removed in Tomcat 10.1.x onwards
*/
+ @Deprecated
protected String doRFC2254Encoding(String inString) {
+ return doFilterEscaping(inString);
+ }
+
+
+ /**
+ * Given an LDAP search string, returns the string with certain characters
+ * escaped according to RFC 2254 guidelines.
+ * The character mapping is as follows:
+ * char -> Replacement
+ * ---------------------------
+ * * -> \2a
+ * ( -> \28
+ * ) -> \29
+ * \ -> \5c
+ * \0 -> \00
+ *
+ * @param inString string to escape according to RFC 2254 guidelines
+ *
+ * @return String the escaped/encoded result
+ */
+ protected String doFilterEscaping(String inString) {
StringBuilder buf = new StringBuilder(inString.length());
for (int i = 0; i < inString.length(); i++) {
char c = inString.charAt(i);
@@ -2743,47 +2826,42 @@ public class JNDIRealm extends RealmBase
* @return String containing the distinguished name
* @exception NamingException if a directory server error occurs
*/
- protected String getDistinguishedName(DirContext context, String base,
- SearchResult result) throws NamingException {
+ protected String getDistinguishedName(DirContext context, String base, SearchResult result) throws NamingException {
// Get the entry's distinguished name. For relative results, this means
// we need to composite a name with the base name, the context name, and
// the result name. For non-relative names, use the returned name.
String resultName = result.getName();
Name name;
if (result.isRelative()) {
- if (containerLog.isTraceEnabled()) {
- containerLog.trace(" search returned relative name: " + resultName);
- }
- NameParser parser = context.getNameParser("");
- Name contextName = parser.parse(context.getNameInNamespace());
- Name baseName = parser.parse(base);
+ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" search returned relative name: " + resultName);
+ }
+ NameParser parser = context.getNameParser("");
+ Name contextName = parser.parse(context.getNameInNamespace());
+ Name baseName = parser.parse(base);
- // Bugzilla 32269
- Name entryName = parser.parse(new CompositeName(resultName).get(0));
+ // Bugzilla 32269
+ Name entryName = parser.parse(new CompositeName(resultName).get(0));
- name = contextName.addAll(baseName);
- name = name.addAll(entryName);
+ name = contextName.addAll(baseName);
+ name = name.addAll(entryName);
} else {
- if (containerLog.isTraceEnabled()) {
- containerLog.trace(" search returned absolute name: " + resultName);
- }
- try {
- // Normalize the name by running it through the name parser.
- NameParser parser = context.getNameParser("");
- URI userNameUri = new URI(resultName);
- String pathComponent = userNameUri.getPath();
- // Should not ever have an empty path component, since that is /{DN}
- if (pathComponent.length() < 1 ) {
- throw new InvalidNameException(
- "Search returned unparseable absolute name: " +
- resultName );
- }
- name = parser.parse(pathComponent.substring(1));
- } catch ( URISyntaxException e ) {
- throw new InvalidNameException(
- "Search returned unparseable absolute name: " +
- resultName );
- }
+ if (containerLog.isTraceEnabled()) {
+ containerLog.trace(" search returned absolute name: " + resultName);
+ }
+ try {
+ // Normalize the name by running it through the name parser.
+ NameParser parser = context.getNameParser("");
+ URI userNameUri = new URI(resultName);
+ String pathComponent = userNameUri.getPath();
+ // Should not ever have an empty path component, since that is /{DN}
+ if (pathComponent.length() < 1 ) {
+ throw new InvalidNameException("Search returned unparseable absolute name: " + resultName);
+ }
+ name = parser.parse(pathComponent.substring(1));
+ } catch ( URISyntaxException e ) {
+ throw new InvalidNameException("Search returned unparseable absolute name: " + resultName);
+ }
}
if (getForceDnHexEscape()) {
@@ -2795,6 +2873,78 @@ public class JNDIRealm extends RealmBase
}
+ /**
+ * Implements the necessary escaping to represent an attribute value as a
+ * String as per RFC 4514.
+ *
+ * @param input The original attribute value
+ * @return The string representation of the attribute value
+ */
+ protected String doAttributeValueEscaping(String input) {
+ int len = input.length();
+ StringBuilder result = new StringBuilder();
+
+ for (int i = 0; i < len; i++) {
+ char c = input.charAt(i);
+ switch (c) {
+ case ' ': {
+ if (i == 0 || i == (len -1)) {
+ result.append("\\20");
+ } else {
+ result.append(c);
+ }
+ break;
+ }
+ case '#': {
+ if (i == 0 ) {
+ result.append("\\23");
+ } else {
+ result.append(c);
+ }
+ break;
+ }
+ case '\"': {
+ result.append("\\22");
+ break;
+ }
+ case '+': {
+ result.append("\\2B");
+ break;
+ }
+ case ',': {
+ result.append("\\2C");
+ break;
+ }
+ case ';': {
+ result.append("\\3B");
+ break;
+ }
+ case '<': {
+ result.append("\\3C");
+ break;
+ }
+ case '>': {
+ result.append("\\3E");
+ break;
+ }
+ case '\\': {
+ result.append("\\5C");
+ break;
+ }
+ case '\u0000': {
+ result.append("\\00");
+ break;
+ }
+ default:
+ result.append(c);
+ }
+
+ }
+
+ return result.toString();
+ }
+
+
protected static String convertToHexEscape(String input) {
if (input.indexOf('\\') == -1) {
// No escaping present. Return original.
@@ -2871,7 +3021,7 @@ public class JNDIRealm extends RealmBase
}
- // ------------------------------------------------------ Private Classes
+ // ------------------------------------------------------ Protected Classes
/**
* A protected class representing a User
@@ -2884,9 +3034,7 @@ public class JNDIRealm extends RealmBase
private final List<String> roles;
private final String userRoleId;
-
- public User(String username, String dn, String password,
- List<String> roles, String userRoleId) {
+ public User(String username, String dn, String password, List<String> roles, String userRoleId) {
this.username = username;
this.dn = dn;
this.password = password;
@@ -2918,5 +3066,41 @@ public class JNDIRealm extends RealmBase
return userRoleId;
}
}
-}
+
+ /**
+ * Class holding the connection to the directory plus the associated
+ * non thread safe message formats.
+ */
+ protected static class JNDIConnection {
+
+ /**
+ * The MessageFormat object associated with the current
+ * <code>userSearch</code>.
+ */
+ protected MessageFormat userSearchFormat = null;
+
+ /**
+ * An array of MessageFormat objects associated with the current
+ * <code>userPatternArray</code>.
+ */
+ protected MessageFormat[] userPatternFormatArray = null;
+
+ /**
+ * The MessageFormat object associated with the current
+ * <code>roleBase</code>.
+ */
+ protected MessageFormat roleBaseFormat = null;
+
+ /**
+ * The MessageFormat object associated with the current
+ * <code>roleSearch</code>.
+ */
+ protected MessageFormat roleFormat = null;
+
+ /**
+ * The directory context linking us to our directory server.
+ */
+ protected DirContext context = null;
+ }
+}
Index: apache-tomcat-9.0.36-src/test/org/apache/catalina/realm/TestJNDIRealmIntegration.java
===================================================================
--- /dev/null
+++ apache-tomcat-9.0.36-src/test/org/apache/catalina/realm/TestJNDIRealmIntegration.java
@@ -0,0 +1,263 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.realm;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+import org.apache.juli.logging.LogFactory;
+
+import com.unboundid.ldap.listener.InMemoryDirectoryServer;
+import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
+import com.unboundid.ldap.sdk.AddRequest;
+import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPResult;
+import com.unboundid.ldap.sdk.ResultCode;
+
+@RunWith(Parameterized.class)
+public class TestJNDIRealmIntegration {
+
+ private static final String USER_PATTERN = "cn={0},ou=people,dc=example,dc=com";
+ private static final String USER_SEARCH = "cn={0}";
+ private static final String USER_BASE = "ou=people,dc=example,dc=com";
+ private static final String ROLE_SEARCH_A = "member={0}";
+ private static final String ROLE_SEARCH_B = "member=cn={1},ou=people,dc=example,dc=com";
+ private static final String ROLE_SEARCH_C = "member=cn={2},ou=people,dc=example,dc=com";
+ private static final String ROLE_BASE = "ou=people,dc=example,dc=com";
+
+ private static InMemoryDirectoryServer ldapServer;
+
+ @Parameterized.Parameters(name = "{index}: user[{5}], pwd[{6}]")
+ public static Collection<Object[]> parameters() {
+ List<Object[]> parameterSets = new ArrayList<>();
+ for (String roleSearch : new String[] { ROLE_SEARCH_A, ROLE_SEARCH_B, ROLE_SEARCH_C }) {
+ addUsers(USER_PATTERN, null, null, roleSearch, ROLE_BASE, parameterSets);
+ addUsers(null, USER_SEARCH, USER_BASE, roleSearch, ROLE_BASE, parameterSets);
+ }
+ parameterSets.add(new Object[] { "cn={0},ou=s\\;ub,ou=people,dc=example,dc=com", null, null, ROLE_SEARCH_A,
+ "{3},ou=people,dc=example,dc=com", "testsub", "test", new String[] {"TestGroup4"} });
+ return parameterSets;
+ }
+
+
+ private static void addUsers(String userPattern, String userSearch, String userBase, String roleSearch,
+ String roleBase, List<Object[]> parameterSets) {
+ parameterSets.add(new Object[] { userPattern, userSearch, userBase, roleSearch, roleBase,
+ "test", "test", new String[] {"TestGroup"} });
+ parameterSets.add(new Object[] { userPattern, userSearch, userBase, roleSearch, roleBase,
+ "t;", "test", new String[] {"TestGroup"} });
+ parameterSets.add(new Object[] { userPattern, userSearch, userBase, roleSearch, roleBase,
+ "t*", "test", new String[] {"TestGroup"} });
+ parameterSets.add(new Object[] { userPattern, userSearch, userBase, roleSearch, roleBase,
+ "t=", "test", new String[] {"Test<Group*2", "Test>Group*3"} });
+ }
+
+
+ @Parameter(0)
+ public String realmConfigUserPattern;
+ @Parameter(1)
+ public String realmConfigUserSearch;
+ @Parameter(2)
+ public String realmConfigUserBase;
+ @Parameter(3)
+ public String realmConfigRoleSearch;
+ @Parameter(4)
+ public String realmConfigRoleBase;
+ @Parameter(5)
+ public String username;
+ @Parameter(6)
+ public String credentials;
+ @Parameter(7)
+ public String[] groups;
+
+ @Test
+ public void testAuthenication() throws Exception {
+ JNDIRealm realm = new JNDIRealm();
+ realm.containerLog = LogFactory.getLog(TestJNDIRealmIntegration.class);
+
+ realm.setConnectionURL("ldap://localhost:" + ldapServer.getListenPort());
+ realm.setUserPattern(realmConfigUserPattern);
+ realm.setUserSearch(realmConfigUserSearch);
+ realm.setUserBase(realmConfigUserBase);
+ realm.setUserRoleAttribute("cn");
+ realm.setRoleName("cn");
+ realm.setRoleBase(realmConfigRoleBase);
+ realm.setRoleSearch(realmConfigRoleSearch);
+ realm.setRoleNested(true);
+
+ GenericPrincipal p = (GenericPrincipal) realm.authenticate(username, credentials);
+
+ Assert.assertNotNull(p);
+ Assert.assertEquals(username, p.name);
+
+ Set<String> actualGroups = new HashSet<>(Arrays.asList(p.getRoles()));
+ Set<String> expectedGroups = new HashSet<>(Arrays.asList(groups));
+
+ Assert.assertEquals(expectedGroups.size(), actualGroups.size());
+ Set<String> tmp = new HashSet<>();
+ tmp.addAll(expectedGroups);
+ tmp.removeAll(actualGroups);
+ Assert.assertEquals(0, tmp.size());
+ }
+
+
+ @BeforeClass
+ public static void createLDAP() throws Exception {
+ InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
+ config.addAdditionalBindCredentials("cn=admin", "password");
+ ldapServer = new InMemoryDirectoryServer(config);
+
+ ldapServer.startListening();
+
+ try (LDAPConnection conn = ldapServer.getConnection()) {
+
+ // Note: Only the DNs need attribute value escaping
+ AddRequest addBase = new AddRequest(
+ "dn: dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: domain",
+ "dc: example");
+ LDAPResult result = conn.processOperation(addBase);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addPeople = new AddRequest(
+ "dn: ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: organizationalUnit");
+ result = conn.processOperation(addPeople);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addUserTest = new AddRequest(
+ "dn: cn=test,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "cn: test",
+ "sn: Test",
+ "userPassword: test");
+ result = conn.processOperation(addUserTest);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addUserTestSemicolon = new AddRequest(
+ "dn: cn=t\\;,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "cn: t;",
+ "sn: Tsemicolon",
+ "userPassword: test");
+ result = conn.processOperation(addUserTestSemicolon);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addUserTestAsterisk = new AddRequest(
+ "dn: cn=t*,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "cn: t*",
+ "sn: Tasterisk",
+ "userPassword: test");
+ result = conn.processOperation(addUserTestAsterisk);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addUserTestEquals = new AddRequest(
+ "dn: cn=t\\=,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "cn: t=",
+ "sn: Tequals",
+ "userPassword: test");
+ result = conn.processOperation(addUserTestEquals);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addGroupTest = new AddRequest(
+ "dn: cn=TestGroup,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: groupOfNames",
+ "cn: TestGroup",
+ "member: cn=test,ou=people,dc=example,dc=com",
+ "member: cn=t\\;,ou=people,dc=example,dc=com",
+ "member: cn=t\\*,ou=people,dc=example,dc=com");
+ result = conn.processOperation(addGroupTest);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addGroupTest2 = new AddRequest(
+ "dn: cn=Test\\<Group*2,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: groupOfNames",
+ "cn: Test<Group*2",
+ "member: cn=t\\=,ou=people,dc=example,dc=com");
+ result = conn.processOperation(addGroupTest2);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addGroupTest3 = new AddRequest(
+ "dn: cn=Test\\>Group*3,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: groupOfNames",
+ "cn: Test>Group*3",
+ "member: cn=Test\\<Group*2,ou=people,dc=example,dc=com");
+ result = conn.processOperation(addGroupTest3);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addPeopleSub = new AddRequest(
+ "dn: ou=s\\;ub,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: organizationalUnit");
+ result = conn.processOperation(addPeopleSub);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addUserTestSub = new AddRequest(
+ "dn: cn=testsub,ou=s\\;ub,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: person",
+ "objectClass: organizationalPerson",
+ "cn: testsub",
+ "sn: Testsub",
+ "userPassword: test");
+ result = conn.processOperation(addUserTestSub);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+
+ AddRequest addGroupTest4 = new AddRequest(
+ "dn: cn=TestGroup4,ou=s\\;ub,ou=people,dc=example,dc=com",
+ "objectClass: top",
+ "objectClass: groupOfNames",
+ "cn: TestGroup4",
+ "member: cn=testsub,ou=s\\;ub,ou=people,dc=example,dc=com");
+ result = conn.processOperation(addGroupTest4);
+ Assert.assertEquals(ResultCode.SUCCESS, result.getResultCode());
+ }
+ }
+
+
+ @AfterClass
+ public static void destroyLDAP() {
+ ldapServer.shutDown(true);
+ }
+}
Index: apache-tomcat-9.0.36-src/webapps/docs/changelog.xml
===================================================================
--- apache-tomcat-9.0.36-src.orig/webapps/docs/changelog.xml
+++ apache-tomcat-9.0.36-src/webapps/docs/changelog.xml
@@ -85,6 +85,10 @@
<bug>64871</bug>: Log a warning if Tomcat blocks access to a file
because it uses symlinks. (markt)
</add>
+ <scode>
+ Expand coverage of unit tests for JNDIRealm using the UnboundID LDAP SDK
+ for Java. (markt)
+ </scode>
</changelog>
</subsection>
<subsection name="Coyote">
@@ -141,6 +145,10 @@
Make handling of OpenSSL read errors more robust when plain text data is
reported to be available to read. (markt)
</fix>
+ <fix>
+ <bug>65033</bug>: Fix JNDI realm error handling when connecting to a
+ failed server when pooling was not enabled. (remm)
+ </fix>
</changelog>
</subsection>
<subsection name="Other">
@@ -236,6 +244,11 @@
<add>
Improve validation of storage location when using FileStore. (markt)
</add>
+ <fix>
+ JNDIRealm connections should only be created with the container
+ classloader as the thread context classloader, just like for the JAAS
+ realm. (remm)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
@@ -3915,6 +3928,12 @@
subsequently incremented when using asynchronous processing over HTTP/2.
(markt)
</fix>
+ <fix>
+ Fix JNDIRealm pooling problems retrying on another bad connection. Any
+ retries are made on a new connection, just like with the single
+ connection scenario. Also remove all connections from the pool after
+ an error. (remm)
+ </fix>
</changelog>
</subsection>
<subsection name="Jasper">
@@ -9657,6 +9676,9 @@
Update the internal fork of Commons Codec to r1725746 (1.9 plus
additional fixes). (markt)
</update>
+ <update>
+ Add connection pooling to JNDI realm. (remm)
+ </update>
</changelog>
</subsection>
</section>
Index: apache-tomcat-9.0.36-src/java/org/apache/catalina/realm/LocalStrings.properties
===================================================================
--- apache-tomcat-9.0.36-src.orig/java/org/apache/catalina/realm/LocalStrings.properties
+++ apache-tomcat-9.0.36-src/java/org/apache/catalina/realm/LocalStrings.properties
@@ -77,6 +77,7 @@ jndiRealm.exception.retry=Exception perf
jndiRealm.invalidHostnameVerifier=[{0}] not a valid class name for a HostnameVerifier
jndiRealm.invalidSslProtocol=Given protocol [{0}] is invalid. It has to be one of [{1}]
jndiRealm.invalidSslSocketFactory=[{0}] not a valid class name for an SSLSocketFactory
+jndiRealm.multipleEntries=User name [{0}] has multiple entries
jndiRealm.negotiatedTls=Negotiated tls connection using protocol [{0}]
jndiRealm.open=Exception opening directory server connection
jndiRealm.tlsClose=Exception closing tls response
Index: apache-tomcat-9.0.36-src/test/org/apache/catalina/realm/TestJNDIRealm.java
===================================================================
--- apache-tomcat-9.0.36-src.orig/test/org/apache/catalina/realm/TestJNDIRealm.java
+++ apache-tomcat-9.0.36-src/test/org/apache/catalina/realm/TestJNDIRealm.java
@@ -109,6 +109,27 @@ public class TestJNDIRealm {
Assert.assertEquals(ha1(), ((GenericPrincipal)principal).getPassword());
}
+ volatile int count = 0;
+
+ @Test
+ public void testErrorRealm() throws Exception {
+ Context context = new TesterContext();
+ JNDIRealm realm = new JNDIRealm();
+ realm.setContainer(context);
+ realm.setUserSearch("");
+ // Connect to something that will fail
+ realm.setConnectionURL("ldap://127.0.0.1:12345");
+ realm.start();
+
+ count = 0;
+ (new Thread(() -> { realm.authenticate("foo", "bar"); count++; })).start();
+ (new Thread(() -> { realm.authenticate("foo", "bar"); count++; })).start();
+ (new Thread(() -> { realm.authenticate("foo", "bar"); count++; })).start();
+ Thread.sleep(10);
+
+ Assert.assertEquals(3, count);
+ }
+
private JNDIRealm buildRealm(String password) throws javax.naming.NamingException,
NoSuchFieldException, IllegalAccessException, LifecycleException {
@@ -117,9 +138,12 @@ public class TestJNDIRealm {
realm.setContainer(context);
realm.setUserSearch("");
- Field field = JNDIRealm.class.getDeclaredField("context");
+ // Usually everything is created in create() but that's not the case here
+ Field field = JNDIRealm.class.getDeclaredField("singleConnection");
field.setAccessible(true);
- field.set(realm, mockDirContext(mockSearchResults(password)));
+ Field field2 = JNDIRealm.JNDIConnection.class.getDeclaredField("context");
+ field2.setAccessible(true);
+ field2.set(field.get(realm), mockDirContext(mockSearchResults(password)));
realm.start();
Index: apache-tomcat-9.0.36-src/test/org/apache/catalina/realm/TestJNDIRealmAttributeValueEscape.java
===================================================================
--- /dev/null
+++ apache-tomcat-9.0.36-src/test/org/apache/catalina/realm/TestJNDIRealmAttributeValueEscape.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.catalina.realm;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class TestJNDIRealmAttributeValueEscape {
+
+ @Parameterized.Parameters(name = "{index}: in[{0}], out[{1}]")
+ public static Collection<Object[]> parameters() {
+ List<Object[]> parameterSets = new ArrayList<>();
+
+ // No escaping required
+ parameterSets.add(new String[] { "none", "none" });
+ // Simple cases (same order as RFC 4512 section 2)
+ // Each appearing at the beginning, middle and ent
+ parameterSets.add(new String[] { " test", "\\20test" });
+ parameterSets.add(new String[] { "te st", "te st" });
+ parameterSets.add(new String[] { "test ", "test\\20" });
+ parameterSets.add(new String[] { "#test", "\\23test" });
+ parameterSets.add(new String[] { "te#st", "te#st" });
+ parameterSets.add(new String[] { "test#", "test#" });
+ parameterSets.add(new String[] { "\"test", "\\22test" });
+ parameterSets.add(new String[] { "te\"st", "te\\22st" });
+ parameterSets.add(new String[] { "test\"", "test\\22" });
+ parameterSets.add(new String[] { "+test", "\\2Btest" });
+ parameterSets.add(new String[] { "te+st", "te\\2Bst" });
+ parameterSets.add(new String[] { "test+", "test\\2B" });
+ parameterSets.add(new String[] { ",test", "\\2Ctest" });
+ parameterSets.add(new String[] { "te,st", "te\\2Cst" });
+ parameterSets.add(new String[] { "test,", "test\\2C" });
+ parameterSets.add(new String[] { ";test", "\\3Btest" });
+ parameterSets.add(new String[] { "te;st", "te\\3Bst" });
+ parameterSets.add(new String[] { "test;", "test\\3B" });
+ parameterSets.add(new String[] { "<test", "\\3Ctest" });
+ parameterSets.add(new String[] { "te<st", "te\\3Cst" });
+ parameterSets.add(new String[] { "test<", "test\\3C" });
+ parameterSets.add(new String[] { ">test", "\\3Etest" });
+ parameterSets.add(new String[] { "te>st", "te\\3Est" });
+ parameterSets.add(new String[] { "test>", "test\\3E" });
+ parameterSets.add(new String[] { "\\test", "\\5Ctest" });
+ parameterSets.add(new String[] { "te\\st", "te\\5Cst" });
+ parameterSets.add(new String[] { "test\\", "test\\5C" });
+ parameterSets.add(new String[] { "\u0000test", "\\00test" });
+ parameterSets.add(new String[] { "te\u0000st", "te\\00st" });
+ parameterSets.add(new String[] { "test\u0000", "test\\00" });
+ return parameterSets;
+ }
+
+
+ @Parameter(0)
+ public String in;
+ @Parameter(1)
+ public String out;
+
+ private JNDIRealm realm = new JNDIRealm();
+
+ @Test
+ public void testConvertToHexEscape() throws Exception {
+ String result = realm.doAttributeValueEscaping(in);
+ Assert.assertEquals(out, result);
+ }
+}
Index: apache-tomcat-9.0.36-src/webapps/docs/config/realm.xml
===================================================================
--- apache-tomcat-9.0.36-src.orig/webapps/docs/config/realm.xml
+++ apache-tomcat-9.0.36-src/webapps/docs/config/realm.xml
@@ -433,6 +433,13 @@
property.</p>
</attribute>
+ <attribute name="connectionPoolSize" required="false">
+ <p>The JNDI realm can use a pool of connections to the directory server
+ to avoid blocking on a single connection. This attribute value is the
+ maximum pool size. If not specified, it will use <code>1</code>, which
+ means a single connection will be used.</p>
+ </attribute>
+
<attribute name="connectionTimeout" required="false">
<p>The timeout in milliseconds to use when establishing the connection
to the LDAP directory. If not specified, a value of 5000 (5 seconds) is
@@ -622,6 +629,13 @@
specified, the default value of <code>302</code> is used.</p>
</attribute>
+ <attribute name="useContextClassLoader" required="false">
+ <p>Instructs JNDIRealm to use the context class loader when opening the
+ connection for the JNDI provider. The default value is
+ <code>true</code>. To load classes using the container's classloader,
+ specify <code>false</code>.</p>
+ </attribute>
+
<attribute name="useDelegatedCredential" required="false">
<p>When the JNDIRealm is used with the SPNEGO authenticator, delegated
credentials for the user may be available. If such credentials are