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