1
0
mirror of synced 2026-08-04 09:17:02 +00:00

SEC-1124: Refactored LDAP code into separate module

This commit is contained in:
Luke Taylor
2009-03-19 06:30:32 +00:00
parent 69b86fd045
commit 4aae5ec42e
71 changed files with 117 additions and 235 deletions
@@ -1,205 +0,0 @@
package org.springframework.security.config;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.Lifecycle;
import org.springframework.core.io.Resource;
import org.springframework.ldap.core.ContextSource;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.directory.server.configuration.MutableServerStartupConfiguration;
import org.apache.directory.server.jndi.ServerContextFactory;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.apache.directory.server.core.configuration.ShutdownConfiguration;
import org.apache.directory.server.core.DirectoryService;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Properties;
import java.io.File;
import java.io.IOException;
/**
* Provides lifecycle services for the embedded apacheDS server defined by the supplied configuration.
* Used by {@link LdapServerBeanDefinitionParser}. An instance will be stored in the application context for
* each embedded server instance. It will start the server when the context is initialized and shut it down when
* it is closed. It is intended for temporary embedded use and will not retain changes across start/stop boundaries. The
* working directory is deleted on shutdown.
*
* <p>
* If used repeatedly in a single JVM process with the same configuration (for example, when
* repeatedly loading an application context during testing), it's important that the
* application context is closed to allow the bean to be disposed of and the server shutdown
* prior to attempting to start it again.
* </p>
*
* @author Luke Taylor
* @version $Id$
*/
class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle, ApplicationContextAware {
private Log logger = LogFactory.getLog(getClass());
private MutableServerStartupConfiguration configuration;
private ApplicationContext ctxt;
private File workingDir;
private ContextSource contextSource;
private boolean running;
private String ldifResources;
public ApacheDSContainer(MutableServerStartupConfiguration config, ContextSource contextSource, String ldifs) {
this.configuration = config;
this.contextSource = contextSource;
this.ldifResources = ldifs;
}
public void afterPropertiesSet() throws Exception {
if (workingDir == null) {
String apacheWorkDir = System.getProperty("apacheDSWorkDir");
if (apacheWorkDir == null) {
apacheWorkDir = System.getProperty("java.io.tmpdir") + File.separator + "apacheds-spring-security";
}
setWorkingDirectory(new File(apacheWorkDir));
}
start();
}
public void destroy() throws Exception {
stop();
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctxt = applicationContext;
}
private boolean deleteDir(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
for (int i=0; i < children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
return dir.delete();
}
public void setWorkingDirectory(File workingDir) {
Assert.notNull(workingDir);
logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath());
if (workingDir.exists()) {
throw new IllegalArgumentException("The specified working directory '" + workingDir.getAbsolutePath() +
"' already exists. Another directory service instance may be using it or it may be from a " +
" previous unclean shutdown. Please confirm and delete it or configure a different " +
"working directory");
}
this.workingDir = workingDir;
configuration.setWorkingDirectory(workingDir);
}
@SuppressWarnings("unchecked")
public void start() {
if (isRunning()) {
return;
}
DirectoryService ds = DirectoryService.getInstance(configuration.getInstanceId());
if (ds.isStarted()) {
throw new IllegalStateException("A DirectoryService with Id '" + configuration.getInstanceId() + "' is already running.");
}
logger.info("Starting directory server with Id '" + configuration.getInstanceId() + "'");
Properties env = new Properties();
env.setProperty(Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName());
env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
env.setProperty(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
env.setProperty(Context.SECURITY_CREDENTIALS, "secret");
env.putAll(configuration.toJndiEnvironment());
try {
new InitialDirContext(env);
} catch (NamingException e) {
logger.error("Failed to start directory service", e);
return;
}
running = true;
try {
importLdifs();
} catch (Exception e) {
logger.error("Failed to import LDIF file(s)", e);
}
}
private void importLdifs() throws IOException, NamingException {
// Import any ldif files
Resource[] ldifs = ctxt.getResources(ldifResources);
// Note that we can't just import using the ServerContext returned
// from starting Apace DS, apparently because of the long-running issue DIRSERVER-169.
// We need a standard context.
DirContext dirContext = contextSource.getReadWriteContext();
if(ldifs != null && ldifs.length > 0) {
try {
String ldifFile = ldifs[0].getFile().getAbsolutePath();
logger.info("Loading LDIF file: " + ldifFile);
LdifFileLoader loader = new LdifFileLoader(dirContext, ldifFile);
loader.execute();
} finally {
dirContext.close();
}
}
}
@SuppressWarnings("unchecked")
public void stop() {
Properties env = new Properties();
env.setProperty(Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName());
env.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
env.setProperty(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
env.setProperty(Context.SECURITY_CREDENTIALS, "secret");
ShutdownConfiguration shutdown = new ShutdownConfiguration(configuration.getInstanceId());
env.putAll(shutdown.toJndiEnvironment());
logger.info("Shutting down directory server with Id '" + configuration.getInstanceId() + "'");
try {
new InitialContext(env);
} catch (NamingException e) {
logger.error("Failed to shutdown directory server", e);
return;
}
running = false;
if (workingDir.exists()) {
logger.info("Deleting working directory " + workingDir.getAbsolutePath());
deleteDir(workingDir);
}
}
public boolean isRunning() {
return running;
}
}
@@ -6,7 +6,7 @@ package org.springframework.security.config;
* @author Ben Alex
* @version $Id$
*/
abstract class Elements {
public abstract class Elements {
public static final String AUTHENTICATION_MANAGER = "authentication-manager";
public static final String USER_SERVICE = "user-service";
@@ -1,71 +0,0 @@
package org.springframework.security.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
/**
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
class LdapConfigUtils {
/**
* Checks for the presence of a ContextSource instance. Also supplies the standard reference to any
* unconfigured <ldap-authentication-provider> or <ldap-user-service> beans. This is
* necessary in cases where the user has given the server a specific Id, but hasn't used
* the server-ref attribute to link this to the other ldap definitions. See SEC-799.
*/
private static class ContextSourceSettingPostProcessor implements BeanFactoryPostProcessor, Ordered {
/** If set to true, a bean parser has indicated that the default context source name needs to be set */
private boolean defaultNameRequired;
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) throws BeansException {
String[] sources = bf.getBeanNamesForType(BaseLdapPathContextSource.class);
if (sources.length == 0) {
throw new SecurityConfigurationException("No BaseLdapPathContextSource instances found. Have you " +
"added an <" + Elements.LDAP_SERVER + " /> element to your application context?");
}
if (!bf.containsBean(BeanIds.CONTEXT_SOURCE) && defaultNameRequired) {
if (sources.length > 1) {
throw new SecurityConfigurationException("More than one BaseLdapPathContextSource instance found. " +
"Please specify a specific server id using the 'server-ref' attribute when configuring your <" +
Elements.LDAP_PROVIDER + "> " + "or <" + Elements.LDAP_USER_SERVICE + ">.");
}
bf.registerAlias(sources[0], BeanIds.CONTEXT_SOURCE);
}
}
public void setDefaultNameRequired(boolean defaultNameRequired) {
this.defaultNameRequired = defaultNameRequired;
}
public int getOrder() {
return LOWEST_PRECEDENCE;
}
}
static void registerPostProcessorIfNecessary(BeanDefinitionRegistry registry, boolean defaultNameRequired) {
if (registry.containsBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR)) {
if (defaultNameRequired) {
BeanDefinition bd = registry.getBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR);
bd.getPropertyValues().addPropertyValue("defaultNameRequired", Boolean.valueOf(defaultNameRequired));
}
return;
}
BeanDefinition bd = new RootBeanDefinition(ContextSourceSettingPostProcessor.class);
registry.registerBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR, bd);
bd.getPropertyValues().addPropertyValue("defaultNameRequired", Boolean.valueOf(defaultNameRequired));
}
}
@@ -1,17 +1,14 @@
package org.springframework.security.config;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
@@ -83,9 +80,9 @@ public class LdapProviderBeanDefinitionParser implements BeanDefinitionParser {
parserContext.getReaderContext().warning("Salt source information isn't valid when used with LDAP",
passwordEncoderElement);
}
} else if (StringUtils.hasText(hash)) {
Class<? extends PasswordEncoder> encoderClass = PasswordEncoderParser.ENCODER_CLASSES.get(hash);
authenticatorBuilder.addPropertyValue("passwordEncoder", new RootBeanDefinition(encoderClass));
} else if (StringUtils.hasText(hash)) {;
authenticatorBuilder.addPropertyValue("passwordEncoder",
PasswordEncoderParser.createPasswordEncoderBeanDefinition(hash, false));
}
}
@@ -148,7 +148,7 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser {
contextSource.addPropertyValue("userDn", "uid=admin,ou=system");
contextSource.addPropertyValue("password", "secret");
RootBeanDefinition apacheContainer = new RootBeanDefinition("org.springframework.security.config.ApacheDSContainer", null, null);
RootBeanDefinition apacheContainer = new RootBeanDefinition("org.springframework.security.config.ldap.ApacheDSContainer", null, null);
apacheContainer.setSource(source);
apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(configuration.getBeanDefinition());
apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(contextSource.getBeanDefinition());
@@ -2,7 +2,9 @@ package org.springframework.security.config;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.util.StringUtils;
@@ -88,11 +90,25 @@ public class LdapUserServiceBeanDefinitionParser extends AbstractUserDetailsServ
RuntimeBeanReference contextSource = new RuntimeBeanReference(server);
contextSource.setSource(parserContext.extractSource(elt));
LdapConfigUtils.registerPostProcessorIfNecessary(parserContext.getRegistry(), requiresDefaultName);
registerPostProcessorIfNecessary(parserContext.getRegistry(), requiresDefaultName);
return contextSource;
}
private static void registerPostProcessorIfNecessary(BeanDefinitionRegistry registry, boolean defaultNameRequired) {
if (registry.containsBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR)) {
if (defaultNameRequired) {
BeanDefinition bd = registry.getBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR);
bd.getPropertyValues().addPropertyValue("defaultNameRequired", Boolean.valueOf(defaultNameRequired));
}
return;
}
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition("org.springframework.security.config.ldap.ContextSourceSettingPostProcessor");
bdb.addPropertyValue("defaultNameRequired", Boolean.valueOf(defaultNameRequired));
registry.registerBeanDefinition(BeanIds.CONTEXT_SOURCE_SETTING_POST_PROCESSOR, bdb.getBeanDefinition());
}
static RootBeanDefinition parseUserDetailsClass(Element elt, ParserContext parserContext) {
String userDetailsClass = elt.getAttribute(ATT_USER_CLASS);
@@ -6,16 +6,18 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.providers.encoding.BaseDigestPasswordEncoder;
import org.springframework.security.providers.encoding.LdapShaPasswordEncoder;
import org.springframework.security.providers.encoding.Md4PasswordEncoder;
import org.springframework.security.providers.encoding.Md5PasswordEncoder;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.providers.encoding.PlaintextPasswordEncoder;
import org.springframework.security.providers.encoding.ShaPasswordEncoder;
import org.springframework.security.providers.ldap.authenticator.LdapShaPasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
@@ -39,10 +41,10 @@ class PasswordEncoderParser {
static final String OPT_HASH_MD5 = "md5";
static final String OPT_HASH_LDAP_SHA = "{sha}";
static final Map<String, Class<? extends PasswordEncoder>> ENCODER_CLASSES;
private static final Map<String, Class<? extends PasswordEncoder>> ENCODER_CLASSES;
static {
ENCODER_CLASSES = new HashMap<String, Class<? extends PasswordEncoder>>(6);
ENCODER_CLASSES = new HashMap<String, Class<? extends PasswordEncoder>>();
ENCODER_CLASSES.put(OPT_HASH_PLAINTEXT, PlaintextPasswordEncoder.class);
ENCODER_CLASSES.put(OPT_HASH_SHA, ShaPasswordEncoder.class);
ENCODER_CLASSES.put(OPT_HASH_SHA256, ShaPasswordEncoder.class);
@@ -51,7 +53,7 @@ class PasswordEncoderParser {
ENCODER_CLASSES.put(OPT_HASH_LDAP_SHA, LdapShaPasswordEncoder.class);
}
private Log logger = LogFactory.getLog(getClass());
private static Log logger = LogFactory.getLog(PasswordEncoderParser.class);
private BeanMetadataElement passwordEncoder;
private BeanMetadataElement saltSource;
@@ -73,22 +75,8 @@ class PasswordEncoderParser {
if (StringUtils.hasText(ref)) {
passwordEncoder = new RuntimeBeanReference(ref);
} else {
Class<? extends PasswordEncoder> beanClass = ENCODER_CLASSES.get(hash);
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
if (OPT_HASH_SHA256.equals(hash)) {
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, new Integer(256));
}
beanDefinition.setSource(parserContext.extractSource(element));
if (useBase64) {
if (BaseDigestPasswordEncoder.class.isAssignableFrom(beanClass)) {
beanDefinition.getPropertyValues().addPropertyValue("encodeHashAsBase64", "true");
} else {
logger.warn(ATT_BASE_64 + " isn't compatible with " + hash + " and will be ignored");
}
}
passwordEncoder = beanDefinition;
passwordEncoder = createPasswordEncoderBeanDefinition(hash, useBase64);
((RootBeanDefinition)passwordEncoder).setSource(parserContext.extractSource(element));
}
Element saltSourceElt = DomUtils.getChildElementByTagName(element, Elements.SALT_SOURCE);
@@ -98,6 +86,24 @@ class PasswordEncoderParser {
}
}
static BeanDefinition createPasswordEncoderBeanDefinition(String hash, boolean useBase64) {
Class<? extends PasswordEncoder> beanClass = ENCODER_CLASSES.get(hash);
BeanDefinitionBuilder beanBldr = BeanDefinitionBuilder.rootBeanDefinition(beanClass);
if (OPT_HASH_SHA256.equals(hash)) {
beanBldr.addConstructorArgValue(new Integer(256));
}
if (useBase64) {
if (BaseDigestPasswordEncoder.class.isAssignableFrom(beanClass)) {
beanBldr.addPropertyValue("encodeHashAsBase64", "true");
} else {
logger.warn(ATT_BASE_64 + " isn't compatible with " + hash + " and will be ignored");
}
}
return beanBldr.getBeanDefinition();
}
public BeanMetadataElement getPasswordEncoder() {
return passwordEncoder;
}
@@ -1,37 +0,0 @@
package org.springframework.security.ldap;
import org.springframework.ldap.core.DistinguishedName;
/**
* This implementation appends a name component to the <tt>userDnBase</tt> context using the
* <tt>usernameAttributeName</tt> property. So if the <tt>uid</tt> attribute is used to store the username, and the
* base DN is <tt>cn=users</tt> and we are creating a new user called "sam", then the DN will be
* <tt>uid=sam,cn=users</tt>.
*
* @author Luke Taylor
* @version $Id$
*/
public class DefaultLdapUsernameToDnMapper implements LdapUsernameToDnMapper {
private String userDnBase;
private String usernameAttribute;
/**
* @param userDnBase the base name of the DN
* @param usernameAttribute the attribute to append for the username component.
*/
public DefaultLdapUsernameToDnMapper(String userDnBase, String usernameAttribute) {
this.userDnBase = userDnBase;
this.usernameAttribute = usernameAttribute;
}
/**
* Assembles the Distinguished Name that should be used the given username.
*/
public DistinguishedName buildDn(String username) {
DistinguishedName dn = new DistinguishedName(userDnBase);
dn.add(usernameAttribute, username);
return dn;
}
}
@@ -1,80 +0,0 @@
package org.springframework.security.ldap;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.core.support.DirContextAuthenticationStrategy;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy;
import org.springframework.util.Assert;
/**
* ContextSource implementation which uses Spring LDAP's <tt>LdapContextSource</tt> as a base
* class. Used internally by the Spring Security LDAP namespace configuration.
* <p>
* From Spring Security 2.5, Spring LDAP 1.3 is used and the <tt>ContextSource</tt> interface
* provides support for binding with a username and password. As a result, Spring LDAP <tt>ContextSource</tt>
* implementations such as <tt>LdapContextSource</tt> may be used directly with Spring Security.
* <p>
* Spring LDAP 1.3 doesn't have JVM-level LDAP connection pooling enabled by default. This class sets the
* <tt>pooled</tt> property to true, but customizes the {@link DirContextAuthenticationStrategy} used to disable
* pooling when the <tt>DN</tt> doesn't match the <tt>userDn</tt> property. This prevents pooling for calls
* to {@link #getContext(String, String)} to authenticate as specific users.
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public class DefaultSpringSecurityContextSource extends LdapContextSource {
private static final Log logger = LogFactory.getLog(DefaultSpringSecurityContextSource.class);
private String rootDn;
/**
* Create and initialize an instance which will connect to the supplied LDAP URL.
*
* @param providerUrl an LDAP URL of the form <code>ldap://localhost:389/base_dn<code>
*/
public DefaultSpringSecurityContextSource(String providerUrl) {
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
StringTokenizer st = new StringTokenizer(providerUrl);
ArrayList<String> urls = new ArrayList<String>();
// 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);
urls.add(url.substring(0, url.lastIndexOf(urlRootDn)));
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");
}
}
setUrls(urls.toArray(new String[urls.size()]));
setBase(rootDn);
setPooled(true);
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
@Override
@SuppressWarnings("unchecked")
public void setupEnvironment(Hashtable env, String dn, String password) {
super.setupEnvironment(env, dn, password);
// Remove the pooling flag unless we are authenticating as the 'manager' user.
if (!userDn.equals(dn) && env.containsKey(SUN_LDAP_POOLING_FLAG)) {
logger.debug("Removing pooling flag for user " + dn);
env.remove(SUN_LDAP_POOLING_FLAG);
}
}
});
}
}
@@ -1,47 +0,0 @@
/* 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.springframework.security.ldap;
import java.util.List;
import org.springframework.security.GrantedAuthority;
import org.springframework.ldap.core.DirContextOperations;
/**
* Obtains a list of granted authorities for an Ldap user.
* <p>
* Used by the <tt>LdapAuthenticationProvider</tt> once a user has been
* authenticated to create the final user details object.
* </p>
*
* @author Luke Taylor
* @version $Id$
*/
public interface LdapAuthoritiesPopulator {
//~ Methods ========================================================================================================
/**
* Get the list of authorities for the user.
*
* @param userData the context object which was returned by the LDAP authenticator.
*
* @return the granted authorities for the given user.
*
*/
List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username);
}
@@ -1,35 +0,0 @@
/* 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.springframework.security.ldap;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
/**
* Callback object for use with SpringSecurityLdapTemplate.
*
* @deprecated use spring-ldap ContextExecutor instead.
* @TODO: Delete before 2.0 release
*
* @author Ben Alex
*/
public interface LdapCallback {
//~ Methods ========================================================================================================
Object doInDirContext(DirContext dirContext)
throws NamingException;
}
@@ -1,35 +0,0 @@
/* 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.springframework.security.ldap;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
/**
* A mapper for use with {@link SpringSecurityLdapTemplate}. Creates a customized object from
* a set of attributes retrieved from a directory entry.
*
* @author Luke Taylor
* @deprecated in favour of Spring LDAP ContextMapper
* @version $Id$
*/
public interface LdapEntryMapper {
//~ Methods ========================================================================================================
Object mapAttributes(String dn, Attributes attributes)
throws NamingException;
}
@@ -1,45 +0,0 @@
/* 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.springframework.security.ldap;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.userdetails.UsernameNotFoundException;
/**
* Obtains a user's information from the LDAP directory given a login name.
* <p>
* May be optionally used to configure the LDAP authentication implementation when
* a more sophisticated approach is required than just using a simple username->DN
* mapping.
* </p>
*
* @author Luke Taylor
* @version $Id$
*/
public interface LdapUserSearch {
//~ Methods ========================================================================================================
/**
* Locates a single user in the directory and returns the LDAP information for that user.
*
* @param username the login name supplied to the authentication service.
*
* @return a DirContextOperations object containing the user's full DN and requested attributes.
* @throws UsernameNotFoundException if no user with the supplied name could be located by the search.
*/
DirContextOperations searchForUser(String username) throws UsernameNotFoundException;
}
@@ -1,13 +0,0 @@
package org.springframework.security.ldap;
import org.springframework.ldap.core.DistinguishedName;
/**
* Constructs an Ldap Distinguished Name from a username.
*
* @author Luke Taylor
* @version $Id$
*/
public interface LdapUsernameToDnMapper {
DistinguishedName buildDn(String username);
}
@@ -1,192 +0,0 @@
/* 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.springframework.security.ldap;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.naming.Context;
import javax.naming.NamingException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* LDAP Utility methods.
*
* @author Luke Taylor
* @version $Id$
*/
public final class LdapUtils {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(LdapUtils.class);
//~ Constructors ===================================================================================================
private LdapUtils() {
}
//~ Methods ========================================================================================================
public static void closeContext(Context ctx) {
if(ctx instanceof DirContextAdapter) {
return;
}
try {
if (ctx != null) {
ctx.close();
}
} catch (NamingException e) {
logger.error("Failed to close context.", e);
}
}
/**
* Obtains the part of a DN relative to a supplied base context.
* <p>If the DN is "cn=bob,ou=people,dc=springframework,dc=org" and the base context name is
* "ou=people,dc=springframework,dc=org" it would return "cn=bob".
* </p>
*
* @param fullDn the DN
* @param baseCtx the context to work out the name relative to.
*
* @return the
*
* @throws NamingException any exceptions thrown by the context are propagated.
*/
public static String getRelativeName(String fullDn, Context baseCtx) throws NamingException {
String baseDn = baseCtx.getNameInNamespace();
if (baseDn.length() == 0) {
return fullDn;
}
DistinguishedName base = new DistinguishedName(baseDn);
DistinguishedName full = new DistinguishedName(fullDn);
if(base.equals(full)) {
return "";
}
Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN");
full.removeFirst(base);
return full.toString();
}
/**
* Gets the full dn of a name by prepending the name of the context it is relative to.
* If the name already contains the base name, it is returned unaltered.
*/
public static DistinguishedName getFullDn(DistinguishedName dn, Context baseCtx)
throws NamingException {
DistinguishedName baseDn = new DistinguishedName(baseCtx.getNameInNamespace());
if(dn.contains(baseDn)) {
return dn;
}
baseDn.append(dn);
return baseDn;
}
public static byte[] getUtf8Bytes(String s) {
try {
return s.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// Should be impossible since UTF-8 is required by all implementations
throw new IllegalStateException("Failed to convert string to UTF-8 bytes. Shouldn't be possible");
}
}
public static String getUtf8BytesAsString(byte[] utf8) {
try {
return new String(utf8, "UTF-8");
} catch (UnsupportedEncodingException e) {
// Should be impossible since UTF-8 is required by all implementations
throw new IllegalStateException("Failed to convert string to UTF-8 bytes. Shouldn't be possible");
}
}
public static String convertPasswordToString(Object passObj) {
Assert.notNull(passObj, "Password object to convert must not be null");
if(passObj instanceof byte[]) {
return getUtf8BytesAsString((byte[])passObj);
} else if (passObj instanceof String) {
return (String)passObj;
} else {
throw new IllegalArgumentException("Password object was not a String or byte array.");
}
}
/**
* Works out the root DN for an LDAP URL.<p>For example, the URL
* <tt>ldap://monkeymachine:11389/dc=springframework,dc=org</tt> has the root DN "dc=springframework,dc=org".</p>
*
* @param url the LDAP URL
*
* @return the root DN
*/
public static String parseRootDnFromUrl(String url) {
Assert.hasLength(url);
String urlRootDn = "";
if (url.startsWith("ldap:") || url.startsWith("ldaps:")) {
URI uri = parseLdapUrl(url);
urlRootDn = uri.getRawPath();
} else {
// Assume it's an embedded server
urlRootDn = url;
}
if (urlRootDn.startsWith("/")) {
urlRootDn = urlRootDn.substring(1);
}
return urlRootDn;
}
/**
* Parses the supplied LDAP URL.
* @param url the URL (e.g. <tt>ldap://monkeymachine:11389/dc=springframework,dc=org</tt>).
* @return the URI object created from the URL
* @throws IllegalArgumentException if the URL is null, empty or the URI syntax is invalid.
*/
private static URI parseLdapUrl(String url) {
Assert.hasLength(url);
try {
return new URI(url);
} catch (URISyntaxException e) {
IllegalArgumentException iae = new IllegalArgumentException("Unable to parse url: " + url);
iae.initCause(e);
throw iae;
}
}
}
@@ -1,32 +0,0 @@
/* 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.springframework.security.ldap;
import org.springframework.dao.DataAccessException;
import javax.naming.NamingException;
/**
* @author Luke Taylor
* @deprecated Spring ldap is used instead.
* @version $Id$
*/
public interface NamingExceptionTranslator {
//~ Methods ========================================================================================================
DataAccessException translate(String task, NamingException e);
}
@@ -1,69 +0,0 @@
package org.springframework.security.ldap;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
import org.springframework.security.userdetails.ldap.LdapUserDetails;
import org.springframework.ldap.core.AuthenticationSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* An AuthenticationSource to retrieve authentication information stored in Spring Security's
* {@link SecurityContextHolder}.
* <p>
* This is a copy of Spring LDAP's AcegiAuthenticationSource, updated for use with Spring Security 2.0.
*
* @author Mattias Arthursson
* @author Luke Taylor
* @since 2.0
* @version $Id$
*/
public class SpringSecurityAuthenticationSource implements AuthenticationSource {
private static final Log log = LogFactory.getLog(SpringSecurityAuthenticationSource.class);
/**
* Get the principals of the logged in user, in this case the distinguished
* name.
*
* @return the distinguished name of the logged in user.
*/
public String getPrincipal() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
log.warn("No Authentication object set in SecurityContext - returning empty String as Principal");
return "";
}
Object principal = authentication.getPrincipal();
if (principal instanceof LdapUserDetails) {
LdapUserDetails details = (LdapUserDetails) principal;
return details.getDn();
} else if (authentication instanceof AnonymousAuthenticationToken) {
if (log.isDebugEnabled()) {
log.debug("Anonymous Authentication, returning empty String as Principal");
}
return "";
} else {
throw new IllegalArgumentException("The principal property of the authentication object"
+ "needs to be an LdapUserDetails.");
}
}
/**
* @see org.springframework.ldap.core.AuthenticationSource#getCredentials()
*/
public String getCredentials() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
log.warn("No Authentication object set in SecurityContext - returning empty String as Credentials");
return "";
}
return (String) authentication.getCredentials();
}
}
@@ -1,28 +0,0 @@
package org.springframework.security.ldap;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.core.ContextSource;
import javax.naming.directory.DirContext;
/**
* Extension of {@link ContextSource} which allows binding explicitly as a particular user.
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*
* @deprecated As of Spring LDAP 1.3, ContextSource provides this method itself.
*/
public interface SpringSecurityContextSource extends BaseLdapPathContextSource {
/**
* Obtains a context using the supplied distinguished name and credentials.
*
* @param userDn the distinguished name of the user to authenticate as
* @param credentials the user's password
* @return a context authenticated as the supplied user
*/
DirContext getReadWriteContext(String userDn, Object credentials);
}
@@ -1,237 +0,0 @@
/* 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.springframework.security.ldap;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.PartialResultException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapEncoder;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.util.Assert;
/**
* Extension of Spring LDAP's LdapTemplate class which adds extra functionality required by Spring Security.
*
* @author Ben Alex
* @author Luke Taylor
* @since 2.0
*/
public class SpringSecurityLdapTemplate extends LdapTemplate {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(SpringSecurityLdapTemplate.class);
public static final String[] NO_ATTRS = new String[0];
//~ Instance fields ================================================================================================
/** Default search controls */
private SearchControls searchControls = new SearchControls();
//~ Constructors ===================================================================================================
public SpringSecurityLdapTemplate(ContextSource contextSource) {
Assert.notNull(contextSource, "ContextSource cannot be null");
setContextSource(contextSource);
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
}
//~ Methods ========================================================================================================
/**
* Performs an LDAP compare operation of the value of an attribute for a particular directory entry.
*
* @param dn the entry who's attribute is to be used
* @param attributeName the attribute who's value we want to compare
* @param value the value to be checked against the directory value
*
* @return true if the supplied value matches that in the directory
*/
public boolean compare(final String dn, final String attributeName, final Object value) {
final String comparisonFilter = "(" + attributeName + "={0})";
class LdapCompareCallback implements ContextExecutor {
public Object executeWithContext(DirContext ctx) throws NamingException {
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(NO_ATTRS);
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, new Object[] {value}, ctls);
return Boolean.valueOf(results.hasMore());
}
}
Boolean matches = (Boolean) executeReadOnly(new LdapCompareCallback());
return matches.booleanValue();
}
/**
* Composes an object from the attributes of the given DN.
*
* @param dn the directory entry which will be read
* @param attributesToRetrieve the named attributes which will be retrieved from the directory entry.
*
* @return the object created by the mapper
*/
public DirContextOperations retrieveEntry(final String dn, final String[] attributesToRetrieve) {
return (DirContextOperations) executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
// Object object = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
return new DirContextAdapter(attrs, new DistinguishedName(dn),
new DistinguishedName(ctx.getNameInNamespace()));
}
});
}
/**
* Performs a search using the supplied filter and returns the union of the values of the named attribute
* found in all entries matched by the search. Note that one directory entry may have several values for the
* attribute. Intended for role searches and similar scenarios.
*
* @param base the DN to search in
* @param filter search filter to use
* @param params the parameters to substitute in the search filter
* @param attributeName the attribute who's values are to be retrieved.
*
* @return the set of String values for the attribute as a union of the values found in all the matching entries.
*/
public Set<String> searchForSingleAttributeValues(final String base, final String filter, final Object[] params,
final String attributeName) {
// Escape the params acording to RFC2254
Object[] encodedParams = new String[params.length];
for (int i=0; i < params.length; i++) {
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
}
String formattedFilter = MessageFormat.format(filter, encodedParams);
logger.debug("Using filter: " + formattedFilter);
final HashSet<String> set = new HashSet<String>();
ContextMapper roleMapper = new ContextMapper() {
public Object mapFromContext(Object ctx) {
DirContextAdapter adapter = (DirContextAdapter) ctx;
String[] values = adapter.getStringAttributes(attributeName);
if (values == null || values.length == 0) {
logger.debug("No attribute value found for '" + attributeName + "'");
} else {
set.addAll(Arrays.asList(values));
}
return null;
}
};
SearchControls ctls = new SearchControls();
ctls.setSearchScope(searchControls.getSearchScope());
ctls.setReturningAttributes(new String[] {attributeName});
search(base, formattedFilter, ctls, roleMapper);
return set;
}
/**
* Performs a search, with the requirement that the search shall return a single directory entry, and uses
* the supplied mapper to create the object from that entry.
* <p>
* Ignores <tt>PartialResultException</tt> if thrown, for compatibility with Active Directory
* (see {@link LdapTemplate#setIgnorePartialResultException(boolean)}).
*
* @param base the search base, relative to the base context supplied by the context source.
* @param filter the LDAP search filter
* @param params parameters to be substituted in the search.
*
* @return a DirContextOperations instance created from the matching entry.
*
* @throws IncorrectResultSizeDataAccessException if no results are found or the search returns more than one
* result.
*/
public DirContextOperations searchForSingleEntry(final String base, final String filter, final Object[] params) {
return (DirContextOperations) executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
DistinguishedName ctxBaseDn = new DistinguishedName(ctx.getNameInNamespace());
NamingEnumeration<SearchResult> resultsEnum = ctx.search(base, filter, params, searchControls);
Set<DirContextOperations> results = new HashSet<DirContextOperations>();
try {
while (resultsEnum.hasMore()) {
SearchResult searchResult = resultsEnum.next();
// Work out the DN of the matched entry
StringBuffer dn = new StringBuffer(searchResult.getName());
if (base.length() > 0) {
dn.append(",");
dn.append(base);
}
results.add(new DirContextAdapter(searchResult.getAttributes(),
new DistinguishedName(dn.toString()), ctxBaseDn));
}
} catch (PartialResultException e) {
logger.info("Ignoring PartialResultException");
}
if (results.size() == 0) {
throw new IncorrectResultSizeDataAccessException(1, 0);
}
if (results.size() > 1) {
throw new IncorrectResultSizeDataAccessException(1, results.size());
}
return results.toArray()[0];
}
});
}
/**
* Sets the search controls which will be used for search operations by the template.
*
* @param searchControls the SearchControls instance which will be cached in the template.
*/
public void setSearchControls(SearchControls searchControls) {
this.searchControls = searchControls;
}
}
@@ -1,295 +0,0 @@
/* 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.springframework.security.ldap.populator;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.naming.directory.SearchControls;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* The default strategy for obtaining user role information from the directory.
* <p>
* It obtains roles by performing a search for "groups" the user is a member of.
* <p>
* A typical group search scenario would be where each group/role is specified using the <tt>groupOfNames</tt>
* (or <tt>groupOfUniqueNames</tt>) LDAP objectClass and the user's DN is listed in the <tt>member</tt> (or
* <tt>uniqueMember</tt>) attribute to indicate that they should be assigned that role. The following LDIF sample has
* the groups stored under the DN <tt>ou=groups,dc=springframework,dc=org</tt> and a group called "developers" with
* "ben" and "luke" as members:
* <pre>
* dn: ou=groups,dc=springframework,dc=org
* objectClass: top
* objectClass: organizationalUnit
* ou: groups
*
* dn: cn=developers,ou=groups,dc=springframework,dc=org
* objectClass: groupOfNames
* objectClass: top
* cn: developers
* description: Spring Security Developers
* member: uid=ben,ou=people,dc=springframework,dc=org
* member: uid=luke,ou=people,dc=springframework,dc=org
* ou: developer
* </pre>
* <p>
* The group search is performed within a DN specified by the <tt>groupSearchBase</tt> property, which should
* be relative to the root DN of its <tt>InitialDirContextFactory</tt>. If the search base is null, group searching is
* disabled. The filter used in the search is defined by the <tt>groupSearchFilter</tt> property, with the filter
* argument {0} being the full DN of the user. You can also optionally use the parameter {1}, which will be substituted
* with the username. You can also specify which attribute defines the role name by setting
* the <tt>groupRoleAttribute</tt> property (the default is "cn").
* <p>
* The configuration below shows how the group search might be performed with the above schema.
* <pre>
* &lt;bean id="ldapAuthoritiesPopulator"
* class="org.springframework.security.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
* &lt;constructor-arg ref="contextSource"/>
* &lt;constructor-arg value="ou=groups"/>
* &lt;property name="groupRoleAttribute" value="ou"/>
* &lt;!-- the following properties are shown with their default values -->
* &lt;property name="searchSubTree" value="false"/>
* &lt;property name="rolePrefix" value="ROLE_"/>
* &lt;property name="convertToUpperCase" value="true"/>
* &lt;/bean>
* </pre>
* A search for roles for user "uid=ben,ou=people,dc=springframework,dc=org" would return the single granted authority
* "ROLE_DEVELOPER".
* <p>
* The single-level search is performed by default. Setting the <tt>searchSubTree</tt> property to true will enable
* a search of the entire subtree under <tt>groupSearchBase</tt>.
*
* @author Luke Taylor
* @version $Id$
*/
public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(DefaultLdapAuthoritiesPopulator.class);
//~ Instance fields ================================================================================================
/**
* A default role which will be assigned to all authenticated users if set
*/
private GrantedAuthority defaultRole;
private SpringSecurityLdapTemplate ldapTemplate;
/**
* Controls used to determine whether group searches should be performed over the full sub-tree from the
* base DN. Modified by searchSubTree property
*/
private SearchControls searchControls = new SearchControls();
/**
* The ID of the attribute which contains the role name for a group
*/
private String groupRoleAttribute = "cn";
/**
* The base DN from which the search for group membership should be performed
*/
private String groupSearchBase;
/**
* The pattern to be used for the user search. {0} is the user's DN
*/
private String groupSearchFilter = "(member={0})";
/**
* Attributes of the User's LDAP Object that contain role name information.
*/
// private String[] userRoleAttributes = null;
private String rolePrefix = "ROLE_";
private boolean convertToUpperCase = true;
//~ Constructors ===================================================================================================
/**
* Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be
* set as a property.
*
* @param contextSource supplies the contexts used to search for user roles.
* @param groupSearchBase if this is an empty string the search will be performed from the root DN of the
* context factory.
*/
public DefaultLdapAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
Assert.notNull(contextSource, "contextSource must not be null");
ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
ldapTemplate.setSearchControls(searchControls);
setGroupSearchBase(groupSearchBase);
}
//~ Methods ========================================================================================================
/**
* This method should be overridden if required to obtain any additional
* roles for the given user (on top of those obtained from the standard
* search implemented by this class).
*
* @param user the context representing the user who's roles are required
* @return the extra roles which will be merged with those returned by the group search
*/
protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user, String username) {
return null;
}
/**
* Obtains the authorities for the user who's directory entry is represented by
* the supplied LdapUserDetails object.
*
* @param user the user who's authorities are required
* @return the set of roles granted to the user.
*/
public final List<GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username) {
String userDn = user.getNameInNamespace();
if (logger.isDebugEnabled()) {
logger.debug("Getting authorities for user " + userDn);
}
Set<GrantedAuthority> roles = getGroupMembershipRoles(userDn, username);
Set<GrantedAuthority> extraRoles = getAdditionalRoles(user, username);
if (extraRoles != null) {
roles.addAll(extraRoles);
}
if (defaultRole != null) {
roles.add(defaultRole);
}
List<GrantedAuthority> result = new ArrayList<GrantedAuthority>(roles.size());
result.addAll(roles);
return result;
}
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
if (getGroupSearchBase() == null) {
return Collections.emptySet();
}
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
if (logger.isDebugEnabled()) {
logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
+ groupSearchFilter + " in search base '" + getGroupSearchBase() + "'");
}
Set<String> userRoles = ldapTemplate.searchForSingleAttributeValues(getGroupSearchBase(), groupSearchFilter,
new String[]{userDn, username}, groupRoleAttribute);
if (logger.isDebugEnabled()) {
logger.debug("Roles from search: " + userRoles);
}
for (String role : userRoles) {
if (convertToUpperCase) {
role = role.toUpperCase();
}
authorities.add(new GrantedAuthorityImpl(rolePrefix + role));
}
return authorities;
}
protected ContextSource getContextSource() {
return ldapTemplate.getContextSource();
}
/**
* Set the group search base (name to search under)
*
* @param groupSearchBase if this is an empty string the search will be performed from the root DN of the context
* factory.
*/
private void setGroupSearchBase(String groupSearchBase) {
Assert.notNull(groupSearchBase, "The groupSearchBase (name to search under), must not be null.");
this.groupSearchBase = groupSearchBase;
if (groupSearchBase.length() == 0) {
logger.info("groupSearchBase is empty. Searches will be performed from the context source base");
}
}
protected String getGroupSearchBase() {
return groupSearchBase;
}
public void setConvertToUpperCase(boolean convertToUpperCase) {
this.convertToUpperCase = convertToUpperCase;
}
/**
* The default role which will be assigned to all users.
*
* @param defaultRole the role name, including any desired prefix.
*/
public void setDefaultRole(String defaultRole) {
Assert.notNull(defaultRole, "The defaultRole property cannot be set to null");
this.defaultRole = new GrantedAuthorityImpl(defaultRole);
}
public void setGroupRoleAttribute(String groupRoleAttribute) {
Assert.notNull(groupRoleAttribute, "groupRoleAttribute must not be null");
this.groupRoleAttribute = groupRoleAttribute;
}
public void setGroupSearchFilter(String groupSearchFilter) {
Assert.notNull(groupSearchFilter, "groupSearchFilter must not be null");
this.groupSearchFilter = groupSearchFilter;
}
/**
* Sets the prefix which will be prepended to the values loaded from the directory.
* Defaults to "ROLE_" for compatibility with <tt>RoleVoter/tt>.
*/
public void setRolePrefix(String rolePrefix) {
Assert.notNull(rolePrefix, "rolePrefix must not be null");
this.rolePrefix = rolePrefix;
}
/**
* If set to true, a subtree scope search will be performed. If false a single-level search is used.
*
* @param searchSubtree set to true to enable searching of the entire tree below the <tt>groupSearchBase</tt>.
*/
public void setSearchSubtree(boolean searchSubtree) {
int searchScope = searchSubtree ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE;
searchControls.setSearchScope(searchScope);
}
}
@@ -1,31 +0,0 @@
package org.springframework.security.ldap.populator;
import java.util.List;
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.util.Assert;
/**
* Simple LdapAuthoritiesPopulator which delegates to a UserDetailsService, using the name which
* was supplied at login as the username.
*
*
* @author Luke Taylor
* @version $Id$
* @since 2.0
*/
public class UserDetailsServiceLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
private UserDetailsService userDetailsService;
public UserDetailsServiceLdapAuthoritiesPopulator(UserDetailsService userService) {
Assert.notNull(userService, "userDetailsService cannot be null");
this.userDetailsService = userService;
}
public List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
return userDetailsService.loadUserByUsername(username).getAuthorities();
}
}
@@ -1,5 +0,0 @@
<html>
<body>
LdapAuthoritiesPopulator implementations.
</body>
</html>
@@ -1,182 +0,0 @@
/* 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.springframework.security.ldap.search;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.ldap.LdapUserSearch;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.util.Assert;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import javax.naming.directory.SearchControls;
/**
* LdapUserSearch implementation which uses an Ldap filter to locate the user.
*
* @author Robert Sanders
* @author Luke Taylor
* @version $Id$
*
* @see SearchControls
*/
public class FilterBasedLdapUserSearch implements LdapUserSearch {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(FilterBasedLdapUserSearch.class);
//~ Instance fields ================================================================================================
private ContextSource contextSource;
/**
* The LDAP SearchControls object used for the search. Shared between searches so shouldn't be modified
* once the bean has been configured.
*/
private SearchControls searchControls = new SearchControls();
/** Context name to search in, relative to the base of the configured ContextSource. */
private String searchBase = "";
/**
* The filter expression used in the user search. This is an LDAP search filter (as defined in 'RFC 2254')
* with optional arguments. See the documentation for the <tt>search</tt> methods in {@link
* javax.naming.directory.DirContext DirContext} for more information.
*
* <p>In this case, the username is the only parameter.</p>
* Possible examples are:
* <ul>
* <li>(uid={0}) - this would search for a username match on the uid attribute.</li>
* </ul>
*/
private String searchFilter;
//~ Constructors ===================================================================================================
public FilterBasedLdapUserSearch(String searchBase, String searchFilter, BaseLdapPathContextSource contextSource) {
Assert.notNull(contextSource, "contextSource must not be null");
Assert.notNull(searchFilter, "searchFilter must not be null.");
Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable).");
this.searchFilter = searchFilter;
this.contextSource = contextSource;
this.searchBase = searchBase;
setSearchSubtree(true);
if (searchBase.length() == 0) {
logger.info("SearchBase not set. Searches will be performed from the root: "
+ contextSource.getBaseLdapPath());
}
}
//~ Methods ========================================================================================================
/**
* Return the LdapUserDetails containing the user's information
*
* @param username the username to search for.
*
* @return An LdapUserDetails object containing the details of the located user's directory entry
*
* @throws UsernameNotFoundException if no matching entry is found.
*/
public DirContextOperations searchForUser(String username) {
if (logger.isDebugEnabled()) {
logger.debug("Searching for user '" + username + "', with user search " + this);
}
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource);
template.setSearchControls(searchControls);
try {
return template.searchForSingleEntry(searchBase, searchFilter, new String[] {username});
} catch (IncorrectResultSizeDataAccessException notFound) {
if (notFound.getActualSize() == 0) {
throw new UsernameNotFoundException("User " + username + " not found in directory.", username);
}
// Search should never return multiple results if properly configured, so just rethrow
throw notFound;
}
}
/**
* Sets the corresponding property on the {@link SearchControls} instance used in the search.
*
* @param deref the derefLinkFlag value as defined in SearchControls..
*/
public void setDerefLinkFlag(boolean deref) {
searchControls.setDerefLinkFlag(deref);
}
/**
* If true then searches the entire subtree as identified by context, if false (the default) then only
* searches the level identified by the context.
*
* @param searchSubtree true the underlying search controls should be set to SearchControls.SUBTREE_SCOPE
* rather than SearchControls.ONELEVEL_SCOPE.
*/
public void setSearchSubtree(boolean searchSubtree) {
searchControls.setSearchScope(searchSubtree ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE);
}
/**
* The time to wait before the search fails; the default is zero, meaning forever.
*
* @param searchTimeLimit the time limit for the search (in milliseconds).
*/
public void setSearchTimeLimit(int searchTimeLimit) {
searchControls.setTimeLimit(searchTimeLimit);
}
/**
* Specifies the attributes that will be returned as part of the search.
*<p>
* null indicates that all attributes will be returned.
* An empty array indicates no attributes are returned.
*
* @param attrs An array of attribute names identifying the attributes that
* will be returned. Can be null.
*/
public void setReturningAttributes(String[] attrs) {
searchControls.setReturningAttributes(attrs);
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("[ searchFilter: '").append(searchFilter).append("', ");
sb.append("searchBase: '").append(searchBase).append("'");
sb.append(", scope: ")
.append(searchControls.getSearchScope() == SearchControls.SUBTREE_SCOPE ? "subtree" : "single-level, ");
sb.append(", searchTimeLimit: ").append(searchControls.getTimeLimit());
sb.append(", derefLinkFlag: ").append(searchControls.getDerefLinkFlag()).append(" ]");
return sb.toString();
}
}
@@ -1,5 +0,0 @@
<html>
<body>
<tt>LdapUserSearch</tt> implementations. These may be used to locate the user in the directory.
</body>
</html>
@@ -13,10 +13,8 @@
* limitations under the License.
*/
package org.springframework.security.providers.ldap.authenticator;
package org.springframework.security.providers.encoding;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.providers.encoding.ShaPasswordEncoder;
import org.apache.commons.codec.binary.Base64;
@@ -1,304 +0,0 @@
/* 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.springframework.security.providers.ldap;
import java.util.List;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.AuthenticationServiceException;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.providers.AuthenticationProvider;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.userdetails.ldap.LdapUserDetailsMapper;
import org.springframework.security.userdetails.ldap.UserDetailsContextMapper;
import org.springframework.security.util.AuthorityUtils;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* An {@link org.springframework.security.providers.AuthenticationProvider} implementation that authenticates
* against an LDAP server.
* <p>
* There are many ways in which an LDAP directory can be configured so this class delegates most of
* its responsibilites to two separate strategy interfaces, {@link LdapAuthenticator}
* and {@link LdapAuthoritiesPopulator}.
*
* <h3>LdapAuthenticator</h3>
* This interface is responsible for performing the user authentication and retrieving
* the user's information from the directory. Example implementations are {@link
* org.springframework.security.providers.ldap.authenticator.BindAuthenticator BindAuthenticator} which authenticates
* the user by "binding" as that user, and
* {@link org.springframework.security.providers.ldap.authenticator.PasswordComparisonAuthenticator PasswordComparisonAuthenticator}
* which compares the supplied password with the value stored in the directory, using an LDAP "compare"
* operation.
* <p>
* The task of retrieving the user attributes is delegated to the authenticator because the permissions on the
* attributes may depend on the type of authentication being used; for example, if binding as the user, it may be
* necessary to read them with the user's own permissions (using the same context used for the bind operation).
*
* <h3>LdapAuthoritiesPopulator</h3>
* Once the user has been authenticated, this interface is called to obtain the set of granted authorities for the
* user.
* The {@link DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator}
* can be configured to obtain user role information from the user's attributes and/or to perform a search for
* "groups" that the user is a member of and map these to roles.
*
* <p>
* A custom implementation could obtain the roles from a completely different source, for example from a database.
*
* <h3>Configuration</h3>
*
* A simple configuration might be as follows:
* <pre>
* &lt;bean id=&quot;contextSource&quot;
* class=&quot;org.springframework.security.ldap.DefaultSpringSecurityContextSource&quot;&gt;
* &lt;constructor-arg value=&quot;ldap://monkeymachine:389/dc=springframework,dc=org&quot;/&gt;
* &lt;property name=&quot;userDn&quot; value=&quot;cn=manager,dc=springframework,dc=org&quot;/&gt;
* &lt;property name=&quot;password&quot; value=&quot;password&quot;/&gt;
* &lt;/bean&gt;
*
* &lt;bean id=&quot;ldapAuthProvider&quot;
* class=&quot;org.springframework.security.providers.ldap.LdapAuthenticationProvider&quot;&gt;
* &lt;constructor-arg&gt;
* &lt;bean class=&quot;org.springframework.security.providers.ldap.authenticator.BindAuthenticator&quot;&gt;
* &lt;constructor-arg ref=&quot;contextSource&quot;/&gt;
* &lt;property name=&quot;userDnPatterns&quot;&gt;&lt;list&gt;&lt;value&gt;uid={0},ou=people&lt;/value&gt;&lt;/list&gt;&lt;/property&gt;
* &lt;/bean&gt;
* &lt;/constructor-arg&gt;
* &lt;constructor-arg&gt;
* &lt;bean class=&quot;org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator&quot;&gt;
* &lt;constructor-arg ref=&quot;contextSource&quot;/&gt;
* &lt;constructor-arg value=&quot;ou=groups&quot;/&gt;
* &lt;property name=&quot;groupRoleAttribute&quot; value=&quot;ou&quot;/&gt;
* &lt;/bean&gt;
* &lt;/constructor-arg&gt;
* &lt;/bean&gt;
*</pre>
*
* <p>
* This would set up the provider to access an LDAP server with URL
* <tt>ldap://monkeymachine:389/dc=springframework,dc=org</tt>. Authentication will be performed by attempting to bind
* with the DN <tt>uid=&lt;user-login-name&gt;,ou=people,dc=springframework,dc=org</tt>. After successful
* authentication, roles will be assigned to the user by searching under the DN
* <tt>ou=groups,dc=springframework,dc=org</tt> with the default filter <tt>(member=&lt;user's-DN&gt;)</tt>. The role
* name will be taken from the "ou" attribute of each match.
* <p>
* The authenticate method will reject empty passwords outright. LDAP servers may allow an anonymous
* bind operation with an empty password, even if a DN is supplied. In practice this means that if
* the LDAP directory is configured to allow unauthenticated access, it might be possible to
* authenticate as <i>any</i> user just by supplying an empty password.
* More information on the misuse of unauthenticated access can be found in
* <a href="http://www.ietf.org/internet-drafts/draft-ietf-ldapbis-authmeth-19.txt">
* draft-ietf-ldapbis-authmeth-19.txt</a>.
*
*
* @author Luke Taylor
* @version $Id$
*
* @see org.springframework.security.providers.ldap.authenticator.BindAuthenticator
* @see DefaultLdapAuthoritiesPopulator
*/
public class LdapAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(LdapAuthenticationProvider.class);
//~ Instance fields ================================================================================================
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private LdapAuthenticator authenticator;
private LdapAuthoritiesPopulator authoritiesPopulator;
private UserDetailsContextMapper userDetailsContextMapper = new LdapUserDetailsMapper();
private boolean useAuthenticationRequestCredentials = true;
private boolean hideUserNotFoundExceptions = true;
//~ Constructors ===================================================================================================
/**
* Create an instance with the supplied authenticator and authorities populator implementations.
*
* @param authenticator the authentication strategy (bind, password comparison, etc)
* to be used by this provider for authenticating users.
* @param authoritiesPopulator the strategy for obtaining the authorities for a given user after they've been
* authenticated.
*/
public LdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
this.setAuthenticator(authenticator);
this.setAuthoritiesPopulator(authoritiesPopulator);
}
/**
* Creates an instance with the supplied authenticator and a null authorities populator.
* In this case, the authorities must be mapped from the user context.
*
* @param authenticator the authenticator strategy.
*/
public LdapAuthenticationProvider(LdapAuthenticator authenticator) {
this.setAuthenticator(authenticator);
this.setAuthoritiesPopulator(new NullAuthoritiesPopulator());
}
//~ Methods ========================================================================================================
private void setAuthenticator(LdapAuthenticator authenticator) {
Assert.notNull(authenticator, "An LdapAuthenticator must be supplied");
this.authenticator = authenticator;
}
private LdapAuthenticator getAuthenticator() {
return authenticator;
}
private void setAuthoritiesPopulator(LdapAuthoritiesPopulator authoritiesPopulator) {
Assert.notNull(authoritiesPopulator, "An LdapAuthoritiesPopulator must be supplied");
this.authoritiesPopulator = authoritiesPopulator;
}
protected LdapAuthoritiesPopulator getAuthoritiesPopulator() {
return authoritiesPopulator;
}
/**
* Allows a custom strategy to be used for creating the <tt>UserDetails</tt> which will be stored as the principal
* in the <tt>Authentication</tt> returned by the
* {@link #createSuccessfulAuthentication(UsernamePasswordAuthenticationToken, UserDetails)} method.
*
* @param userDetailsContextMapper the strategy instance. If not set, defaults to a simple
* <tt>LdapUserDetailsMapper</tt>.
*/
public void setUserDetailsContextMapper(UserDetailsContextMapper userDetailsContextMapper) {
Assert.notNull(userDetailsContextMapper, "UserDetailsContextMapper must not be null");
this.userDetailsContextMapper = userDetailsContextMapper;
}
/**
* Provides access to the injected <tt>UserDetailsContextMapper</tt> strategy for use by subclasses.
*/
protected UserDetailsContextMapper getUserDetailsContextMapper() {
return userDetailsContextMapper;
}
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}
/**
* Determines whether the supplied password will be used as the credentials in the successful authentication
* token. If set to false, then the password will be obtained from the UserDetails object
* created by the configured <tt>UserDetailsContextMapper</tt>.
* Often it will not be possible to read the password from the directory, so defaults to true.
*
* @param useAuthenticationRequestCredentials
*/
public void setUseAuthenticationRequestCredentials(boolean useAuthenticationRequestCredentials) {
this.useAuthenticationRequestCredentials = useAuthenticationRequestCredentials;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken)authentication;
String username = userToken.getName();
if (!StringUtils.hasLength(username)) {
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyUsername",
"Empty Username"));
}
String password = (String) authentication.getCredentials();
Assert.notNull(password, "Null password was supplied in authentication token");
if (password.length() == 0) {
logger.debug("Rejecting empty password for user " + username);
throw new BadCredentialsException(messages.getMessage("LdapAuthenticationProvider.emptyPassword",
"Empty Password"));
}
try {
DirContextOperations userData = getAuthenticator().authenticate(authentication);
List<GrantedAuthority> extraAuthorities = loadUserAuthorities(userData, username, password);
UserDetails user = userDetailsContextMapper.mapUserFromContext(userData, username, extraAuthorities);
return createSuccessfulAuthentication(userToken, user);
} catch (UsernameNotFoundException notFound) {
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"LdapAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
throw notFound;
}
} catch (NamingException ldapAccessFailure) {
throw new AuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure);
}
}
protected List<GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, String password) {
return getAuthoritiesPopulator().getGrantedAuthorities(userData, username);
}
/**
* Creates the final <tt>Authentication</tt> object which will be returned from the <tt>authenticate</tt> method.
*
* @param authentication the original authentication request token
* @param user the <tt>UserDetails</tt> instance returned by the configured <tt>UserDetailsContextMapper</tt>.
* @return the Authentication object for the fully authenticated user.
*/
protected Authentication createSuccessfulAuthentication(UsernamePasswordAuthenticationToken authentication,
UserDetails user) {
Object password = useAuthenticationRequestCredentials ? authentication.getCredentials() : user.getPassword();
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
//~ Inner Classes ==================================================================================================
private static class NullAuthoritiesPopulator implements LdapAuthoritiesPopulator {
public List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userDetails, String username) {
return AuthorityUtils.NO_AUTHORITIES;
}
}
}
@@ -1,44 +0,0 @@
/* 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.springframework.security.providers.ldap;
import org.springframework.security.Authentication;
import org.springframework.ldap.core.DirContextOperations;
/**
* The strategy interface for locating and authenticating an Ldap user.
* <p>
* The LdapAuthenticationProvider calls this interface to authenticate a user
* and obtain the information for that user from the directory.
*
* @author Luke Taylor
* @version $Id$
*
* @see org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator
* @see org.springframework.security.ldap.populator.UserDetailsServiceLdapAuthoritiesPopulator
*/
public interface LdapAuthenticator {
//~ Methods ========================================================================================================
/**
* Authenticates as a user and obtains additional user information from the directory.
*
* @param authentication
* @return the details of the successfully authenticated user.
*/
DirContextOperations authenticate(Authentication authentication);
}
@@ -1,146 +0,0 @@
/* 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.springframework.security.providers.ldap.authenticator;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.ldap.LdapUserSearch;
import org.springframework.security.providers.ldap.LdapAuthenticator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.ldap.core.ContextSource;
import org.springframework.util.Assert;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Base class for the authenticator implementations.
*
* @author Luke Taylor
* @version $Id$
*/
public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, InitializingBean, MessageSourceAware {
//~ Instance fields ================================================================================================
private ContextSource contextSource;
/** Optional search object which can be used to locate a user when a simple DN match isn't sufficient */
private LdapUserSearch userSearch;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
/** The attributes which will be retrieved from the directory. Null means all attributes */
private String[] userAttributes = null;
//private String[] userDnPattern = null;
/** Stores the patterns which are used as potential DN matches */
private MessageFormat[] userDnFormat = null;
//~ Constructors ===================================================================================================
/**
* Create an initialized instance with the {@link ContextSource} provided.
*
* @param contextSource
*/
public AbstractLdapAuthenticator(ContextSource contextSource) {
Assert.notNull(contextSource, "contextSource must not be null.");
this.contextSource = contextSource;
}
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.isTrue((userDnFormat != null) || (userSearch != null),
"Either an LdapUserSearch or DN pattern (or both) must be supplied.");
}
protected ContextSource getContextSource() {
return contextSource;
}
public String[] getUserAttributes() {
return userAttributes;
}
/**
* Builds list of possible DNs for the user, worked out from the <tt>userDnPatterns</tt> property.
*
* @param username the user's login name
*
* @return the list of possible DN matches, empty if <tt>userDnPatterns</tt> wasn't set.
*/
protected List<String> getUserDns(String username) {
if (userDnFormat == null) {
return Collections.emptyList();
}
List<String> userDns = new ArrayList<String>(userDnFormat.length);
String[] args = new String[] {username};
synchronized (userDnFormat) {
for (int i = 0; i < userDnFormat.length; i++) {
userDns.add(userDnFormat[i].format(args));
}
}
return userDns;
}
protected LdapUserSearch getUserSearch() {
return userSearch;
}
public void setMessageSource(MessageSource messageSource) {
Assert.notNull("Message source must not be null");
this.messages = new MessageSourceAccessor(messageSource);
}
/**
* Sets the user attributes which will be retrieved from the directory.
*
* @param userAttributes
*/
public void setUserAttributes(String[] userAttributes) {
Assert.notNull(userAttributes, "The userAttributes property cannot be set to null");
this.userAttributes = userAttributes;
}
/**
* Sets the pattern which will be used to supply a DN for the user. The pattern should be the name relative
* to the root DN. The pattern argument {0} will contain the username. An example would be "cn={0},ou=people".
*
* @param dnPattern the array of patterns which will be tried when converting a username to a DN.
*/
public void setUserDnPatterns(String[] dnPattern) {
Assert.notNull(dnPattern, "The array of DN patterns cannot be set to null");
// this.userDnPattern = dnPattern;
userDnFormat = new MessageFormat[dnPattern.length];
for (int i = 0; i < dnPattern.length; i++) {
userDnFormat[i] = new MessageFormat(dnPattern[i]);
}
}
public void setUserSearch(LdapUserSearch userSearch) {
Assert.notNull(userSearch, "The userSearch cannot be set to null");
this.userSearch = userSearch;
}
}
@@ -1,128 +0,0 @@
/* 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.springframework.security.providers.ldap.authenticator;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.security.Authentication;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.util.Assert;
/**
* An authenticator which binds as a user.
*
* @author Luke Taylor
* @version $Id$
*
* @see AbstractLdapAuthenticator
*/
public class BindAuthenticator extends AbstractLdapAuthenticator {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(BindAuthenticator.class);
//~ Constructors ===================================================================================================
/**
* Create an initialized instance using the {@link BaseLdapPathContextSource} provided.
*
* @param contextSource the BaseLdapPathContextSource instance against which bind operations will be
* performed.
*
*/
public BindAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
//~ Methods ========================================================================================================
public DirContextOperations authenticate(Authentication authentication) {
DirContextOperations user = null;
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects");
String username = authentication.getName();
String password = (String)authentication.getCredentials();
// If DN patterns are configured, try authenticating with them directly
for (String dn : getUserDns(username)) {
user = bindWithDn(dn, username, password);
}
// Otherwise use the configured search object to find the user and authenticate with the returned DN.
if (user == null && getUserSearch() != null) {
DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
user = bindWithDn(userFromSearch.getDn().toString(), username, password);
}
if (user == null) {
throw new BadCredentialsException(
messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
}
return user;
}
private DirContextOperations bindWithDn(String userDn, String username, String password) {
BaseLdapPathContextSource ctxSource = (BaseLdapPathContextSource) getContextSource();
DistinguishedName fullDn = new DistinguishedName(userDn);
fullDn.prepend(ctxSource.getBaseLdapPath());
logger.debug("Attempting to bind as " + fullDn);
try {
DirContext ctx = getContextSource().getContext(fullDn.toString(), password);
Attributes attrs = ctx.getAttributes(userDn, getUserAttributes());
return new DirContextAdapter(attrs, new DistinguishedName(userDn), ctxSource.getBaseLdapPath());
} catch (NamingException e) {
// This will be thrown if an invalid user name is used and the method may
// be called multiple times to try different names, so we trap the exception
// unless a subclass wishes to implement more specialized behaviour.
if ((e instanceof org.springframework.ldap.AuthenticationException)
|| (e instanceof org.springframework.ldap.OperationNotSupportedException)) {
handleBindException(userDn, username, e);
} else {
throw e;
}
} catch (javax.naming.NamingException e) {
throw LdapUtils.convertLdapException(e);
}
return null;
}
/**
* Allows subclasses to inspect the exception thrown by an attempt to bind with a particular DN.
* The default implementation just reports the failure to the debug log.
*/
protected void handleBindException(String userDn, String username, Throwable cause) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to bind as " + userDn + ": " + cause);
}
}
}
@@ -1,116 +0,0 @@
/* 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.springframework.security.providers.ldap.authenticator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.Authentication;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.ldap.LdapUtils;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;
/**
* An {@link org.springframework.security.providers.ldap.LdapAuthenticator LdapAuthenticator} which compares the login
* password with the value stored in the directory using a remote LDAP "compare" operation.
*
* <p>
* If passwords are stored in digest form in the repository, then a suitable {@link PasswordEncoder}
* implementation must be supplied. By default, passwords are encoded using the {@link LdapShaPasswordEncoder}.
*
* @author Luke Taylor
* @version $Id$
*/
public final class PasswordComparisonAuthenticator extends AbstractLdapAuthenticator {
//~ Static fields/initializers =====================================================================================
private static final Log logger = LogFactory.getLog(PasswordComparisonAuthenticator.class);
//~ Instance fields ================================================================================================
private PasswordEncoder passwordEncoder = new LdapShaPasswordEncoder();
private String passwordAttributeName = "userPassword";
//~ Constructors ===================================================================================================
public PasswordComparisonAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
//~ Methods ========================================================================================================
public DirContextOperations authenticate(final Authentication authentication) {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects");
// locate the user and check the password
DirContextOperations user = null;
String username = authentication.getName();
String password = (String)authentication.getCredentials();
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getContextSource());
for (String userDn : getUserDns(username)) {
try {
user = ldapTemplate.retrieveEntry(userDn, getUserAttributes());
} catch (NameNotFoundException ignore) {
}
if (user != null) {
break;
}
}
if (user == null && getUserSearch() != null) {
user = getUserSearch().searchForUser(username);
}
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username, username);
}
if (logger.isDebugEnabled()) {
logger.debug("Performing LDAP compare of password attribute '" + passwordAttributeName + "' for user '" +
user.getDn() +"'");
}
String encodedPassword = passwordEncoder.encodePassword(password, null);
byte[] passwordBytes = LdapUtils.getUtf8Bytes(encodedPassword);
if (!ldapTemplate.compare(user.getDn().toString(), passwordAttributeName, passwordBytes)) {
throw new BadCredentialsException(messages.getMessage("PasswordComparisonAuthenticator.badCredentials",
"Bad credentials"));
}
return user;
}
public void setPasswordAttributeName(String passwordAttribute) {
Assert.hasLength(passwordAttribute, "passwordAttributeName must not be empty or null");
this.passwordAttributeName = passwordAttribute;
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder must not be null.");
this.passwordEncoder = passwordEncoder;
}
}
@@ -1,5 +0,0 @@
<html>
<body>
LDAP authenticator implementations.
</body>
</html>
@@ -1,15 +0,0 @@
<html>
<body>
<p>
The LDAP authentication provider package. Interfaces are provided for
both authentication and retrieval of user roles from an LDAP server.
</p>
<p>
The main provider class is <tt>LdapAuthenticationProvider</tt>.
This is configured with an <tt>LdapAuthenticator</tt> instance and
an <tt>LdapAuthoritiesPopulator</tt>. The latter is used to obtain the
list of roles for the user.
</p>
</body>
</html>
@@ -1,277 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
/**
* UserDetails implementation whose properties are based on a subset of the
* LDAP schema for <tt>inetOrgPerson</tt>.
*
* <p>
* The username will be mapped from the <tt>uid</tt> attribute by default.
*
* @author Luke
* @version $Id$
*/
public class InetOrgPerson extends Person {
private String carLicense;
// Person.cn
private String destinationIndicator;
private String departmentNumber;
// Person.description
private String displayName;
private String employeeNumber;
private String homePhone;
private String homePostalAddress;
private String initials;
private String mail;
private String mobile;
private String o;
private String ou;
private String postalAddress;
private String postalCode;
private String roomNumber;
private String street;
// Person.sn
// Person.telephoneNumber
private String title;
private String uid;
public String getUid() {
return uid;
}
public String getMail() {
return mail;
}
public String getEmployeeNumber() {
return employeeNumber;
}
public String getInitials() {
return initials;
}
public String getDestinationIndicator() {
return destinationIndicator;
}
public String getO() {
return o;
}
public String getOu() {
return ou;
}
public String getTitle() {
return title;
}
public String getCarLicense() {
return carLicense;
}
public String getDepartmentNumber() {
return departmentNumber;
}
public String getDisplayName() {
return displayName;
}
public String getHomePhone() {
return homePhone;
}
public String getRoomNumber() {
return roomNumber;
}
public String getHomePostalAddress() {
return homePostalAddress;
}
public String getMobile() {
return mobile;
}
public String getPostalAddress() {
return postalAddress;
}
public String getPostalCode() {
return postalCode;
}
public String getStreet() {
return street;
}
protected void populateContext(DirContextAdapter adapter) {
super.populateContext(adapter);
adapter.setAttributeValue("carLicense", carLicense);
adapter.setAttributeValue("departmentNumber", departmentNumber);
adapter.setAttributeValue("destinationIndicator", destinationIndicator);
adapter.setAttributeValue("displayName", displayName);
adapter.setAttributeValue("employeeNumber", employeeNumber);
adapter.setAttributeValue("homePhone", homePhone);
adapter.setAttributeValue("homePostalAddress", homePostalAddress);
adapter.setAttributeValue("initials", initials);
adapter.setAttributeValue("mail", mail);
adapter.setAttributeValue("mobile", mobile);
adapter.setAttributeValue("postalAddress", postalAddress);
adapter.setAttributeValue("postalCode", postalCode);
adapter.setAttributeValue("ou", ou);
adapter.setAttributeValue("o", o);
adapter.setAttributeValue("roomNumber", roomNumber);
adapter.setAttributeValue("street", street);
adapter.setAttributeValue("uid", uid);
adapter.setAttributeValues("objectclass", new String[] {"top", "person", "organizationalPerson", "inetOrgPerson"});
}
public static class Essence extends Person.Essence {
public Essence() {
}
public Essence(InetOrgPerson copyMe) {
super(copyMe);
setCarLicense(copyMe.getCarLicense());
setDepartmentNumber(copyMe.getDepartmentNumber());
setDestinationIndicator(copyMe.getDestinationIndicator());
setDisplayName(copyMe.getDisplayName());
setEmployeeNumber(copyMe.getEmployeeNumber());
setHomePhone(copyMe.getHomePhone());
setHomePostalAddress(copyMe.getHomePostalAddress());
setInitials(copyMe.getInitials());
setMail(copyMe.getMail());
setMobile(copyMe.getMobile());
setO(copyMe.getO());
setOu(copyMe.getOu());
setPostalAddress(copyMe.getPostalAddress());
setPostalCode(copyMe.getPostalCode());
setRoomNumber(copyMe.getRoomNumber());
setStreet(copyMe.getStreet());
setTitle(copyMe.getTitle());
setUid(copyMe.getUid());
}
public Essence(DirContextOperations ctx) {
super(ctx);
setCarLicense(ctx.getStringAttribute("carLicense"));
setDepartmentNumber(ctx.getStringAttribute("departmentNumber"));
setDestinationIndicator(ctx.getStringAttribute("destinationIndicator"));
setDisplayName(ctx.getStringAttribute("displayName"));
setEmployeeNumber(ctx.getStringAttribute("employeeNumber"));
setHomePhone(ctx.getStringAttribute("homePhone"));
setHomePostalAddress(ctx.getStringAttribute("homePostalAddress"));
setInitials(ctx.getStringAttribute("initials"));
setMail(ctx.getStringAttribute("mail"));
setMobile(ctx.getStringAttribute("mobile"));
setO(ctx.getStringAttribute("o"));
setOu(ctx.getStringAttribute("ou"));
setPostalAddress(ctx.getStringAttribute("postalAddress"));
setPostalCode(ctx.getStringAttribute("postalCode"));
setRoomNumber(ctx.getStringAttribute("roomNumber"));
setStreet(ctx.getStringAttribute("street"));
setTitle(ctx.getStringAttribute("title"));
setUid(ctx.getStringAttribute("uid"));
}
protected LdapUserDetailsImpl createTarget() {
return new InetOrgPerson();
}
public void setMail(String email) {
((InetOrgPerson) instance).mail = email;
}
public void setUid(String uid) {
((InetOrgPerson) instance).uid = uid;
if(instance.getUsername() == null) {
setUsername(uid);
}
}
public void setInitials(String initials) {
((InetOrgPerson) instance).initials = initials;
}
public void setO(String organization) {
((InetOrgPerson) instance).o = organization;
}
public void setOu(String ou) {
((InetOrgPerson) instance).ou = ou;
}
public void setRoomNumber(String no) {
((InetOrgPerson) instance).roomNumber = no;
}
public void setTitle(String title) {
((InetOrgPerson) instance).title = title;
}
public void setCarLicense(String carLicense) {
((InetOrgPerson) instance).carLicense = carLicense;
}
public void setDepartmentNumber(String departmentNumber) {
((InetOrgPerson) instance).departmentNumber = departmentNumber;
}
public void setDisplayName(String displayName) {
((InetOrgPerson) instance).displayName = displayName;
}
public void setEmployeeNumber(String no) {
((InetOrgPerson) instance).employeeNumber = no;
}
public void setDestinationIndicator(String destination) {
((InetOrgPerson) instance).destinationIndicator = destination;
}
public void setHomePhone(String homePhone) {
((InetOrgPerson) instance).homePhone = homePhone;
}
public void setStreet(String street) {
((InetOrgPerson) instance).street = street;
}
public void setPostalCode(String postalCode) {
((InetOrgPerson) instance).postalCode = postalCode;
}
public void setPostalAddress(String postalAddress) {
((InetOrgPerson) instance).postalAddress = postalAddress;
}
public void setMobile(String mobile) {
((InetOrgPerson) instance).mobile = mobile;
}
public void setHomePostalAddress(String homePostalAddress) {
((InetOrgPerson) instance).homePostalAddress = homePostalAddress;
}
}
}
@@ -1,48 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import java.util.List;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.GrantedAuthority;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.util.Assert;
/**
* @author Luke Taylor
* @version $Id$
*/
public class InetOrgPersonContextMapper implements UserDetailsContextMapper {
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, List<GrantedAuthority> authorities) {
InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx);
p.setUsername(username);
p.setAuthorities(authorities);
return p.createUserDetails();
}
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
Assert.isInstanceOf(InetOrgPerson.class, user, "UserDetails must be an InetOrgPerson instance");
InetOrgPerson p = (InetOrgPerson) user;
p.populateContext(ctx);
}
}
@@ -1,35 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import org.springframework.security.userdetails.UserDetails;
/**
* Captures the information for a user's LDAP entry.
*
* @author Luke Taylor
* @version $Id$
*/
public interface LdapUserDetails extends UserDetails {
//~ Methods ========================================================================================================
/**
* The DN of the entry for this user's account.
*
* @return the user's DN
*/
String getDn();
}
@@ -1,221 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import java.util.ArrayList;
import java.util.List;
import javax.naming.Name;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.util.AuthorityUtils;
import org.springframework.util.Assert;
/**
* A UserDetails implementation which is used internally by the Ldap services. It also contains the user's
* distinguished name and a set of attributes that have been retrieved from the Ldap server.
* <p>
* An instance may be created as the result of a search, or when user information is retrieved during authentication.
* </p>
* <p>
* An instance of this class will be used by the <tt>LdapAuthenticationProvider</tt> to construct the final user details
* object that it returns.
* </p>
*
* @author Luke Taylor
* @version $Id$
*/
public class LdapUserDetailsImpl implements LdapUserDetails {
//~ Instance fields ================================================================================================
private String dn;
private String password;
private String username;
private List<GrantedAuthority> authorities = AuthorityUtils.NO_AUTHORITIES;
private boolean accountNonExpired = true;
private boolean accountNonLocked = true;
private boolean credentialsNonExpired = true;
private boolean enabled = true;
//~ Constructors ===================================================================================================
protected LdapUserDetailsImpl() {}
//~ Methods ========================================================================================================
public List<GrantedAuthority> getAuthorities() {
return authorities;
}
public String getDn() {
return dn;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public boolean isEnabled() {
return enabled;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(super.toString()).append(": ");
sb.append("Username: ").append(this.username).append("; ");
sb.append("Password: [PROTECTED]; ");
sb.append("Enabled: ").append(this.enabled).append("; ");
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
sb.append("credentialsNonExpired: ").append(this.credentialsNonExpired).append("; ");
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
if (this.getAuthorities() != null) {
sb.append("Granted Authorities: ");
for (int i = 0; i < this.getAuthorities().size(); i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(this.getAuthorities().get(i).toString());
}
} else {
sb.append("Not granted any authorities");
}
return sb.toString();
}
//~ Inner Classes ==================================================================================================
/**
* Variation of essence pattern. Used to create mutable intermediate object
*/
public static class Essence {
protected LdapUserDetailsImpl instance = createTarget();
private List<GrantedAuthority> mutableAuthorities = new ArrayList<GrantedAuthority>();
public Essence() { }
public Essence(DirContextOperations ctx) {
setDn(ctx.getDn());
}
public Essence(LdapUserDetails copyMe) {
setDn(copyMe.getDn());
setUsername(copyMe.getUsername());
setPassword(copyMe.getPassword());
setEnabled(copyMe.isEnabled());
setAccountNonExpired(copyMe.isAccountNonExpired());
setCredentialsNonExpired(copyMe.isCredentialsNonExpired());
setAccountNonLocked(copyMe.isAccountNonLocked());
setAuthorities(copyMe.getAuthorities());
}
protected LdapUserDetailsImpl createTarget() {
return new LdapUserDetailsImpl();
}
/** Adds the authority to the list, unless it is already there, in which case it is ignored */
public void addAuthority(GrantedAuthority a) {
if(!hasAuthority(a)) {
mutableAuthorities.add(a);
}
}
private boolean hasAuthority(GrantedAuthority a) {
for (GrantedAuthority authority : mutableAuthorities) {
if(authority.equals(a)) {
return true;
}
}
return false;
}
public LdapUserDetails createUserDetails() {
Assert.notNull(instance, "Essence can only be used to create a single instance");
Assert.notNull(instance.username, "username must not be null");
Assert.notNull(instance.getDn(), "Distinguished name must not be null");
instance.authorities = getGrantedAuthorities();
LdapUserDetails newInstance = instance;
instance = null;
return newInstance;
}
public List<GrantedAuthority> getGrantedAuthorities() {
return mutableAuthorities;
}
public void setAccountNonExpired(boolean accountNonExpired) {
instance.accountNonExpired = accountNonExpired;
}
public void setAccountNonLocked(boolean accountNonLocked) {
instance.accountNonLocked = accountNonLocked;
}
public void setAuthorities(List<GrantedAuthority> authorities) {
mutableAuthorities = authorities;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
instance.credentialsNonExpired = credentialsNonExpired;
}
public void setDn(String dn) {
instance.dn = dn;
}
public void setDn(Name dn) {
instance.dn = dn.toString();
}
public void setEnabled(boolean enabled) {
instance.enabled = enabled;
}
public void setPassword(String password) {
instance.password = password;
}
public void setUsername(String username) {
instance.username = username;
}
}
}
@@ -1,395 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import org.springframework.security.Authentication;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.ldap.LdapUsernameToDnMapper;
import org.springframework.security.ldap.LdapUtils;
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsManager;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.dao.DataAccessException;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.AttributesMapperCallbackHandler;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.SearchExecutor;
import org.springframework.util.Assert;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.naming.Context;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
/**
* An Ldap implementation of UserDetailsManager.
* <p>
* It is designed around a standard setup where users and groups/roles are stored under separate contexts,
* defined by the "userDnBase" and "groupSearchBase" properties respectively.
* <p>
* In this case, LDAP is being used purely to retrieve information and this class can be used in place of any other
* UserDetailsService for authentication. Authentication isn't performed directly against the directory, unlike with the
* LDAP authentication provider setup.
*
* @author Luke Taylor
* @since 2.0
* @version $Id$
*/
public class LdapUserDetailsManager implements UserDetailsManager {
private final Log logger = LogFactory.getLog(LdapUserDetailsManager.class);
/**
* The strategy for mapping usernames to LDAP distinguished names.
* This will be used when building DNs for creating new users etc.
*/
LdapUsernameToDnMapper usernameMapper = new DefaultLdapUsernameToDnMapper("cn=users", "uid");
/** The DN under which groups are stored */
private DistinguishedName groupSearchBase = new DistinguishedName("cn=groups");
/** Password attribute name */
private String passwordAttributeName = "userPassword";
/** The attribute which corresponds to the role name of a group. */
private String groupRoleAttributeName ="cn";
/** The attribute which contains members of a group */
private String groupMemberAttributeName = "uniquemember";
private String rolePrefix = "ROLE_";
/** The pattern to be used for the user search. {0} is the user's DN */
private String groupSearchFilter = "(uniquemember={0})";
/**
* The strategy used to create a UserDetails object from the LDAP context, username and list of authorities.
* This should be set to match the required UserDetails implementation.
*/
private UserDetailsContextMapper userDetailsMapper = new InetOrgPersonContextMapper();
private LdapTemplate template;
/** Default context mapper used to create a set of roles from a list of attributes */
private AttributesMapper roleMapper = new AttributesMapper() {
public Object mapFromAttributes(Attributes attributes) throws NamingException {
Attribute roleAttr = attributes.get(groupRoleAttributeName);
NamingEnumeration<?> ne = roleAttr.getAll();
// assert ne.hasMore();
Object group = ne.next();
String role = group.toString();
return new GrantedAuthorityImpl(rolePrefix + role.toUpperCase());
}
};
private String[] attributesToRetrieve;
public LdapUserDetailsManager(ContextSource contextSource) {
template = new LdapTemplate(contextSource);
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
DistinguishedName dn = usernameMapper.buildDn(username);
List<GrantedAuthority> authorities = getUserAuthorities(dn, username);
logger.debug("Loading user '"+ username + "' with DN '" + dn + "'");
DirContextAdapter userCtx = loadUserAsContext(dn, username);
return userDetailsMapper.mapUserFromContext(userCtx, username, authorities);
}
private DirContextAdapter loadUserAsContext(final DistinguishedName dn, final String username) {
return (DirContextAdapter) template.executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
try {
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx));
} catch(NameNotFoundException notFound) {
throw new UsernameNotFoundException("User " + username + " not found", notFound);
}
}
});
}
/**
* Changes the password for the current user. The username is obtained from the security context.
* <p>
* If the old password is supplied, the update will be made by rebinding as the user, thus modifying the password
* using the user's permissions. If <code>oldPassword</code> is null, the update will be attempted using a
* standard read/write context supplied by the context source.
* </p>
*
* @param oldPassword the old password
* @param newPassword the new value of the password.
*/
public void changePassword(final String oldPassword, final String newPassword) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Assert.notNull(authentication,
"No authentication object found in security context. Can't change current user's password!");
String username = authentication.getName();
logger.debug("Changing password for user '"+ username);
final DistinguishedName dn = usernameMapper.buildDn(username);
final ModificationItem[] passwordChange = new ModificationItem[] {
new ModificationItem(DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(passwordAttributeName, newPassword))
};
if(oldPassword == null) {
template.modifyAttributes(dn, passwordChange);
return;
}
template.executeReadWrite(new ContextExecutor() {
public Object executeWithContext(DirContext dirCtx) throws NamingException {
LdapContext ctx = (LdapContext) dirCtx;
ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, LdapUtils.getFullDn(dn, ctx).toString());
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, oldPassword);
// TODO: reconnect doesn't appear to actually change the credentials
try {
ctx.reconnect(null);
} catch (javax.naming.AuthenticationException e) {
throw new BadCredentialsException("Authentication for password change failed.");
}
ctx.modifyAttributes(dn, passwordChange);
return null;
}
});
}
/**
*
* @param dn the distinguished name of the entry - may be either relative to the base context
* or a complete DN including the name of the context (either is supported).
* @param username the user whose roles are required.
* @return the granted authorities returned by the group search
*/
@SuppressWarnings("unchecked")
List<GrantedAuthority> getUserAuthorities(final DistinguishedName dn, final String username) {
SearchExecutor se = new SearchExecutor() {
public NamingEnumeration<SearchResult> executeSearch(DirContext ctx) throws NamingException {
DistinguishedName fullDn = LdapUtils.getFullDn(dn, ctx);
SearchControls ctrls = new SearchControls();
ctrls.setReturningAttributes(new String[] {groupRoleAttributeName});
return ctx.search(groupSearchBase, groupSearchFilter, new String[] {fullDn.toUrl(), username}, ctrls);
}
};
AttributesMapperCallbackHandler roleCollector =
new AttributesMapperCallbackHandler(roleMapper);
template.search(se, roleCollector);
return roleCollector.getList();
}
// protected String getRoleFilter(DistinguishedName dn, String username) {
// return new EqualsFilter("uniquemember", dn.toString()).encode();
// }
public void createUser(UserDetails user) {
DirContextAdapter ctx = new DirContextAdapter();
copyToContext(user, ctx);
DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
// Check for any existing authorities which might be set for this DN
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
if(authorities.size() > 0) {
removeAuthorities(dn, authorities);
}
logger.debug("Creating new user '"+ user.getUsername() + "' with DN '" + dn + "'");
template.bind(dn, ctx, null);
addAuthorities(dn, user.getAuthorities());
}
public void updateUser(UserDetails user) {
// Assert.notNull(attributesToRetrieve, "Configuration must specify a list of attributes in order to use update.");
DistinguishedName dn = usernameMapper.buildDn(user.getUsername());
logger.debug("Updating user '"+ user.getUsername() + "' with DN '" + dn + "'");
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
DirContextAdapter ctx = loadUserAsContext(dn, user.getUsername());
ctx.setUpdateMode(true);
copyToContext(user, ctx);
// Remove the objectclass attribute from the list of mods (if present).
List<ModificationItem> mods = new LinkedList<ModificationItem>(Arrays.asList(ctx.getModificationItems()));
ListIterator<ModificationItem> modIt = mods.listIterator();
while(modIt.hasNext()) {
ModificationItem mod = (ModificationItem) modIt.next();
Attribute a = mod.getAttribute();
if("objectclass".equalsIgnoreCase(a.getID())) {
modIt.remove();
}
}
template.modifyAttributes(dn, mods.toArray(new ModificationItem[mods.size()]));
// template.rebind(dn, ctx, null);
// Remove the old authorities and replace them with the new one
removeAuthorities(dn, authorities);
addAuthorities(dn, user.getAuthorities());
}
public void deleteUser(String username) {
DistinguishedName dn = usernameMapper.buildDn(username);
removeAuthorities(dn, getUserAuthorities(dn, username));
template.unbind(dn);
}
public boolean userExists(String username) {
DistinguishedName dn = usernameMapper.buildDn(username);
try {
Object obj = template.lookup(dn);
if (obj instanceof Context) {
LdapUtils.closeContext((Context) obj);
}
return true;
} catch(org.springframework.ldap.NameNotFoundException e) {
return false;
}
}
/**
* Creates a DN from a group name.
*
* @param group the name of the group
* @return the DN of the corresponding group, including the groupSearchBase
*/
protected DistinguishedName buildGroupDn(String group) {
DistinguishedName dn = new DistinguishedName(groupSearchBase);
dn.add(groupRoleAttributeName, group.toLowerCase());
return dn;
}
protected void copyToContext(UserDetails user, DirContextAdapter ctx) {
userDetailsMapper.mapUserToContext(user, ctx);
}
protected void addAuthorities(DistinguishedName userDn, List<GrantedAuthority> authorities) {
modifyAuthorities(userDn, authorities, DirContext.ADD_ATTRIBUTE);
}
protected void removeAuthorities(DistinguishedName userDn, List<GrantedAuthority> authorities) {
modifyAuthorities(userDn, authorities, DirContext.REMOVE_ATTRIBUTE);
}
private void modifyAuthorities(final DistinguishedName userDn, final List<GrantedAuthority> authorities, final int modType) {
template.executeReadWrite(new ContextExecutor() {
public Object executeWithContext(DirContext ctx) throws NamingException {
for(int i=0; i < authorities.size(); i++) {
GrantedAuthority authority = authorities.get(i);
String group = convertAuthorityToGroup(authority);
DistinguishedName fullDn = LdapUtils.getFullDn(userDn, ctx);
ModificationItem addGroup = new ModificationItem(modType,
new BasicAttribute(groupMemberAttributeName, fullDn.toUrl()));
ctx.modifyAttributes(buildGroupDn(group), new ModificationItem[] {addGroup});
}
return null;
}
});
}
private String convertAuthorityToGroup(GrantedAuthority authority) {
String group = authority.getAuthority();
if(group.startsWith(rolePrefix)) {
group = group.substring(rolePrefix.length());
}
return group;
}
public void setUsernameMapper(LdapUsernameToDnMapper usernameMapper) {
this.usernameMapper = usernameMapper;
}
public void setPasswordAttributeName(String passwordAttributeName) {
this.passwordAttributeName = passwordAttributeName;
}
public void setGroupSearchBase(String groupSearchBase) {
this.groupSearchBase = new DistinguishedName(groupSearchBase);
}
public void setGroupRoleAttributeName(String groupRoleAttributeName) {
this.groupRoleAttributeName = groupRoleAttributeName;
}
public void setAttributesToRetrieve(String[] attributesToRetrieve) {
Assert.notNull(attributesToRetrieve);
this.attributesToRetrieve = attributesToRetrieve;
}
public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper) {
this.userDetailsMapper = userDetailsMapper;
}
/**
* Sets the name of the multi-valued attribute which holds the DNs of users who are members of a group.
* <p>
* Usually this will be <tt>uniquemember</tt> (the default value) or <tt>member</tt>.
* </p>
*
* @param groupMemberAttributeName the name of the attribute used to store group members.
*/
public void setGroupMemberAttributeName(String groupMemberAttributeName) {
Assert.hasText(groupMemberAttributeName);
this.groupMemberAttributeName = groupMemberAttributeName;
this.groupSearchFilter = "(" + groupMemberAttributeName + "={0})";
}
public void setRoleMapper(AttributesMapper roleMapper) {
this.roleMapper = roleMapper;
}
}
@@ -1,177 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import java.util.List;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.userdetails.UserDetails;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
/**
* The context mapper used by the LDAP authentication provider to create an LDAP user object.
*
* @author Luke Taylor
* @version $Id$
*/
public class LdapUserDetailsMapper implements UserDetailsContextMapper {
//~ Instance fields ================================================================================================
private final Log logger = LogFactory.getLog(LdapUserDetailsMapper.class);
private String passwordAttributeName = "userPassword";
private String rolePrefix = "ROLE_";
private String[] roleAttributes = null;
private boolean convertToUpperCase = true;
//~ Methods ========================================================================================================
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, List<GrantedAuthority> authorities) {
String dn = ctx.getNameInNamespace();
logger.debug("Mapping user details from context with DN: " + dn);
LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence();
essence.setDn(dn);
Object passwordValue = ctx.getObjectAttribute(passwordAttributeName);
if (passwordValue != null) {
essence.setPassword(mapPassword(passwordValue));
}
essence.setUsername(username);
// Map the roles
for (int i = 0; (roleAttributes != null) && (i < roleAttributes.length); i++) {
String[] rolesForAttribute = ctx.getStringAttributes(roleAttributes[i]);
if (rolesForAttribute == null) {
logger.debug("Couldn't read role attribute '" + roleAttributes[i] + "' for user " + dn);
continue;
}
for (int j = 0; j < rolesForAttribute.length; j++) {
GrantedAuthority authority = createAuthority(rolesForAttribute[j]);
if (authority != null) {
essence.addAuthority(authority);
}
}
}
// Add the supplied authorities
for (int i=0; i < authorities.size(); i++) {
essence.addAuthority(authorities.get(i));
}
return essence.createUserDetails();
}
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
throw new UnsupportedOperationException("LdapUserDetailsMapper only supports reading from a context. Please" +
"use a subclass if mapUserToContext() is required.");
}
/**
* Extension point to allow customized creation of the user's password from
* the attribute stored in the directory.
*
* @param passwordValue the value of the password attribute
* @return a String representation of the password.
*/
protected String mapPassword(Object passwordValue) {
if (!(passwordValue instanceof String)) {
// Assume it's binary
passwordValue = new String((byte[]) passwordValue);
}
return (String) passwordValue;
}
/**
* Creates a GrantedAuthority from a role attribute. Override to customize
* authority object creation.
* <p>
* The default implementation converts string attributes to roles, making use of the <tt>rolePrefix</tt>
* and <tt>convertToUpperCase</tt> properties. Non-String attributes are ignored.
* </p>
*
* @param role the attribute returned from
* @return the authority to be added to the list of authorities for the user, or null
* if this attribute should be ignored.
*/
protected GrantedAuthority createAuthority(Object role) {
if (role instanceof String) {
if (convertToUpperCase) {
role = ((String) role).toUpperCase();
}
return new GrantedAuthorityImpl(rolePrefix + role);
}
return null;
}
/**
* Determines whether role field values will be converted to upper case when loaded.
* The default is true.
*
* @param convertToUpperCase true if the roles should be converted to upper case.
*/
public void setConvertToUpperCase(boolean convertToUpperCase) {
this.convertToUpperCase = convertToUpperCase;
}
/**
* The name of the attribute which contains the user's password.
* Defaults to "userPassword".
*
* @param passwordAttributeName the name of the attribute
*/
public void setPasswordAttributeName(String passwordAttributeName) {
this.passwordAttributeName = passwordAttributeName;
}
/**
* The names of any attributes in the user's entry which represent application
* roles. These will be converted to <tt>GrantedAuthority</tt>s and added to the
* list in the returned LdapUserDetails object. The attribute values must be Strings by default.
*
* @param roleAttributes the names of the role attributes.
*/
public void setRoleAttributes(String[] roleAttributes) {
Assert.notNull(roleAttributes, "roleAttributes array cannot be null");
this.roleAttributes = roleAttributes;
}
/**
* The prefix that should be applied to the role names
* @param rolePrefix the prefix (defaults to "ROLE_").
*/
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
}
@@ -1,42 +0,0 @@
package org.springframework.security.userdetails.ldap;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.LdapUserSearch;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;
/**
* LDAP implementation of UserDetailsService based around an {@link LdapUserSearch}
* and an {@link LdapAuthoritiesPopulator}. The final <tt>UserDetails</tt> object
* returned from <tt>loadUserByUsername</tt> is created by the configured <tt>UserDetailsContextMapper</tt>.
*
* @author Luke Taylor
* @version $Id$
*/
public class LdapUserDetailsService implements UserDetailsService {
private LdapUserSearch userSearch;
private LdapAuthoritiesPopulator authoritiesPopulator;
private UserDetailsContextMapper userDetailsMapper = new LdapUserDetailsMapper();
public LdapUserDetailsService(LdapUserSearch userSearch, LdapAuthoritiesPopulator authoritiesPopulator) {
Assert.notNull(userSearch, "userSearch must not be null");
Assert.notNull(authoritiesPopulator, "authoritiesPopulator must not be null");
this.userSearch = userSearch;
this.authoritiesPopulator = authoritiesPopulator;
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DirContextOperations userData = userSearch.searchForUser(username);
return userDetailsMapper.mapUserFromContext(userData, username,
authoritiesPopulator.getGrantedAuthorities(userData, username));
}
public void setUserDetailsMapper(UserDetailsContextMapper userDetailsMapper) {
Assert.notNull(userDetailsMapper, "userDetailsMapper must not be null");
this.userDetailsMapper = userDetailsMapper;
}
}
@@ -1,140 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import org.springframework.util.Assert;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.ldap.LdapUtils;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
/**
* UserDetails implementation whose properties are based on the LDAP schema for <tt>Person</tt>.
*
* @author Luke
* @since 2.0
* @version $Id$
*/
public class Person extends LdapUserDetailsImpl {
private String sn;
private String description;
private String telephoneNumber;
private List<String> cn = new ArrayList<String>();
protected Person() {
}
public String getSn() {
return sn;
}
public String[] getCn() {
return cn.toArray(new String[cn.size()]);
}
public String getDescription() {
return description;
}
public String getTelephoneNumber() {
return telephoneNumber;
}
protected void populateContext(DirContextAdapter adapter) {
adapter.setAttributeValue("sn", sn);
adapter.setAttributeValues("cn", getCn());
adapter.setAttributeValue("description", getDescription());
adapter.setAttributeValue("telephoneNumber", getTelephoneNumber());
if(getPassword() != null) {
adapter.setAttributeValue("userPassword", getPassword());
}
adapter.setAttributeValues("objectclass", new String[] {"top", "person"});
}
public static class Essence extends LdapUserDetailsImpl.Essence {
public Essence() {
}
public Essence(DirContextOperations ctx) {
super(ctx);
setCn(ctx.getStringAttributes("cn"));
setSn(ctx.getStringAttribute("sn"));
setDescription(ctx.getStringAttribute("description"));
setTelephoneNumber(ctx.getStringAttribute("telephoneNumber"));
Object passo = ctx.getObjectAttribute("userPassword");
if(passo != null) {
String password = LdapUtils.convertPasswordToString(passo);
setPassword(password);
}
}
public Essence(Person copyMe) {
super(copyMe);
setSn(copyMe.sn);
setDescription(copyMe.getDescription());
setTelephoneNumber(copyMe.getTelephoneNumber());
((Person) instance).cn = new ArrayList<String>(copyMe.cn);
}
protected LdapUserDetailsImpl createTarget() {
return new Person();
}
public void setSn(String sn) {
((Person) instance).sn = sn;
}
public void setCn(String[] cn) {
((Person) instance).cn = Arrays.asList(cn);
}
public void addCn(String value) {
((Person) instance).cn.add(value);
}
public void setTelephoneNumber(String tel) {
((Person) instance).telephoneNumber = tel;
}
public void setDescription(String desc) {
((Person) instance).description = desc;
}
public LdapUserDetails createUserDetails() {
Person p = (Person) super.createUserDetails();
Assert.hasLength(p.sn);
Assert.notNull(p.cn);
Assert.notEmpty(p.cn);
// TODO: Check contents for null entries
return p;
}
}
}
@@ -1,33 +0,0 @@
package org.springframework.security.userdetails.ldap;
import java.util.List;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.GrantedAuthority;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.util.Assert;
/**
* @author Luke Taylor
* @version $Id$
*/
public class PersonContextMapper implements UserDetailsContextMapper {
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, List<GrantedAuthority> authorities) {
Person.Essence p = new Person.Essence(ctx);
p.setUsername(username);
p.setAuthorities(authorities);
return p.createUserDetails();
}
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
Assert.isInstanceOf(Person.class, user, "UserDetails must be a Person instance");
Person p = (Person) user;
p.populateContext(ctx);
}
}
@@ -1,49 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import java.util.List;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.GrantedAuthority;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DirContextAdapter;
/**
* Operations to map a UserDetails object to and from a Spring LDAP <tt>DirContextOperations</tt> implementation.
* Used by LdapUserDetailsManager when loading and saving/creating user information.
*
* @author Luke Taylor
* @since 2.0
* @version $Id$
*/
public interface UserDetailsContextMapper {
/**
* Creates a fully populated UserDetails object for use by the security framework.
*
* @param ctx the context object which contains the user information.
* @param username the user's supplied login name.
* @param authority the list of authorities which the user should be given.
* @return the user object.
*/
UserDetails mapUserFromContext(DirContextOperations ctx, String username, List<GrantedAuthority> authority);
/**
* Reverse of the above operation. Populates a context object from the supplied user object.
* Called when saving a user, for example.
*/
void mapUserToContext(UserDetails user, DirContextAdapter ctx);
}
@@ -1,110 +0,0 @@
package org.springframework.security.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Test;
import org.springframework.security.Authentication;
import org.springframework.security.providers.ProviderManager;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.providers.ldap.LdapAuthenticationProvider;
import org.springframework.security.userdetails.ldap.InetOrgPersonContextMapper;
import org.springframework.security.userdetails.ldap.LdapUserDetailsImpl;
import org.springframework.security.util.FieldUtils;
import org.springframework.security.util.InMemoryXmlApplicationContext;
/**
* @author Luke Taylor
* @version $Id$
*/
public class LdapProviderBeanDefinitionParserTests {
InMemoryXmlApplicationContext appCtx;
@After
public void closeAppContext() {
if (appCtx != null) {
appCtx.close();
appCtx = null;
}
}
@Test
public void simpleProviderAuthenticatesCorrectly() {
setContext("<ldap-server /> <ldap-authentication-provider group-search-filter='member={0}' />");
LdapAuthenticationProvider provider = getProvider();
Authentication auth = provider.authenticate(new UsernamePasswordAuthenticationToken("ben", "benspassword"));
LdapUserDetailsImpl ben = (LdapUserDetailsImpl) auth.getPrincipal();
assertEquals(3, ben.getAuthorities().size());
}
@Test(expected = SecurityConfigurationException.class)
public void missingServerEltCausesConfigException() {
setContext("<ldap-authentication-provider />");
}
@Test
public void supportsPasswordComparisonAuthentication() {
setContext("<ldap-server /> " +
"<ldap-authentication-provider user-dn-pattern='uid={0},ou=people'>" +
" <password-compare />" +
"</ldap-authentication-provider>");
LdapAuthenticationProvider provider = getProvider();
provider.authenticate(new UsernamePasswordAuthenticationToken("ben", "benspassword"));
}
@Test
public void supportsPasswordComparisonAuthenticationWithHashAttribute() {
setContext("<ldap-server /> " +
"<ldap-authentication-provider user-dn-pattern='uid={0},ou=people'>" +
" <password-compare password-attribute='uid' hash='plaintext'/>" +
"</ldap-authentication-provider>");
LdapAuthenticationProvider provider = getProvider();
provider.authenticate(new UsernamePasswordAuthenticationToken("ben", "ben"));
}
@Test
public void supportsPasswordComparisonAuthenticationWithPasswordEncoder() {
setContext("<ldap-server /> " +
"<ldap-authentication-provider user-dn-pattern='uid={0},ou=people'>" +
" <password-compare password-attribute='uid'>" +
" <password-encoder hash='plaintext'/>" +
" </password-compare>" +
"</ldap-authentication-provider>");
LdapAuthenticationProvider provider = getProvider();
provider.authenticate(new UsernamePasswordAuthenticationToken("ben", "ben"));
}
@Test
public void detectsNonStandardServerId() {
setContext("<ldap-server id='myServer'/> " +
"<ldap-authentication-provider />");
}
@Test
public void inetOrgContextMapperIsSupported() throws Exception {
setContext(
"<ldap-server id='someServer' url='ldap://127.0.0.1:343/dc=springframework,dc=org'/>" +
"<ldap-authentication-provider user-details-class='inetOrgPerson'/>");
LdapAuthenticationProvider provider = getProvider();
assertTrue(FieldUtils.getFieldValue(provider, "userDetailsContextMapper") instanceof InetOrgPersonContextMapper);
}
private void setContext(String context) {
appCtx = new InMemoryXmlApplicationContext(context);
}
private LdapAuthenticationProvider getProvider() {
ProviderManager authManager = (ProviderManager) appCtx.getBean(BeanIds.AUTHENTICATION_MANAGER);
assertEquals(1, authManager.getProviders().size());
LdapAuthenticationProvider provider = (LdapAuthenticationProvider) authManager.getProviders().get(0);
return provider;
}
}
@@ -1,63 +0,0 @@
package org.springframework.security.config;
import org.junit.After;
import org.junit.Test;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.util.InMemoryXmlApplicationContext;
/**
* @author Luke Taylor
* @version $Id$
*/
public class LdapServerBeanDefinitionParserTests {
InMemoryXmlApplicationContext appCtx;
@After
public void closeAppContext() {
if (appCtx != null) {
appCtx.close();
appCtx = null;
}
}
@Test
public void embeddedServerCreationContainsExpectedContextSourceAndData() {
appCtx = new InMemoryXmlApplicationContext("<ldap-server />");
DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx.getBean(BeanIds.CONTEXT_SOURCE);
// Check data is loaded
LdapTemplate template = new LdapTemplate(contextSource);
template.lookup("uid=ben,ou=people");
}
@Test
public void useOfUrlAttributeCreatesCorrectContextSource() {
// Create second "server" with a url pointing at embedded one
appCtx = new InMemoryXmlApplicationContext("<ldap-server port='33388'/>" +
"<ldap-server id='blah' url='ldap://127.0.0.1:33388/dc=springframework,dc=org' />");
// Check the default context source is still there.
appCtx.getBean(BeanIds.CONTEXT_SOURCE);
DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx.getBean("blah");
// Check data is loaded as before
LdapTemplate template = new LdapTemplate(contextSource);
template.lookup("uid=ben,ou=people");
}
@Test
public void loadingSpecificLdifFileIsSuccessful() {
appCtx = new InMemoryXmlApplicationContext(
"<ldap-server ldif='classpath*:test-server2.xldif' root='dc=monkeymachine,dc=co,dc=uk' />");
DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx.getBean(BeanIds.CONTEXT_SOURCE);
LdapTemplate template = new LdapTemplate(contextSource);
template.lookup("uid=pg,ou=gorillas");
}
}
@@ -1,129 +0,0 @@
package org.springframework.security.config;
import java.util.Set;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.util.AuthorityUtils;
import org.springframework.security.util.InMemoryXmlApplicationContext;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.ldap.InetOrgPerson;
import org.springframework.security.userdetails.ldap.Person;
import org.junit.Test;
import org.junit.After;
import static org.junit.Assert.*;
/**
* @author Luke Taylor
* @version $Id$
*/
public class LdapUserServiceBeanDefinitionParserTests {
private InMemoryXmlApplicationContext appCtx;
@After
public void closeAppContext() {
if (appCtx != null) {
appCtx.close();
appCtx = null;
}
}
@Test
public void minimalConfigurationIsParsedOk() throws Exception {
setContext("<ldap-user-service user-search-filter='(uid={0})' /><ldap-server url='ldap://127.0.0.1:343/dc=springframework,dc=org' />");
}
@Test
public void userServiceReturnsExpectedData() throws Exception {
setContext("<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' group-search-filter='member={0}' /><ldap-server />");
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
UserDetails ben = uds.loadUserByUsername("ben");
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
assertEquals(3, authorities.size());
assertTrue(authorities.contains("ROLE_DEVELOPERS"));
}
@Test
public void differentUserSearchBaseWorksAsExpected() throws Exception {
setContext("<ldap-user-service id='ldapUDS' " +
" user-search-base='ou=otherpeople' " +
" user-search-filter='(cn={0})' " +
" group-search-filter='member={0}' /><ldap-server />");
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
UserDetails joe = uds.loadUserByUsername("Joe Smeth");
assertEquals("Joe Smeth", joe.getUsername());
}
@Test
public void rolePrefixIsSupported() throws Exception {
setContext(
"<ldap-user-service id='ldapUDS' " +
" user-search-filter='(uid={0})' " +
" group-search-filter='member={0}' role-prefix='PREFIX_'/>" +
"<ldap-user-service id='ldapUDSNoPrefix' " +
" user-search-filter='(uid={0})' " +
" group-search-filter='member={0}' role-prefix='none'/><ldap-server />");
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
UserDetails ben = uds.loadUserByUsername("ben");
assertTrue(AuthorityUtils.authorityListToSet(ben.getAuthorities()).contains("PREFIX_DEVELOPERS"));
uds = (UserDetailsService) appCtx.getBean("ldapUDSNoPrefix");
ben = uds.loadUserByUsername("ben");
assertTrue(AuthorityUtils.authorityListToSet(ben.getAuthorities()).contains("DEVELOPERS"));
}
@Test
public void differentGroupRoleAttributeWorksAsExpected() throws Exception {
setContext("<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' group-role-attribute='ou' group-search-filter='member={0}' /><ldap-server />");
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
UserDetails ben = uds.loadUserByUsername("ben");
Set<String> authorities = AuthorityUtils.authorityListToSet(ben.getAuthorities());
assertEquals(3, authorities.size());
assertTrue(authorities.contains(new GrantedAuthorityImpl("ROLE_DEVELOPER")));
}
@Test
public void isSupportedByAuthenticationProviderElement() {
setContext(
"<ldap-server url='ldap://127.0.0.1:343/dc=springframework,dc=org'/>" +
"<authentication-provider>" +
" <ldap-user-service user-search-filter='(uid={0})' />" +
"</authentication-provider>");
}
@Test
public void personContextMapperIsSupported() {
setContext(
"<ldap-server />" +
"<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' user-details-class='person'/>");
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
UserDetails ben = uds.loadUserByUsername("ben");
assertTrue(ben instanceof Person);
}
@Test
public void inetOrgContextMapperIsSupported() {
setContext(
"<ldap-server id='someServer'/>" +
"<ldap-user-service id='ldapUDS' user-search-filter='(uid={0})' user-details-class='inetOrgPerson'/>");
UserDetailsService uds = (UserDetailsService) appCtx.getBean("ldapUDS");
UserDetails ben = uds.loadUserByUsername("ben");
assertTrue(ben instanceof InetOrgPerson);
}
private void setContext(String context) {
appCtx = new InMemoryXmlApplicationContext(context);
}
}
@@ -1,135 +0,0 @@
/* 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.springframework.security.ldap;
import javax.naming.Binding;
import javax.naming.ContextNotEmptyException;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.config.BeanIds;
import org.springframework.security.util.InMemoryXmlApplicationContext;
/**
* Based on class borrowed from Spring Ldap project.
*
* @author Luke Taylor
* @version $Id$
*/
public abstract class AbstractLdapIntegrationTests {
private static InMemoryXmlApplicationContext appContext;
protected AbstractLdapIntegrationTests() {
}
@BeforeClass
public static void loadContext() throws NamingException {
shutdownRunningServers();
appContext = new InMemoryXmlApplicationContext("<ldap-server port='53389' ldif='classpath:test-server.ldif'/>");
}
@AfterClass
public static void closeContext() throws Exception {
if(appContext != null) {
appContext.close();
}
shutdownRunningServers();
}
private static void shutdownRunningServers() throws NamingException {
DirectoryService ds = DirectoryService.getInstance();
if (ds.isStarted()) {
System.out.println("WARNING: Discovered running DirectoryService with configuration: " + ds.getConfiguration().getStartupConfiguration().toString());
System.out.println("Shutting it down...");
ds.shutdown();
}
}
@Before
public void onSetUp() throws Exception {
}
@After
public final void reloadServerDataIfDirty() throws Exception {
ClassPathResource ldifs = new ClassPathResource("test-server.ldif");
if (!ldifs.getFile().exists()) {
throw new IllegalStateException("Ldif file not found: " + ldifs.getFile().getAbsolutePath());
}
DirContext ctx = getContextSource().getReadWriteContext();
// First of all, make sure the database is empty.
Name startingPoint = new DistinguishedName("dc=springframework,dc=org");
try {
clearSubContexts(ctx, startingPoint);
LdifFileLoader loader = new LdifFileLoader(ctx, ldifs.getFile().getAbsolutePath());
loader.execute();
} finally {
ctx.close();
}
}
public BaseLdapPathContextSource getContextSource() {
return (BaseLdapPathContextSource)appContext.getBean(BeanIds.CONTEXT_SOURCE);
}
private void clearSubContexts(DirContext ctx, Name name) throws NamingException {
NamingEnumeration<Binding> enumeration = null;
try {
enumeration = ctx.listBindings(name);
while (enumeration.hasMore()) {
Binding element = (Binding) enumeration.next();
DistinguishedName childName = new DistinguishedName(element.getName());
childName.prepend((DistinguishedName) name);
try {
ctx.destroySubcontext(childName);
} catch (ContextNotEmptyException e) {
clearSubContexts(ctx, childName);
ctx.destroySubcontext(childName);
}
}
} catch(NameNotFoundException ignored) {
}
catch (NamingException e) {
e.printStackTrace();
} finally {
try {
enumeration.close();
} catch (Exception ignored) {
}
}
}
}
@@ -1,60 +0,0 @@
package org.springframework.security.ldap;
import static org.junit.Assert.*;
import java.util.Hashtable;
import org.junit.Test;
import org.springframework.ldap.core.support.AbstractContextSource;
/**
* @author Luke Taylor
* @version $Id$
*/
public class DefaultSpringSecurityContextSourceTests {
@Test
public void instantiationSucceedsWithExpectedProperties() {
DefaultSpringSecurityContextSource ctxSrc =
new DefaultSpringSecurityContextSource("ldap://blah:789/dc=springframework,dc=org");
assertFalse(ctxSrc.isAnonymousReadOnly());
assertTrue(ctxSrc.isPooled());
}
@Test
public void supportsSpacesInUrl() {
new DefaultSpringSecurityContextSource("ldap://myhost:10389/dc=spring%20framework,dc=org");
}
@Test
public void poolingFlagIsSetWhenAuthenticationDnMatchesManagerUserDn() throws Exception {
EnvExposingDefaultSpringSecurityContextSource ctxSrc =
new EnvExposingDefaultSpringSecurityContextSource("ldap://blah:789/dc=springframework,dc=org");
ctxSrc.setUserDn("manager");
ctxSrc.setPassword("password");
ctxSrc.afterPropertiesSet();
assertTrue(ctxSrc.getAuthenticatedEnvForTest("manager", "password").containsKey(AbstractContextSource.SUN_LDAP_POOLING_FLAG));
}
@Test
public void poolingFlagIsNotSetWhenAuthenticationDnIsNotManagerUserDn() throws Exception {
EnvExposingDefaultSpringSecurityContextSource ctxSrc =
new EnvExposingDefaultSpringSecurityContextSource("ldap://blah:789/dc=springframework,dc=org");
ctxSrc.setUserDn("manager");
ctxSrc.setPassword("password");
ctxSrc.afterPropertiesSet();
assertFalse(ctxSrc.getAuthenticatedEnvForTest("user", "password").containsKey(AbstractContextSource.SUN_LDAP_POOLING_FLAG));
}
static class EnvExposingDefaultSpringSecurityContextSource extends DefaultSpringSecurityContextSource {
public EnvExposingDefaultSpringSecurityContextSource(String providerUrl) {
super(providerUrl);
}
@SuppressWarnings("unchecked")
Hashtable getAuthenticatedEnvForTest(String userDn, String password) {
return getAuthenticatedEnv(userDn, password);
}
}
}
@@ -1,105 +0,0 @@
/* 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.springframework.security.ldap;
import static org.junit.Assert.assertEquals;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests {@link LdapUtils}
*
* @author Luke Taylor
* @version $Id$
*/
@RunWith(JMock.class)
public class LdapUtilsTests {
Mockery context = new JUnit4Mockery();
//~ Methods ========================================================================================================
@Test
public void testCloseContextSwallowsNamingException() throws Exception {
final DirContext dirCtx = context.mock(DirContext.class);
context.checking(new Expectations() {{
oneOf(dirCtx).close(); will(throwException(new NamingException()));
}});
LdapUtils.closeContext(dirCtx);
}
@Test
public void testGetRelativeNameReturnsEmptyStringForDnEqualToBaseName() throws Exception {
final DirContext mockCtx = context.mock(DirContext.class);
context.checking(new Expectations() {{
atLeast(1).of(mockCtx).getNameInNamespace(); will(returnValue("dc=springframework,dc=org"));
}});
assertEquals("", LdapUtils.getRelativeName("dc=springframework,dc=org", mockCtx));
}
@Test
public void testGetRelativeNameReturnsFullDnWithEmptyBaseName() throws Exception {
final DirContext mockCtx = context.mock(DirContext.class);
context.checking(new Expectations() {{
atLeast(1).of(mockCtx).getNameInNamespace(); will(returnValue(""));
}});
assertEquals("cn=jane,dc=springframework,dc=org",
LdapUtils.getRelativeName("cn=jane,dc=springframework,dc=org", mockCtx));
}
@Test
public void testGetRelativeNameWorksWithArbitrarySpaces() throws Exception {
final DirContext mockCtx = context.mock(DirContext.class);
context.checking(new Expectations() {{
atLeast(1).of(mockCtx).getNameInNamespace(); will(returnValue("dc=springsecurity,dc = org"));
}});
assertEquals("cn=jane smith",
LdapUtils.getRelativeName("cn=jane smith, dc = springsecurity , dc=org", mockCtx));
}
@Test
public void testRootDnsAreParsedFromUrlsCorrectly() {
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine:11389"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine/"));
assertEquals("", LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk/"));
assertEquals("dc=springframework,dc=org",
LdapUtils.parseRootDnFromUrl("ldaps://monkeymachine.co.uk/dc=springframework,dc=org"));
assertEquals("dc=springframework,dc=org", LdapUtils.parseRootDnFromUrl("ldap:///dc=springframework,dc=org"));
assertEquals("dc=springframework,dc=org",
LdapUtils.parseRootDnFromUrl("ldap://monkeymachine/dc=springframework,dc=org"));
assertEquals("dc=springframework,dc=org/ou=blah",
LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk/dc=springframework,dc=org/ou=blah"));
assertEquals("dc=springframework,dc=org/ou=blah",
LdapUtils.parseRootDnFromUrl("ldap://monkeymachine.co.uk:389/dc=springframework,dc=org/ou=blah"));
}
}
@@ -1,70 +0,0 @@
package org.springframework.security.ldap;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.TestingAuthenticationToken;
import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
import org.springframework.security.userdetails.ldap.LdapUserDetailsImpl;
import org.springframework.security.util.AuthorityUtils;
import org.springframework.ldap.core.AuthenticationSource;
import org.springframework.ldap.core.DistinguishedName;
import org.junit.After;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
/**
* @author Luke Taylor
* @version $Id$
*/
public class SpringSecurityAuthenticationSourceTests {
@Before
@After
public void clearContext() {
SecurityContextHolder.clearContext();
}
@Test
public void principalAndCredentialsAreEmptyWithNoAuthentication() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
assertEquals("", source.getPrincipal());
assertEquals("", source.getCredentials());
}
@Test
public void principalIsEmptyForAnonymousUser() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(
new AnonymousAuthenticationToken("key", "anonUser", AuthorityUtils.createAuthorityList("ignored")));
assertEquals("", source.getPrincipal());
}
@Test(expected=IllegalArgumentException.class)
public void getPrincipalRejectsNonLdapUserDetailsObject() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(new Object(), "password"));
source.getPrincipal();
}
@Test
public void expectedCredentialsAreReturned() {
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken(new Object(), "password"));
assertEquals("password", source.getCredentials());
}
@Test
public void expectedPrincipalIsReturned() {
LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence();
user.setUsername("joe");
user.setDn(new DistinguishedName("uid=joe,ou=users"));
AuthenticationSource source = new SpringSecurityAuthenticationSource();
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken(user.createUserDetails(), null));
assertEquals("uid=joe,ou=users", source.getPrincipal());
}
}
@@ -1,151 +0,0 @@
/* 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.springframework.security.ldap;
import static org.junit.Assert.*;
import java.util.Set;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import org.junit.Test;
import org.springframework.ldap.UncategorizedLdapException;
import org.springframework.ldap.core.ContextExecutor;
/**
* @author Luke Taylor
* @version $Id$
*/
public class SpringSecurityLdapTemplateTests extends AbstractLdapIntegrationTests {
//~ Instance fields ================================================================================================
private SpringSecurityLdapTemplate template;
//~ Methods ========================================================================================================
public void onSetUp() throws Exception {
super.onSetUp();
template = new SpringSecurityLdapTemplate(getContextSource());
}
@Test
public void compareOfCorrectValueSucceeds() {
assertTrue(template.compare("uid=bob,ou=people", "uid", "bob"));
}
@Test
public void compareOfCorrectByteValueSucceeds() {
assertTrue(template.compare("uid=bob,ou=people", "userPassword", LdapUtils.getUtf8Bytes("bobspassword")));
}
@Test
public void compareOfWrongByteValueFails() {
assertFalse(template.compare("uid=bob,ou=people", "userPassword", LdapUtils.getUtf8Bytes("wrongvalue")));
}
@Test
public void compareOfWrongValueFails() {
assertFalse(template.compare("uid=bob,ou=people", "uid", "wrongvalue"));
}
// @Test
// public void testNameExistsForInValidNameFails() {
// assertFalse(template.nameExists("ou=doesntexist,dc=springframework,dc=org"));
// }
//
// @Test
// public void testNameExistsForValidNameSucceeds() {
// assertTrue(template.nameExists("ou=groups,dc=springframework,dc=org"));
// }
@Test
public void namingExceptionIsTranslatedCorrectly() {
try {
template.executeReadOnly(new ContextExecutor() {
public Object executeWithContext(DirContext dirContext) throws NamingException {
throw new NamingException();
}
});
fail("Expected UncategorizedLdapException on NamingException");
} catch (UncategorizedLdapException expected) {}
}
@Test
public void roleSearchReturnsCorrectNumberOfRoles() {
String param = "uid=ben,ou=people,dc=springframework,dc=org";
Set<String> values = template.searchForSingleAttributeValues("ou=groups", "(member={0})", new String[] {param}, "ou");
assertEquals("Expected 3 results from search", 3, values.size());
assertTrue(values.contains("developer"));
assertTrue(values.contains("manager"));
assertTrue(values.contains("submanager"));
}
@Test
public void testRoleSearchForMissingAttributeFailsGracefully() {
String param = "uid=ben,ou=people,dc=springframework,dc=org";
Set<String> values = template.searchForSingleAttributeValues("ou=groups", "(member={0})", new String[] {param}, "mail");
assertEquals(0, values.size());
}
@Test
public void roleSearchWithEscapedCharacterSucceeds() throws Exception {
String param = "cn=mouse\\, jerry,ou=people,dc=springframework,dc=org";
Set<String> values = template.searchForSingleAttributeValues("ou=groups", "(member={0})", new String[] {param}, "cn");
assertEquals(1, values.size());
}
@Test
public void nonSpringLdapSearchCodeTestMethod() throws Exception {
java.util.Hashtable<String, String> env = new java.util.Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:53389");
env.put(Context.SECURITY_PRINCIPAL, "");
env.put(Context.SECURITY_CREDENTIALS, "");
DirContext ctx = new javax.naming.directory.InitialDirContext(env);
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
controls.setReturningObjFlag(true);
controls.setReturningAttributes(null);
String param = "cn=mouse\\, jerry,ou=people,dc=springframework,dc=org";
javax.naming.NamingEnumeration<SearchResult> results =
ctx.search("ou=groups,dc=springframework,dc=org",
"(member={0})", new String[] {param},
controls);
assertTrue("Expected a result", results.hasMore());
}
@Test
public void searchForSingleEntryWithEscapedCharsInDnSucceeds() {
String param = "mouse, jerry";
template.searchForSingleEntry("ou=people", "(cn={0})", new String[] {param});
}
}
@@ -1,148 +0,0 @@
/* 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.springframework.security.ldap.populator;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.*;
import org.junit.Test;
/**
*
* @author Luke Taylor
* @version $Id$
*/
public class DefaultLdapAuthoritiesPopulatorTests extends AbstractLdapIntegrationTests {
private DefaultLdapAuthoritiesPopulator populator;
//~ Methods ========================================================================================================
public void onSetUp() throws Exception {
super.onSetUp();
populator = new DefaultLdapAuthoritiesPopulator(getContextSource(), "ou=groups");
}
@Test
public void testDefaultRoleIsAssignedWhenSet() {
populator.setDefaultRole("ROLE_USER");
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("cn=notfound"));
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "notfound");
assertEquals(1, authorities.size());
assertEquals("ROLE_USER", authorities.get(0).getAuthority());
}
@Test
public void testGroupSearchReturnsExpectedRoles() {
populator.setRolePrefix("ROLE_");
populator.setGroupRoleAttribute("ou");
populator.setSearchSubtree(true);
populator.setSearchSubtree(false);
populator.setConvertToUpperCase(true);
populator.setGroupSearchFilter("(member={0})");
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "ben");
assertEquals("Should have 2 roles", 2, authorities.size());
Set<String> roles = new HashSet<String>();
roles.add(authorities.get(0).toString());
roles.add(authorities.get(1).toString());
assertTrue(roles.contains("ROLE_DEVELOPER"));
assertTrue(roles.contains("ROLE_MANAGER"));
}
@Test
public void testUseOfUsernameParameterReturnsExpectedRoles() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
populator.setGroupSearchFilter("(ou={1})");
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "manager");
assertEquals("Should have 1 role", 1, authorities.size());
assertEquals("ROLE_MANAGER", authorities.get(0).getAuthority());
}
@Test
public void testSubGroupRolesAreNotFoundByDefault() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "manager");
assertEquals("Should have 2 roles", 2, authorities.size());
Set<String> roles = new HashSet<String>(2);
roles.add(authorities.get(0).getAuthority());
roles.add(authorities.get(1).getAuthority());
assertTrue(roles.contains("ROLE_MANAGER"));
assertTrue(roles.contains("ROLE_DEVELOPER"));
}
@Test
public void testSubGroupRolesAreFoundWhenSubtreeSearchIsEnabled() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
populator.setSearchSubtree(true);
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=ben,ou=people,dc=springframework,dc=org"));
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "manager");
assertEquals("Should have 3 roles", 3, authorities.size());
Set<String> roles = new HashSet<String>(3);
roles.add(authorities.get(0).getAuthority());
roles.add(authorities.get(1).getAuthority());
roles.add(authorities.get(2).getAuthority());
assertTrue(roles.contains("ROLE_MANAGER"));
assertTrue(roles.contains("ROLE_DEVELOPER"));
assertTrue(roles.contains("ROLE_SUBMANAGER"));
}
@Test
public void testUserDnWithEscapedCharacterParameterReturnsExpectedRoles() {
populator.setGroupRoleAttribute("ou");
populator.setConvertToUpperCase(true);
populator.setGroupSearchFilter("(member={0})");
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("cn=mouse\\, jerry,ou=people,dc=springframework,dc=org"));
List<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx, "notused");
assertEquals("Should have 1 role", 1, authorities.size());
assertEquals("ROLE_MANAGER", authorities.get(0).getAuthority());
}
}
@@ -1,30 +0,0 @@
package org.springframework.security.ldap.populator;
import java.util.List;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.MockUserDetailsService;
import org.springframework.security.GrantedAuthority;
import org.springframework.ldap.core.DirContextAdapter;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author Luke Taylor
* @version $Id$
*/
public class UserDetailsServiceLdapAuthoritiesPopulatorTests {
UserDetailsService uds = new MockUserDetailsService();
@Test
public void delegationToUserDetailsServiceReturnsCorrectRoles() throws Exception {
UserDetailsServiceLdapAuthoritiesPopulator populator = new UserDetailsServiceLdapAuthoritiesPopulator(uds);
List<GrantedAuthority> auths = populator.getGrantedAuthorities(new DirContextAdapter(), "valid");
assertEquals(1, auths.size());
assertEquals("ROLE_USER", auths.get(0).getAuthority());
}
}
@@ -1,114 +0,0 @@
/* 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.springframework.security.ldap.search;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
/**
* Tests for FilterBasedLdapUserSearch.
*
* @author Luke Taylor
* @version $Id$
*/
public class FilterBasedLdapUserSearchTests extends AbstractLdapIntegrationTests {
//~ Instance fields ================================================================================================
private BaseLdapPathContextSource dirCtxFactory;
//~ Methods ========================================================================================================
public void onSetUp() throws Exception {
super.onSetUp();
dirCtxFactory = getContextSource();
}
@Test
public void basicSearchSucceeds() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory);
locator.setSearchSubtree(false);
locator.setSearchTimeLimit(0);
locator.setDerefLinkFlag(false);
DirContextOperations bob = locator.searchForUser("bob");
assertEquals("bob", bob.getStringAttribute("uid"));
assertEquals(new DistinguishedName("uid=bob,ou=people"), bob.getDn());
}
@Test
public void searchForNameWithCommaSucceeds() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory);
locator.setSearchSubtree(false);
DirContextOperations jerry = locator.searchForUser("jerry");
assertEquals("jerry", jerry.getStringAttribute("uid"));
assertEquals(new DistinguishedName("cn=mouse\\, jerry,ou=people"), jerry.getDn());
}
// Try some funny business with filters.
@Test
public void extraFilterPartToExcludeBob() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people",
"(&(cn=*)(!(|(uid={0})(uid=rod)(uid=jerry))))", dirCtxFactory);
// Search for bob, get back ben...
DirContextOperations ben = locator.searchForUser("bob");
assertEquals("Ben Alex", ben.getStringAttribute("cn"));
// assertEquals("uid=ben,ou=people,"+ROOT_DN, ben.getDn());
}
@Test(expected=IncorrectResultSizeDataAccessException.class)
public void searchFailsOnMultipleMatches() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(cn=*)", dirCtxFactory);
locator.searchForUser("Ignored");
}
@Test(expected=UsernameNotFoundException.class)
public void searchForInvalidUserFails() {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory);
locator.searchForUser("Joe");
}
@Test
public void subTreeSearchSucceeds() {
// Don't set the searchBase, so search from the root.
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("", "(cn={0})", dirCtxFactory);
locator.setSearchSubtree(true);
DirContextOperations ben = locator.searchForUser("Ben Alex");
assertEquals("ben", ben.getStringAttribute("uid"));
assertEquals(new DistinguishedName("uid=ben,ou=people"), ben.getDn());
}
@Test
public void searchWithDifferentSearchBaseIsSuccessful() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=otherpeople", "(cn={0})", dirCtxFactory);
DirContextOperations joe = locator.searchForUser("Joe Smeth");
assertEquals("Joe Smeth", joe.getStringAttribute("cn"));
}
}
@@ -1,211 +0,0 @@
/* 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.springframework.security.providers.ldap;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Test;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.security.Authentication;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.userdetails.ldap.LdapUserDetailsMapper;
import org.springframework.security.util.AuthorityUtils;
/**
* Tests {@link LdapAuthenticationProvider}.
*
* @author Luke Taylor
* @version $Id$
*/
public class LdapAuthenticationProviderTests {
Mockery jmock = new JUnit4Mockery();
//~ Methods ========================================================================================================
@Test
public void testSupportsUsernamePasswordAuthenticationToken() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
assertTrue(ldapProvider.supports(UsernamePasswordAuthenticationToken.class));
}
@Test
public void testDefaultMapperIsSet() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
assertTrue(ldapProvider.getUserDetailsContextMapper() instanceof LdapUserDetailsMapper);
}
@Test
public void testEmptyOrNullUserNameThrowsException() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
try {
ldapProvider.authenticate(new UsernamePasswordAuthenticationToken(null, "password"));
fail("Expected BadCredentialsException for empty username");
} catch (BadCredentialsException expected) {}
try {
ldapProvider.authenticate(new UsernamePasswordAuthenticationToken("", "bobspassword"));
fail("Expected BadCredentialsException for null username");
} catch (BadCredentialsException expected) {}
}
@Test(expected=BadCredentialsException.class)
public void emptyPasswordIsRejected() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator());
ldapProvider.authenticate(new UsernamePasswordAuthenticationToken("jen", ""));
}
@Test
public void usernameNotFoundExceptionIsHiddenByDefault() {
final LdapAuthenticator authenticator = jmock.mock(LdapAuthenticator.class);
final UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken("joe", "password");
jmock.checking(new Expectations() {{
oneOf(authenticator).authenticate(joe); will(throwException(new UsernameNotFoundException("nobody")));
}});
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
try {
provider.authenticate(joe);
fail();
} catch (BadCredentialsException expected) {
if (expected instanceof UsernameNotFoundException) {
fail("Exception should have been hidden");
}
}
}
@Test(expected=UsernameNotFoundException.class)
public void usernameNotFoundExceptionIsNotHiddenIfConfigured() {
final LdapAuthenticator authenticator = jmock.mock(LdapAuthenticator.class);
final UsernamePasswordAuthenticationToken joe = new UsernamePasswordAuthenticationToken("joe", "password");
jmock.checking(new Expectations() {{
oneOf(authenticator).authenticate(joe); will(throwException(new UsernameNotFoundException("nobody")));
}});
LdapAuthenticationProvider provider = new LdapAuthenticationProvider(authenticator);
provider.setHideUserNotFoundExceptions(false);
provider.authenticate(joe);
}
@Test
public void normalUsage() {
MockAuthoritiesPopulator populator = new MockAuthoritiesPopulator();
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(), populator);
LdapUserDetailsMapper userMapper = new LdapUserDetailsMapper();
userMapper.setRoleAttributes(new String[] {"ou"});
ldapProvider.setUserDetailsContextMapper(userMapper);
assertNotNull(ldapProvider.getAuthoritiesPopulator());
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword");
Authentication authResult = ldapProvider.authenticate(authRequest);
assertEquals("benspassword", authResult.getCredentials());
UserDetails user = (UserDetails) authResult.getPrincipal();
assertEquals(2, user.getAuthorities().size());
assertEquals("{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=", user.getPassword());
assertEquals("ben", user.getUsername());
assertEquals("ben", populator.getRequestedUsername());
ArrayList<String> authorities = new ArrayList<String>();
authorities.add(user.getAuthorities().get(0).getAuthority());
authorities.add(user.getAuthorities().get(1).getAuthority());
assertTrue(authorities.contains("ROLE_FROM_ENTRY"));
assertTrue(authorities.contains("ROLE_FROM_POPULATOR"));
}
@Test
public void passwordIsSetFromUserDataIfUseAuthenticationRequestCredentialsIsFalse() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator(),
new MockAuthoritiesPopulator());
ldapProvider.setUseAuthenticationRequestCredentials(false);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword");
Authentication authResult = ldapProvider.authenticate(authRequest);
assertEquals("{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=", authResult.getCredentials());
}
@Test
public void useWithNullAuthoritiesPopulatorReturnsCorrectRole() {
LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(new MockAuthenticator());
LdapUserDetailsMapper userMapper = new LdapUserDetailsMapper();
userMapper.setRoleAttributes(new String[] {"ou"});
ldapProvider.setUserDetailsContextMapper(userMapper);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken("ben", "benspassword");
UserDetails user = (UserDetails) ldapProvider.authenticate(authRequest).getPrincipal();
assertEquals(1, user.getAuthorities().size());
assertEquals("ROLE_FROM_ENTRY", user.getAuthorities().get(0).getAuthority());
}
//~ Inner Classes ==================================================================================================
class MockAuthenticator implements LdapAuthenticator {
public DirContextOperations authenticate(Authentication authentication) {
DirContextAdapter ctx = new DirContextAdapter();
ctx.setAttributeValue("ou", "FROM_ENTRY");
String username = authentication.getName();
String password = (String) authentication.getCredentials();
if (username.equals("ben") && password.equals("benspassword")) {
ctx.setDn(new DistinguishedName("cn=ben,ou=people,dc=springframework,dc=org"));
ctx.setAttributeValue("userPassword","{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=");
return ctx;
} else if (username.equals("jen") && password.equals("")) {
ctx.setDn(new DistinguishedName("cn=jen,ou=people,dc=springframework,dc=org"));
return ctx;
}
throw new BadCredentialsException("Authentication failed.");
}
}
class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator {
String username;
public List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userCtx, String username) {
this.username = username;
return AuthorityUtils.createAuthorityList("ROLE_FROM_POPULATOR");
}
String getRequestedUsername() {
return username;
}
}
}
@@ -1,97 +0,0 @@
/* 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.springframework.security.providers.ldap.authenticator;
import org.springframework.security.Authentication;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import org.junit.Test;
/**
* Tests for {@link BindAuthenticator}.
*
* @author Luke Taylor
* @version $Id$
*/
public class BindAuthenticatorTests extends AbstractLdapIntegrationTests {
//~ Instance fields ================================================================================================
private BindAuthenticator authenticator;
private Authentication bob;
// private Authentication ben;
//~ Methods ========================================================================================================
public void onSetUp() {
authenticator = new BindAuthenticator(getContextSource());
authenticator.setMessageSource(new SpringSecurityMessageSource());
bob = new UsernamePasswordAuthenticationToken("bob", "bobspassword");
// ben = new UsernamePasswordAuthenticationToken("ben", "benspassword");
}
@Test
public void testAuthenticationWithCorrectPasswordSucceeds() {
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"});
DirContextOperations user = authenticator.authenticate(bob);
assertEquals("bob", user.getStringAttribute("uid"));
}
@Test
public void testAuthenticationWithInvalidUserNameFails() {
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"});
try {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("nonexistentsuser", "password"));
fail("Shouldn't be able to bind with invalid username");
} catch (BadCredentialsException expected) {}
}
@Test
public void testAuthenticationWithUserSearch() throws Exception {
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=bob,ou=people"));
authenticator.setUserSearch(new MockUserSearch(ctx));
authenticator.afterPropertiesSet();
authenticator.authenticate(bob);
}
@Test
public void testAuthenticationWithWrongPasswordFails() {
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"});
try {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("bob", "wrongpassword"));
fail("Shouldn't be able to bind with wrong password");
} catch (BadCredentialsException expected) {}
}
@Test
public void testUserDnPatternReturnsCorrectDn() {
authenticator.setUserDnPatterns(new String[] {"cn={0},ou=people"});
assertEquals("cn=Joe,ou=people", authenticator.getUserDns("Joe").get(0));
}
}
@@ -1,115 +0,0 @@
/* 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.springframework.security.providers.ldap.authenticator;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
/**
* Tests {@link LdapShaPasswordEncoder}.
*
* @author Luke Taylor
* @version $Id$
*/
public class LdapShaPasswordEncoderTests {
//~ Instance fields ================================================================================================
LdapShaPasswordEncoder sha;
//~ Methods ========================================================================================================
@Before
public void setUp() throws Exception {
sha = new LdapShaPasswordEncoder();
}
@Test
public void invalidPasswordFails() {
assertFalse(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "wrongpassword", null));
}
@Test
public void invalidSaltedPasswordFails() {
assertFalse(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "wrongpassword", null));
assertFalse(sha.isPasswordValid("{SSHA}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "wrongpassword", null));
}
@Test(expected=IllegalArgumentException.class)
public void nonByteArraySaltThrowsException() {
sha.encodePassword("password", "AStringNotAByteArray");
}
/**
* Test values generated by 'slappasswd -h {SHA} -s boabspasswurd'
*/
@Test
public void validPasswordSucceeds() {
sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
sha.setForceLowerCasePrefix(true);
assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
}
/**
* Test values generated by 'slappasswd -s boabspasswurd'
*/
@Test
public void validSaltedPasswordSucceeds() {
sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null));
sha.setForceLowerCasePrefix(true);
assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null));
}
@Test
// SEC-1031
public void fullLengthOfHashIsUsedInComparison() throws Exception {
// Change the first hash character from '2' to '3'
assertFalse(sha.isPasswordValid("{SSHA}35ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null));
// Change the last hash character from 'X' to 'Y'
assertFalse(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgY", "boabspasswurd", null));
}
@Test
public void correctPrefixCaseIsUsed() {
sha.setForceLowerCasePrefix(false);
assertEquals("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null));
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{SSHA}"));
sha.setForceLowerCasePrefix(true);
assertEquals("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null));
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{ssha}"));
}
@Test(expected=IllegalArgumentException.class)
public void invalidPrefixIsRejected() {
sha.isPasswordValid("{MD9}xxxxxxxxxx" , "somepassword", null);
}
@Test(expected=IllegalArgumentException.class)
public void malformedPrefixIsRejected() {
// No right brace
sha.isPasswordValid("{SSHA25ro4PKC8jhQZ26jVsozhX/xaP0suHgX" , "somepassword", null);
}
}
@@ -1,48 +0,0 @@
/* 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.springframework.security.providers.ldap.authenticator;
import org.springframework.security.ldap.LdapUserSearch;
import org.springframework.ldap.core.DirContextOperations;
/**
*
*
* @author Luke Taylor
* @version $Id$
*/
public class MockUserSearch implements LdapUserSearch {
//~ Instance fields ================================================================================================
DirContextOperations user;
//~ Constructors ===================================================================================================
public MockUserSearch() {
}
public MockUserSearch(DirContextOperations user) {
this.user = user;
}
//~ Methods ========================================================================================================
public DirContextOperations searchForUser(String username) {
return user;
}
}
@@ -1,76 +0,0 @@
/* 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.springframework.security.providers.ldap.authenticator;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Test;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
/**
*
* @author Luke Taylor
* @version $Id$
*/
public class PasswordComparisonAuthenticatorMockTests {
Mockery jmock = new JUnit4Mockery();
//~ Methods ========================================================================================================
@Test
public void ldapCompareOperationIsUsedWhenPasswordIsNotRetrieved() throws Exception {
final DirContext dirCtx = jmock.mock(DirContext.class);
final BaseLdapPathContextSource source = jmock.mock(BaseLdapPathContextSource.class);
final BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("uid", "bob"));
PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(source);
authenticator.setUserDnPatterns(new String[] {"cn={0},ou=people"});
// Get the mock to return an empty attribute set
jmock.checking(new Expectations() {{
allowing(source).getReadOnlyContext(); will(returnValue(dirCtx));
oneOf(dirCtx).getAttributes(with(equal("cn=Bob,ou=people")), with(aNull(String[].class))); will(returnValue(attrs));
oneOf(dirCtx).getNameInNamespace(); will(returnValue("dc=springframework,dc=org"));
}});
// Setup a single return value (i.e. success)
final Attributes searchResults = new BasicAttributes("", null);
jmock.checking(new Expectations() {{
oneOf(dirCtx).search(with(equal("cn=Bob,ou=people")),
with(equal("(userPassword={0})")),
with(aNonNull(Object[].class)),
with(aNonNull(SearchControls.class)));
will(returnValue(searchResults.getAll()));
atLeast(1).of(dirCtx).close();
}});
authenticator.authenticate(new UsernamePasswordAuthenticationToken("Bob","bobspassword"));
jmock.assertIsSatisfied();
}
}
@@ -1,143 +0,0 @@
/* 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.springframework.security.providers.ldap.authenticator;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.Authentication;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.providers.encoding.PlaintextPasswordEncoder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Tests for {@link PasswordComparisonAuthenticator}.
*
* @author Luke Taylor
* @version $Id$
*/
public class PasswordComparisonAuthenticatorTests extends AbstractLdapIntegrationTests {
//~ Instance fields ================================================================================================
private PasswordComparisonAuthenticator authenticator;
private Authentication bob;
private Authentication ben;
//~ Methods ========================================================================================================
public void onSetUp() throws Exception {
super.onSetUp();
authenticator = new PasswordComparisonAuthenticator(getContextSource());
authenticator.setPasswordEncoder(new PlaintextPasswordEncoder());
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"});
bob = new UsernamePasswordAuthenticationToken("bob", "bobspassword");
ben = new UsernamePasswordAuthenticationToken("ben", "benspassword");
}
@Test
public void testAllAttributesAreRetrievedByDefault() {
DirContextAdapter user = (DirContextAdapter) authenticator.authenticate(bob);
//System.out.println(user.getAttributes().toString());
assertEquals("User should have 5 attributes", 5, user.getAttributes().size());
}
@Test
public void testFailedSearchGivesUserNotFoundException() throws Exception {
authenticator = new PasswordComparisonAuthenticator(getContextSource());
assertTrue("User DN matches shouldn't be available", authenticator.getUserDns("Bob").isEmpty());
authenticator.setUserSearch(new MockUserSearch(null));
authenticator.afterPropertiesSet();
try {
authenticator.authenticate(new UsernamePasswordAuthenticationToken("Joe", "pass"));
fail("Expected exception on failed user search");
} catch (UsernameNotFoundException expected) {}
}
@Test(expected = BadCredentialsException.class)
public void testLdapPasswordCompareFailsWithWrongPassword() {
// Don't retrieve the password
authenticator.setUserAttributes(new String[] {"uid", "cn", "sn"});
authenticator.authenticate(new UsernamePasswordAuthenticationToken("bob", "wrongpass"));
}
@Test
public void testMultipleDnPatternsWorkOk() {
authenticator.setUserDnPatterns(new String[] {"uid={0},ou=nonexistent", "uid={0},ou=people"});
authenticator.authenticate(bob);
}
@Test
public void testOnlySpecifiedAttributesAreRetrieved() throws Exception {
authenticator.setUserAttributes(new String[] {"uid", "userPassword"});
DirContextAdapter user = (DirContextAdapter) authenticator.authenticate(bob);
assertEquals("Should have retrieved 2 attribute (uid, userPassword)", 2, user.getAttributes().size());
}
@Test
public void testLdapCompareSucceedsWithCorrectPassword() {
// Don't retrieve the password
authenticator.setUserAttributes(new String[] {"uid"});
authenticator.authenticate(bob);
}
@Test
public void testLdapCompareSucceedsWithShaEncodedPassword() {
// Don't retrieve the password
authenticator.setUserAttributes(new String[] {"uid"});
authenticator.setPasswordEncoder(new LdapShaPasswordEncoder());
authenticator.authenticate(ben);
}
@Test(expected = IllegalArgumentException.class)
public void testPasswordEncoderCantBeNull() {
authenticator.setPasswordEncoder(null);
}
@Test
public void testUseOfDifferentPasswordAttributeSucceeds() {
authenticator.setPasswordAttributeName("uid");
authenticator.authenticate(new UsernamePasswordAuthenticationToken("bob", "bob"));
}
@Test
public void testLdapCompareWithDifferentPasswordAttributeSucceeds() {
authenticator.setUserAttributes(new String[] {"uid"});
authenticator.setPasswordAttributeName("cn");
authenticator.authenticate(new UsernamePasswordAuthenticationToken("ben", "Ben Alex"));
}
@Test
public void testWithUserSearch() {
authenticator = new PasswordComparisonAuthenticator(getContextSource());
authenticator.setPasswordEncoder(new PlaintextPasswordEncoder());
assertTrue("User DN matches shouldn't be available", authenticator.getUserDns("Bob").isEmpty());
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("uid=Bob,ou=people"));
ctx.setAttributeValue("userPassword", "bobspassword");
authenticator.setUserSearch(new MockUserSearch(ctx));
authenticator.authenticate(new UsernamePasswordAuthenticationToken("shouldntbeused", "bobspassword"));
}
}
@@ -1,113 +0,0 @@
package org.springframework.security.userdetails.ldap;
import junit.framework.TestCase;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
/**
* @author Luke Taylor
* @version $Id$
*/
public class InetOrgPersonTests extends TestCase {
public void testUsernameIsMappedFromContextUidIfNotSet() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
assertEquals("ghengis", p.getUsername());
}
public void testUsernameIsDifferentFromContextUidIfSet() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
essence.setUsername("joe");
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
assertEquals("joe", p.getUsername());
assertEquals("ghengis", p.getUid());
}
public void testAttributesMapCorrectlyFromContext() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
assertEquals("HORS1", p.getCarLicense());
assertEquals("ghengis@mongolia", p.getMail());
assertEquals("Khan", p.getSn());
assertEquals("Ghengis Khan", p.getCn()[0]);
assertEquals("00001", p.getEmployeeNumber());
assertEquals("+442075436521", p.getTelephoneNumber());
assertEquals("Steppes", p.getHomePostalAddress());
assertEquals("+467575436521", p.getHomePhone());
assertEquals("Hordes", p.getO());
assertEquals("Horde1", p.getOu());
assertEquals("On the Move", p.getPostalAddress());
assertEquals("Changes Frequently", p.getPostalCode());
assertEquals("Yurt 1", p.getRoomNumber());
assertEquals("Westward Avenue", p.getStreet());
assertEquals("Scary", p.getDescription());
assertEquals("Ghengis McCann", p.getDisplayName());
assertEquals("G", p.getInitials());
}
public void testPasswordIsSetFromContextUserPassword() {
InetOrgPerson.Essence essence = new InetOrgPerson.Essence(createUserContext());
InetOrgPerson p = (InetOrgPerson) essence.createUserDetails();
assertEquals("pillage", p.getPassword());
}
public void testMappingBackToContextMatchesOriginalData() {
DirContextAdapter ctx1 = createUserContext();
DirContextAdapter ctx2 = new DirContextAdapter();
ctx1.setAttributeValues("objectclass", new String[] {"top", "person", "organizationalPerson", "inetOrgPerson"});
ctx2.setDn(new DistinguishedName("ignored=ignored"));
InetOrgPerson p = (InetOrgPerson) (new InetOrgPerson.Essence(ctx1)).createUserDetails();
p.populateContext(ctx2);
assertEquals(ctx1, ctx2);
}
public void testCopyMatchesOriginalData() {
DirContextAdapter ctx1 = createUserContext();
DirContextAdapter ctx2 = new DirContextAdapter();
ctx2.setDn(new DistinguishedName("ignored=ignored"));
ctx1.setAttributeValues("objectclass", new String[] {"top", "person", "organizationalPerson", "inetOrgPerson"});
InetOrgPerson p = (InetOrgPerson) (new InetOrgPerson.Essence(ctx1)).createUserDetails();
InetOrgPerson p2 = (InetOrgPerson) new InetOrgPerson.Essence(p).createUserDetails();
p2.populateContext(ctx2);
assertEquals(ctx1, ctx2);
}
private DirContextAdapter createUserContext() {
DirContextAdapter ctx = new DirContextAdapter();
ctx.setDn(new DistinguishedName("ignored=ignored"));
ctx.setAttributeValue("uid", "ghengis");
ctx.setAttributeValue("userPassword", "pillage");
ctx.setAttributeValue("carLicense", "HORS1");
ctx.setAttributeValue("cn", "Ghengis Khan");
ctx.setAttributeValue("description", "Scary");
ctx.setAttributeValue("destinationIndicator", "West");
ctx.setAttributeValue("displayName", "Ghengis McCann");
ctx.setAttributeValue("homePhone", "+467575436521");
ctx.setAttributeValue("initials", "G");
ctx.setAttributeValue("employeeNumber", "00001");
ctx.setAttributeValue("homePostalAddress", "Steppes");
ctx.setAttributeValue("mail", "ghengis@mongolia");
ctx.setAttributeValue("mobile", "always");
ctx.setAttributeValue("o", "Hordes");
ctx.setAttributeValue("ou", "Horde1");
ctx.setAttributeValue("postalAddress", "On the Move");
ctx.setAttributeValue("postalCode", "Changes Frequently");
ctx.setAttributeValue("roomNumber", "Yurt 1");
ctx.setAttributeValue("roomNumber", "Yurt 1");
ctx.setAttributeValue("sn", "Khan");
ctx.setAttributeValue("street", "Westward Avenue");
ctx.setAttributeValue("telephoneNumber", "+442075436521");
return ctx;
}
}
@@ -1,210 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.security.util.AuthorityUtils;
/**
* @author Luke Taylor
* @version $Id$
*/
public class LdapUserDetailsManagerTests extends AbstractLdapIntegrationTests {
private static final List<GrantedAuthority> TEST_AUTHORITIES = AuthorityUtils.createAuthorityList("ROLE_CLOWNS","ROLE_ACROBATS");
private LdapUserDetailsManager mgr;
private SpringSecurityLdapTemplate template;
public void onSetUp() throws Exception {
super.onSetUp();
mgr = new LdapUserDetailsManager(getContextSource());
template = new SpringSecurityLdapTemplate(getContextSource());
DirContextAdapter ctx = new DirContextAdapter();
ctx.setAttributeValue("objectclass", "organizationalUnit");
ctx.setAttributeValue("ou", "test people");
template.bind("ou=test people", ctx, null);
ctx.setAttributeValue("ou", "testgroups");
template.bind("ou=testgroups", ctx, null);
DirContextAdapter group = new DirContextAdapter();
group.setAttributeValue("objectclass", "groupOfNames");
group.setAttributeValue("cn", "clowns");
group.setAttributeValue("member", "cn=nobody,ou=test people,dc=springframework,dc=org");
template.bind("cn=clowns,ou=testgroups", group, null);
group.setAttributeValue("cn", "acrobats");
template.bind("cn=acrobats,ou=testgroups", group, null);
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=test people","uid"));
mgr.setGroupSearchBase("ou=testgroups");
mgr.setGroupRoleAttributeName("cn");
mgr.setGroupMemberAttributeName("member");
mgr.setUserDetailsMapper(new PersonContextMapper());
}
@After
public void onTearDown() throws Exception {
// Iterator people = template.list("ou=testpeople").iterator();
// DirContext rootCtx = new DirContextAdapter(new DistinguishedName(getInitialCtxFactory().getRootDn()));
//
// while(people.hasNext()) {
// template.unbind((String) people.next() + ",ou=testpeople");
// }
template.unbind("ou=test people",true);
template.unbind("ou=testgroups",true);
SecurityContextHolder.clearContext();
}
@Test
public void testLoadUserByUsernameReturnsCorrectData() {
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people","uid"));
mgr.setGroupSearchBase("ou=groups");
LdapUserDetails bob = (LdapUserDetails) mgr.loadUserByUsername("bob");
assertEquals("bob", bob.getUsername());
assertEquals("uid=bob,ou=people,dc=springframework,dc=org", bob.getDn());
assertEquals("bobspassword", bob.getPassword());
assertEquals(1, bob.getAuthorities().size());
}
@Test(expected = UsernameNotFoundException.class)
public void testLoadingInvalidUsernameThrowsUsernameNotFoundException() {
mgr.loadUserByUsername("jim");
}
@Test
public void testUserExistsReturnsTrueForValidUser() {
mgr.setUsernameMapper(new DefaultLdapUsernameToDnMapper("ou=people","uid"));
assertTrue(mgr.userExists("bob"));
}
@Test
public void testUserExistsReturnsFalseForInValidUser() {
assertFalse(mgr.userExists("jim"));
}
@Test
public void testCreateNewUserSucceeds() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setCarLicense("XXX");
p.setCn(new String[] {"Joe Smeth"});
p.setDepartmentNumber("5679");
p.setDescription("Some description");
p.setDn("whocares");
p.setEmployeeNumber("E781");
p.setInitials("J");
p.setMail("joe@smeth.com");
p.setMobile("+44776542911");
p.setOu("Joes Unit");
p.setO("Organization");
p.setRoomNumber("500X");
p.setSn("Smeth");
p.setUid("joe");
p.setAuthorities(TEST_AUTHORITIES);
mgr.createUser(p.createUserDetails());
}
@Test
public void testDeleteUserSucceeds() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setDn("whocares");
p.setCn(new String[] {"Don Smeth"});
p.setSn("Smeth");
p.setUid("don");
p.setAuthorities(TEST_AUTHORITIES);
mgr.createUser(p.createUserDetails());
mgr.setUserDetailsMapper(new InetOrgPersonContextMapper());
InetOrgPerson don = (InetOrgPerson) mgr.loadUserByUsername("don");
assertEquals(2, don.getAuthorities().size());
mgr.deleteUser("don");
try {
mgr.loadUserByUsername("don");
fail("Expected UsernameNotFoundException after deleting user");
} catch(UsernameNotFoundException expected) {
// expected
}
// Check that no authorities are left
assertEquals(0, mgr.getUserAuthorities(mgr.usernameMapper.buildDn("don"), "don").size());
}
@Test
public void testPasswordChangeWithCorrectOldPasswordSucceeds() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setDn("whocares");
p.setCn(new String[] {"John Yossarian"});
p.setSn("Yossarian");
p.setUid("johnyossarian");
p.setPassword("yossarianspassword");
p.setAuthorities(TEST_AUTHORITIES);
mgr.createUser(p.createUserDetails());
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("johnyossarian", "yossarianspassword", TEST_AUTHORITIES));
mgr.changePassword("yossarianspassword", "yossariansnewpassword");
assertTrue(template.compare("uid=johnyossarian,ou=test people",
"userPassword", "yossariansnewpassword"));
}
@Test(expected = BadCredentialsException.class)
public void testPasswordChangeWithWrongOldPasswordFails() {
InetOrgPerson.Essence p = new InetOrgPerson.Essence();
p.setDn("whocares");
p.setCn(new String[] {"John Yossarian"});
p.setSn("Yossarian");
p.setUid("johnyossarian");
p.setPassword("yossarianspassword");
p.setAuthorities(TEST_AUTHORITIES);
mgr.createUser(p.createUserDetails());
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("johnyossarian", "yossarianspassword", TEST_AUTHORITIES));
mgr.changePassword("wrongpassword", "yossariansnewpassword");
}
}
@@ -1,86 +0,0 @@
/* 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.springframework.security.userdetails.ldap;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import junit.framework.TestCase;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.security.util.AuthorityUtils;
/**
* Tests {@link LdapUserDetailsMapper}.
*
* @author Luke Taylor
* @version $Id$
*/
public class LdapUserDetailsMapperTests extends TestCase {
public void testMultipleRoleAttributeValuesAreMappedToAuthorities() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
mapper.setConvertToUpperCase(false);
mapper.setRolePrefix("");
mapper.setRoleAttributes(new String[] {"userRole"});
DirContextAdapter ctx = new DirContextAdapter();
ctx.setAttributeValues("userRole", new String[] {"X", "Y", "Z"});
ctx.setAttributeValue("uid", "ani");
LdapUserDetailsImpl user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx, "ani", AuthorityUtils.NO_AUTHORITIES);
assertEquals(3, user.getAuthorities().size());
}
/**
* SEC-303. Non-retrieved role attribute causes NullPointerException
*/
public void testNonRetrievedRoleAttributeIsIgnored() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
mapper.setRoleAttributes(new String[] {"userRole", "nonRetrievedAttribute"});
BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("userRole", "x"));
DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName("cn=someName"));
ctx.setAttributeValue("uid", "ani");
LdapUserDetailsImpl user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx, "ani", AuthorityUtils.NO_AUTHORITIES);
assertEquals(1, user.getAuthorities().size());
assertEquals("ROLE_X", user.getAuthorities().get(0).getAuthority());
}
public void testPasswordAttributeIsMappedCorrectly() throws Exception {
LdapUserDetailsMapper mapper = new LdapUserDetailsMapper();
mapper.setPasswordAttributeName("myappsPassword");
BasicAttributes attrs = new BasicAttributes();
attrs.put(new BasicAttribute("myappsPassword", "mypassword".getBytes()));
DirContextAdapter ctx = new DirContextAdapter(attrs, new DistinguishedName("cn=someName"));
ctx.setAttributeValue("uid", "ani");
LdapUserDetails user = (LdapUserDetailsImpl) mapper.mapUserFromContext(ctx, "ani", AuthorityUtils.NO_AUTHORITIES);
assertEquals("mypassword", user.getPassword());
}
}
@@ -1,57 +0,0 @@
package org.springframework.security.userdetails.ldap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.ldap.LdapAuthoritiesPopulator;
import org.springframework.security.providers.ldap.authenticator.MockUserSearch;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.util.AuthorityUtils;
/**
* Tests for {@link LdapUserDetailsService}
*
* @author Luke Taylor
* @version $Id$
*/
public class LdapUserDetailsServiceTests {
@Test(expected = IllegalArgumentException.class)
public void rejectsNullSearchObject() {
new LdapUserDetailsService(null, new MockAuthoritiesPopulator());
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullAuthoritiesPopulator() {
new LdapUserDetailsService(new MockUserSearch(), null);
}
@Test
public void correctAuthoritiesAreReturned() {
DirContextAdapter userData = new DirContextAdapter(new DistinguishedName("uid=joe"));
LdapUserDetailsService service =
new LdapUserDetailsService(new MockUserSearch(userData), new MockAuthoritiesPopulator());
service.setUserDetailsMapper(new LdapUserDetailsMapper());
UserDetails user = service.loadUserByUsername("doesntmatterwegetjoeanyway");
Set<String> authorities = AuthorityUtils.authorityListToSet(user.getAuthorities());
assertEquals(1, authorities.size());
assertTrue(authorities.contains("ROLE_FROM_POPULATOR"));
}
class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator {
public List<GrantedAuthority> getGrantedAuthorities(DirContextOperations userCtx, String username) {
return AuthorityUtils.createAuthorityList("ROLE_FROM_POPULATOR");
}
}
}
-3
View File
@@ -9,6 +9,3 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p %c{1} - %m%n
log4j.logger.org.springframework.security=DEBUG
log4j.logger.org.springframework.ldap=DEBUG
log4j.logger.org.apache.directory=ERROR
-82
View File
@@ -1,82 +0,0 @@
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups
dn: ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: subgroups
dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people
dn: ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: otherpeople
dn: uid=ben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Ben Alex
sn: Alex
uid: ben
userPassword: {SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=
dn: uid=bob,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword: bobspassword
dn: uid=joe,ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Joe Smeth
sn: Smeth
uid: joe
userPassword: joespassword
dn: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Mouse, Jerry
sn: Mouse
uid: jerry
userPassword: jerryspassword
dn: cn=developers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: developers
ou: developer
member: uid=ben,ou=people,dc=springframework,dc=org
member: uid=bob,ou=people,dc=springframework,dc=org
dn: cn=managers,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: managers
ou: manager
member: uid=ben,ou=people,dc=springframework,dc=org
member: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
dn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: submanagers
ou: submanager
member: uid=ben,ou=people,dc=springframework,dc=org
@@ -1,16 +0,0 @@
dn: ou=gorillas,dc=monkeymachine,dc=co,dc=uk
objectclass: top
objectclass: organizationalUnit
ou: gorillas
dn: uid=pg,ou=gorillas,dc=monkeymachine,dc=co,dc=uk
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Pierre
sn: Gorille
uid: pg
userPassword: password