/*
* 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.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
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;
import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.PartialResultException;
import javax.naming.ServiceUnavailableException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.StartTlsRequest;
import javax.naming.ldap.StartTlsResponse;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
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;
Implementation of Realm that works with a directory
server accessed via the Java Naming and Directory Interface (JNDI) APIs.
The following constraints are imposed on the data structure in the
underlying directory server:
- Each user that can be authenticated is represented by an individual
element in the top level
DirContext
that is accessed
via the connectionURL
property.
- If a socket connection cannot be made to the
connectURL
an attempt will be made to use the alternateURL
if it
exists.
- Each user element has a distinguished name that can be formed by
substituting the presented username into a pattern configured by the
userPattern
property.
- Alternatively, if the
userPattern
property is not
specified, a unique element can be located by searching the directory
context. In this case:
- The
userSearch
pattern specifies the search filter
after substitution of the username.
- The
userBase
property can be set to the element that
is the base of the subtree containing users. If not specified,
the search base is the top-level context.
- The
userSubtree
property can be set to
true
if you wish to search the entire subtree of the
directory context. The default value of false
requests a search of only the current level.
- The user may be authenticated by binding to the directory with the
username and password presented. This method is used when the
userPassword
property is not specified.
- The user may be authenticated by retrieving the value of an attribute
from the directory and comparing it explicitly with the value presented
by the user. This method is used when the
userPassword
property is specified, in which case:
- The element for this user must contain an attribute named by the
userPassword
property.
- The value of the user password attribute is either a cleartext
String, or the result of passing a cleartext String through the
RealmBase.digest()
method (using the standard digest
support included in RealmBase
).
- The user is considered to be authenticated if the presented
credentials (after being passed through
RealmBase.digest()
) are equal to the retrieved value
for the user password attribute.
- Each group of users that has been assigned a particular role may be
represented by an individual element in the top level
DirContext
that is accessed via the
connectionURL
property. This element has the following
characteristics:
- The set of all possible groups of interest can be selected by a
search pattern configured by the
roleSearch
property.
- The
roleSearch
pattern optionally includes pattern
replacements "{0}" for the distinguished name, and/or "{1}" for
the username, and/or "{2}" the value of an attribute from the
user's directory entry (the attribute is specified by the
userRoleAttribute
property), of the authenticated user
for which roles will be retrieved.
- The
roleBase
property can be set to the element that
is the base of the search for matching roles. If not specified,
the entire context will be searched.
- The
roleSubtree
property can be set to
true
if you wish to search the entire subtree of the
directory context. The default value of false
requests a search of only the current level.
- The element includes an attribute (whose name is configured by
the
roleName
property) containing the name of the
role represented by this element.
- In addition, roles may be represented by the values of an attribute
in the user's element whose name is configured by the
userRoleName
property.
- A default role can be assigned to each user that was successfully
authenticated by setting the
commonRole
property to the
name of this role. The role doesn't have to exist in the directory.
- If the directory server contains nested roles, you can search for them
by setting
roleNested
to true
.
The default value is false
, so role searches will not find
nested roles.
- Note that the standard
<security-role-ref>
element in
the web application deployment descriptor allows applications to refer
to roles programmatically by names other than those used in the
directory server itself.
WARNING - 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
report is here: https://bz.apache.org/bugzilla/show_bug.cgi?id=11210 .
With luck, Netscape has updated their provider code and this is not an
issue.
Author: John Holman, Craig R. McClanahan
/**
* <p>Implementation of <strong>Realm</strong> that works with a directory
* server accessed via the Java Naming and Directory Interface (JNDI) APIs.
* The following constraints are imposed on the data structure in the
* underlying directory server:</p>
* <ul>
*
* <li>Each user that can be authenticated is represented by an individual
* element in the top level <code>DirContext</code> that is accessed
* via the <code>connectionURL</code> property.</li>
*
* <li>If a socket connection cannot be made to the <code>connectURL</code>
* an attempt will be made to use the <code>alternateURL</code> if it
* exists.</li>
*
* <li>Each user element has a distinguished name that can be formed by
* substituting the presented username into a pattern configured by the
* <code>userPattern</code> property.</li>
*
* <li>Alternatively, if the <code>userPattern</code> property is not
* specified, a unique element can be located by searching the directory
* context. In this case:
* <ul>
* <li>The <code>userSearch</code> pattern specifies the search filter
* after substitution of the username.</li>
* <li>The <code>userBase</code> property can be set to the element that
* is the base of the subtree containing users. If not specified,
* the search base is the top-level context.</li>
* <li>The <code>userSubtree</code> property can be set to
* <code>true</code> if you wish to search the entire subtree of the
* directory context. The default value of <code>false</code>
* requests a search of only the current level.</li>
* </ul>
* </li>
*
* <li>The user may be authenticated by binding to the directory with the
* username and password presented. This method is used when the
* <code>userPassword</code> property is not specified.</li>
*
* <li>The user may be authenticated by retrieving the value of an attribute
* from the directory and comparing it explicitly with the value presented
* by the user. This method is used when the <code>userPassword</code>
* property is specified, in which case:
* <ul>
* <li>The element for this user must contain an attribute named by the
* <code>userPassword</code> property.
* <li>The value of the user password attribute is either a cleartext
* String, or the result of passing a cleartext String through the
* <code>RealmBase.digest()</code> method (using the standard digest
* support included in <code>RealmBase</code>).
* <li>The user is considered to be authenticated if the presented
* credentials (after being passed through
* <code>RealmBase.digest()</code>) are equal to the retrieved value
* for the user password attribute.</li>
* </ul></li>
*
* <li>Each group of users that has been assigned a particular role may be
* represented by an individual element in the top level
* <code>DirContext</code> that is accessed via the
* <code>connectionURL</code> property. This element has the following
* characteristics:
* <ul>
* <li>The set of all possible groups of interest can be selected by a
* search pattern configured by the <code>roleSearch</code>
* property.</li>
* <li>The <code>roleSearch</code> pattern optionally includes pattern
* replacements "{0}" for the distinguished name, and/or "{1}" for
* the username, and/or "{2}" the value of an attribute from the
* user's directory entry (the attribute is specified by the
* <code>userRoleAttribute</code> property), of the authenticated user
* for which roles will be retrieved.</li>
* <li>The <code>roleBase</code> property can be set to the element that
* is the base of the search for matching roles. If not specified,
* the entire context will be searched.</li>
* <li>The <code>roleSubtree</code> property can be set to
* <code>true</code> if you wish to search the entire subtree of the
* directory context. The default value of <code>false</code>
* requests a search of only the current level.</li>
* <li>The element includes an attribute (whose name is configured by
* the <code>roleName</code> property) containing the name of the
* role represented by this element.</li>
* </ul></li>
*
* <li>In addition, roles may be represented by the values of an attribute
* in the user's element whose name is configured by the
* <code>userRoleName</code> property.</li>
*
* <li>A default role can be assigned to each user that was successfully
* authenticated by setting the <code>commonRole</code> property to the
* name of this role. The role doesn't have to exist in the directory.</li>
*
* <li>If the directory server contains nested roles, you can search for them
* by setting <code>roleNested</code> to <code>true</code>.
* The default value is <code>false</code>, so role searches will not find
* nested roles.</li>
*
* <li>Note that the standard <code><security-role-ref></code> element in
* the web application deployment descriptor allows applications to refer
* to roles programmatically by names other than those used in the
* directory server itself.</li>
* </ul>
*
* <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
* report is here: https://bz.apache.org/bugzilla/show_bug.cgi?id=11210 .
* With luck, Netscape has updated their provider code and this is not an
* issue. </p>
*
* @author John Holman
* @author Craig R. McClanahan
*/
public class JNDIRealm extends RealmBase {
// ----------------------------------------------------- Instance Variables
The type of authentication to use
/**
* The type of authentication to use
*/
protected String authentication = null;
The connection username for the server we will contact.
/**
* The connection username for the server we will contact.
*/
protected String connectionName = null;
The connection password for the server we will contact.
/**
* The connection password for the server we will contact.
*/
protected String connectionPassword = null;
The connection URL for the server we will contact.
/**
* The connection URL for the server we will contact.
*/
protected String connectionURL = null;
The JNDI context factory used to acquire our InitialContext. By
default, assumes use of an LDAP server using the standard JNDI LDAP
provider.
/**
* The JNDI context factory used to acquire our InitialContext. By
* default, assumes use of an LDAP server using the standard JNDI LDAP
* provider.
*/
protected String contextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
How aliases should be dereferenced during search operations.
/**
* How aliases should be dereferenced during search operations.
*/
protected String derefAliases = null;
Constant that holds the name of the environment property for specifying
the manner in which aliases should be dereferenced.
/**
* Constant that holds the name of the environment property for specifying
* the manner in which aliases should be dereferenced.
*/
public static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
The protocol that will be used in the communication with the
directory server.
/**
* 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
to PartialResultExceptions. Unfortunately there's no stable way to detect,
if the Exceptions really come from an AD referral.
Set to true to ignore PartialResultExceptions.
/**
* Should we ignore PartialResultExceptions when iterating over NamingEnumerations?
* Microsoft Active Directory often returns referrals, which lead
* to PartialResultExceptions. Unfortunately there's no stable way to detect,
* if the Exceptions really come from an AD referral.
* Set to true to ignore PartialResultExceptions.
*/
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".
Caution: if your DNS is not part of AD, the LDAP client lib might try
to resolve your domain name in DNS to find another LDAP server.
/**
* How should we handle referrals? Microsoft Active Directory often returns
* referrals. If you need to follow them set referrals to "follow".
* Caution: if your DNS is not part of AD, the LDAP client lib might try
* to resolve your domain name in DNS to find another LDAP server.
*/
protected String referrals = null;
The base element for user searches.
/**
* 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.
/**
* 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, connectionName
and connectionPassword
will be used if specified, else an anonymous connection will be used. /**
* When searching for users, should the search be performed as the user
* currently being authenticated? If false, {@link #connectionName} and
* {@link #connectionPassword} will be used if specified, else an anonymous
* connection will be used.
*/
private boolean userSearchAsUser = false;
Should we search the entire subtree for matching users?
/**
* Should we search the entire subtree for matching users?
*/
protected boolean userSubtree = false;
The attribute name used to retrieve the user password.
/**
* The attribute name used to retrieve the user password.
*/
protected String userPassword = null;
The name of the attribute inside the users
directory entry where the value will be
taken to search for roles
This attribute is not used during a nested search
/**
* The name of the attribute inside the users
* directory entry where the value will be
* taken to search for roles
* This attribute is not used during a nested search
*/
protected String userRoleAttribute = null;
A string of LDAP user patterns or paths, ":"-separated
These will be used to form the distinguished name of a
user, with "{0}" marking the spot where the specified username
goes.
This is similar to userPattern, but allows for multiple searches
for a user.
/**
* A string of LDAP user patterns or paths, ":"-separated
* These will be used to form the distinguished name of a
* user, with "{0}" marking the spot where the specified username
* goes.
* This is similar to userPattern, but allows for multiple searches
* for a user.
*/
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
goes.
/**
* The message format used to form the distinguished name of a
* user, with "{0}" marking the spot where the specified username
* goes.
*/
protected String userPattern = null;
The base element for role searches.
/**
* The base element for role searches.
*/
protected String roleBase = "";
The name of an attribute in the user's entry containing
roles for that user
/**
* 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
/**
* 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}"
and "{2}" are described in the Configuration Reference.
/**
* 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}"
* and "{2}" are described in the Configuration Reference.
*/
protected String roleSearch = null;
Should we search the entire subtree for matching memberships?
/**
* Should we search the entire subtree for matching memberships?
*/
protected boolean roleSubtree = false;
Should we look for nested group in order to determine roles?
/**
* Should we look for nested group in order to determine roles?
*/
protected boolean roleNested = false;
When searching for user roles, should the search be performed as the user currently being authenticated? If false, connectionName
and connectionPassword
will be used if specified, else an anonymous connection will be used. /**
* When searching for user roles, should the search be performed as the user
* currently being authenticated? If false, {@link #connectionName} and
* {@link #connectionPassword} will be used if specified, else an anonymous
* connection will be used.
*/
protected boolean roleSearchAsUser = false;
An alternate URL, to which, we should connect if connectionURL fails.
/**
* An alternate URL, to which, we should connect if connectionURL fails.
*/
protected String alternateURL;
The number of connection attempts. If greater than zero we use the
alternate url.
/**
* The number of connection attempts. If greater than zero we use the
* alternate url.
*/
protected int connectionAttempt = 0;
Add this role to every authenticated user
/**
* Add this role to every authenticated user
*/
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).
/**
* The timeout, in milliseconds, to use when trying to create a connection
* to the directory. The default is 5000 (5 seconds).
*/
protected String connectionTimeout = "5000";
The timeout, in milliseconds, to use when trying to read from a connection
to the directory. The default is 5000 (5 seconds).
/**
* The timeout, in milliseconds, to use when trying to read from a connection
* to the directory. The default is 5000 (5 seconds).
*/
protected String readTimeout = "5000";
The sizeLimit (also known as the countLimit) to use when the realm is configured with userSearch
. Zero for no limit. /**
* The sizeLimit (also known as the countLimit) to use when the realm is
* configured with {@link #userSearch}. Zero for no limit.
*/
protected long sizeLimit = 0;
The timeLimit (in milliseconds) to use when the realm is configured with userSearch
. Zero for no limit. /**
* The timeLimit (in milliseconds) to use when the realm is configured with
* {@link #userSearch}. Zero for no limit.
*/
protected int timeLimit = 0;
Should delegated credentials from the SPNEGO authenticator be used if
available
/**
* 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
javax.security.sasl.qop
environment property for the LDAP
connection.
/**
* The QOP that should be used for the connection to the LDAP server after
* authentication. This value is used to set the
* <code>javax.security.sasl.qop</code> environment property for the LDAP
* connection.
*/
protected String spnegoDelegationQop = "auth-conf";
Whether to use TLS for connections
/**
* Whether to use TLS for connections
*/
private boolean useStartTls = false;
private StartTlsResponse tls = null;
The list of enabled cipher suites used for establishing tls connections.
null
means to use the default cipher suites.
/**
* The list of enabled cipher suites used for establishing tls connections.
* <code>null</code> means to use the default cipher suites.
*/
private String[] cipherSuitesArray = null;
Verifier for hostnames in a StartTLS secured connection. null
means to use the default verifier.
/**
* Verifier for hostnames in a StartTLS secured connection. <code>null</code>
* means to use the default verifier.
*/
private HostnameVerifier hostnameVerifier = null;
SSLSocketFactory
to use when connection with StartTLS enabled. /**
* {@link SSLSocketFactory} to use when connection with StartTLS enabled.
*/
private SSLSocketFactory sslSocketFactory = null;
Name of the class of the SSLSocketFactory
. null
means to use the default factory.
/**
* Name of the class of the {@link SSLSocketFactory}. <code>null</code>
* means to use the default factory.
*/
private String sslSocketFactoryClassName;
Comma separated list of cipher suites to use for StartTLS. If empty, the
default suites are used.
/**
* Comma separated list of cipher suites to use for StartTLS. If empty, the
* default suites are used.
*/
private String cipherSuites;
Name of the class of the HostnameVerifier
. null
means to use the default verifier.
/**
* Name of the class of the {@link HostnameVerifier}. <code>null</code>
* means to use the default verifier.
*/
private String hostNameVerifierClassName;
The ssl Protocol which will be used by StartTLS.
/**
* The ssl Protocol which will be used by StartTLS.
*/
private String sslProtocol;
private boolean forceDnHexEscape = false;
Non pooled connection to our directory server.
/**
* Non pooled connection to our directory server.
*/
protected JNDIConnection singleConnection = new JNDIConnection();
The lock to ensure single connection thread safety.
/**
* The lock to ensure single connection thread safety.
*/
protected final Lock singleConnectionLock = new ReentrantLock();
Connection pool.
/**
* Connection pool.
*/
protected SynchronizedStack<JNDIConnection> connectionPool = null;
The pool size limit. If 1, pooling is not used.
/**
* 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.
/**
* Whether to use context ClassLoader or default ClassLoader.
* True means use context ClassLoader, and True is the default
* value.
*/
protected boolean useContextClassLoader = true;
// ------------------------------------------------------------- Properties
public boolean getForceDnHexEscape() {
return forceDnHexEscape;
}
public void setForceDnHexEscape(boolean forceDnHexEscape) {
this.forceDnHexEscape = forceDnHexEscape;
}
Returns: the type of authentication to use.
/**
* @return the type of authentication to use.
*/
public String getAuthentication() {
return authentication;
}
Set the type of authentication to use.
Params: - authentication – The authentication
/**
* Set the type of authentication to use.
*
* @param authentication The authentication
*/
public void setAuthentication(String authentication) {
this.authentication = authentication;
}
Returns: the connection username for this Realm.
/**
* @return the connection username for this Realm.
*/
public String getConnectionName() {
return this.connectionName;
}
Set the connection username for this Realm.
Params: - connectionName – The new connection username
/**
* Set the connection username for this Realm.
*
* @param connectionName The new connection username
*/
public void setConnectionName(String connectionName) {
this.connectionName = connectionName;
}
Returns: the connection password for this Realm.
/**
* @return the connection password for this Realm.
*/
public String getConnectionPassword() {
return this.connectionPassword;
}
Set the connection password for this Realm.
Params: - connectionPassword – The new connection password
/**
* Set the connection password for this Realm.
*
* @param connectionPassword The new connection password
*/
public void setConnectionPassword(String connectionPassword) {
this.connectionPassword = connectionPassword;
}
Returns: the connection URL for this Realm.
/**
* @return the connection URL for this Realm.
*/
public String getConnectionURL() {
return this.connectionURL;
}
Set the connection URL for this Realm.
Params: - connectionURL – The new connection URL
/**
* Set the connection URL for this Realm.
*
* @param connectionURL The new connection URL
*/
public void setConnectionURL(String connectionURL) {
this.connectionURL = connectionURL;
}
Returns: the JNDI context factory for this Realm.
/**
* @return the JNDI context factory for this Realm.
*/
public String getContextFactory() {
return this.contextFactory;
}
Set the JNDI context factory for this Realm.
Params: - contextFactory – The new context factory
/**
* Set the JNDI context factory for this Realm.
*
* @param contextFactory The new context factory
*/
public void setContextFactory(String contextFactory) {
this.contextFactory = contextFactory;
}
Returns: the derefAliases setting to be used.
/**
* @return the derefAliases setting to be used.
*/
public java.lang.String getDerefAliases() {
return derefAliases;
}
Set the value for derefAliases to be used when searching the directory.
Params: - derefAliases – New value of property 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;
}
Returns: the protocol to be used.
/**
* @return the protocol to be used.
*/
public String getProtocol() {
return protocol;
}
Set the protocol for this Realm.
Params: - protocol – The new protocol.
/**
* Set the protocol for this Realm.
*
* @param protocol The new protocol.
*/
public void setProtocol(String protocol) {
this.protocol = protocol;
}
Returns: the current settings for handling PartialResultExceptions
/**
* @return the current settings for handling PartialResultExceptions
*/
public boolean getAdCompat () {
return adCompat;
}
How do we handle PartialResultExceptions?
True: ignore all PartialResultExceptions.
Params: - adCompat –
true
to ignore partial results
/**
* How do we handle PartialResultExceptions?
* True: ignore all PartialResultExceptions.
* @param adCompat <code>true</code> to ignore partial results
*/
public void setAdCompat (boolean adCompat) {
this.adCompat = adCompat;
}
Returns: the current settings for handling JNDI referrals.
/**
* @return the current settings for handling JNDI referrals.
*/
public String getReferrals () {
return referrals;
}
How do we handle JNDI referrals? ignore, follow, or throw
(see javax.naming.Context.REFERRAL for more information).
Params: - referrals – The referral handling
/**
* How do we handle JNDI referrals? ignore, follow, or throw
* (see javax.naming.Context.REFERRAL for more information).
* @param referrals The referral handling
*/
public void setReferrals (String referrals) {
this.referrals = referrals;
}
Returns: the base element for user searches.
/**
* @return the base element for user searches.
*/
public String getUserBase() {
return this.userBase;
}
Set the base element for user searches.
Params: - userBase – The new base element
/**
* Set the base element for user searches.
*
* @param userBase The new base element
*/
public void setUserBase(String userBase) {
this.userBase = userBase;
}
Returns: the message format pattern for selecting users in this Realm.
/**
* @return the message format pattern for selecting users in this Realm.
*/
public String getUserSearch() {
return this.userSearch;
}
Set the message format pattern for selecting users in this Realm.
Params: - userSearch – The new user search pattern
/**
* Set the message format pattern for selecting users in this Realm.
*
* @param userSearch The new user search pattern
*/
public void setUserSearch(String userSearch) {
this.userSearch = userSearch;
singleConnection = create();
}
public boolean isUserSearchAsUser() {
return userSearchAsUser;
}
public void setUserSearchAsUser(boolean userSearchAsUser) {
this.userSearchAsUser = userSearchAsUser;
}
Returns: the "search subtree for users" flag.
/**
* @return the "search subtree for users" flag.
*/
public boolean getUserSubtree() {
return this.userSubtree;
}
Set the "search subtree for users" flag.
Params: - userSubtree – The new search flag
/**
* Set the "search subtree for users" flag.
*
* @param userSubtree The new search flag
*/
public void setUserSubtree(boolean userSubtree) {
this.userSubtree = userSubtree;
}
Returns: the user role name attribute name for this Realm.
/**
* @return the user role name attribute name for this Realm.
*/
public String getUserRoleName() {
return userRoleName;
}
Set the user role name attribute name for this Realm.
Params: - userRoleName – The new userRole name attribute name
/**
* Set the user role name attribute name for this Realm.
*
* @param userRoleName The new userRole name attribute name
*/
public void setUserRoleName(String userRoleName) {
this.userRoleName = userRoleName;
}
Returns: the base element for role searches.
/**
* @return the base element for role searches.
*/
public String getRoleBase() {
return this.roleBase;
}
Set the base element for role searches.
Params: - roleBase – The new base element
/**
* Set the base element for role searches.
*
* @param roleBase The new base element
*/
public void setRoleBase(String roleBase) {
this.roleBase = roleBase;
singleConnection = create();
}
Returns: the role name attribute name for this Realm.
/**
* @return the role name attribute name for this Realm.
*/
public String getRoleName() {
return this.roleName;
}
Set the role name attribute name for this Realm.
Params: - roleName – The new role name attribute name
/**
* Set the role name attribute name for this Realm.
*
* @param roleName The new role name attribute name
*/
public void setRoleName(String roleName) {
this.roleName = roleName;
}
Returns: the message format pattern for selecting roles in this Realm.
/**
* @return the message format pattern for selecting roles in this Realm.
*/
public String getRoleSearch() {
return this.roleSearch;
}
Set the message format pattern for selecting roles in this Realm.
Params: - roleSearch – The new role search pattern
/**
* Set the message format pattern for selecting roles in this Realm.
*
* @param roleSearch The new role search pattern
*/
public void setRoleSearch(String roleSearch) {
this.roleSearch = roleSearch;
singleConnection = create();
}
public boolean isRoleSearchAsUser() {
return roleSearchAsUser;
}
public void setRoleSearchAsUser(boolean roleSearchAsUser) {
this.roleSearchAsUser = roleSearchAsUser;
}
Returns: the "search subtree for roles" flag.
/**
* @return the "search subtree for roles" flag.
*/
public boolean getRoleSubtree() {
return this.roleSubtree;
}
Set the "search subtree for roles" flag.
Params: - roleSubtree – The new search flag
/**
* Set the "search subtree for roles" flag.
*
* @param roleSubtree The new search flag
*/
public void setRoleSubtree(boolean roleSubtree) {
this.roleSubtree = roleSubtree;
}
Returns: the "The nested group search flag" flag.
/**
* @return the "The nested group search flag" flag.
*/
public boolean getRoleNested() {
return this.roleNested;
}
Set the "search subtree for roles" flag.
Params: - roleNested – The nested group search flag
/**
* Set the "search subtree for roles" flag.
*
* @param roleNested The nested group search flag
*/
public void setRoleNested(boolean roleNested) {
this.roleNested = roleNested;
}
Returns: the password attribute used to retrieve the user password.
/**
* @return the password attribute used to retrieve the user password.
*/
public String getUserPassword() {
return this.userPassword;
}
Set the password attribute used to retrieve the user password.
Params: - userPassword – The new password attribute
/**
* Set the password attribute used to retrieve the user password.
*
* @param userPassword The new password attribute
*/
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
public String getUserRoleAttribute() {
return userRoleAttribute;
}
public void setUserRoleAttribute(String userRoleAttribute) {
this.userRoleAttribute = userRoleAttribute;
}
Returns: the message format pattern for selecting users in this Realm.
/**
* @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,
separated by parentheses. (for example, either "cn={0}", or
"(cn={0})(cn={0},o=myorg)" Full LDAP search strings are also supported,
but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is
also valid. Complex search strings with &, etc are NOT supported.
Params: - userPattern – The new user pattern
/**
* Set the message format pattern for selecting users in this Realm.
* This may be one simple pattern, or multiple patterns to be tried,
* separated by parentheses. (for example, either "cn={0}", or
* "(cn={0})(cn={0},o=myorg)" Full LDAP search strings are also supported,
* but only the "OR", "|" syntax, so "(|(cn={0})(cn={0},o=myorg))" is
* also valid. Complex search strings with &, etc are NOT supported.
*
* @param userPattern The new user pattern
*/
public void setUserPattern(String userPattern) {
this.userPattern = userPattern;
if (userPattern == null) {
userPatternArray = null;
} else {
userPatternArray = parseUserPatternString(userPattern);
singleConnection = create();
}
}
Getter for property alternateURL.
Returns: Value of property alternateURL.
/**
* Getter for property alternateURL.
*
* @return Value of property alternateURL.
*/
public String getAlternateURL() {
return this.alternateURL;
}
Setter for property alternateURL.
Params: - alternateURL – New value of property alternateURL.
/**
* Setter for property alternateURL.
*
* @param alternateURL New value of property alternateURL.
*/
public void setAlternateURL(String alternateURL) {
this.alternateURL = alternateURL;
}
Returns: the common role
/**
* @return the common role
*/
public String getCommonRole() {
return commonRole;
}
Set the common role
Params: - commonRole – The common role
/**
* Set the common role
*
* @param commonRole The common role
*/
public void setCommonRole(String commonRole) {
this.commonRole = commonRole;
}
Returns: the connection timeout.
/**
* @return the connection timeout.
*/
public String getConnectionTimeout() {
return connectionTimeout;
}
Set the connection timeout.
Params: - timeout – The new connection timeout
/**
* Set the connection timeout.
*
* @param timeout The new connection timeout
*/
public void setConnectionTimeout(String timeout) {
this.connectionTimeout = timeout;
}
Returns: the read timeout.
/**
* @return the read timeout.
*/
public String getReadTimeout() {
return readTimeout;
}
Set the read timeout.
Params: - timeout – The new read timeout
/**
* Set the read timeout.
*
* @param timeout The new read timeout
*/
public void setReadTimeout(String timeout) {
this.readTimeout = timeout;
}
public long getSizeLimit() {
return sizeLimit;
}
public void setSizeLimit(long sizeLimit) {
this.sizeLimit = sizeLimit;
}
public int getTimeLimit() {
return timeLimit;
}
public void setTimeLimit(int timeLimit) {
this.timeLimit = timeLimit;
}
public boolean isUseDelegatedCredential() {
return useDelegatedCredential;
}
public void setUseDelegatedCredential(boolean useDelegatedCredential) {
this.useDelegatedCredential = useDelegatedCredential;
}
public String getSpnegoDelegationQop() {
return spnegoDelegationQop;
}
public void setSpnegoDelegationQop(String spnegoDelegationQop) {
this.spnegoDelegationQop = spnegoDelegationQop;
}
Returns: flag whether to use StartTLS for connections to the ldap server
/**
* @return flag whether to use StartTLS for connections to the ldap server
*/
public boolean getUseStartTls() {
return useStartTls;
}
Flag whether StartTLS should be used when connecting to the ldap server
Params: - useStartTls –
true
when StartTLS should be used. Default is false
.
/**
* Flag whether StartTLS should be used when connecting to the ldap server
*
* @param useStartTls
* {@code true} when StartTLS should be used. Default is
* {@code false}.
*/
public void setUseStartTls(boolean useStartTls) {
this.useStartTls = useStartTls;
}
Returns: list of the allowed cipher suites when connections are made using
StartTLS
/**
* @return list of the allowed cipher suites when connections are made using
* StartTLS
*/
private String[] getCipherSuitesArray() {
if (cipherSuites == null || cipherSuitesArray != null) {
return cipherSuitesArray;
}
if (this.cipherSuites.trim().isEmpty()) {
containerLog.warn(sm.getString("jndiRealm.emptyCipherSuites"));
this.cipherSuitesArray = null;
} else {
this.cipherSuitesArray = cipherSuites.trim().split("\\s*,\\s*");
containerLog.debug(sm.getString("jndiRealm.cipherSuites",
Arrays.toString(this.cipherSuitesArray)));
}
return this.cipherSuitesArray;
}
Set the allowed cipher suites when opening a connection using StartTLS.
The cipher suites are expected as a comma separated list.
Params: - suites –
comma separated list of allowed cipher suites
/**
* Set the allowed cipher suites when opening a connection using StartTLS.
* The cipher suites are expected as a comma separated list.
*
* @param suites
* comma separated list of allowed cipher suites
*/
public void setCipherSuites(String suites) {
this.cipherSuites = suites;
}
Returns: the connection pool size, or the default value 1 if pooling
is disabled
/**
* @return the connection pool size, or the default value 1 if pooling
* is disabled
*/
public int getConnectionPoolSize() {
return connectionPoolSize;
}
Set the connection pool size
Params: - connectionPoolSize – the new pool size
/**
* Set the connection pool size
* @param connectionPoolSize the new pool size
*/
public void setConnectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
}
Returns: name of the HostnameVerifier
class used for connections using StartTLS, or the empty string, if the default verifier should be used.
/**
* @return name of the {@link HostnameVerifier} class used for connections
* using StartTLS, or the empty string, if the default verifier
* should be used.
*/
public String getHostnameVerifierClassName() {
if (this.hostnameVerifier == null) {
return "";
}
return this.hostnameVerifier.getClass().getCanonicalName();
}
Set the HostnameVerifier
to be used when opening connections using StartTLS. An instance of the given class name will be constructed using the default constructor. Params: - verifierClassName – class name of the
HostnameVerifier
to be constructed
/**
* Set the {@link HostnameVerifier} to be used when opening connections
* using StartTLS. An instance of the given class name will be constructed
* using the default constructor.
*
* @param verifierClassName
* class name of the {@link HostnameVerifier} to be constructed
*/
public void setHostnameVerifierClassName(String verifierClassName) {
if (verifierClassName != null) {
this.hostNameVerifierClassName = verifierClassName.trim();
} else {
this.hostNameVerifierClassName = null;
}
}
Returns: the HostnameVerifier
to use for peer certificate verification when opening connections using StartTLS.
/**
* @return the {@link HostnameVerifier} to use for peer certificate
* verification when opening connections using StartTLS.
*/
public HostnameVerifier getHostnameVerifier() {
if (this.hostnameVerifier != null) {
return this.hostnameVerifier;
}
if (this.hostNameVerifierClassName == null
|| hostNameVerifierClassName.equals("")) {
return null;
}
try {
Object o = constructInstance(hostNameVerifierClassName);
if (o instanceof HostnameVerifier) {
this.hostnameVerifier = (HostnameVerifier) o;
return this.hostnameVerifier;
} else {
throw new IllegalArgumentException(sm.getString(
"jndiRealm.invalidHostnameVerifier",
hostNameVerifierClassName));
}
} catch (ReflectiveOperationException | SecurityException e) {
throw new IllegalArgumentException(sm.getString(
"jndiRealm.invalidHostnameVerifier",
hostNameVerifierClassName), e);
}
}
Set the SSLSocketFactory
to be used when opening connections using StartTLS. An instance of the factory with the given name will be created using the default constructor. The SSLSocketFactory can also be set using setSslProtocol(String)
. Params: - factoryClassName –
class name of the factory to be constructed
/**
* Set the {@link SSLSocketFactory} to be used when opening connections
* using StartTLS. An instance of the factory with the given name will be
* created using the default constructor. The SSLSocketFactory can also be
* set using {@link JNDIRealm#setSslProtocol(String) setSslProtocol(String)}.
*
* @param factoryClassName
* class name of the factory to be constructed
*/
public void setSslSocketFactoryClassName(String factoryClassName) {
this.sslSocketFactoryClassName = factoryClassName;
}
Set the ssl protocol to be used for connections using StartTLS.
Params: - protocol –
one of the allowed ssl protocol names
/**
* Set the ssl protocol to be used for connections using StartTLS.
*
* @param protocol
* one of the allowed ssl protocol names
*/
public void setSslProtocol(String protocol) {
this.sslProtocol = protocol;
}
Returns: the list of supported ssl protocols by the default SSLContext
/**
* @return the list of supported ssl protocols by the default
* {@link SSLContext}
*/
private String[] getSupportedSslProtocols() {
try {
SSLContext sslContext = SSLContext.getDefault();
return sslContext.getSupportedSSLParameters().getProtocols();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(sm.getString("jndiRealm.exception"), e);
}
}
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.
Params: - useContext – True means use context ClassLoader
/**
* 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.
Returns: The value of useContextClassLoader
/**
* 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
Return the Principal associated with the specified username and
credentials, if there is one; otherwise return null
.
If there are any errors with the JDBC connection, executing
the query or anything we return null (don't authenticate). This
event is also logged, and the connection will be closed so that
a subsequent request will automatically re-open it.
Params: - username – Username of the Principal to look up
- credentials – Password or other credentials to use in
authenticating this username
Returns: the associated principal, or null
if there is none.
/**
* Return the Principal associated with the specified username and
* credentials, if there is one; otherwise return <code>null</code>.
*
* If there are any errors with the JDBC connection, executing
* the query or anything we return null (don't authenticate). This
* event is also logged, and the connection will be closed so that
* a subsequent request will automatically re-open it.
*
* @param username Username of the Principal to look up
* @param credentials Password or other credentials to use in
* authenticating this username
* @return the associated principal, or <code>null</code> if there is none.
*/
@Override
public Principal authenticate(String username, String credentials) {
JNDIConnection connection = null;
Principal principal = null;
try {
// Ensure that we have a directory context available
connection = get();
// Occasionally the directory context will timeout. Try one more
// time before giving up.
try {
// Authenticate the specified username if possible
principal = authenticate(connection, username, credentials);
} catch (NullPointerException | NamingException e) {
/*
* BZ 61313
* NamingException may or may not indicate an error that is
* recoverable via fail over. Therefore a decision needs to be
* made whether to fail over or not. Generally, attempting to
* fail over when it is not appropriate is better than not
* failing over when it is appropriate so the code always
* attempts to fail over for NamingExceptions.
*/
/*
* BZ 42449
* Catch NPE - Kludge Sun's LDAP provider with broken SSL.
*/
// 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.
principal = authenticate(connection, username, credentials);
}
// Release this 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);
// Return "not authenticated" for this request
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 null
.
Params: - connection – The directory context
- username – Username of the Principal to look up
- credentials – Password or other credentials to use in
authenticating this username
Throws: - NamingException – if a directory server error occurs
Returns: the associated principal, or null
if there is none.
/**
* Return the Principal associated with the specified username and
* credentials, if there is one; otherwise return <code>null</code>.
*
* @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
* @return the associated principal, or <code>null</code> if there is none.
*
* @exception NamingException if a directory server error occurs
*/
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 < userPatternArray.length;
curUserPattern++) {
// Retrieve user information
User user = getUser(connection, username, credentials, curUserPattern);
if (user != null) {
try {
// Check the user's credentials
if (checkCredentials(connection.context, user, credentials)) {
// Search for additional roles
List<String> roles = getRoles(connection, user);
if (containerLog.isDebugEnabled()) {
containerLog.debug("Found roles: " + roles.toString());
}
return new GenericPrincipal(username, roles);
}
} catch (InvalidNameException ine) {
// Log the problem for posterity
containerLog.warn(sm.getString("jndiRealm.exception"), ine);
// ignore; this is probably due to a name not fitting
// the search path format exactly, as in a fully-
// qualified name being munged into a search path
// that already contains cn= or vice-versa
}
}
}
return null;
} else {
// Retrieve user information
User user = getUser(connection, username, credentials);
if (user == null)
return null;
// Check the user's credentials
if (!checkCredentials(connection.context, user, credentials))
return null;
// Search for additional roles
List<String> roles = getRoles(connection, user);
if (containerLog.isDebugEnabled()) {
containerLog.debug("Found roles: " + roles.toString());
}
// Create and return a suitable Principal for this user
return new GenericPrincipal(username, roles);
}
}
Return a User object containing information about the user
with the specified username, if found in the directory;
otherwise return null
.
Params: - connection – The directory context
- username – Username to be looked up
Throws: - NamingException – if a directory server error occurs
See Also: Returns: the User object
/**
* Return a User object containing information about the user
* with the specified username, if found in the directory;
* otherwise return <code>null</code>.
*
* @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(JNDIConnection, String, String, int)
*/
protected User getUser(JNDIConnection connection, String username)
throws NamingException {
return getUser(connection, username, null, -1);
}
Return a User object containing information about the user
with the specified username, if found in the directory;
otherwise return null
.
Params: - connection – The directory context
- username – Username to be looked up
- credentials – User credentials (optional)
Throws: - NamingException – if a directory server error occurs
See Also: Returns: the User object
/**
* Return a User object containing information about the user
* with the specified username, if found in the directory;
* otherwise return <code>null</code>.
*
* @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(JNDIConnection, String, String, int)
*/
protected User getUser(JNDIConnection connection, String username, String credentials)
throws NamingException {
return getUser(connection, username, credentials, -1);
}
Return a User object containing information about the user
with the specified username, if found in the directory;
otherwise return null
.
If the userPassword
configuration attribute is
specified, the value of that attribute is retrieved from the
user's directory entry. If the userRoleName
configuration attribute is specified, all values of that
attribute are retrieved from the directory entry.
Params: - connection – The directory context
- username – Username to be looked up
- credentials – User credentials (optional)
- curUserPattern – Index into userPatternFormatArray
Throws: - NamingException – if a directory server error occurs
Returns: the User object
/**
* Return a User object containing information about the user
* with the specified username, if found in the directory;
* otherwise return <code>null</code>.
*
* If the <code>userPassword</code> configuration attribute is
* specified, the value of that attribute is retrieved from the
* user's directory entry. If the <code>userRoleName</code>
* configuration attribute is specified, all values of that
* attribute are retrieved from the directory entry.
*
* @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(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)
list.add(userPassword);
if (userRoleName != null)
list.add(userRoleName);
if (userRoleAttribute != null) {
list.add(userRoleAttribute);
}
String[] attrIds = new String[list.size()];
list.toArray(attrIds);
// Use pattern or search for user entry
if (userPatternArray != null && curUserPattern >= 0) {
user = getUserByPattern(connection, username, credentials, attrIds, curUserPattern);
if (containerLog.isDebugEnabled()) {
containerLog.debug("Found user by pattern [" + user + "]");
}
} else {
boolean thisUserSearchAsUser = isUserSearchAsUser();
try {
if (thisUserSearchAsUser) {
userCredentialsAdd(connection.context, username, credentials);
}
user = getUserBySearch(connection, username, attrIds);
} finally {
if (thisUserSearchAsUser) {
userCredentialsRemove(connection.context);
}
}
if (containerLog.isDebugEnabled()) {
containerLog.debug("Found user by search [" + user + "]");
}
}
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 user;
}
Use the distinguished name to locate the directory
entry for the user with the specified username and
return a User object; otherwise return null
.
Params: - context – The directory context
- username – The username
- attrIds – String[]containing names of attributes to
- dn – Distinguished name of the user
retrieve.
Throws: - NamingException – if a directory server error occurs
Returns: the User object
/**
* Use the distinguished name to locate the directory
* entry for the user with the specified username and
* return a User object; otherwise return <code>null</code>.
*
* @param context The directory context
* @param username The username
* @param attrIds String[]containing names of attributes to
* @param dn Distinguished name of the user
* retrieve.
* @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 {
// If no attributes are requested, no need to look for them
if (attrIds == null || attrIds.length == 0) {
return new User(username, dn, null, null,null);
}
// Get required attributes from user entry
Attributes attrs = null;
try {
attrs = context.getAttributes(dn, attrIds);
} catch (NameNotFoundException e) {
return null;
}
if (attrs == null)
return null;
// Retrieve value of userPassword
String password = null;
if (userPassword != null)
password = getAttributeValue(userPassword, attrs);
String userRoleAttrValue = null;
if (userRoleAttribute != null) {
userRoleAttrValue = getAttributeValue(userRoleAttribute, attrs);
}
// Retrieve values of userRoleName attribute
ArrayList<String> roles = null;
if (userRoleName != null)
roles = addAttributeValues(userRoleName, attrs, roles);
return new User(username, dn, password, roles, userRoleAttrValue);
}
Use the UserPattern
configuration attribute to
locate the directory entry for the user with the specified
username and return a User object; otherwise return
null
.
Params: - connection – The directory context
- username – The username
- credentials – User credentials (optional)
- attrIds – String[]containing names of attributes to
- curUserPattern – Index into userPatternFormatArray
Throws: - NamingException – if a directory server error occurs
See Also: Returns: the User object
/**
* Use the <code>UserPattern</code> configuration attribute to
* locate the directory entry for the user with the specified
* username and return a User object; otherwise return
* <code>null</code>.
*
* @param connection The directory context
* @param username The username
* @param credentials User credentials (optional)
* @param attrIds String[]containing names of attributes to
* @param curUserPattern Index into userPatternFormatArray
* @return the User object
* @exception NamingException if a directory server error occurs
* @see #getUserByPattern(DirContext, String, String[], String)
*/
protected User getUserByPattern(JNDIConnection connection,
String username,
String credentials,
String[] attrIds,
int curUserPattern)
throws NamingException {
User user = null;
if (username == null || userPatternArray[curUserPattern] == null)
return null;
// Form the dn from the user pattern
String dn = connection.userPatternFormatArray[curUserPattern].format(new String[] { username });
try {
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(connection.context, dn, credentials);
user = getUserByPattern(connection.context, username, attrIds, dn);
} finally {
userCredentialsRemove(connection.context);
}
}
return user;
}
Search the directory to return a User object containing
information about the user with the specified username, if
found in the directory; otherwise return null
.
Params: - connection – The directory context
- username – The username
- attrIds – String[]containing names of attributes to retrieve.
Throws: - NamingException – if a directory server error occurs
Returns: the User object
/**
* Search the directory to return a User object containing
* information about the user with the specified username, if
* found in the directory; otherwise return <code>null</code>.
*
* @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(JNDIConnection connection,
String username,
String[] attrIds)
throws NamingException {
if (username == null || connection.userSearchFormat == null)
return null;
// Form the search filter
String filter = connection.userSearchFormat.format(new String[] { username });
// Set up the search controls
SearchControls constraints = new SearchControls();
if (userSubtree) {
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
} else {
constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
}
constraints.setCountLimit(sizeLimit);
constraints.setTimeLimit(timeLimit);
// Specify the attributes to be retrieved
if (attrIds == null)
attrIds = new String[0];
constraints.setReturningAttributes(attrIds);
NamingEnumeration<SearchResult> results =
connection.context.search(userBase, filter, constraints);
try {
// Fail if no entries found
try {
if (results == null || !results.hasMore()) {
return null;
}
} catch (PartialResultException ex) {
if (!adCompat)
throw ex;
else
return null;
}
// Get result for the first entry found
SearchResult result = results.next();
// Check no further entries were found
try {
if (results.hasMore()) {
if (containerLog.isInfoEnabled()) {
containerLog.info(sm.getString("jndiRealm.multipleEntries", username));
}
return null;
}
} catch (PartialResultException ex) {
if (!adCompat)
throw ex;
}
String dn = getDistinguishedName(connection.context, userBase, result);
if (containerLog.isTraceEnabled())
containerLog.trace(" entry found for " + username + " with dn " + dn);
// Get the entry's attributes
Attributes attrs = result.getAttributes();
if (attrs == null)
return null;
// Retrieve value of userPassword
String password = null;
if (userPassword != null)
password = getAttributeValue(userPassword, attrs);
String userRoleAttrValue = null;
if (userRoleAttribute != null) {
userRoleAttrValue = getAttributeValue(userRoleAttribute, attrs);
}
// Retrieve values of userRoleName attribute
ArrayList<String> roles = null;
if (userRoleName != null)
roles = addAttributeValues(userRoleName, attrs, roles);
return new User(username, dn, password, roles, userRoleAttrValue);
} finally {
if (results != null) {
results.close();
}
}
}
Check whether the given User can be authenticated with the
given credentials. If the userPassword
configuration attribute is specified, the credentials
previously retrieved from the directory are compared explicitly
with those presented by the user. Otherwise the presented
credentials are checked by binding to the directory as the
user.
Params: - context – The directory context
- user – The User to be authenticated
- credentials – The credentials presented by the user
Throws: - NamingException – if a directory server error occurs
Returns: true
if the credentials are validated
/**
* Check whether the given User can be authenticated with the
* given credentials. If the <code>userPassword</code>
* configuration attribute is specified, the credentials
* previously retrieved from the directory are compared explicitly
* with those presented by the user. Otherwise the presented
* credentials are checked by binding to the directory as the
* user.
*
* @param context The directory context
* @param user The User to be authenticated
* @param credentials The credentials presented by the user
* @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;
}
Check whether the credentials presented by the user match those
retrieved from the directory.
Params: - context – The directory context
- info – The User to be authenticated
- credentials – Authentication credentials
Throws: - NamingException – if a directory server error occurs
Returns: true
if the credentials are validated
/**
* Check whether the credentials presented by the user match those
* retrieved from the directory.
*
* @param context The directory context
* @param info The User to be authenticated
* @param credentials Authentication credentials
* @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 {
// Validate the credentials specified by the user
if (containerLog.isTraceEnabled())
containerLog.trace(" validating credentials");
if (info == null || credentials == null)
return false;
String password = info.getPassword();
return getCredentialHandler().matches(credentials, password);
}
Check credentials by binding to the directory as the user
Params: - context – The directory context
- user – The User to be authenticated
- credentials – Authentication credentials
Throws: - NamingException – if a directory server error occurs
Returns: true
if the credentials are validated
/**
* Check credentials by binding to the directory as the user
*
* @param context The directory context
* @param user The User to be authenticated
* @param credentials Authentication credentials
* @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");
}
userCredentialsAdd(context, dn, credentials);
// Elicit an LDAP bind operation
boolean validated = false;
try {
if (containerLog.isTraceEnabled()) {
containerLog.trace(" binding as " + dn);
}
context.getAttributes("", null);
validated = true;
}
catch (AuthenticationException e) {
if (containerLog.isTraceEnabled()) {
containerLog.trace(" bind attempt failed");
}
}
userCredentialsRemove(context);
return validated;
}
Configure the context to use the provided credentials for
authentication.
Params: - context – DirContext to configure
- dn – Distinguished name of user
- credentials – Credentials of user
Throws: - NamingException – if a directory server error occurs
/**
* 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 connectionName
and connectionPassword
if specified or an anonymous connection if those attributes are not specified. Params: - context – DirContext to configure
Throws: - NamingException – if a directory server error occurs
/**
* 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
*/
private void userCredentialsRemove(DirContext context)
throws NamingException {
// Restore the original security environment
if (connectionName != null) {
context.addToEnvironment(Context.SECURITY_PRINCIPAL,
connectionName);
} else {
context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
}
if (connectionPassword != null) {
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.
Params: - connection – The directory context we are searching
- user – The User to be checked
Throws: - NamingException – if a directory server error occurs
Returns: the list of role names
/**
* 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 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(JNDIConnection connection, User user)
throws NamingException {
if (user == null)
return null;
String dn = user.getDN();
String username = user.getUserName();
String userRoleId = user.getUserRoleId();
if (dn == null || username == null)
return null;
if (containerLog.isTraceEnabled())
containerLog.trace(" getRoles(" + dn + ")");
// Start with roles retrieved from the user entry
List<String> list = new ArrayList<>();
List<String> userRoles = user.getRoles();
if (userRoles != null) {
list.addAll(userRoles);
}
if (commonRole != null)
list.add(commonRole);
if (containerLog.isTraceEnabled()) {
containerLog.trace(" Found " + list.size() + " user internal roles");
containerLog.trace(" Found user internal roles " + list.toString());
}
// Are we configured to do role searches?
if ((connection.roleFormat == null) || (roleName == null))
return list;
// Set up parameters for an appropriate search
String filter = connection.roleFormat.format(new String[] { doRFC2254Encoding(dn), username, userRoleId });
SearchControls controls = new SearchControls();
if (roleSubtree)
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
else
controls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
controls.setReturningAttributes(new String[] {roleName});
String base = null;
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);
}
base = connection.roleBaseFormat.format(nameParts);
} else {
base = "";
}
// Perform the configured search and process the results
NamingEnumeration<SearchResult> results = searchAsUser(connection.context, user, base, filter, controls,
isRoleSearchAsUser());
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)
continue;
String dname = getDistinguishedName(connection.context, roleBase, result);
String name = getAttributeValue(roleName, attrs);
if (name != null && dname != null) {
groupMap.put(dname, name);
}
}
} catch (PartialResultException ex) {
if (!adCompat)
throw ex;
} finally {
results.close();
}
if (containerLog.isTraceEnabled()) {
Set<Entry<String, String>> entries = groupMap.entrySet();
containerLog.trace(" Found " + entries.size() + " direct roles");
for (Entry<String, String> entry : entries) {
containerLog.trace( " Found direct role " + entry.getKey() + " -> " + entry.getValue());
}
}
// if nested group search is enabled, perform searches for nested groups until no new group is found
if (getRoleNested()) {
// The following efficient algorithm is known as memberOf Algorithm, as described in "Practices in
// Directory Groups". It avoids group slurping and handles cyclic group memberships as well.
// See http://middleware.internet2.edu/dir/ for details
Map<String, String> newGroups = new HashMap<>(groupMap);
while (!newGroups.isEmpty()) {
Map<String, String> newThisRound = new HashMap<>(); // Stores the groups we find in this iteration
for (Entry<String, String> group : newGroups.entrySet()) {
filter = connection.roleFormat.format(new String[] { doRFC2254Encoding(group.getKey()),
group.getValue(), group.getValue() });
if (containerLog.isTraceEnabled()) {
containerLog.trace("Perform a nested group search with base "+ roleBase + " and filter " + filter);
}
results = searchAsUser(connection.context, user, roleBase, filter, controls,
isRoleSearchAsUser());
try {
while (results.hasMore()) {
SearchResult result = results.next();
Attributes attrs = result.getAttributes();
if (attrs == null)
continue;
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);
newThisRound.put(dname, name);
if (containerLog.isTraceEnabled()) {
containerLog.trace(" Found nested role " + dname + " -> " + name);
}
}
}
} catch (PartialResultException ex) {
if (!adCompat)
throw ex;
} finally {
results.close();
}
}
newGroups = newThisRound;
}
}
list.addAll(groupMap.values());
return list;
}
Perform the search on the context as the dn
, when searchAsUser
is true
, otherwise search the context with the default credentials. Params: - context –
context to search on
- user –
user to bind on
- base –
base to start the search from
- filter –
filter to use for the search
- controls –
controls to use for the search
- searchAsUser –
true
when the search should be done as user, or false
for using the default credentials
Throws: - NamingException –
if a directory server error occurs
Returns: enumeration with all found entries
/**
* Perform the search on the context as the {@code dn}, when
* {@code searchAsUser} is {@code true}, otherwise search the context with
* the default credentials.
*
* @param context
* context to search on
* @param user
* user to bind on
* @param base
* base to start the search from
* @param filter
* filter to use for the search
* @param controls
* controls to use for the search
* @param searchAsUser
* {@code true} when the search should be done as user, or
* {@code false} for using the default credentials
* @return enumeration with all found entries
* @throws NamingException
* if a directory server error occurs
*/
private NamingEnumeration<SearchResult> searchAsUser(DirContext context,
User user, String base, String filter,
SearchControls controls, boolean searchAsUser) throws NamingException {
NamingEnumeration<SearchResult> results;
try {
if (searchAsUser) {
userCredentialsAdd(context, user.getDN(), user.getPassword());
}
results = context.search(base, filter, controls);
} finally {
if (searchAsUser) {
userCredentialsRemove(context);
}
}
return results;
}
Return a String representing the value of the specified attribute.
Params: - attrId – Attribute name
- attrs – Attributes containing the required value
Throws: - NamingException – if a directory server error occurs
Returns: the attribute value
/**
* Return a String representing the value of the specified attribute.
*
* @param attrId Attribute name
* @param attrs Attributes containing the required value
* @return the attribute value
* @exception NamingException if a directory server error occurs
*/
private String getAttributeValue(String attrId, Attributes attrs)
throws NamingException {
if (containerLog.isTraceEnabled())
containerLog.trace(" retrieving attribute " + attrId);
if (attrId == null || attrs == null)
return null;
Attribute attr = attrs.get(attrId);
if (attr == null)
return null;
Object value = attr.get();
if (value == null)
return null;
String valueString = null;
if (value instanceof byte[])
valueString = new String((byte[]) value);
else
valueString = value.toString();
return valueString;
}
Add values of a specified attribute to a list
Params: - attrId – Attribute name
- attrs – Attributes containing the new values
- values – ArrayList containing values found so far
Throws: - NamingException – if a directory server error occurs
Returns: the list of attribute values
/**
* Add values of a specified attribute to a list
*
* @param attrId Attribute name
* @param attrs Attributes containing the new values
* @param values ArrayList containing values found so far
* @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{
if (containerLog.isTraceEnabled())
containerLog.trace(" retrieving values for attribute " + attrId);
if (attrId == null || attrs == null)
return values;
if (values == null)
values = new ArrayList<>();
Attribute attr = attrs.get(attrId);
if (attr == null)
return values;
NamingEnumeration<?> e = attr.getAll();
try {
while(e.hasMore()) {
String value = (String)e.next();
values.add(value);
}
} catch (PartialResultException ex) {
if (!adCompat)
throw ex;
} finally {
e.close();
}
return values;
}
Close any open connection to the directory server for this Realm.
Params: - connection – The directory context to be closed
/**
* Close any open connection to the directory server for this Realm.
*
* @param connection The directory context to be closed
*/
protected void close(JNDIConnection connection) {
// Do nothing if there is no opened connection
if (connection.context == null)
return;
// Close tls startResponse if used
if (tls != null) {
try {
tls.close();
} catch (IOException e) {
containerLog.error(sm.getString("jndiRealm.tlsClose"), e);
}
}
// Close our opened connection
try {
if (containerLog.isDebugEnabled())
containerLog.debug("Closing directory context");
connection.context.close();
} catch (NamingException e) {
containerLog.error(sm.getString("jndiRealm.close"), e);
}
connection.context = null;
// The lock will be reacquired before any manipulation of the connection
if (connectionPool == null) {
singleConnectionLock.unlock();
}
}
Close all pooled connections.
/**
* 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);
}
}
}
}
Get the password for the specified user.
Params: - username – The user name
Returns: the password associated with the given principal's user name.
/**
* Get the password for the specified user.
* @param username The user name
* @return the password associated with the given principal's user name.
*/
@Override
protected String getPassword(String username) {
String userPassword = getUserPassword();
if (userPassword == null || userPassword.isEmpty()) {
return null;
}
JNDIConnection connection = null;
User user = null;
try {
// 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;
} else {
// ... and have a password
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.
Params: - username – The user name
Returns: the Principal associated with the given certificate.
/**
* Get the principal associated with the specified certificate.
* @param username The user name
* @return the Principal associated with the given certificate.
*/
@Override
protected Principal getPrincipal(String username) {
return getPrincipal(username, null);
}
@Override
protected Principal getPrincipal(GSSName gssName,
GSSCredential gssCredential) {
String name = gssName.toString();
if (isStripRealmForGss()) {
int i = name.indexOf('@');
if (i > 0) {
// Zero so we don't leave a zero length name
name = name.substring(0, i);
}
}
return getPrincipal(name, gssCredential);
}
protected Principal getPrincipal(String username,
GSSCredential gssCredential) {
JNDIConnection connection = null;
Principal principal = null;
try {
// Ensure that we have a directory context available
connection = get();
// Occasionally the directory context will timeout. Try one more
// time before giving up.
try {
// Authenticate the specified username if possible
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.
close(connection);
closePooledConnections();
// open a new directory context.
connection = get();
// Try the authentication again.
principal = getPrincipal(connection, username, gssCredential);
}
// Release this 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);
// Return "not authenticated" for this request
return null;
}
}
Get the principal associated with the specified certificate.
Params: - connection – The directory context
- username – The user name
- gssCredential – The credentials
Throws: - NamingException – if a directory server error occurs
Returns: the Principal associated with the given certificate.
/**
* Get the principal associated with the specified certificate.
* @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 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);
// Note: Subject already set in SPNEGO authenticator so no need
// for Subject.doAs() here
}
user = getUser(connection, username);
if (user != null) {
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);
}
}
if (user != null) {
return new GenericPrincipal(user.getUserName(),
roles, null, null, gssCredential);
}
return null;
}
private void restoreEnvironmentParameter(DirContext context,
String parameterName, Hashtable<?, ?> preservedEnvironment) {
try {
context.removeFromEnvironment(parameterName);
if (preservedEnvironment != null && preservedEnvironment.containsKey(parameterName)) {
context.addToEnvironment(parameterName,
preservedEnvironment.get(parameterName));
}
} catch (NamingException e) {
// Ignore
}
}
Open (if necessary) and return a connection to the configured
directory server for this Realm.
Throws: - NamingException – if a directory server error occurs
Returns: the connection
/**
* Open (if necessary) and return a connection to the configured
* directory server for this Realm.
* @return the connection
* @exception NamingException if a directory server error occurs
*/
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;
}
Release our use of this connection so that it can be recycled.
Params: - connection – The directory context to release
/**
* 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();
}
}
Create a new connection wrapper, along with the
message formats.
Returns: the new connection
/**
* 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.
Params: - connection – The directory server connection wrapper
Throws: - NamingException – if a directory server error occurs
/**
* 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.
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);
}
}
}
@Override
public boolean isAvailable() {
// Simple best effort check
return (connectionPool != null || singleConnection.context != null);
}
private DirContext createDirContext(Hashtable<String, String> env) throws NamingException {
if (useStartTls) {
return createTlsDirContext(env);
} else {
return new InitialDirContext(env);
}
}
private SSLSocketFactory getSSLSocketFactory() {
if (sslSocketFactory != null) {
return sslSocketFactory;
}
final SSLSocketFactory result;
if (this.sslSocketFactoryClassName != null
&& !sslSocketFactoryClassName.trim().equals("")) {
result = createSSLSocketFactoryFromClassName(this.sslSocketFactoryClassName);
} else {
result = createSSLContextFactoryFromProtocol(sslProtocol);
}
this.sslSocketFactory = result;
return result;
}
private SSLSocketFactory createSSLSocketFactoryFromClassName(String className) {
try {
Object o = constructInstance(className);
if (o instanceof SSLSocketFactory) {
return sslSocketFactory;
} else {
throw new IllegalArgumentException(sm.getString(
"jndiRealm.invalidSslSocketFactory",
className));
}
} catch (ReflectiveOperationException | SecurityException e) {
throw new IllegalArgumentException(sm.getString(
"jndiRealm.invalidSslSocketFactory",
className), e);
}
}
private SSLSocketFactory createSSLContextFactoryFromProtocol(String protocol) {
try {
SSLContext sslContext;
if (protocol != null) {
sslContext = SSLContext.getInstance(protocol);
sslContext.init(null, null, null);
} else {
sslContext = SSLContext.getDefault();
}
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException 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.
Params: - env –
Environment to use for context creation
Throws: - NamingException –
when something goes wrong while negotiating the connection
Returns: configured LdapContext
/**
* Create a tls enabled LdapContext and set the StartTlsResponse tls
* instance variable.
*
* @param env
* Environment to use for context creation
* @return configured {@link LdapContext}
* @throws NamingException
* when something goes wrong while negotiating the connection
*/
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)) {
Object entry = env.remove(key);
if (entry != null) {
savedEnv.put(key, entry);
}
}
LdapContext result = null;
try {
result = new InitialLdapContext(env, null);
tls = (StartTlsResponse) result
.extendedOperation(new StartTlsRequest());
if (getHostnameVerifier() != null) {
tls.setHostnameVerifier(getHostnameVerifier());
}
if (getCipherSuitesArray() != null) {
tls.setEnabledCipherSuites(getCipherSuitesArray());
}
try {
SSLSession negotiate = tls.negotiate(getSSLSocketFactory());
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());
}
}
}
return result;
}
Create our directory context configuration.
Returns: java.util.Hashtable the configuration for the directory context.
/**
* Create our directory context configuration.
*
* @return java.util.Hashtable the configuration for the directory context.
*/
protected Hashtable<String,String> getDirectoryContextEnvironment() {
Hashtable<String,String> env = new Hashtable<>();
// Configure our directory context environment.
if (containerLog.isDebugEnabled() && connectionAttempt == 0)
containerLog.debug("Connecting to URL " + connectionURL);
else if (containerLog.isDebugEnabled() && connectionAttempt > 0)
containerLog.debug("Connecting to URL " + alternateURL);
env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
if (connectionName != null)
env.put(Context.SECURITY_PRINCIPAL, connectionName);
if (connectionPassword != null)
env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
if (connectionURL != null && connectionAttempt == 0)
env.put(Context.PROVIDER_URL, connectionURL);
else if (alternateURL != null && connectionAttempt > 0)
env.put(Context.PROVIDER_URL, alternateURL);
if (authentication != null)
env.put(Context.SECURITY_AUTHENTICATION, authentication);
if (protocol != null)
env.put(Context.SECURITY_PROTOCOL, protocol);
if (referrals != null)
env.put(Context.REFERRAL, referrals);
if (derefAliases != null)
env.put(JNDIRealm.DEREF_ALIASES, derefAliases);
if (connectionTimeout != null)
env.put("com.sun.jndi.ldap.connect.timeout", connectionTimeout);
if (readTimeout != null)
env.put("com.sun.jndi.ldap.read.timeout", readTimeout);
return env;
}
// ------------------------------------------------------ Lifecycle Methods
Prepare for the beginning of active use of the public methods of this component and implement the requirements of LifecycleBase.startInternal()
. Throws: - LifecycleException – if this component detects a fatal error
that prevents this component from being used
/**
* Prepare for the beginning of active use of the public methods of this
* component and implement the requirements of
* {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected void startInternal() throws LifecycleException {
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 {
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();
}
Gracefully terminate the active use of the public methods of this component and implement the requirements of LifecycleBase.stopInternal()
. Throws: - LifecycleException – if this component detects a fatal error
that needs to be reported
/**
* Gracefully terminate the active use of the public methods of this
* component and implement the requirements of
* {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that needs to be reported
*/
@Override
protected void stopInternal() throws LifecycleException {
super.stopInternal();
// Close any open directory server connection
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 -
"(location1)(location2)", returns an array of those paths. Real LDAP
search strings are supported as well (though only the "|" "OR" type).
Params: - userPatternString – - a string LDAP search paths surrounded by
parentheses
Returns: a parsed string array
/**
* Given a string containing LDAP patterns for user locations (separated by
* parentheses in a pseudo-LDAP search string format -
* "(location1)(location2)", returns an array of those paths. Real LDAP
* search strings are supported as well (though only the "|" "OR" type).
*
* @param userPatternString - a string LDAP search paths surrounded by
* parentheses
* @return a parsed string array
*/
protected String[] parseUserPatternString(String userPatternString) {
if (userPatternString != null) {
List<String> pathList = new ArrayList<>();
int startParenLoc = userPatternString.indexOf('(');
if (startParenLoc == -1) {
// no parens here; return whole thing
return new String[] {userPatternString};
}
int startingPoint = 0;
while (startParenLoc > -1) {
int endParenLoc = 0;
// weed out escaped open parens and parens enclosing the
// whole statement (in the case of valid LDAP search
// strings: (|(something)(somethingelse))
while ( (userPatternString.charAt(startParenLoc + 1) == '|') ||
(startParenLoc != 0 && userPatternString.charAt(startParenLoc - 1) == '\\') ) {
startParenLoc = userPatternString.indexOf('(', startParenLoc+1);
}
endParenLoc = userPatternString.indexOf(')', startParenLoc+1);
// weed out escaped end-parens
while (userPatternString.charAt(endParenLoc - 1) == '\\') {
endParenLoc = userPatternString.indexOf(')', endParenLoc+1);
}
String nextPathPart = userPatternString.substring
(startParenLoc+1, endParenLoc);
pathList.add(nextPathPart);
startingPoint = endParenLoc+1;
startParenLoc = userPatternString.indexOf('(', startingPoint);
}
return pathList.toArray(new String[] {});
}
return null;
}
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
Params: - inString – string to escape according to RFC 2254 guidelines
Returns: String the escaped/encoded result
/**
* 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 doRFC2254Encoding(String inString) {
StringBuilder buf = new StringBuilder(inString.length());
for (int i = 0; i < inString.length(); i++) {
char c = inString.charAt(i);
switch (c) {
case '\\':
buf.append("\\5c");
break;
case '*':
buf.append("\\2a");
break;
case '(':
buf.append("\\28");
break;
case ')':
buf.append("\\29");
break;
case '\0':
buf.append("\\00");
break;
default:
buf.append(c);
break;
}
}
return buf.toString();
}
Returns the distinguished name of a search result.
Params: - context – Our DirContext
- base – The base DN
- result – The search result
Throws: - NamingException – if a directory server error occurs
Returns: String containing the distinguished name
/**
* Returns the distinguished name of a search result.
*
* @param context Our DirContext
* @param base The base DN
* @param result The search result
* @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 {
// 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);
// Bugzilla 32269
Name entryName = parser.parse(new CompositeName(resultName).get(0));
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 (getForceDnHexEscape()) {
// Bug 63026
return convertToHexEscape(name.toString());
} else {
return name.toString();
}
}
protected static String convertToHexEscape(String input) {
if (input.indexOf('\\') == -1) {
// No escaping present. Return original.
return input;
}
// +6 allows for 3 escaped characters by default
StringBuilder result = new StringBuilder(input.length() + 6);
boolean previousSlash = false;
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
if (previousSlash) {
switch (c) {
case ' ': {
result.append("\\20");
break;
}
case '\"': {
result.append("\\22");
break;
}
case '#': {
result.append("\\23");
break;
}
case '+': {
result.append("\\2B");
break;
}
case ',': {
result.append("\\2C");
break;
}
case ';': {
result.append("\\3B");
break;
}
case '<': {
result.append("\\3C");
break;
}
case '=': {
result.append("\\3D");
break;
}
case '>': {
result.append("\\3E");
break;
}
case '\\': {
result.append("\\5C");
break;
}
default:
result.append('\\');
result.append(c);
}
previousSlash = false;
} else {
if (c == '\\') {
previousSlash = true;
} else {
result.append(c);
}
}
}
if (previousSlash) {
result.append('\\');
}
return result.toString();
}
// ------------------------------------------------------ Private Classes
A protected class representing a User
/**
* A protected class representing a User
*/
protected static class User {
private final String username;
private final String dn;
private final String password;
private final List<String> roles;
private final String userRoleId;
public User(String username, String dn, String password,
List<String> roles, String userRoleId) {
this.username = username;
this.dn = dn;
this.password = password;
if (roles == null) {
this.roles = Collections.emptyList();
} else {
this.roles = Collections.unmodifiableList(roles);
}
this.userRoleId = userRoleId;
}
public String getUserName() {
return username;
}
public String getDN() {
return dn;
}
public String getPassword() {
return password;
}
public List<String> getRoles() {
return roles;
}
public String getUserRoleId() {
return userRoleId;
}
}
Class holding the connection to the directory plus the associated
non thread safe message formats.
/**
* 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
userSearch
.
/**
* The MessageFormat object associated with the current
* <code>userSearch</code>.
*/
protected MessageFormat userSearchFormat = null;
An array of MessageFormat objects associated with the current
userPatternArray
.
/**
* An array of MessageFormat objects associated with the current
* <code>userPatternArray</code>.
*/
protected MessageFormat[] userPatternFormatArray = null;
The MessageFormat object associated with the current
roleBase
.
/**
* The MessageFormat object associated with the current
* <code>roleBase</code>.
*/
protected MessageFormat roleBaseFormat = null;
The MessageFormat object associated with the current
roleSearch
.
/**
* The MessageFormat object associated with the current
* <code>roleSearch</code>.
*/
protected MessageFormat roleFormat = null;
The directory context linking us to our directory server.
/**
* The directory context linking us to our directory server.
*/
protected DirContext context = null;
}
}