org.acegisecurity.ldap.DefaultInitialDirContextFactory Maven / Gradle / Ivy
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed 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.acegisecurity.ldap;
import org.acegisecurity.AcegiMessageSource;
import org.acegisecurity.BadCredentialsException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
import java.util.Hashtable;
import java.util.Map;
import java.util.StringTokenizer;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.OperationNotSupportedException;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
/**
* Encapsulates the information for connecting to an LDAP server and provides an access point for obtaining
* DirContext references.The directory location is configured using by setting the constructor argument
* providerUrl. This should be in the form ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org.
* The Sun JNDI provider also supports lists of space-separated URLs, each of which will be tried in turn until a
* connection is obtained.
* To obtain an initial context, the client calls the newInitialDirContext method. There are two
* signatures - one with no arguments and one which allows binding with a specific username and password.
* The no-args version will bind anonymously unless a manager login has been configured using the properties
* managerDn and managerPassword, in which case it will bind as the manager user.
* Connection pooling is enabled by default for anonymous or manager connections, but not when binding as a
* specific user.
*
* @author Robert Sanders
* @author Luke Taylor
* @version $Id: DefaultInitialDirContextFactory.java 1603 2006-08-25 16:03:50Z luke_t $
*
* @see The Java tutorial's guide to LDAP
* connection pooling
*/
public class DefaultInitialDirContextFactory implements InitialDirContextFactory, MessageSourceAware {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(DefaultInitialDirContextFactory.class);
private static final String CONNECTION_POOL_KEY = "com.sun.jndi.ldap.connect.pool";
private static final String AUTH_TYPE_NONE = "none";
//~ Instance fields ================================================================================================
/** Allows extra environment variables to be added at config time. */
private Map extraEnvVars = null;
protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
/** Type of authentication within LDAP; default is simple. */
private String authenticationType = "simple";
/**
* The INITIAL_CONTEXT_FACTORY used to create the JNDI Factory. Default is
* "com.sun.jndi.ldap.LdapCtxFactory"; you should not need to set this unless you have unusual needs.
*/
private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";
/**
* If your LDAP server does not allow anonymous searches then you will need to provide a "manager" user's
* DN to log in with.
*/
private String managerDn = null;
/** The manager user's password. */
private String managerPassword = "manager_password_not_set";
/** The LDAP url of the server (and root context) to connect to. */
private String providerUrl;
/**
* The root DN. This is worked out from the url. It is used by client classes when forming a full DN for
* bind authentication (for example).
*/
private String rootDn = null;
/**
* Use the LDAP Connection pool; if true, then the LDAP environment property
* "com.sun.jndi.ldap.connect.pool" is added to any other JNDI properties.
*/
private boolean useConnectionPool = true;
/** Set to true for ldap v3 compatible servers */
private boolean useLdapContext = false;
//~ Constructors ===================================================================================================
/**
* Create and initialize an instance to the LDAP url provided
*
* @param providerUrl a String of the form ldap://localhost:389/base_dn
*/
public DefaultInitialDirContextFactory(String providerUrl) {
this.setProviderUrl(providerUrl);
}
//~ Methods ========================================================================================================
/**
* Set the LDAP url
*
* @param providerUrl a String of the form ldap://localhost:389/base_dn
*/
private void setProviderUrl(String providerUrl) {
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
this.providerUrl = providerUrl;
StringTokenizer st = new StringTokenizer(providerUrl);
// Work out rootDn from the first URL and check that the other URLs (if any) match
while (st.hasMoreTokens()) {
String url = st.nextToken();
String urlRootDn = LdapUtils.parseRootDnFromUrl(url);
logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'");
if (rootDn == null) {
rootDn = urlRootDn;
} else if (!rootDn.equals(urlRootDn)) {
throw new IllegalArgumentException("Root DNs must be the same when using multiple URLs");
}
}
// This doesn't necessarily hold for embedded servers.
//Assert.isTrue(uri.getScheme().equals("ldap"), "Ldap URL must start with 'ldap://'");
}
/**
* Get the LDAP url
*
* @return the url
*/
private String getProviderUrl() {
return providerUrl;
}
private InitialDirContext connect(Hashtable env) {
if (logger.isDebugEnabled()) {
Hashtable envClone = (Hashtable) env.clone();
if (envClone.containsKey(Context.SECURITY_CREDENTIALS)) {
envClone.put(Context.SECURITY_CREDENTIALS, "******");
}
logger.debug("Creating InitialDirContext with environment " + envClone);
}
try {
return useLdapContext ? new InitialLdapContext(env, null) : new InitialDirContext(env);
} catch (NamingException ne) {
if ((ne instanceof javax.naming.AuthenticationException) ||
(ne instanceof OperationNotSupportedException)) {
throw new BadCredentialsException(messages.getMessage("DefaultIntitalDirContextFactory.badCredentials",
"Bad credentials"), ne);
}
if (ne instanceof CommunicationException) {
throw new LdapDataAccessException(messages.getMessage(
"DefaultIntitalDirContextFactory.communicationFailure", "Unable to connect to LDAP server"), ne);
}
throw new LdapDataAccessException(messages.getMessage(
"DefaultIntitalDirContextFactory.unexpectedException",
"Failed to obtain InitialDirContext due to unexpected exception"), ne);
}
}
/**
* Sets up the environment parameters for creating a new context.
*
* @return the Hashtable describing the base DirContext that will be created, minus the username/password if any.
*/
protected Hashtable getEnvironment() {
Hashtable env = new Hashtable();
env.put(Context.SECURITY_AUTHENTICATION, authenticationType);
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
env.put(Context.PROVIDER_URL, getProviderUrl());
if (useConnectionPool) {
env.put(CONNECTION_POOL_KEY, "true");
}
if ((extraEnvVars != null) && (extraEnvVars.size() > 0)) {
env.putAll(extraEnvVars);
}
return env;
}
/**
* Returns the root DN of the configured provider URL. For example, if the URL is
* ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org the value will be
* dc=acegisecurity,dc=org.
*
* @return the root DN calculated from the path of the LDAP url.
*/
public String getRootDn() {
return rootDn;
}
/**
* Connects anonymously unless a manager user has been specified, in which case it will bind as the
* manager.
*
* @return the resulting context object.
*/
public DirContext newInitialDirContext() {
if (managerDn != null) {
return newInitialDirContext(managerDn, managerPassword);
}
Hashtable env = getEnvironment();
env.put(Context.SECURITY_AUTHENTICATION, AUTH_TYPE_NONE);
return connect(env);
}
public DirContext newInitialDirContext(String username, String password) {
Hashtable env = getEnvironment();
// Don't pool connections for individual users
if (!username.equals(managerDn)) {
env.remove(CONNECTION_POOL_KEY);
}
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password);
return connect(env);
}
public void setAuthenticationType(String authenticationType) {
Assert.hasLength(authenticationType, "LDAP Authentication type must not be empty or null");
this.authenticationType = authenticationType;
}
/**
* Sets any custom environment variables which will be added to the those returned
* by the getEnvironment method.
*
* @param extraEnvVars extra environment variables to be added at config time.
*/
public void setExtraEnvVars(Map extraEnvVars) {
Assert.notNull(extraEnvVars, "Extra environment map cannot be null.");
this.extraEnvVars = extraEnvVars;
}
public void setInitialContextFactory(String initialContextFactory) {
Assert.hasLength(initialContextFactory, "Initial context factory name cannot be empty or null");
this.initialContextFactory = initialContextFactory;
}
/**
* Sets the directory user to authenticate as when obtaining a context using the
* newInitialDirContext() method.
* If no name is supplied then the context will be obtained anonymously.
*
* @param managerDn The name of the "manager" user for default authentication.
*/
public void setManagerDn(String managerDn) {
Assert.hasLength(managerDn, "Manager user name cannot be empty or null.");
this.managerDn = managerDn;
}
/**
* Sets the password which will be used in combination with the manager DN.
*
* @param managerPassword The "manager" user's password.
*/
public void setManagerPassword(String managerPassword) {
Assert.hasLength(managerPassword, "Manager password must not be empty or null.");
this.managerPassword = managerPassword;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
/**
* Connection pooling is enabled by default for anonymous or "manager" connections when using the default
* Sun provider. To disable all connection pooling, set this property to false.
*
* @param useConnectionPool whether to pool connections for non-specific users.
*/
public void setUseConnectionPool(boolean useConnectionPool) {
this.useConnectionPool = useConnectionPool;
}
public void setUseLdapContext(boolean useLdapContext) {
this.useLdapContext = useLdapContext;
}
}