All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.ldap.core.support.AbstractContextSource Maven / Gradle / Ivy

/*
 * Copyright 2005-2013 the original author or authors.
 *
 * 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.springframework.ldap.core.support;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.JdkVersion;
import org.springframework.ldap.core.AuthenticationSource;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.support.LdapUtils;

import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import java.util.Hashtable;
import java.util.Map;

/**
 * Abstract implementation of the {@link ContextSource} interface. By default,
 * returns an authenticated
 * DirContext implementation for both read-only and
 * read-write operations. To have an anonymous environment created for read-only
 * operations, set the anonymousReadOnly property to
 * true.
 * 

* Implementing classes need to implement * {@link #getDirContextInstance(Hashtable)} to create a DirContext * instance of the desired type. *

* If an {@link AuthenticationSource} is set, this will be used for getting user * principal and password for each new connection, otherwise a default one will * be created using the specified userDn and password. *

* Note: When using implementations of this class outside of a Spring * Context it is necessary to call {@link #afterPropertiesSet()} when all * properties are set, in order to finish up initialization. * * @see org.springframework.ldap.core.LdapTemplate * @see org.springframework.ldap.core.support.DefaultDirObjectFactory * @see org.springframework.ldap.core.support.LdapContextSource * @see org.springframework.ldap.core.support.DirContextSource * * @author Mattias Hellborg Arthursson * @author Adam Skogman * @author Ulrik Sandberg */ public abstract class AbstractContextSource implements BaseLdapPathContextSource, InitializingBean { private static final Class DEFAULT_CONTEXT_FACTORY = com.sun.jndi.ldap.LdapCtxFactory.class; private static final Class DEFAULT_DIR_OBJECT_FACTORY = DefaultDirObjectFactory.class; private static final boolean DONT_DISABLE_POOLING = false; private static final boolean EXPLICITLY_DISABLE_POOLING = true; private Class dirObjectFactory = DEFAULT_DIR_OBJECT_FACTORY; private Class contextFactory = DEFAULT_CONTEXT_FACTORY; private DistinguishedName base = DistinguishedName.EMPTY_PATH; protected String userDn = ""; protected String password = ""; private String[] urls; private boolean pooled = false; private Hashtable baseEnv = new Hashtable(); private Hashtable anonymousEnv; private AuthenticationSource authenticationSource; private boolean cacheEnvironmentProperties = true; private boolean anonymousReadOnly = false; private String referral = null; private static final Log log = LogFactory.getLog(AbstractContextSource.class); public static final String SUN_LDAP_POOLING_FLAG = "com.sun.jndi.ldap.connect.pool"; private static final String JDK_142 = "1.4.2"; private DirContextAuthenticationStrategy authenticationStrategy = new SimpleDirContextAuthenticationStrategy(); public DirContext getContext(String principal, String credentials) { // This method is typically called for authentication purposes, which means that we // should explicitly disable pooling in case passwords are changed (LDAP-183). return doGetContext(principal, credentials, EXPLICITLY_DISABLE_POOLING); } private DirContext doGetContext(String principal, String credentials, boolean explicitlyDisablePooling) { Hashtable env = getAuthenticatedEnv(principal, credentials); if(explicitlyDisablePooling) { env.remove(SUN_LDAP_POOLING_FLAG); } DirContext ctx = createContext(env); try { authenticationStrategy.processContextAfterCreation(ctx, principal, credentials); return ctx; } catch (NamingException e) { closeContext(ctx); throw LdapUtils.convertLdapException(e); } } /* * (non-Javadoc) * * @see org.springframework.ldap.core.ContextSource#getReadOnlyContext() */ public DirContext getReadOnlyContext() { if (!anonymousReadOnly) { return doGetContext( authenticationSource.getPrincipal(), authenticationSource.getCredentials(), DONT_DISABLE_POOLING); } else { return createContext(getAnonymousEnv()); } } /* * (non-Javadoc) * * @see org.springframework.ldap.core.ContextSource#getReadWriteContext() */ public DirContext getReadWriteContext() { return doGetContext( authenticationSource.getPrincipal(), authenticationSource.getCredentials(), DONT_DISABLE_POOLING); } /** * Default implementation of setting the environment up to be authenticated. * This method should typically NOT be overridden; any customization to the * authentication mechanism should be managed by setting a different * {@link DirContextAuthenticationStrategy} on this instance. * * @param env the environment to modify. * @param principal the principal to authenticate with. * @param credentials the credentials to authenticate with. * @see DirContextAuthenticationStrategy * @see #setAuthenticationStrategy(DirContextAuthenticationStrategy) */ protected void setupAuthenticatedEnvironment(Hashtable env, String principal, String credentials) { try { authenticationStrategy.setupEnvironment(env, principal, credentials); } catch (NamingException e) { throw LdapUtils.convertLdapException(e); } } /** * Close the context and swallow any exceptions. * * @param ctx the DirContext to close. */ private void closeContext(DirContext ctx) { if (ctx != null) { try { ctx.close(); } catch (Exception e) { } } } /** * Assemble a valid url String from all registered urls to add as * PROVIDER_URL to the environment. * * @param ldapUrls all individual url Strings. * @return the full url String */ protected String assembleProviderUrlString(String[] ldapUrls) { StringBuffer providerUrlBuffer = new StringBuffer(1024); for (int i = 0; i < ldapUrls.length; i++) { providerUrlBuffer.append(ldapUrls[i]); if (!DistinguishedName.EMPTY_PATH.equals(base)) { if (!ldapUrls[i].endsWith("/")) { providerUrlBuffer.append("/"); } } providerUrlBuffer.append(base.toUrl()); providerUrlBuffer.append(' '); } return providerUrlBuffer.toString().trim(); } /** * Set the base suffix from which all operations should origin. If a base * suffix is set, you will not have to (and, indeed, must not) specify the * full distinguished names in any operations performed. * * @param base the base suffix. */ public void setBase(String base) { this.base = new DistinguishedName(base); } /** * Get the base suffix from which all operations should originate. If a base * suffix is set, you will not have to (and, indeed, must not) specify the * full distinguished names in any operations performed. * * @return the base suffix */ protected DistinguishedName getBase() { return new DistinguishedName(base); } /* * (non-Javadoc) * * @see * org.springframework.ldap.core.support.BaseLdapPathSource#getBaseLdapPath * () */ public DistinguishedName getBaseLdapPath() { return getBase().immutableDistinguishedName(); } /* * (non-Javadoc) * * @seeorg.springframework.ldap.core.support.BaseLdapPathSource# * getBaseLdapPathAsString() */ public String getBaseLdapPathAsString() { return getBaseLdapPath().toString(); } /** * Create a DirContext using the supplied environment. * * @param environment the LDAP environment to use when creating the * DirContext. * @return a new DirContext implementation initialized with the supplied * environment. */ protected DirContext createContext(Hashtable environment) { DirContext ctx = null; try { ctx = getDirContextInstance(environment); if (log.isInfoEnabled()) { Hashtable ctxEnv = ctx.getEnvironment(); String ldapUrl = (String) ctxEnv.get(Context.PROVIDER_URL); log.debug("Got Ldap context on server '" + ldapUrl + "'"); } return ctx; } catch (NamingException e) { closeContext(ctx); throw LdapUtils.convertLdapException(e); } } /** * Set the context factory. Default is com.sun.jndi.ldap.LdapCtxFactory. * * @param contextFactory the context factory used when creating Contexts. */ public void setContextFactory(Class contextFactory) { this.contextFactory = contextFactory; } /** * Get the context factory. * * @return the context factory used when creating Contexts. */ public Class getContextFactory() { return contextFactory; } /** * Set the DirObjectFactory to use. Default is * {@link DefaultDirObjectFactory}. The specified class needs to be an * implementation of javax.naming.spi.DirObjectFactory. Note: Setting * this value to null may have cause connection leaks when using * ContextMapper methods in LdapTemplate. * * @param dirObjectFactory the DirObjectFactory to be used. Null means that * no DirObjectFactory will be used. */ public void setDirObjectFactory(Class dirObjectFactory) { this.dirObjectFactory = dirObjectFactory; } /** * Get the DirObjectFactory to use. * * @return the DirObjectFactory to be used. null means that no * DirObjectFactory will be used. */ public Class getDirObjectFactory() { return dirObjectFactory; } /** * Checks that all necessary data is set and that there is no compatibility * issues, after which the instance is initialized. Note that you need to * call this method explicitly after setting all desired properties if using * the class outside of a Spring Context. */ public void afterPropertiesSet() throws Exception { if (ArrayUtils.isEmpty(urls)) { throw new IllegalArgumentException("At least one server url must be set"); } if (!DistinguishedName.EMPTY_PATH.equals(base) && getJdkVersion().compareTo(JDK_142) < 0) { throw new IllegalArgumentException("Base path is not supported for JDK versions < 1.4.2"); } if (authenticationSource == null) { log.debug("AuthenticationSource not set - " + "using default implementation"); if (StringUtils.isBlank(userDn)) { log.info("Property 'userDn' not set - " + "anonymous context will be used for read-write operations"); } else if (StringUtils.isBlank(password)) { log.info("Property 'password' not set - " + "blank password will be used"); } authenticationSource = new SimpleAuthenticationSource(); } if (cacheEnvironmentProperties) { anonymousEnv = setupAnonymousEnv(); } } private Hashtable setupAnonymousEnv() { if (pooled) { baseEnv.put(SUN_LDAP_POOLING_FLAG, "true"); log.debug("Using LDAP pooling."); } else { baseEnv.remove(SUN_LDAP_POOLING_FLAG); log.debug("Not using LDAP pooling"); } Hashtable env = new Hashtable(baseEnv); env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory.getName()); env.put(Context.PROVIDER_URL, assembleProviderUrlString(urls)); if (dirObjectFactory != null) { env.put(Context.OBJECT_FACTORIES, dirObjectFactory.getName()); } if (!StringUtils.isBlank(referral)) { env.put(Context.REFERRAL, referral); } if (!DistinguishedName.EMPTY_PATH.equals(base)) { // Save the base path for use in the DefaultDirObjectFactory. env.put(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY, base); } log.debug("Trying provider Urls: " + assembleProviderUrlString(urls)); return env; } /** * Set the password (credentials) to use for getting authenticated contexts. * * @param password the password. */ public void setPassword(String password) { this.password = password; } /** * Set the user distinguished name (principal) to use for getting * authenticated contexts. * * @param userDn the user distinguished name. */ public void setUserDn(String userDn) { this.userDn = userDn; } /** * Set the urls of the LDAP servers. Use this method if several servers are * required. * * @param urls the urls of all servers. */ public void setUrls(String[] urls) { this.urls = (String[]) urls.clone(); } /** * Get the urls of the LDAP servers. * * @return the urls of all servers. */ public String[] getUrls() { return (String[]) urls.clone(); } /** * Set the url of the LDAP server. Utility method if only one server is * used. * * @param url the url of the LDAP server. */ public void setUrl(String url) { this.urls = new String[] { url }; } /** * Set whether the pooling flag should be set, enabling the built-in LDAP * connection pooling. Default is false. The built-in LDAP * connection pooling suffers from a number of deficiencies, e.g. no * connection validation. Also, enabling this flag when using TLS * connections will explicitly not work. Consider using the Spring LDAP * PoolingContextSource as an alternative instead of enabling * this flag. *

* Note that since LDAP pooling is system wide, full configuration of this * needs be done using system parameters as specified in the LDAP/JNDI * documentation. Also note, that pooling is done on user dn basis, i.e. * each individually authenticated connection will be pooled separately. * This means that LDAP pooling will be most efficient using anonymous * connections or connections authenticated using one single system user. * * @param pooled whether Contexts should be pooled. */ public void setPooled(boolean pooled) { this.pooled = pooled; } /** * Get whether the pooling flag should be set. * * @return whether Contexts should be pooled. */ public boolean isPooled() { return pooled; } /** * If any custom environment properties are needed, these can be set using * this method. * * @param baseEnvironmentProperties */ public void setBaseEnvironmentProperties(Map baseEnvironmentProperties) { this.baseEnv = new Hashtable(baseEnvironmentProperties); } String getJdkVersion() { return JdkVersion.getJavaVersion(); } protected Hashtable getAnonymousEnv() { if (cacheEnvironmentProperties) { return anonymousEnv; } else { return setupAnonymousEnv(); } } protected Hashtable getAuthenticatedEnv(String principal, String credentials) { // The authenticated environment should always be rebuilt. Hashtable env = new Hashtable(getAnonymousEnv()); setupAuthenticatedEnvironment(env, principal, credentials); return env; } /** * Set the authentication source to use when retrieving user principal and * credentials. * * @param authenticationSource the {@link AuthenticationSource} that will * provide user info. */ public void setAuthenticationSource(AuthenticationSource authenticationSource) { this.authenticationSource = authenticationSource; } /** * Get the authentication source. * * @return the {@link AuthenticationSource} that will provide user info. */ public AuthenticationSource getAuthenticationSource() { return authenticationSource; } /** * Set whether environment properties should be cached between requsts for * anonymous environment. Default is true; setting this * property to false causes the environment Hashmap to be * rebuilt from the current property settings of this instance between each * request for an anonymous environment. * * @param cacheEnvironmentProperties true causes that the * anonymous environment properties should be cached, false * causes the Hashmap to be rebuilt for each request. */ public void setCacheEnvironmentProperties(boolean cacheEnvironmentProperties) { this.cacheEnvironmentProperties = cacheEnvironmentProperties; } /** * Set whether an anonymous environment should be used for read-only * operations. Default is false. * * @param anonymousReadOnly true if an anonymous environment * should be used for read-only operations, false otherwise. */ public void setAnonymousReadOnly(boolean anonymousReadOnly) { this.anonymousReadOnly = anonymousReadOnly; } /** * Get whether an anonymous environment should be used for read-only * operations. * * @return true if an anonymous environment should be used for * read-only operations, false otherwise. */ public boolean isAnonymousReadOnly() { return anonymousReadOnly; } /** * Set the {@link DirContextAuthenticationStrategy} to use for preparing the * environment and processing the created DirContext instances. * * @param authenticationStrategy the * {@link DirContextAuthenticationStrategy} to use; default is * {@link SimpleDirContextAuthenticationStrategy}. */ public void setAuthenticationStrategy(DirContextAuthenticationStrategy authenticationStrategy) { this.authenticationStrategy = authenticationStrategy; } /** * Set the method to handle referrals. Default is 'ignore'; setting this * flag to 'follow' will enable referrals to be automatically followed. Note * that this might require particular name server setup in order to work * (the referred URLs will need to be automatically found using standard DNS * resolution). * @param referral the value to set the system property * Context.REFERRAL to, customizing the way that referrals are * handled. */ public void setReferral(String referral) { this.referral = referral; } /** * Implement in subclass to create a DirContext of the desired type (e.g. * InitialDirContext or InitialLdapContext). * * @param environment the environment to use when creating the instance. * @return a new DirContext instance. * @throws NamingException if one is encountered when creating the instance. */ protected abstract DirContext getDirContextInstance(Hashtable environment) throws NamingException; class SimpleAuthenticationSource implements AuthenticationSource { public String getPrincipal() { return userDn; } public String getCredentials() { return password; } } }