Coverage Report - org.apache.shiro.realm.ldap.JndiLdapContextFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
JndiLdapContextFactory
100%
64/64
71%
33/46
1.96
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.shiro.realm.ldap;
 20  
 
 21  
 import org.apache.shiro.util.StringUtils;
 22  
 import org.slf4j.Logger;
 23  
 import org.slf4j.LoggerFactory;
 24  
 
 25  
 import javax.naming.AuthenticationException;
 26  
 import javax.naming.Context;
 27  
 import javax.naming.NamingException;
 28  
 import javax.naming.ldap.InitialLdapContext;
 29  
 import javax.naming.ldap.LdapContext;
 30  
 import java.util.HashMap;
 31  
 import java.util.Hashtable;
 32  
 import java.util.Map;
 33  
 
 34  
 /**
 35  
  * {@link LdapContextFactory} implementation using the default Sun/Oracle JNDI Ldap API, utilizing JNDI
 36  
  * environment properties and an {@link javax.naming.InitialContext}.
 37  
  * <h2>Configuration</h2>
 38  
  * This class basically wraps a default template JNDI environment properties Map.  This properties map is the base
 39  
  * configuration template used to acquire JNDI {@link LdapContext} connections at runtime.  The
 40  
  * {@link #getLdapContext(Object, Object)} method implementation merges this default template with other properties
 41  
  * accessible at runtime only (for example per-method principals and credentials).  The constructed runtime map is the
 42  
  * one used to acquire the {@link LdapContext}.
 43  
  * <p/>
 44  
  * The template can be configured directly via the {@link #getEnvironment()}/{@link #setEnvironment(java.util.Map)}
 45  
  * properties directly if necessary, but it is usually more convenient to use the supporting wrapper get/set methods
 46  
  * for various environment properties.  These wrapper methods interact with the environment
 47  
  * template on your behalf, leaving your configuration cleaner and easier to understand.
 48  
  * <p/>
 49  
  * For example, consider the following two identical configurations:
 50  
  * <pre>
 51  
  * [main]
 52  
  * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
 53  
  * ldapRealm.contextFactory.url = ldap://localhost:389
 54  
  * ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5
 55  
  * </pre>
 56  
  * and
 57  
  * <pre>
 58  
  * [main]
 59  
  * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
 60  
  * ldapRealm.contextFactory.environment[java.naming.provider.url] = ldap://localhost:389
 61  
  * ldapRealm.contextFactory.environment[java.naming.security.authentication] = DIGEST-MD5
 62  
  * </pre>
 63  
  * As you can see, the 2nd configuration block is a little more difficult to read and also requires knowledge
 64  
  * of the underlying JNDI Context property keys.  The first is easier to read and understand.
 65  
  * <p/>
 66  
  * Note that occasionally it will be necessary to use the latter configuration style to set environment properties
 67  
  * where no corresponding wrapper method exists.  In this case, the hybrid approach is still a little easier to read.
 68  
  * For example:
 69  
  * <pre>
 70  
  * [main]
 71  
  * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
 72  
  * ldapRealm.contextFactory.url = ldap://localhost:389
 73  
  * ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5
 74  
  * ldapRealm.contextFactory.environment[some.other.obscure.jndi.key] = some value
 75  
  * </pre>
 76  
  *
 77  
  * @since 1.1
 78  
  */
 79  
 public class JndiLdapContextFactory implements LdapContextFactory {
 80  
 
 81  
     /*-------------------------------------------
 82  
      |             C O N S T A N T S            |
 83  
      ===========================================*/
 84  
     /**
 85  
      * The Sun LDAP property used to enable connection pooling.  This is used in the default implementation
 86  
      * to enable LDAP connection pooling.
 87  
      */
 88  
     protected static final String SUN_CONNECTION_POOLING_PROPERTY = "com.sun.jndi.ldap.connect.pool";
 89  
     protected static final String DEFAULT_CONTEXT_FACTORY_CLASS_NAME = "com.sun.jndi.ldap.LdapCtxFactory";
 90  
     protected static final String SIMPLE_AUTHENTICATION_MECHANISM_NAME = "simple";
 91  
     protected static final String DEFAULT_REFERRAL = "follow";
 92  
 
 93  1
     private static final Logger log = LoggerFactory.getLogger(JndiLdapContextFactory.class);
 94  
 
 95  
     /*-------------------------------------------
 96  
      |    I N S T A N C E   V A R I A B L E S   |
 97  
      ============================================*/
 98  
     private Map<String, Object> environment;
 99  
     private boolean poolingEnabled;
 100  
     private String systemPassword;
 101  
     private String systemUsername;
 102  
 
 103  
     /*-------------------------------------------
 104  
      |         C O N S T R U C T O R S          |
 105  
      ===========================================*/
 106  
 
 107  
     /**
 108  
      * Default no-argument constructor that initializes the backing {@link #getEnvironment() environment template} with
 109  
      * the {@link #setContextFactoryClassName(String) contextFactoryClassName} equal to
 110  
      * {@code com.sun.jndi.ldap.LdapCtxFactory} (the Sun/Oracle default) and the default
 111  
      * {@link #setReferral(String) referral} behavior to {@code follow}.
 112  
      */
 113  33
     public JndiLdapContextFactory() {
 114  33
         this.environment = new HashMap<String, Object>();
 115  33
         setContextFactoryClassName(DEFAULT_CONTEXT_FACTORY_CLASS_NAME);
 116  33
         setReferral(DEFAULT_REFERRAL);
 117  33
         poolingEnabled = true;
 118  33
     }
 119  
 
 120  
     /*-------------------------------------------
 121  
      |  A C C E S S O R S / M O D I F I E R S   |
 122  
      ===========================================*/
 123  
 
 124  
     /**
 125  
      * Sets the type of LDAP authentication mechanism to use when connecting to the LDAP server.
 126  
      * This is a wrapper method for setting the JNDI {@link #getEnvironment() environment template}'s
 127  
      * {@link Context#SECURITY_AUTHENTICATION} property.
 128  
      * <p/>
 129  
      * "none" (i.e. anonymous) and "simple" authentications are supported automatically and don't need to be configured
 130  
      * via this property.  However, if you require a different mechanism, such as a SASL or External mechanism, you
 131  
      * must configure that explicitly via this property.  See the
 132  
      * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">JNDI LDAP
 133  
      * Authentication Mechanisms</a> for more information.
 134  
      *
 135  
      * @param authenticationMechanism the type of LDAP authentication to perform.
 136  
      * @see <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">
 137  
      *      http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html</a>
 138  
      */
 139  
     public void setAuthenticationMechanism(String authenticationMechanism) {
 140  3
         setEnvironmentProperty(Context.SECURITY_AUTHENTICATION, authenticationMechanism);
 141  3
     }
 142  
 
 143  
     /**
 144  
      * Returns the type of LDAP authentication mechanism to use when connecting to the LDAP server.
 145  
      * This is a wrapper method for getting the JNDI {@link #getEnvironment() environment template}'s
 146  
      * {@link Context#SECURITY_AUTHENTICATION} property.
 147  
      * <p/>
 148  
      * If this property remains un-configured (i.e. {@code null} indicating the
 149  
      * {@link #setAuthenticationMechanism(String)} method wasn't used), this indicates that the default JNDI
 150  
      * "none" (anonymous) and "simple" authentications are supported automatically.  Any non-null value returned
 151  
      * represents an explicitly configured mechanism (e.g. a SASL or external mechanism). See the
 152  
      * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">JNDI LDAP
 153  
      * Authentication Mechanisms</a> for more information.
 154  
      *
 155  
      * @return the type of LDAP authentication mechanism to use when connecting to the LDAP server.
 156  
      * @see <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">
 157  
      *      http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html</a>
 158  
      */
 159  
     public String getAuthenticationMechanism() {
 160  11
         return (String) getEnvironmentProperty(Context.SECURITY_AUTHENTICATION);
 161  
     }
 162  
 
 163  
     /**
 164  
      * The name of the ContextFactory class to use. This defaults to the SUN LDAP JNDI implementation
 165  
      * but can be overridden to use custom LDAP factories.
 166  
      * <p/>
 167  
      * This is a wrapper method for setting the JNDI environment's {@link Context#INITIAL_CONTEXT_FACTORY} property.
 168  
      *
 169  
      * @param contextFactoryClassName the context factory that should be used.
 170  
      */
 171  
     public void setContextFactoryClassName(String contextFactoryClassName) {
 172  33
         setEnvironmentProperty(Context.INITIAL_CONTEXT_FACTORY, contextFactoryClassName);
 173  33
     }
 174  
 
 175  
     /**
 176  
      * Sets the name of the ContextFactory class to use. This defaults to the SUN LDAP JNDI implementation
 177  
      * but can be overridden to use custom LDAP factories.
 178  
      * <p/>
 179  
      * This is a wrapper method for getting the JNDI environment's {@link Context#INITIAL_CONTEXT_FACTORY} property.
 180  
      *
 181  
      * @return the name of the ContextFactory class to use.
 182  
      */
 183  
     public String getContextFactoryClassName() {
 184  1
         return (String) getEnvironmentProperty(Context.INITIAL_CONTEXT_FACTORY);
 185  
     }
 186  
 
 187  
     /**
 188  
      * Returns the base JNDI environment template to use when acquiring an LDAP connection (an {@link LdapContext}).
 189  
      * This property is the base configuration template to use for all connections.  This template is then
 190  
      * merged with appropriate runtime values as necessary in the
 191  
      * {@link #getLdapContext(Object, Object)} implementation.  The merged environment instance is what is used to
 192  
      * acquire the {@link LdapContext} at runtime.
 193  
      * <p/>
 194  
      * Most other get/set methods in this class act as thin proxy wrappers that interact with this property.  The
 195  
      * benefit of using them is you have an easier-to-use configuration mechanism compared to setting map properties
 196  
      * based on JNDI context keys.
 197  
      *
 198  
      * @return the base JNDI environment template to use when acquiring an LDAP connection (an {@link LdapContext})
 199  
      */
 200  
     public Map getEnvironment() {
 201  7
         return this.environment;
 202  
     }
 203  
 
 204  
     /**
 205  
      * Sets the base JNDI environment template to use when acquiring LDAP connections.  It is typically more common
 206  
      * to use the other get/set methods in this class to set individual environment settings rather than use
 207  
      * this method, but it is available for advanced users that want full control over the base JNDI environment
 208  
      * settings.
 209  
      * <p/>
 210  
      * Note that this template only represents the base/default environment settings.  It is then merged with
 211  
      * appropriate runtime values as necessary in the {@link #getLdapContext(Object, Object)} implementation.
 212  
      * The merged environment instance is what is used to acquire the connection ({@link LdapContext}) at runtime.
 213  
      *
 214  
      * @param env the base JNDI environment template to use when acquiring LDAP connections.
 215  
      */
 216  
     @SuppressWarnings({"unchecked"})
 217  
     public void setEnvironment(Map env) {
 218  1
         this.environment = env;
 219  1
     }
 220  
 
 221  
     /**
 222  
      * Returns the environment property value bound under the specified key.
 223  
      *
 224  
      * @param name the name of the environment property
 225  
      * @return the property value or {@code null} if the value has not been set.
 226  
      */
 227  
     private Object getEnvironmentProperty(String name) {
 228  23
         return this.environment.get(name);
 229  
     }
 230  
 
 231  
     /**
 232  
      * Will apply the value to the environment attribute if and only if the value is not null or empty.  If it is
 233  
      * null or empty, the corresponding environment attribute will be removed.
 234  
      *
 235  
      * @param name  the environment property key
 236  
      * @param value the environment property value.  A null/empty value will trigger removal.
 237  
      */
 238  
     private void setEnvironmentProperty(String name, String value) {
 239  79
         if (StringUtils.hasText(value)) {
 240  78
             this.environment.put(name, value);
 241  
         } else {
 242  1
             this.environment.remove(name);
 243  
         }
 244  79
     }
 245  
 
 246  
     /**
 247  
      * Returns whether or not connection pooling should be used when possible and appropriate.  This property is NOT
 248  
      * backed by the {@link #getEnvironment() environment template} like most other properties in this class.  It
 249  
      * is a flag to indicate that pooling is preferred.  The default value is {@code true}.
 250  
      * <p/>
 251  
      * However, pooling will only actually be enabled if this property is {@code true} <em>and</em> the connection
 252  
      * being created is for the {@link #getSystemUsername() systemUsername} user.  Connection pooling is not used for
 253  
      * general authentication attempts by application end-users because the probability of re-use for that same
 254  
      * user-specific connection after an authentication attempt is extremely low.
 255  
      * <p/>
 256  
      * If this attribute is {@code true} and it has been determined that the connection is being made with the
 257  
      * {@link #getSystemUsername() systemUsername}, the
 258  
      * {@link #getLdapContext(Object, Object)} implementation will set the Sun/Oracle-specific
 259  
      * {@code com.sun.jndi.ldap.connect.pool} environment property to &quot;{@code true}&quot;.  This means setting
 260  
      * this property is only likely to work if using the Sun/Oracle default context factory class (i.e. not using
 261  
      * a custom {@link #getContextFactoryClassName() contextFactoryClassName}).
 262  
      *
 263  
      * @return whether or not connection pooling should be used when possible and appropriate
 264  
      */
 265  
     public boolean isPoolingEnabled() {
 266  9
         return poolingEnabled;
 267  
     }
 268  
 
 269  
     /**
 270  
      * Sets whether or not connection pooling should be used when possible and appropriate.  This property is NOT
 271  
      * a wrapper to the {@link #getEnvironment() environment template} like most other properties in this class.  It
 272  
      * is a flag to indicate that pooling is preferred.  The default value is {@code true}.
 273  
      * <p/>
 274  
      * However, pooling will only actually be enabled if this property is {@code true} <em>and</em> the connection
 275  
      * being created is for the {@link #getSystemUsername() systemUsername} user.  Connection pooling is not used for
 276  
      * general authentication attempts by application end-users because the probability of re-use for that same
 277  
      * user-specific connection after an authentication attempt is extremely low.
 278  
      * <p/>
 279  
      * If this attribute is {@code true} and it has been determined that the connection is being made with the
 280  
      * {@link #getSystemUsername() systemUsername}, the
 281  
      * {@link #getLdapContext(Object, Object)} implementation will set the Sun/Oracle-specific
 282  
      * {@code com.sun.jndi.ldap.connect.pool} environment property to &quot;{@code true}&quot;.  This means setting
 283  
      * this property is only likely to work if using the Sun/Oracle default context factory class (i.e. not using
 284  
      * a custom {@link #getContextFactoryClassName() contextFactoryClassName}).
 285  
      *
 286  
      * @param poolingEnabled whether or not connection pooling should be used when possible and appropriate
 287  
      */
 288  
     public void setPoolingEnabled(boolean poolingEnabled) {
 289  1
         this.poolingEnabled = poolingEnabled;
 290  1
     }
 291  
 
 292  
     /**
 293  
      * Sets the LDAP referral behavior when creating a connection.  Defaults to {@code follow}.  See the Sun/Oracle LDAP
 294  
      * <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">referral documentation</a> for more.
 295  
      *
 296  
      * @param referral the referral property.
 297  
      * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">Referrals in JNDI</a>
 298  
      */
 299  
     public void setReferral(String referral) {
 300  34
         setEnvironmentProperty(Context.REFERRAL, referral);
 301  34
     }
 302  
 
 303  
     /**
 304  
      * Returns the LDAP referral behavior when creating a connection.  Defaults to {@code follow}.
 305  
      * See the Sun/Oracle LDAP
 306  
      * <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">referral documentation</a> for more.
 307  
      *
 308  
      * @return the LDAP referral behavior when creating a connection.
 309  
      * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">Referrals in JNDI</a>
 310  
      */
 311  
     public String getReferral() {
 312  1
         return (String) getEnvironmentProperty(Context.REFERRAL);
 313  
     }
 314  
 
 315  
     /**
 316  
      * The LDAP url to connect to. (e.g. ldap://&lt;ldapDirectoryHostname&gt;:&lt;port&gt;).  This must be configured.
 317  
      *
 318  
      * @param url the LDAP url to connect to. (e.g. ldap://&lt;ldapDirectoryHostname&gt;:&lt;port&gt;)
 319  
      */
 320  
     public void setUrl(String url) {
 321  9
         setEnvironmentProperty(Context.PROVIDER_URL, url);
 322  9
     }
 323  
 
 324  
     /**
 325  
      * Returns the LDAP url to connect to. (e.g. ldap://&lt;ldapDirectoryHostname&gt;:&lt;port&gt;).
 326  
      * This must be configured.
 327  
      *
 328  
      * @return the LDAP url to connect to. (e.g. ldap://&lt;ldapDirectoryHostname&gt;:&lt;port&gt;)
 329  
      */
 330  
     public String getUrl() {
 331  10
         return (String) getEnvironmentProperty(Context.PROVIDER_URL);
 332  
     }
 333  
 
 334  
     /**
 335  
      * Sets the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an
 336  
      * LDAP connection used for authorization queries.
 337  
      * <p/>
 338  
      * Note that setting this property is not required if the calling LDAP Realm does not perform authorization
 339  
      * checks.
 340  
      *
 341  
      * @param systemPassword the password of the {@link #setSystemUsername(String) systemUsername} that will be used
 342  
      *                       when creating an LDAP connection used for authorization queries.
 343  
      */
 344  
     public void setSystemPassword(String systemPassword) {
 345  2
         this.systemPassword = systemPassword;
 346  2
     }
 347  
 
 348  
     /**
 349  
      * Returns the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an
 350  
      * LDAP connection used for authorization queries.
 351  
      * <p/>
 352  
      * Note that setting this property is not required if the calling LDAP Realm does not perform authorization
 353  
      * checks.
 354  
      *
 355  
      * @return the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an
 356  
      *         LDAP connection used for authorization queries.
 357  
      */
 358  
     public String getSystemPassword() {
 359  2
         return this.systemPassword;
 360  
     }
 361  
 
 362  
     /**
 363  
      * Sets the system username that will be used when creating an LDAP connection used for authorization queries.
 364  
      * The user must have the ability to query for authorization data for any application user.
 365  
      * <p/>
 366  
      * Note that setting this property is not required if the calling LDAP Realm does not perform authorization
 367  
      * checks.
 368  
      *
 369  
      * @param systemUsername the system username that will be used when creating an LDAP connection used for
 370  
      *                       authorization queries.
 371  
      */
 372  
     public void setSystemUsername(String systemUsername) {
 373  2
         this.systemUsername = systemUsername;
 374  2
     }
 375  
 
 376  
     /**
 377  
      * Returns the system username that will be used when creating an LDAP connection used for authorization queries.
 378  
      * The user must have the ability to query for authorization data for any application user.
 379  
      * <p/>
 380  
      * Note that setting this property is not required if the calling LDAP Realm does not perform authorization
 381  
      * checks.
 382  
      *
 383  
      * @return the system username that will be used when creating an LDAP connection used for authorization queries.
 384  
      */
 385  
     public String getSystemUsername() {
 386  10
         return systemUsername;
 387  
     }
 388  
 
 389  
     /*--------------------------------------------
 390  
     |               M E T H O D S               |
 391  
     ============================================*/
 392  
 
 393  
     /**
 394  
      * This implementation delegates to {@link #getLdapContext(Object, Object)} using the
 395  
      * {@link #getSystemUsername() systemUsername} and {@link #getSystemPassword() systemPassword} properties as
 396  
      * arguments.
 397  
      *
 398  
      * @return the system LdapContext
 399  
      * @throws NamingException if there is a problem connecting to the LDAP directory
 400  
      */
 401  
     public LdapContext getSystemLdapContext() throws NamingException {
 402  2
         return getLdapContext((Object)getSystemUsername(), getSystemPassword());
 403  
     }
 404  
 
 405  
     /**
 406  
      * Deprecated - use {@link #getLdapContext(Object, Object)} instead.  This will be removed before Apache Shiro 2.0.
 407  
      *
 408  
      * @param username the username to use when creating the connection.
 409  
      * @param password the password to use when creating the connection.
 410  
      * @return a {@code LdapContext} bound using the given username and password.
 411  
      * @throws javax.naming.NamingException if there is an error creating the context.
 412  
      * @deprecated the {@link #getLdapContext(Object, Object)} method should be used in all cases to ensure more than
 413  
      *             String principals and credentials can be used.  Shiro no longer calls this method - it will be
 414  
      *             removed before the 2.0 release.
 415  
      */
 416  
     @Deprecated
 417  
     public LdapContext getLdapContext(String username, String password) throws NamingException {
 418  1
         return getLdapContext((Object) username, password);
 419  
     }
 420  
 
 421  
     /**
 422  
      * Returns {@code true} if LDAP connection pooling should be used when acquiring a connection based on the specified
 423  
      * account principal, {@code false} otherwise.
 424  
      * <p/>
 425  
      * This implementation returns {@code true} only if {@link #isPoolingEnabled()} and the principal equals the
 426  
      * {@link #getSystemUsername()}.  The reasoning behind this is that connection pooling is not desirable for
 427  
      * general authentication attempts by application end-users because the probability of re-use for that same
 428  
      * user-specific connection after an authentication attempt is extremely low.
 429  
      *
 430  
      * @param principal the principal under which the connection will be made
 431  
      * @return {@code true} if LDAP connection pooling should be used when acquiring a connection based on the specified
 432  
      *         account principal, {@code false} otherwise.
 433  
      */
 434  
     protected boolean isPoolingConnections(Object principal) {
 435  9
         return isPoolingEnabled() && principal != null && principal.equals(getSystemUsername());
 436  
     }
 437  
 
 438  
     /**
 439  
      * This implementation returns an LdapContext based on the configured JNDI/LDAP environment configuration.
 440  
      * The environnmet (Map) used at runtime is created by merging the default/configured
 441  
      * {@link #getEnvironment() environment template} with some runtime values as necessary (e.g. a principal and
 442  
      * credential available at runtime only).
 443  
      * <p/>
 444  
      * After the merged Map instance is created, the LdapContext connection is
 445  
      * {@link #createLdapContext(java.util.Hashtable) created} and returned.
 446  
      *
 447  
      * @param principal   the principal to use when acquiring a connection to the LDAP directory
 448  
      * @param credentials the credentials (password, X.509 certificate, etc) to use when acquiring a connection to the
 449  
      *                    LDAP directory
 450  
      * @return the acquired {@code LdapContext} connection bound using the specified principal and credentials.
 451  
      * @throws NamingException
 452  
      * @throws IllegalStateException
 453  
      */
 454  
     public LdapContext getLdapContext(Object principal, Object credentials) throws NamingException,
 455  
             IllegalStateException {
 456  
 
 457  10
         String url = getUrl();
 458  10
         if (url == null) {
 459  1
             throw new IllegalStateException("An LDAP URL must be specified of the form ldap://<hostname>:<port>");
 460  
         }
 461  
 
 462  
         //copy the environment template into the runtime instance that will be further edited based on
 463  
         //the method arguments and other class attributes.
 464  9
         Hashtable<String, Object> env = new Hashtable<String, Object>(this.environment);
 465  
 
 466  9
         Object authcMech = getAuthenticationMechanism();
 467  9
         if (authcMech == null && (principal != null || credentials != null)) {
 468  
             //authenticationMechanism has not been set, but either a principal and/or credentials were
 469  
             //supplied, indicating that at least a 'simple' authentication attempt is indeed occurring - the Shiro
 470  
             //end-user just didn't configure it explicitly.  So we set it to be 'simple' here as a convenience;
 471  
             //the Sun provider implementation already does this same logic, but by repeating that logic here, we ensure
 472  
             //this convenience exists regardless of provider implementation):
 473  9
             env.put(Context.SECURITY_AUTHENTICATION, SIMPLE_AUTHENTICATION_MECHANISM_NAME);
 474  
         }
 475  9
         if (principal != null) {
 476  9
             env.put(Context.SECURITY_PRINCIPAL, principal);
 477  
         }
 478  9
         if (credentials != null) {
 479  8
             env.put(Context.SECURITY_CREDENTIALS, credentials);
 480  
         }
 481  
 
 482  9
         boolean pooling = isPoolingConnections(principal);
 483  9
         if (pooling) {
 484  1
             env.put(SUN_CONNECTION_POOLING_PROPERTY, "true");
 485  
         }
 486  
 
 487  9
         if (log.isDebugEnabled()) {
 488  9
             log.debug("Initializing LDAP context using URL [{}] and principal [{}] with pooling {}",
 489  
                     new Object[]{url, principal, (pooling ? "enabled" : "disabled")});
 490  
         }
 491  
 
 492  
         // validate the config before creating the context
 493  9
         validateAuthenticationInfo(env);
 494  
 
 495  5
         return createLdapContext(env);
 496  
     }
 497  
 
 498  
     /**
 499  
      * Creates and returns a new {@link javax.naming.ldap.InitialLdapContext} instance.  This method exists primarily
 500  
      * to support testing where a mock LdapContext can be returned instead of actually creating a connection, but
 501  
      * subclasses are free to provide a different implementation if necessary.
 502  
      *
 503  
      * @param env the JNDI environment settings used to create the LDAP connection
 504  
      * @return an LdapConnection
 505  
      * @throws NamingException if a problem occurs creating the connection
 506  
      */
 507  
     protected LdapContext createLdapContext(Hashtable env) throws NamingException {
 508  1
         return new InitialLdapContext(env, null);
 509  
     }
 510  
 
 511  
 
 512  
     /**
 513  
      * Validates the configuration in the JNDI <code>environment</code> settings and throws an exception if a problem
 514  
      * exists.
 515  
      * <p/>
 516  
      * This implementation will throw a {@link AuthenticationException} if the authentication mechanism is set to
 517  
      * 'simple', the principal is non-empty, and the credentials are empty (as per
 518  
      * <a href="http://tools.ietf.org/html/rfc4513#section-5.1.2">rfc4513 section-5.1.2</a>).
 519  
      *
 520  
      * @param environment the JNDI environment settings to be validated
 521  
      * @throws AuthenticationException if a configuration problem is detected
 522  
      */
 523  
     protected void validateAuthenticationInfo(Hashtable<String, Object> environment)
 524  
         throws AuthenticationException
 525  
     {
 526  
         // validate when using Simple auth both principal and credentials are set
 527  9
         if(SIMPLE_AUTHENTICATION_MECHANISM_NAME.equals(environment.get(Context.SECURITY_AUTHENTICATION))) {
 528  
 
 529  
             // only validate credentials if we have a non-empty principal
 530  9
             if( environment.get(Context.SECURITY_PRINCIPAL) != null &&
 531  
                 StringUtils.hasText( String.valueOf( environment.get(Context.SECURITY_PRINCIPAL) ))) {
 532  
 
 533  9
                 Object credentials = environment.get(Context.SECURITY_CREDENTIALS);
 534  
 
 535  
                 // from the FAQ, we need to check for empty credentials:
 536  
                 // http://docs.oracle.com/javase/tutorial/jndi/ldap/faq.html
 537  9
                 if( credentials == null ||
 538  
                     (credentials instanceof byte[] && ((byte[])credentials).length <= 0) || // empty byte[]
 539  
                     (credentials instanceof char[] && ((char[])credentials).length <= 0) || // empty char[]
 540  
                     (String.class.isInstance(credentials) && !StringUtils.hasText(String.valueOf(credentials)))) {
 541  
 
 542  4
                     throw new javax.naming.AuthenticationException("LDAP Simple authentication requires both a "
 543  
                                                                        + "principal and credentials.");
 544  
                 }
 545  
             }
 546  
         }
 547  5
     }
 548  
 
 549  
 }