Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  JNDIRealm.java   Sprache: JAVA

 
/*
 * 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.security.cert.X509Certificate;
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.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.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.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSName;

/**
 * <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
     */

    protected String authentication = null;

    /**
     * The connection username for the server we will contact.
     */

    protected String connectionName = null;

    /**
     * The connection password for the server we will contact.
     */

    protected String connectionPassword = null;

    /**
     * The connection URL for the server we will contact.
     */

    protected String connectionURL = null;

    /**
     * The 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.
     */

    protected String derefAliases = null;

    /**
     * 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.
     */

    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.
     */

    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.
     */

    protected String referrals = null;

    /**
     * The base element for user searches.
     */

    protected String userBase = "";

    /**
     * The message format used to search for a user, with "{0}" marking the spot where the username goes.
     */

    protected String userSearch = null;

    /**
     * When searching for users, should the search be performed as the user currently being authenticated? If false,
     * {@link #connectionName} and {@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?
     */

    protected boolean userSubtree = false;

    /**
     * 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
     */

    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.
     */

    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.
     */

    protected String userPattern = null;

    /**
     * The base element for role searches.
     */

    protected String roleBase = "";

    /**
     * The name of an attribute in the user's entry containing roles for that user
     */

    protected String userRoleName = null;

    /**
     * The name of the attribute containing roles held elsewhere
     */

    protected String roleName = null;

    /**
     * The message format used to select roles for a user, with "{0}" marking the spot where the distinguished name of
     * the user goes. The "{1}" and "{2}" are described in the Configuration Reference.
     */

    protected String roleSearch = null;

    /**
     * Should we search the entire subtree for matching memberships?
     */

    protected boolean roleSubtree = false;

    /**
     * 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, {@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.
     */

    protected String alternateURL;

    /**
     * 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
     */

    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).
     */

    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).
     */

    protected String readTimeout = "5000";

    /**
     * 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 {@link #userSearch}. Zero for no limit.
     */

    protected int timeLimit = 0;

    /**
     * Should delegated credentials from the SPNEGO authenticator be used if available
     */

    protected boolean useDelegatedCredential = true;

    /**
     * The QOP that should be used for the connection to the LDAP server after authentication. This value is used to set
     * the <code>javax.security.sasl.qop</code> environment property for the LDAP connection.
     */

    protected String spnegoDelegationQop = "auth-conf";

    /**
     * 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. <code>null</code> means to use the
     * default cipher suites.
     */

    private String[] cipherSuitesArray = null;

    /**
     * Verifier for hostnames in a StartTLS secured connection. <code>null</code> means to use the default verifier.
     */

    private HostnameVerifier hostnameVerifier = null;

    /**
     * {@link SSLSocketFactory} to use when connection with StartTLS enabled.
     */

    private SSLSocketFactory sslSocketFactory = null;

    /**
     * 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.
     */

    private String cipherSuites;

    /**
     * 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.
     */

    private String sslProtocol;

    private boolean forceDnHexEscape = false;

    /**
     * Non pooled connection to our directory server.
     */

    protected JNDIConnection singleConnection;

    /**
     * The lock to ensure single connection thread safety.
     */

    protected final Lock singleConnectionLock = new ReentrantLock();

    /**
     * Connection pool.
     */

    protected SynchronizedStack<JNDIConnection> connectionPool = null;

    /**
     * The pool size limit. If 1, pooling is not used.
     */

    protected int connectionPoolSize = 1;

    /**
     * Whether to use context ClassLoader or default ClassLoader. True means use context ClassLoader, and True is the
     * default value.
     */

    protected boolean useContextClassLoader = true;


    // ------------------------------------------------------------- Properties

    public boolean getForceDnHexEscape() {
        return forceDnHexEscape;
    }


    public void setForceDnHexEscape(boolean forceDnHexEscape) {
        this.forceDnHexEscape = forceDnHexEscape;
    }


    /**
     * @return the type of authentication to use.
     */

    public String getAuthentication() {
        return authentication;
    }


    /**
     * Set the type of authentication to use.
     *
     * @param authentication The authentication
     */

    public void setAuthentication(String authentication) {
        this.authentication = authentication;
    }


    /**
     * @return the connection username for this Realm.
     */

    public String getConnectionName() {
        return this.connectionName;
    }


    /**
     * Set the connection username for this Realm.
     *
     * @param connectionName The new connection username
     */

    public void setConnectionName(String connectionName) {
        this.connectionName = connectionName;
    }


    /**
     * @return the connection password for this Realm.
     */

    public String getConnectionPassword() {
        return this.connectionPassword;
    }


    /**
     * Set the connection password for this Realm.
     *
     * @param connectionPassword The new connection password
     */

    public void setConnectionPassword(String connectionPassword) {
        this.connectionPassword = connectionPassword;
    }


    /**
     * @return the connection URL for this Realm.
     */

    public String getConnectionURL() {
        return this.connectionURL;
    }


    /**
     * Set the connection URL for this Realm.
     *
     * @param connectionURL The new connection URL
     */

    public void setConnectionURL(String connectionURL) {
        this.connectionURL = connectionURL;
    }


    /**
     * @return the JNDI context factory for this Realm.
     */

    public String getContextFactory() {
        return this.contextFactory;
    }


    /**
     * Set the JNDI context factory for this Realm.
     *
     * @param contextFactory The new context factory
     */

    public void setContextFactory(String contextFactory) {
        this.contextFactory = contextFactory;
    }


    /**
     * @return the derefAliases setting to be used.
     */

    public String getDerefAliases() {
        return derefAliases;
    }


    /**
     * Set the value for derefAliases to be used when searching the directory.
     *
     * @param derefAliases New value of property derefAliases.
     */

    public void setDerefAliases(String derefAliases) {
        this.derefAliases = derefAliases;
    }


    /**
     * @return the protocol to be used.
     */

    public String getProtocol() {
        return protocol;
    }


    /**
     * Set the protocol for this Realm.
     *
     * @param protocol The new protocol.
     */

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }


    /**
     * @return the current settings for handling PartialResultExceptions
     */

    public boolean getAdCompat() {
        return adCompat;
    }


    /**
     * 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;
    }


    /**
     * @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).
     *
     * @param referrals The referral handling
     */

    public void setReferrals(String referrals) {
        this.referrals = referrals;
    }


    /**
     * @return the base element for user searches.
     */

    public String getUserBase() {
        return this.userBase;
    }


    /**
     * Set the base element for user searches.
     *
     * @param userBase The new base element
     */

    public void setUserBase(String userBase) {
        this.userBase = userBase;
    }


    /**
     * @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.
     *
     * @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;
    }


    /**
     * @return the "search subtree for users" flag.
     */

    public boolean getUserSubtree() {
        return this.userSubtree;
    }


    /**
     * Set the "search subtree for users" flag.
     *
     * @param userSubtree The new search flag
     */

    public void setUserSubtree(boolean userSubtree) {
        this.userSubtree = userSubtree;
    }


    /**
     * @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.
     *
     * @param userRoleName The new userRole name attribute name
     */

    public void setUserRoleName(String userRoleName) {
        this.userRoleName = userRoleName;
    }


    /**
     * @return the base element for role searches.
     */

    public String getRoleBase() {
        return this.roleBase;
    }


    /**
     * Set the base element for role searches.
     *
     * @param roleBase The new base element
     */

    public void setRoleBase(String roleBase) {
        this.roleBase = roleBase;
        singleConnection = create();
    }


    /**
     * @return the role name attribute name for this Realm.
     */

    public String getRoleName() {
        return this.roleName;
    }


    /**
     * 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;
    }


    /**
     * @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.
     *
     * @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;
    }


    /**
     * @return the "search subtree for roles" flag.
     */

    public boolean getRoleSubtree() {
        return this.roleSubtree;
    }


    /**
     * Set the "search subtree for roles" flag.
     *
     * @param roleSubtree The new search flag
     */

    public void setRoleSubtree(boolean roleSubtree) {
        this.roleSubtree = roleSubtree;
    }


    /**
     * @return the "The nested group search flag" flag.
     */

    public boolean getRoleNested() {
        return this.roleNested;
    }


    /**
     * Set the "search subtree for roles" flag.
     *
     * @param roleNested The nested group search flag
     */

    public void setRoleNested(boolean roleNested) {
        this.roleNested = roleNested;
    }


    /**
     * @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.
     *
     * @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;
    }

    /**
     * @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.
     *
     * @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.
     *
     * @return Value of property alternateURL.
     */

    public String getAlternateURL() {
        return this.alternateURL;
    }


    /**
     * Setter for property alternateURL.
     *
     * @param alternateURL New value of property alternateURL.
     */

    public void setAlternateURL(String alternateURL) {
        this.alternateURL = alternateURL;
    }


    /**
     * @return the common role
     */

    public String getCommonRole() {
        return commonRole;
    }


    /**
     * Set the common role
     *
     * @param commonRole The common role
     */

    public void setCommonRole(String commonRole) {
        this.commonRole = commonRole;
    }


    /**
     * @return the connection timeout.
     */

    public String getConnectionTimeout() {
        return connectionTimeout;
    }


    /**
     * Set the connection timeout.
     *
     * @param timeout The new connection timeout
     */

    public void setConnectionTimeout(String timeout) {
        this.connectionTimeout = timeout;
    }


    /**
     * @return the read timeout.
     */

    public String getReadTimeout() {
        return readTimeout;
    }


    /**
     * 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;
    }


    /**
     * @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
     *
     * @param useStartTls {@code true} when StartTLS should be used. Default is {@code false}.
     */

    public void setUseStartTls(boolean useStartTls) {
        this.useStartTls = useStartTls;
    }


    /**
     * @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.
     *
     * @param suites comma separated list of allowed cipher suites
     */

    public void setCipherSuites(String suites) {
        this.cipherSuites = suites;
    }


    /**
     * @return the connection pool size, or the default value 1 if pooling is disabled
     */

    public int getConnectionPoolSize() {
        return connectionPoolSize;
    }


    /**
     * Set the connection pool size
     *
     * @param connectionPoolSize the new pool size
     */

    public void setConnectionPoolSize(int connectionPoolSize) {
        this.connectionPoolSize = connectionPoolSize;
    }


    /**
     * @return name of the {@link HostnameVerifier} class used for connections using StartTLS, or the empty string, if
     *             the default verifier should be used.
     */

    public String getHostnameVerifierClassName() {
        if (this.hostnameVerifier == null) {
            return "";
        }
        return this.hostnameVerifier.getClass().getCanonicalName();
    }


    /**
     * Set the {@link HostnameVerifier} to be used when opening connections using StartTLS. An instance of the given
     * class name will be constructed 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;
        }
    }


    /**
     * @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 {@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.
     *
     * @param protocol one of the allowed ssl protocol names
     */

    public void setSslProtocol(String protocol) {
        this.sslProtocol = protocol;
    }


    /**
     * @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.
     *
     * @param useContext True means use context ClassLoader
     */

    public void setUseContextClassLoader(boolean useContext) {
        useContextClassLoader = useContext;
    }


    /**
     * Returns whether to use the context or default ClassLoader. True means to use the context ClassLoader.
     *
     * @return The value of useContextClassLoader
     */

    public boolean isUseContextClassLoader() {
        return useContextClassLoader;
    }


    // ---------------------------------------------------------- Realm Methods

    /**
     * {@inheritDoc}
     * <p>
     * If there are any errors with the JNDI 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.
     */

    @Override
    public Principal authenticate(String username, String credentials) {

        ClassLoader ocl = null;
        Thread currentThread = null;
        JNDIConnection connection = null;
        Principal principal = null;

        try {
            // https://bz.apache.org/bugzilla/show_bug.cgi?id=65553
            // This can move back to open() once it is known that Tomcat must be
            // running on a JVM that includes a fix for
            // https://bugs.openjdk.java.net/browse/JDK-8273874
            if (!isUseContextClassLoader()) {
                currentThread = Thread.currentThread();
                ocl = currentThread.getContextClassLoader();
                currentThread.setContextClassLoader(this.getClass().getClassLoader());
            }

            // Ensure that we have a directory context available
            connection = get();

            try {

                // Occasionally the directory context will timeout. Try one more
                // time before giving up.

                // 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 (Exception e) {

            // Log the problem for posterity
            containerLog.error(sm.getString("jndiRealm.exception"), e);

            // close the connection so we know it will be reopened.
            close(connection);
            closePooledConnections();

            // Return "not authenticated" for this request
            if (containerLog.isDebugEnabled()) {
                containerLog.debug("Returning null principal.");
            }
            return null;
        } finally {
            if (currentThread != null) {
                currentThread.setContextClassLoader(ocl);
            }
        }
    }


    /**
     * 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;
        }

        ClassLoader ocl = null;
        Thread currentThread = null;
        try {
            // https://bz.apache.org/bugzilla/show_bug.cgi?id=65553
            // This can move back to open() once it is known that Tomcat must be
            // running on a JVM that includes a fix for
            // https://bugs.openjdk.java.net/browse/JDK-8273874
            if (!isUseContextClassLoader()) {
                currentThread = Thread.currentThread();
                ocl = currentThread.getContextClassLoader();
                currentThread.setContextClassLoader(this.getClass().getClassLoader());
            }

            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 == null) ? "" : 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 == null) ? "" : roles.toString()));
                }

                // Create and return a suitable Principal for this user
                return new GenericPrincipal(username, roles);
            }
        } finally {
            if (currentThread != null) {
                currentThread.setContextClassLoader(ocl);
            }
        }
    }


    /*
     * https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved
     * back to open() once it is known that Tomcat must be running on a JVM that includes a fix for
     * https://bugs.openjdk.java.net/browse/JDK-8273874
     */

    @Override
    public Principal authenticate(String username) {
        ClassLoader ocl = null;
        Thread currentThread = null;
        try {
            if (!isUseContextClassLoader()) {
                currentThread = Thread.currentThread();
                ocl = currentThread.getContextClassLoader();
                currentThread.setContextClassLoader(this.getClass().getClassLoader());
            }
            return super.authenticate(username);
        } finally {
            if (currentThread != null) {
                currentThread.setContextClassLoader(ocl);
            }
        }
    }


    /*
     * https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved
     * back to open() once it is known that Tomcat must be running on a JVM that includes a fix for
     * https://bugs.openjdk.java.net/browse/JDK-8273874
     */

    @Override
    public Principal authenticate(String username, String clientDigest, String nonce, String nc, String cnonce,
            String qop, String realm, String digestA2, String algorithm) {
        ClassLoader ocl = null;
        Thread currentThread = null;
        try {
            if (!isUseContextClassLoader()) {
                currentThread = Thread.currentThread();
                ocl = currentThread.getContextClassLoader();
                currentThread.setContextClassLoader(this.getClass().getClassLoader());
            }
            return super.authenticate(username, clientDigest, nonce, nc, cnonce, qop, realm, digestA2, algorithm);
        } finally {
            if (currentThread != null) {
                currentThread.setContextClassLoader(ocl);
            }
        }
    }


    /*
     * https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved
     * back to open() once it is known that Tomcat must be running on a JVM that includes a fix for
     * https://bugs.openjdk.java.net/browse/JDK-8273874
     */

    @Override
    public Principal authenticate(X509Certificate[] certs) {
        ClassLoader ocl = null;
        Thread currentThread = null;
        try {
            if (!isUseContextClassLoader()) {
                currentThread = Thread.currentThread();
                ocl = currentThread.getContextClassLoader();
                currentThread.setContextClassLoader(this.getClass().getClassLoader());
            }
            return super.authenticate(certs);
        } finally {
            if (currentThread != null) {
                currentThread.setContextClassLoader(ocl);
            }
        }
    }


    /*
     * https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved
     * back to open() once it is known that Tomcat must be running on a JVM that includes a fix for
     * https://bugs.openjdk.java.net/browse/JDK-8273874
     */

    @Override
    public Principal authenticate(GSSContext gssContext, boolean storeCred) {
        ClassLoader ocl = null;
        Thread currentThread = null;
        try {
            if (!isUseContextClassLoader()) {
                currentThread = Thread.currentThread();
                ocl = currentThread.getContextClassLoader();
                currentThread.setContextClassLoader(this.getClass().getClassLoader());
            }
            return super.authenticate(gssContext, storeCred);
        } finally {
            if (currentThread != null) {
                currentThread.setContextClassLoader(ocl);
            }
        }
    }


    /*
     * https://bz.apache.org/bugzilla/show_bug.cgi?id=65553 This method can be removed and the class loader switch moved
     * back to open() once it is known that Tomcat must be running on a JVM that includes a fix for
     * https://bugs.openjdk.java.net/browse/JDK-8273874
     */

    @Override
    public Principal authenticate(GSSName gssName, GSSCredential gssCredential) {
        ClassLoader ocl = null;
        Thread currentThread = null;
        try {
            if (!isUseContextClassLoader()) {
                currentThread = Thread.currentThread();
                ocl = currentThread.getContextClassLoader();
                currentThread.setContextClassLoader(this.getClass().getClassLoader());
            }
            return super.authenticate(gssName, gssCredential);
        } finally {
            if (currentThread != null) {
                currentThread.setContextClassLoader(ocl);
            }
        }
    }


    // ------------------------------------------------------ Protected Methods

    /**
     * Return a User object containing information about the user with the specified username, if found in the
     * directory; otherwise return <code>null</code>.
     *
     * @param 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 <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 <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 = list.toArray(new String[0]);

        // 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 <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, nullnullnull);
        }

        // 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 <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 DistinguishedName from the user pattern.
        // Escape in case username contains a character with special meaning in
        // an attribute value.
        String dn = connection.userPatternFormatArray[curUserPattern]
                .format(new String[] { doAttributeValueEscaping(username) });

        try {
            user = getUserByPattern(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 <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
        // Escape in case username contains a character with special meaning in
        // a search filter.
        String filter = connection.userSearchFormat.format(new String[] { doFilterEscaping(username) });

        // Set up the search controls
        SearchControls constraints = new SearchControls();

        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 <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.
     *
     * @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
     *
     * @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;
        }

        // This is returned from the directory so will be attribute value
        // escaped if required
        String dn = user.getDN();
        if (dn == null) {
            return false;
        }

        // Validate the credentials specified by the user
        if (containerLog.isTraceEnabled()) {
            containerLog.trace(" validating credentials by binding as the user");
        }

        userCredentialsAdd(context, dn, credentials);

        // 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.
     *
     * @param context     DirContext to configure
     * @param dn          Distinguished name of user
     * @param credentials Credentials of user
     *
     * @exception NamingException if a directory server error occurs
     */

    private void userCredentialsAdd(DirContext context, String dn, String credentials) throws NamingException {
        // Set up security environment to bind as the user
        context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
        context.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
    }


    /**
     * Configure the context to use {@link #connectionName} and {@link #connectionPassword} if specified or an anonymous
     * connection if those attributes are not specified.
     *
     * @param context DirContext to configure
     *
     * @exception NamingException if a directory server error occurs
     */

    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.
     *
     * @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;
        }

        // This is returned from the directory so will be attribute value
        // escaped if required
        String dn = user.getDN();
        // This is the name the user provided to the authentication process so
        // it will not be escaped
        String username = user.getUserName();
        String userRoleId = user.getUserRoleId();

        if (dn == null || username == null) {
            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");
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=94 G=92

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge