Coverage Report - org.acegisecurity.ldap.DefaultInitialDirContextFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultInitialDirContextFactory
91% 
92% 
2.125
 
 1  
 /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 2  
  *
 3  
  * Licensed under the Apache License, Version 2.0 (the "License");
 4  
  * you may not use this file except in compliance with the License.
 5  
  * You may obtain a copy of the License at
 6  
  *
 7  
  *     http://www.apache.org/licenses/LICENSE-2.0
 8  
  *
 9  
  * Unless required by applicable law or agreed to in writing, software
 10  
  * distributed under the License is distributed on an "AS IS" BASIS,
 11  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
  * See the License for the specific language governing permissions and
 13  
  * limitations under the License.
 14  
  */
 15  
 
 16  
 package org.acegisecurity.ldap;
 17  
 
 18  
 import org.acegisecurity.AcegiMessageSource;
 19  
 import org.acegisecurity.BadCredentialsException;
 20  
 
 21  
 import org.apache.commons.logging.Log;
 22  
 import org.apache.commons.logging.LogFactory;
 23  
 
 24  
 import org.springframework.context.MessageSource;
 25  
 import org.springframework.context.MessageSourceAware;
 26  
 import org.springframework.context.support.MessageSourceAccessor;
 27  
 
 28  
 import org.springframework.util.Assert;
 29  
 
 30  
 import java.util.Hashtable;
 31  
 import java.util.Map;
 32  
 import java.util.StringTokenizer;
 33  
 
 34  
 import javax.naming.CommunicationException;
 35  
 import javax.naming.Context;
 36  
 import javax.naming.NamingException;
 37  
 import javax.naming.OperationNotSupportedException;
 38  
 import javax.naming.ldap.InitialLdapContext;
 39  
 import javax.naming.directory.DirContext;
 40  
 import javax.naming.directory.InitialDirContext;
 41  
 
 42  
 
 43  
 /**
 44  
  * Encapsulates the information for connecting to an LDAP server and provides an access point for obtaining
 45  
  * <tt>DirContext</tt> references.
 46  
  * <p>
 47  
  * The directory location is configured using by setting the constructor argument
 48  
  * <tt>providerUrl</tt>. This should be in the form <tt>ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org</tt>.
 49  
  * The Sun JNDI provider also supports lists of space-separated URLs, each of which will be tried in turn until a
 50  
  * connection is obtained.
 51  
  * </p>
 52  
  * <p>To obtain an initial context, the client calls the <tt>newInitialDirContext</tt> method. There are two
 53  
  * signatures - one with no arguments and one which allows binding with a specific username and password.
 54  
  * </p>
 55  
  * <p>The no-args version will bind anonymously unless a manager login has been configured using the properties
 56  
  * <tt>managerDn</tt> and <tt>managerPassword</tt>, in which case it will bind as the manager user.</p>
 57  
  * <p>Connection pooling is enabled by default for anonymous or manager connections, but not when binding as a
 58  
  * specific user.</p>
 59  
  *
 60  
  * @author Robert Sanders
 61  
  * @author Luke Taylor
 62  
  * @version $Id: DefaultInitialDirContextFactory.java 1784 2007-02-24 21:00:24Z luke_t $
 63  
  *
 64  
  * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/connect/pool.html">The Java tutorial's guide to LDAP
 65  
  *      connection pooling</a>
 66  
  */
 67  
 public class DefaultInitialDirContextFactory implements InitialDirContextFactory, MessageSourceAware {
 68  
     //~ Static fields/initializers =====================================================================================
 69  
 
 70  2
     private static final Log logger = LogFactory.getLog(DefaultInitialDirContextFactory.class);
 71  
     private static final String CONNECTION_POOL_KEY = "com.sun.jndi.ldap.connect.pool";
 72  
     private static final String AUTH_TYPE_NONE = "none";
 73  
 
 74  
     //~ Instance fields ================================================================================================
 75  
 
 76  
     /** Allows extra environment variables to be added at config time. */
 77  55
     private Map extraEnvVars = null;
 78  55
     protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
 79  
 
 80  
     /** Type of authentication within LDAP; default is simple. */
 81  55
     private String authenticationType = "simple";
 82  
 
 83  
     /**
 84  
      * The INITIAL_CONTEXT_FACTORY used to create the JNDI Factory. Default is
 85  
      * "com.sun.jndi.ldap.LdapCtxFactory"; you <b>should not</b> need to set this unless you have unusual needs.
 86  
      */
 87  55
     private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
 88  
 
 89  
     /**
 90  
      * If your LDAP server does not allow anonymous searches then you will need to provide a "manager" user's
 91  
      * DN to log in with.
 92  
      */
 93  55
     private String managerDn = null;
 94  
 
 95  
     /** The manager user's password. */
 96  55
     private String managerPassword = "manager_password_not_set";
 97  
 
 98  
     /** The LDAP url of the server (and root context) to connect to. */
 99  
     private String providerUrl;
 100  
 
 101  
     /**
 102  
      * The root DN. This is worked out from the url. It is used by client classes when forming a full DN for
 103  
      * bind authentication (for example).
 104  
      */
 105  55
     private String rootDn = null;
 106  
 
 107  
     /**
 108  
      * Use the LDAP Connection pool; if true, then the LDAP environment property
 109  
      * "com.sun.jndi.ldap.connect.pool" is added to any other JNDI properties.
 110  
      */
 111  55
     private boolean useConnectionPool = true;
 112  
 
 113  
     /** Set to true for ldap v3 compatible servers */
 114  55
     private boolean useLdapContext = false;
 115  
 
 116  
     //~ Constructors ===================================================================================================
 117  
 
 118  
     /**
 119  
      * Create and initialize an instance to the LDAP url provided
 120  
      *
 121  
      * @param providerUrl a String of the form <code>ldap://localhost:389/base_dn<code>
 122  
      */
 123  55
     public DefaultInitialDirContextFactory(String providerUrl) {
 124  55
         this.setProviderUrl(providerUrl);
 125  54
     }
 126  
 
 127  
     //~ Methods ========================================================================================================
 128  
 
 129  
     /**
 130  
      * Set the LDAP url
 131  
      *
 132  
      * @param providerUrl a String of the form <code>ldap://localhost:389/base_dn<code>
 133  
      */
 134  
     private void setProviderUrl(String providerUrl) {
 135  55
         Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
 136  
 
 137  55
         this.providerUrl = providerUrl;
 138  
 
 139  55
         StringTokenizer st = new StringTokenizer(providerUrl);
 140  
 
 141  
         // Work out rootDn from the first URL and check that the other URLs (if any) match
 142  111
         while (st.hasMoreTokens()) {
 143  57
             String url = st.nextToken();
 144  57
             String urlRootDn = LdapUtils.parseRootDnFromUrl(url);
 145  
 
 146  57
             logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'");
 147  
 
 148  57
             if (rootDn == null) {
 149  55
                 rootDn = urlRootDn;
 150  2
             } else if (!rootDn.equals(urlRootDn)) {
 151  1
                 throw new IllegalArgumentException("Root DNs must be the same when using multiple URLs");
 152  
             }
 153  56
         }
 154  
 
 155  
         // This doesn't necessarily hold for embedded servers.
 156  
         //Assert.isTrue(uri.getScheme().equals("ldap"), "Ldap URL must start with 'ldap://'");
 157  54
     }
 158  
 
 159  
     /**
 160  
      * Get the LDAP url
 161  
      *
 162  
      * @return the url
 163  
      */
 164  
     private String getProviderUrl() {
 165  48
         return providerUrl;
 166  
     }
 167  
 
 168  
     private InitialDirContext connect(Hashtable env) {
 169  44
         if (logger.isDebugEnabled()) {
 170  0
             Hashtable envClone = (Hashtable) env.clone();
 171  
 
 172  0
             if (envClone.containsKey(Context.SECURITY_CREDENTIALS)) {
 173  0
                 envClone.put(Context.SECURITY_CREDENTIALS, "******");
 174  
             }
 175  
 
 176  0
             logger.debug("Creating InitialDirContext with environment " + envClone);
 177  
         }
 178  
 
 179  
         try {
 180  44
             return useLdapContext ? new InitialLdapContext(env, null) : new InitialDirContext(env);
 181  5
         } catch (NamingException ne) {
 182  5
             if ((ne instanceof javax.naming.AuthenticationException)
 183  
                     || (ne instanceof OperationNotSupportedException)) {
 184  4
                 throw new BadCredentialsException(messages.getMessage("DefaultIntitalDirContextFactory.badCredentials",
 185  
                         "Bad credentials"), ne);
 186  
             }
 187  
 
 188  1
             if (ne instanceof CommunicationException) {
 189  1
                 throw new LdapDataAccessException(messages.getMessage(
 190  
                         "DefaultIntitalDirContextFactory.communicationFailure", "Unable to connect to LDAP server"), ne);
 191  
             }
 192  
 
 193  0
             throw new LdapDataAccessException(messages.getMessage(
 194  
                     "DefaultIntitalDirContextFactory.unexpectedException",
 195  
                     "Failed to obtain InitialDirContext due to unexpected exception"), ne);
 196  
         }
 197  
     }
 198  
 
 199  
     /**
 200  
      * Sets up the environment parameters for creating a new context.
 201  
      *
 202  
      * @return the Hashtable describing the base DirContext that will be created, minus the username/password if any.
 203  
      */
 204  
     protected Hashtable getEnvironment() {
 205  48
         Hashtable env = new Hashtable();
 206  
 
 207  48
         env.put(Context.SECURITY_AUTHENTICATION, authenticationType);
 208  48
         env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
 209  48
         env.put(Context.PROVIDER_URL, getProviderUrl());
 210  
 
 211  48
         if (useConnectionPool) {
 212  47
             env.put(CONNECTION_POOL_KEY, "true");
 213  
         }
 214  
 
 215  48
         if ((extraEnvVars != null) && (extraEnvVars.size() > 0)) {
 216  45
             env.putAll(extraEnvVars);
 217  
         }
 218  
 
 219  48
         return env;
 220  
     }
 221  
 
 222  
     /**
 223  
      * Returns the root DN of the configured provider URL. For example, if the URL is
 224  
      * <tt>ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org</tt> the value will be
 225  
      * <tt>dc=acegisecurity,dc=org</tt>.
 226  
      *
 227  
      * @return the root DN calculated from the path of the LDAP url.
 228  
      */
 229  
     public String getRootDn() {
 230  25
         return rootDn;
 231  
     }
 232  
 
 233  
     /**
 234  
      * Connects anonymously unless a manager user has been specified, in which case it will bind as the
 235  
      * manager.
 236  
      *
 237  
      * @return the resulting context object.
 238  
      */
 239  
     public DirContext newInitialDirContext() {
 240  38
         if (managerDn != null) {
 241  36
             return newInitialDirContext(managerDn, managerPassword);
 242  
         }
 243  
 
 244  2
         Hashtable env = getEnvironment();
 245  2
         env.put(Context.SECURITY_AUTHENTICATION, AUTH_TYPE_NONE);
 246  
 
 247  2
         return connect(env);
 248  
     }
 249  
 
 250  
     public DirContext newInitialDirContext(String username, String password) {
 251  42
         Hashtable env = getEnvironment();
 252  
 
 253  
         // Don't pool connections for individual users
 254  42
         if (!username.equals(managerDn)) {
 255  6
             env.remove(CONNECTION_POOL_KEY);
 256  
         }
 257  
 
 258  42
         env.put(Context.SECURITY_PRINCIPAL, username);
 259  42
         env.put(Context.SECURITY_CREDENTIALS, password);
 260  
 
 261  42
         return connect(env);
 262  
     }
 263  
 
 264  
     public void setAuthenticationType(String authenticationType) {
 265  1
         Assert.hasLength(authenticationType, "LDAP Authentication type must not be empty or null");
 266  1
         this.authenticationType = authenticationType;
 267  1
     }
 268  
 
 269  
     /**
 270  
      * Sets any custom environment variables which will be added to the those returned
 271  
      * by the <tt>getEnvironment</tt> method.
 272  
      *
 273  
      * @param extraEnvVars extra environment variables to be added at config time.
 274  
      */
 275  
     public void setExtraEnvVars(Map extraEnvVars) {
 276  48
         Assert.notNull(extraEnvVars, "Extra environment map cannot be null.");
 277  48
         this.extraEnvVars = extraEnvVars;
 278  48
     }
 279  
 
 280  
     public void setInitialContextFactory(String initialContextFactory) {
 281  48
         Assert.hasLength(initialContextFactory, "Initial context factory name cannot be empty or null");
 282  48
         this.initialContextFactory = initialContextFactory;
 283  48
     }
 284  
 
 285  
     /**
 286  
      * Sets the directory user to authenticate as when obtaining a context using the
 287  
      * <tt>newInitialDirContext()</tt> method.
 288  
      * If no name is supplied then the context will be obtained anonymously.
 289  
      *
 290  
      * @param managerDn The name of the "manager" user for default authentication.
 291  
      */
 292  
     public void setManagerDn(String managerDn) {
 293  31
         Assert.hasLength(managerDn, "Manager user name  cannot be empty or null.");
 294  31
         this.managerDn = managerDn;
 295  31
     }
 296  
 
 297  
     /**
 298  
      * Sets the password which will be used in combination with the manager DN.
 299  
      *
 300  
      * @param managerPassword The "manager" user's password.
 301  
      */
 302  
     public void setManagerPassword(String managerPassword) {
 303  30
         Assert.hasLength(managerPassword, "Manager password must not be empty or null.");
 304  30
         this.managerPassword = managerPassword;
 305  30
     }
 306  
 
 307  
     public void setMessageSource(MessageSource messageSource) {
 308  12
         this.messages = new MessageSourceAccessor(messageSource);
 309  12
     }
 310  
 
 311  
     /**
 312  
      * Connection pooling is enabled by default for anonymous or "manager" connections when using the default
 313  
      * Sun provider. To disable all connection pooling, set this property to false.
 314  
      *
 315  
      * @param useConnectionPool whether to pool connections for non-specific users.
 316  
      */
 317  
     public void setUseConnectionPool(boolean useConnectionPool) {
 318  1
         this.useConnectionPool = useConnectionPool;
 319  1
     }
 320  
 
 321  
     public void setUseLdapContext(boolean useLdapContext) {
 322  0
         this.useLdapContext = useLdapContext;
 323  0
     }
 324  
 }