From 66897e1849ae5bd196d94ffacc670daa917e69d6 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Fri, 28 Nov 2008 22:22:51 +0000 Subject: [PATCH] SEC-1036: Upgraded Spring LDAP to 1.3 and made corresponding code changes. Also some general tidying up of LDAP code. Removed deprecated context factory classes. --- core/pom.xml | 2 +- .../HttpSessionContextIntegrationFilter.java | 16 - .../ldap/DefaultInitialDirContextFactory.java | 364 ------------------ .../ldap/InitialDirContextFactory.java | 58 --- .../ldap/SpringSecurityContextSource.java | 2 + .../ldap/SpringSecurityLdapTemplate.java | 12 +- .../ldap/authenticator/BindAuthenticator.java | 80 ++-- .../ldap/AbstractLdapIntegrationTests.java | 49 +-- .../DefaultInitialDirContextFactoryTests.java | 209 ---------- .../ldap/MockSpringSecurityContextSource.java | 12 +- ...ringSecurityAuthenticationSourceTests.java | 4 +- ...swordComparisonAuthenticatorMockTests.java | 2 +- .../ldap/LdapUserDetailsManagerTests.java | 2 +- .../ldap/ldapIntegrationTestContext.xml | 18 - 14 files changed, 74 insertions(+), 756 deletions(-) delete mode 100644 core/src/main/java/org/springframework/security/ldap/DefaultInitialDirContextFactory.java delete mode 100644 core/src/main/java/org/springframework/security/ldap/InitialDirContextFactory.java delete mode 100644 core/src/test/java/org/springframework/security/ldap/DefaultInitialDirContextFactoryTests.java delete mode 100644 core/src/test/resources/org/springframework/security/ldap/ldapIntegrationTestContext.xml diff --git a/core/pom.xml b/core/pom.xml index fd044c0e43..f177c4218c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -62,7 +62,7 @@ org.springframework.ldap - spring-ldap + spring-ldap-core true diff --git a/core/src/main/java/org/springframework/security/context/HttpSessionContextIntegrationFilter.java b/core/src/main/java/org/springframework/security/context/HttpSessionContextIntegrationFilter.java index 2d69c748ec..1b3a26ef2a 100644 --- a/core/src/main/java/org/springframework/security/context/HttpSessionContextIntegrationFilter.java +++ b/core/src/main/java/org/springframework/security/context/HttpSessionContextIntegrationFilter.java @@ -15,22 +15,9 @@ package org.springframework.security.context; -import java.io.IOException; -import java.lang.reflect.Method; - -import javax.servlet.FilterChain; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpServletResponseWrapper; -import javax.servlet.http.HttpSession; import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; -import org.springframework.security.AuthenticationTrustResolver; -import org.springframework.security.AuthenticationTrustResolverImpl; -import org.springframework.security.ui.SpringSecurityFilter; import org.springframework.security.ui.FilterChainOrder; /** @@ -110,8 +97,6 @@ public class HttpSessionContextIntegrationFilter extends SecurityContextPersiste private Class contextClass = SecurityContextImpl.class; -// private Object contextObject; - /** * Indicates if this filter can create a HttpSession if * needed (sessions are always created sparingly, but setting this value to @@ -160,7 +145,6 @@ public class HttpSessionContextIntegrationFilter extends SecurityContextPersiste private HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository(); public HttpSessionContextIntegrationFilter() throws ServletException { -// this.contextObject = generateNewContext(); super.setSecurityContextRepository(repo); } diff --git a/core/src/main/java/org/springframework/security/ldap/DefaultInitialDirContextFactory.java b/core/src/main/java/org/springframework/security/ldap/DefaultInitialDirContextFactory.java deleted file mode 100644 index c6f673de29..0000000000 --- a/core/src/main/java/org/springframework/security/ldap/DefaultInitialDirContextFactory.java +++ /dev/null @@ -1,364 +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.security.SpringSecurityMessageSource; -import org.springframework.security.BadCredentialsException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.context.MessageSource; -import org.springframework.context.MessageSourceAware; -import org.springframework.context.support.MessageSourceAccessor; - -import org.springframework.util.Assert; -import org.springframework.ldap.UncategorizedLdapException; -import org.springframework.ldap.core.support.DefaultDirObjectFactory; -import org.springframework.ldap.core.DistinguishedName; -import org.springframework.dao.DataAccessException; - -import java.util.Hashtable; -import java.util.Map; -import java.util.StringTokenizer; - -import javax.naming.CommunicationException; -import javax.naming.Context; -import javax.naming.NamingException; -import javax.naming.OperationNotSupportedException; -import javax.naming.ldap.InitialLdapContext; -import javax.naming.directory.DirContext; -import javax.naming.directory.InitialDirContext; - - -/** - * Encapsulates the information for connecting to an LDAP server and provides an access point for obtaining - * DirContext references. - *

- * The directory location is configured using by setting the constructor argument - * providerUrl. This should be in the form ldap://monkeymachine.co.uk:389/dc=springframework,dc=org. - * The Sun JNDI provider also supports lists of space-separated URLs, each of which will be tried in turn until a - * connection is obtained. - *

- *

To obtain an initial context, the client calls the newInitialDirContext method. There are two - * signatures - one with no arguments and one which allows binding with a specific username and password. - *

- *

The no-args version will bind anonymously unless a manager login has been configured using the properties - * managerDn and managerPassword, in which case it will bind as the manager user.

- *

Connection pooling is enabled by default for anonymous or manager connections, but not when binding as a - * specific user.

- * - * @author Robert Sanders - * @author Luke Taylor - * @version $Id$ - * - * - * @deprecated use {@link DefaultSpringSecurityContextSource} instead. - * - * @see The Java tutorial's guide to LDAP - * connection pooling - */ -public class DefaultInitialDirContextFactory implements InitialDirContextFactory, - SpringSecurityContextSource, MessageSourceAware { - //~ Static fields/initializers ===================================================================================== - - private static final Log logger = LogFactory.getLog(DefaultInitialDirContextFactory.class); - private static final String CONNECTION_POOL_KEY = "com.sun.jndi.ldap.connect.pool"; - private static final String AUTH_TYPE_NONE = "none"; - - //~ Instance fields ================================================================================================ - - /** Allows extra environment variables to be added at config time. */ - private Map extraEnvVars = null; - protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - - /** Type of authentication within LDAP; default is simple. */ - private String authenticationType = "simple"; - - /** - * The INITIAL_CONTEXT_FACTORY used to create the JNDI Factory. Default is - * "com.sun.jndi.ldap.LdapCtxFactory"; you should not need to set this unless you have unusual needs. - */ - private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory"; - - private String dirObjectFactoryClass = DefaultDirObjectFactory.class.getName(); - - /** - * If your LDAP server does not allow anonymous searches then you will need to provide a "manager" user's - * DN to log in with. - */ - private String managerDn = null; - - /** The manager user's password. */ - private String managerPassword = "manager_password_not_set"; - - /** The LDAP url of the server (and root context) to connect to. */ - private String providerUrl; - - /** - * The root DN. This is worked out from the url. It is used by client classes when forming a full DN for - * bind authentication (for example). - */ - private String rootDn = null; - - /** - * Use the LDAP Connection pool; if true, then the LDAP environment property - * "com.sun.jndi.ldap.connect.pool" is added to any other JNDI properties. - */ - private boolean useConnectionPool = true; - - /** Set to true for ldap v3 compatible servers */ - private boolean useLdapContext = false; - - //~ Constructors =================================================================================================== - - /** - * Create and initialize an instance to the LDAP url provided - * - * @param providerUrl a String of the form ldap://localhost:389/base_dn - */ - public DefaultInitialDirContextFactory(String providerUrl) { - this.setProviderUrl(providerUrl); - } - - //~ Methods ======================================================================================================== - - /** - * Set the LDAP url - * - * @param providerUrl a String of the form ldap://localhost:389/base_dn - */ - private void setProviderUrl(String providerUrl) { - Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied."); - - this.providerUrl = providerUrl; - - StringTokenizer st = new StringTokenizer(providerUrl); - - // Work out rootDn from the first URL and check that the other URLs (if any) match - while (st.hasMoreTokens()) { - String url = st.nextToken(); - String urlRootDn = LdapUtils.parseRootDnFromUrl(url); - - logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'"); - - if (rootDn == null) { - rootDn = urlRootDn; - } else if (!rootDn.equals(urlRootDn)) { - throw new IllegalArgumentException("Root DNs must be the same when using multiple URLs"); - } - } - - // This doesn't necessarily hold for embedded servers. - //Assert.isTrue(uri.getScheme().equals("ldap"), "Ldap URL must start with 'ldap://'"); - } - - /** - * Get the LDAP url - * - * @return the url - */ - private String getProviderUrl() { - return providerUrl; - } - - private InitialDirContext connect(Hashtable env) { - if (logger.isDebugEnabled()) { - Hashtable envClone = (Hashtable) env.clone(); - - if (envClone.containsKey(Context.SECURITY_CREDENTIALS)) { - envClone.put(Context.SECURITY_CREDENTIALS, "******"); - } - - logger.debug("Creating InitialDirContext with environment " + envClone); - } - - try { - return useLdapContext ? new InitialLdapContext(env, null) : new InitialDirContext(env); - } catch (NamingException ne) { - if ((ne instanceof javax.naming.AuthenticationException) - || (ne instanceof OperationNotSupportedException)) { - throw new BadCredentialsException(messages.getMessage("DefaultIntitalDirContextFactory.badCredentials", - "Bad credentials"), ne); - } - - if (ne instanceof CommunicationException) { - throw new UncategorizedLdapException(messages.getMessage( - "DefaultIntitalDirContextFactory.communicationFailure", "Unable to connect to LDAP server"), ne); - } - - throw new UncategorizedLdapException(messages.getMessage( - "DefaultIntitalDirContextFactory.unexpectedException", - "Failed to obtain InitialDirContext due to unexpected exception"), ne); - } - } - - /** - * Sets up the environment parameters for creating a new context. - * - * @return the Hashtable describing the base DirContext that will be created, minus the username/password if any. - */ - protected Hashtable getEnvironment() { - Hashtable env = new Hashtable(); - - env.put(Context.SECURITY_AUTHENTICATION, authenticationType); - env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory); - env.put(Context.PROVIDER_URL, getProviderUrl()); - - if (useConnectionPool) { - env.put(CONNECTION_POOL_KEY, "true"); - } - - if ((extraEnvVars != null) && (extraEnvVars.size() > 0)) { - env.putAll(extraEnvVars); - } - - return env; - } - - /** - * Returns the root DN of the configured provider URL. For example, if the URL is - * ldap://monkeymachine.co.uk:389/dc=springframework,dc=org the value will be - * dc=springframework,dc=org. - * - * @return the root DN calculated from the path of the LDAP url. - */ - public String getRootDn() { - return rootDn; - } - - /** - * Connects anonymously unless a manager user has been specified, in which case it will bind as the - * manager. - * - * @return the resulting context object. - */ - public DirContext newInitialDirContext() { - if (managerDn != null) { - return newInitialDirContext(managerDn, managerPassword); - } - - Hashtable env = getEnvironment(); - env.put(Context.SECURITY_AUTHENTICATION, AUTH_TYPE_NONE); - - return connect(env); - } - - public DirContext newInitialDirContext(String username, String password) { - Hashtable env = getEnvironment(); - - // Don't pool connections for individual users - if (!username.equals(managerDn)) { - env.remove(CONNECTION_POOL_KEY); - } - - env.put(Context.SECURITY_PRINCIPAL, username); - env.put(Context.SECURITY_CREDENTIALS, password); - - if(dirObjectFactoryClass != null) { - env.put(Context.OBJECT_FACTORIES, dirObjectFactoryClass); - } - - return connect(env); - } - - /** Spring LDAP ContextSource method */ - public DirContext getReadOnlyContext() throws DataAccessException { - return newInitialDirContext(); - } - - /** Spring LDAP ContextSource method */ - public DirContext getReadWriteContext() throws DataAccessException { - return newInitialDirContext(); - } - - public void setAuthenticationType(String authenticationType) { - Assert.hasLength(authenticationType, "LDAP Authentication type must not be empty or null"); - this.authenticationType = authenticationType; - } - - /** - * Sets any custom environment variables which will be added to the those returned - * by the getEnvironment method. - * - * @param extraEnvVars extra environment variables to be added at config time. - */ - public void setExtraEnvVars(Map extraEnvVars) { - Assert.notNull(extraEnvVars, "Extra environment map cannot be null."); - this.extraEnvVars = extraEnvVars; - } - - public void setInitialContextFactory(String initialContextFactory) { - Assert.hasLength(initialContextFactory, "Initial context factory name cannot be empty or null"); - this.initialContextFactory = initialContextFactory; - } - - /** - * Sets the directory user to authenticate as when obtaining a context using the - * newInitialDirContext() method. - * If no name is supplied then the context will be obtained anonymously. - * - * @param managerDn The name of the "manager" user for default authentication. - */ - public void setManagerDn(String managerDn) { - Assert.hasLength(managerDn, "Manager user name cannot be empty or null."); - this.managerDn = managerDn; - } - - /** - * Sets the password which will be used in combination with the manager DN. - * - * @param managerPassword The "manager" user's password. - */ - public void setManagerPassword(String managerPassword) { - Assert.hasLength(managerPassword, "Manager password must not be empty or null."); - this.managerPassword = managerPassword; - } - - public void setMessageSource(MessageSource messageSource) { - this.messages = new MessageSourceAccessor(messageSource); - } - - /** - * Connection pooling is enabled by default for anonymous or "manager" connections when using the default - * Sun provider. To disable all connection pooling, set this property to false. - * - * @param useConnectionPool whether to pool connections for non-specific users. - */ - public void setUseConnectionPool(boolean useConnectionPool) { - this.useConnectionPool = useConnectionPool; - } - - public void setUseLdapContext(boolean useLdapContext) { - this.useLdapContext = useLdapContext; - } - - public void setDirObjectFactory(String dirObjectFactory) { - this.dirObjectFactoryClass = dirObjectFactory; - } - - public DirContext getReadWriteContext(String userDn, Object credentials) { - return newInitialDirContext(userDn, (String) credentials); - } - - public DistinguishedName getBaseLdapPath() { - return new DistinguishedName(rootDn); - } - - public String getBaseLdapPathAsString() { - return getBaseLdapPath().toString(); - } -} diff --git a/core/src/main/java/org/springframework/security/ldap/InitialDirContextFactory.java b/core/src/main/java/org/springframework/security/ldap/InitialDirContextFactory.java deleted file mode 100644 index 87111dd15a..0000000000 --- a/core/src/main/java/org/springframework/security/ldap/InitialDirContextFactory.java +++ /dev/null @@ -1,58 +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.directory.DirContext; - - -/** - * Access point for obtaining LDAP contexts. - * - * @see org.springframework.security.ldap.DefaultInitialDirContextFactory - * - * @deprecated Use SpringSecurityContextSource instead - * @author Luke Taylor - * @version $Id$ - */ -public interface InitialDirContextFactory { - //~ Methods ======================================================================================================== - - /** - * Returns the root DN of the contexts supplied by this factory. - * The names for searches etc. which are performed against contexts - * returned by this factory should be relative to the root DN. - * - * @return The DN of the contexts returned by this factory. - */ - String getRootDn(); - - /** - * Provides an initial context without specific user information. - * - * @return An initial context for the LDAP directory - */ - DirContext newInitialDirContext(); - - /** - * Provides an initial context by binding as a specific user. - * - * @param userDn the user to authenticate as when obtaining the context. - * @param password the user's password. - * - * @return An initial context for the LDAP directory - */ - DirContext newInitialDirContext(String userDn, String password); -} diff --git a/core/src/main/java/org/springframework/security/ldap/SpringSecurityContextSource.java b/core/src/main/java/org/springframework/security/ldap/SpringSecurityContextSource.java index 89663d6c4b..3919d3cdec 100644 --- a/core/src/main/java/org/springframework/security/ldap/SpringSecurityContextSource.java +++ b/core/src/main/java/org/springframework/security/ldap/SpringSecurityContextSource.java @@ -11,6 +11,8 @@ import javax.naming.directory.DirContext; * @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 { diff --git a/core/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java b/core/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java index a3edf94c44..50f10bd53d 100644 --- a/core/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java +++ b/core/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java @@ -90,7 +90,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { ctls.setReturningAttributes(NO_ATTRS); ctls.setSearchScope(SearchControls.OBJECT_SCOPE); - NamingEnumeration results = ctx.search(dn, comparisonFilter, new Object[] {value}, ctls); + NamingEnumeration results = ctx.search(dn, comparisonFilter, new Object[] {value}, ctls); return Boolean.valueOf(results.hasMore()); } @@ -135,7 +135,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { * * @return the set of String values for the attribute as a union of the values found in all the matching entries. */ - public Set searchForSingleAttributeValues(final String base, final String filter, final Object[] params, + public Set 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]; @@ -147,7 +147,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { String formattedFilter = MessageFormat.format(filter, encodedParams); logger.debug("Using filter: " + formattedFilter); - final HashSet set = new HashSet(); + final HashSet set = new HashSet(); ContextMapper roleMapper = new ContextMapper() { public Object mapFromContext(Object ctx) { @@ -193,12 +193,12 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { return (DirContextOperations) executeReadOnly(new ContextExecutor() { public Object executeWithContext(DirContext ctx) throws NamingException { DistinguishedName ctxBaseDn = new DistinguishedName(ctx.getNameInNamespace()); - NamingEnumeration resultsEnum = ctx.search(base, filter, params, searchControls); - Set results = new HashSet(); + NamingEnumeration resultsEnum = ctx.search(base, filter, params, searchControls); + Set results = new HashSet(); try { while (resultsEnum.hasMore()) { - SearchResult searchResult = (SearchResult) resultsEnum.next(); + SearchResult searchResult = resultsEnum.next(); // Work out the DN of the matched entry StringBuffer dn = new StringBuffer(searchResult.getName()); diff --git a/core/src/main/java/org/springframework/security/providers/ldap/authenticator/BindAuthenticator.java b/core/src/main/java/org/springframework/security/providers/ldap/authenticator/BindAuthenticator.java index 82b4af5cb8..088580e2d6 100644 --- a/core/src/main/java/org/springframework/security/providers/ldap/authenticator/BindAuthenticator.java +++ b/core/src/main/java/org/springframework/security/providers/ldap/authenticator/BindAuthenticator.java @@ -15,22 +15,21 @@ package org.springframework.security.providers.ldap.authenticator; -import org.springframework.security.Authentication; -import org.springframework.security.BadCredentialsException; -import org.springframework.security.ldap.SpringSecurityContextSource; -import org.springframework.security.ldap.SpringSecurityLdapTemplate; -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; -import org.springframework.dao.DataAccessException; -import org.springframework.ldap.core.ContextSource; -import org.springframework.ldap.core.DirContextOperations; -import org.springframework.ldap.core.DistinguishedName; -import org.springframework.util.Assert; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - -import javax.naming.directory.DirContext; -import java.util.Iterator; +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; /** @@ -55,7 +54,7 @@ public class BindAuthenticator extends AbstractLdapAuthenticator { * performed. * */ - public BindAuthenticator(SpringSecurityContextSource contextSource) { + public BindAuthenticator(BaseLdapPathContextSource contextSource) { super(contextSource); } @@ -70,14 +69,11 @@ public class BindAuthenticator extends AbstractLdapAuthenticator { String password = (String)authentication.getCredentials(); // If DN patterns are configured, try authenticating with them directly - Iterator dns = getUserDns(username).iterator(); - - while (dns.hasNext() && user == null) { - user = bindWithDn((String) dns.next(), username, password); + for (String dn : getUserDns(username)) { + user = bindWithDn(dn, username, password); } - // Otherwise use the configured locator to find the user - // and authenticate with the returned DN. + // 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); @@ -92,17 +88,29 @@ public class BindAuthenticator extends AbstractLdapAuthenticator { } private DirContextOperations bindWithDn(String userDn, String username, String password) { - SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate( - new BindWithSpecificDnContextSource((SpringSecurityContextSource) getContextSource(), userDn, password)); + BaseLdapPathContextSource ctxSource = (BaseLdapPathContextSource) getContextSource(); + DistinguishedName fullDn = new DistinguishedName(userDn); + fullDn.prepend(ctxSource.getBaseLdapPath()); + + logger.debug("Attempting to bind as " + fullDn); try { - return template.retrieveEntry(userDn, getUserAttributes()); + DirContext ctx = getContextSource().getContext(fullDn.toString(), password); + Attributes attrs = ctx.getAttributes(userDn, getUserAttributes()); - } catch (BadCredentialsException e) { + 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. - handleBindException(userDn, username, e.getCause()); + 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; @@ -117,26 +125,4 @@ public class BindAuthenticator extends AbstractLdapAuthenticator { logger.debug("Failed to bind as " + userDn + ": " + cause); } } - - private class BindWithSpecificDnContextSource implements ContextSource { - private SpringSecurityContextSource ctxFactory; - DistinguishedName userDn; - private String password; - - public BindWithSpecificDnContextSource(SpringSecurityContextSource ctxFactory, String userDn, String password) { - this.ctxFactory = ctxFactory; - this.userDn = new DistinguishedName(userDn); - this.userDn.prepend(ctxFactory.getBaseLdapPath()); - this.password = password; - } - - public DirContext getReadOnlyContext() throws DataAccessException { - return ctxFactory.getReadWriteContext(userDn.toString(), password); - } - - public DirContext getReadWriteContext() throws DataAccessException { - return getReadOnlyContext(); - } - } - } diff --git a/core/src/test/java/org/springframework/security/ldap/AbstractLdapIntegrationTests.java b/core/src/test/java/org/springframework/security/ldap/AbstractLdapIntegrationTests.java index 131ac9d8d0..9fdc8e8d9b 100644 --- a/core/src/test/java/org/springframework/security/ldap/AbstractLdapIntegrationTests.java +++ b/core/src/test/java/org/springframework/security/ldap/AbstractLdapIntegrationTests.java @@ -14,24 +14,25 @@ */ package org.springframework.security.ldap; -import org.springframework.security.config.BeanIds; -import org.springframework.ldap.core.DistinguishedName; -import org.springframework.core.io.ClassPathResource; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.junit.BeforeClass; -import org.junit.Before; -import org.junit.AfterClass; -import org.junit.After; -import org.apache.directory.server.protocol.shared.store.LdifFileLoader; -import org.apache.directory.server.core.DirectoryService; - -import javax.naming.directory.DirContext; -import javax.naming.Name; -import javax.naming.NamingException; -import javax.naming.NamingEnumeration; 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. @@ -40,7 +41,7 @@ import javax.naming.NameNotFoundException; * @version $Id$ */ public abstract class AbstractLdapIntegrationTests { - private static ClassPathXmlApplicationContext appContext; + private static InMemoryXmlApplicationContext appContext; protected AbstractLdapIntegrationTests() { } @@ -48,7 +49,7 @@ public abstract class AbstractLdapIntegrationTests { @BeforeClass public static void loadContext() throws NamingException { shutdownRunningServers(); - appContext = new ClassPathXmlApplicationContext("/org/springframework/security/ldap/ldapIntegrationTestContext.xml"); + appContext = new InMemoryXmlApplicationContext(""); } @@ -98,22 +99,14 @@ public abstract class AbstractLdapIntegrationTests { } } - public SpringSecurityContextSource getContextSource() { - return (SpringSecurityContextSource) appContext.getBean(BeanIds.CONTEXT_SOURCE); + public BaseLdapPathContextSource getContextSource() { + return (BaseLdapPathContextSource)appContext.getBean(BeanIds.CONTEXT_SOURCE); } - /** - * We have both a context source and intitialdircontextfactory. The former is also used in - * the cleanAndSetup method so any mods during tests can mess it up. - * TODO: Once the initialdircontextfactory stuff has been refactored, revisit this and remove this property. - */ - protected DefaultInitialDirContextFactory getInitialDirContextFactory() { - return (DefaultInitialDirContextFactory) appContext.getBean("initialDirContextFactory"); - } private void clearSubContexts(DirContext ctx, Name name) throws NamingException { - NamingEnumeration enumeration = null; + NamingEnumeration enumeration = null; try { enumeration = ctx.listBindings(name); while (enumeration.hasMore()) { diff --git a/core/src/test/java/org/springframework/security/ldap/DefaultInitialDirContextFactoryTests.java b/core/src/test/java/org/springframework/security/ldap/DefaultInitialDirContextFactoryTests.java deleted file mode 100644 index fc7d343c85..0000000000 --- a/core/src/test/java/org/springframework/security/ldap/DefaultInitialDirContextFactoryTests.java +++ /dev/null @@ -1,209 +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.security.SpringSecurityMessageSource; -import org.springframework.security.BadCredentialsException; -import org.springframework.ldap.UncategorizedLdapException; - -import java.util.Hashtable; - -import javax.naming.Context; -import javax.naming.directory.DirContext; - -import static org.junit.Assert.*; -import org.junit.Test; - -/** - * Tests {@link org.springframework.security.ldap.DefaultInitialDirContextFactory}. - * - * @author Luke Taylor - * @version $Id$ - */ -public class DefaultInitialDirContextFactoryTests extends AbstractLdapIntegrationTests { - //~ Instance fields ================================================================================================ - - DefaultInitialDirContextFactory idf; - - //~ Methods ======================================================================================================== - - public void onSetUp() throws Exception { - super.onSetUp(); - idf = getInitialDirContextFactory(); - idf.setMessageSource(new SpringSecurityMessageSource()); - } - - @Test - public void testAnonymousBindSucceeds() throws Exception { - DirContext ctx = idf.newInitialDirContext(); - // Connection pooling should be set by default for anon users. - // Can't rely on this property being there with embedded server - // assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); - ctx.close(); - } - - @Test - public void testBaseDnIsParsedFromCorrectlyFromUrl() { - idf = new DefaultInitialDirContextFactory("ldap://springsecurity.org/dc=springframework,dc=org"); - assertEquals("dc=springframework,dc=org", idf.getRootDn()); - - // Check with an empty root - idf = new DefaultInitialDirContextFactory("ldap://springsecurity.org/"); - assertEquals("", idf.getRootDn()); - - // Empty root without trailing slash - idf = new DefaultInitialDirContextFactory("ldap://springsecurity.org"); - assertEquals("", idf.getRootDn()); - } - - @Test - public void testBindAsManagerFailsIfNoPasswordSet() throws Exception { - idf.setManagerDn("uid=bob,ou=people,dc=springframework,dc=org"); - - DirContext ctx = null; - - try { - ctx = idf.newInitialDirContext(); - fail("Binding with no manager password should fail."); - -// Can't rely on this property being there with embedded server -// assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); - } catch (BadCredentialsException expected) {} - - LdapUtils.closeContext(ctx); - } - - @Test - public void testBindAsManagerSucceeds() throws Exception { - idf.setManagerPassword("bobspassword"); - idf.setManagerDn("uid=bob,ou=people,dc=springframework,dc=org"); - - DirContext ctx = idf.newInitialDirContext(); -// Can't rely on this property being there with embedded server -// assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); - ctx.close(); - } - - @Test - public void testConnectionAsSpecificUserSucceeds() throws Exception { - DirContext ctx = idf.newInitialDirContext("uid=Bob,ou=people,dc=springframework,dc=org", "bobspassword"); - // We don't want pooling for specific users. - // assertNull(ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); -// com.sun.jndi.ldap.LdapPoolManager.showStats(System.out); - ctx.close(); - } - - @Test - public void testConnectionFailure() throws Exception { - // Use the wrong port - idf = new DefaultInitialDirContextFactory("ldap://localhost:60389"); - idf.setInitialContextFactory("com.sun.jndi.ldap.LdapCtxFactory"); - - Hashtable env = new Hashtable(); - env.put("com.sun.jndi.ldap.connect.timeout", "200"); - idf.setExtraEnvVars(env); - idf.setUseConnectionPool(false); // coverage purposes only - - try { - idf.newInitialDirContext(); - fail("Connection succeeded unexpectedly"); - } catch (UncategorizedLdapException expected) {} - } - - @Test - public void testEnvironment() { - idf = new DefaultInitialDirContextFactory("ldap://springsecurity.org/"); - - // check basic env - Hashtable env = idf.getEnvironment(); - //assertEquals("com.sun.jndi.ldap.LdapCtxFactory", env.get(Context.INITIAL_CONTEXT_FACTORY)); - assertEquals("ldap://springsecurity.org/", env.get(Context.PROVIDER_URL)); - assertEquals("simple", env.get(Context.SECURITY_AUTHENTICATION)); - assertNull(env.get(Context.SECURITY_PRINCIPAL)); - assertNull(env.get(Context.SECURITY_CREDENTIALS)); - - // Ctx factory. - idf.setInitialContextFactory("org.springframework.security.NonExistentCtxFactory"); - env = idf.getEnvironment(); - assertEquals("org.springframework.security.NonExistentCtxFactory", env.get(Context.INITIAL_CONTEXT_FACTORY)); - - // Auth type - idf.setAuthenticationType("myauthtype"); - env = idf.getEnvironment(); - assertEquals("myauthtype", env.get(Context.SECURITY_AUTHENTICATION)); - - // Check extra vars - Hashtable extraVars = new Hashtable(); - extraVars.put("extravar", "extravarvalue"); - idf.setExtraEnvVars(extraVars); - env = idf.getEnvironment(); - assertEquals("extravarvalue", env.get("extravar")); - } - - @Test - public void testInvalidPasswordCausesBadCredentialsException() throws Exception { - idf.setManagerDn("uid=bob,ou=people,dc=springframework,dc=org"); - idf.setManagerPassword("wrongpassword"); - - DirContext ctx = null; - - try { - ctx = idf.newInitialDirContext(); - fail("Binding with wrong credentials should fail."); - } catch (BadCredentialsException expected) {} - - LdapUtils.closeContext(ctx); - } - - @Test - public void testMultipleProviderUrlsAreAccepted() { - idf = new DefaultInitialDirContextFactory("ldaps://security.org/dc=springframework,dc=org " - + "ldap://monkeymachine.co.uk/dc=springframework,dc=org"); - } - - @Test - public void testMultipleProviderUrlsWithDifferentRootsAreRejected() { - try { - idf = new DefaultInitialDirContextFactory("ldap://security.org/dc=springframework,dc=org " - + "ldap://monkeymachine.co.uk/dc=someotherplace,dc=org"); - fail("Different root DNs should cause an exception"); - } catch (IllegalArgumentException expected) {} - } - - @Test - public void testSecureLdapUrlIsSupported() { - idf = new DefaultInitialDirContextFactory("ldaps://localhost/dc=springframework,dc=org"); - assertEquals("dc=springframework,dc=org", idf.getRootDn()); - } - -// public void testNonLdapUrlIsRejected() throws Exception { -// DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); -// -// idf.setUrl("http://security.org/dc=springframework,dc=org"); -// idf.setInitialContextFactory(CoreContextFactory.class.getName()); -// -// try { -// idf.afterPropertiesSet(); -// fail("Expected exception for non 'ldap://' URL"); -// } catch(IllegalArgumentException expected) { -// } -// } - @Test - public void testServiceLocationUrlIsSupported() { - idf = new DefaultInitialDirContextFactory("ldap:///dc=springframework,dc=org"); - assertEquals("dc=springframework,dc=org", idf.getRootDn()); - } -} diff --git a/core/src/test/java/org/springframework/security/ldap/MockSpringSecurityContextSource.java b/core/src/test/java/org/springframework/security/ldap/MockSpringSecurityContextSource.java index 8a0276857c..ad34b3dd6a 100644 --- a/core/src/test/java/org/springframework/security/ldap/MockSpringSecurityContextSource.java +++ b/core/src/test/java/org/springframework/security/ldap/MockSpringSecurityContextSource.java @@ -15,18 +15,20 @@ package org.springframework.security.ldap; -import org.springframework.dao.DataAccessException; -import org.springframework.ldap.core.DistinguishedName; - import javax.naming.directory.DirContext; +import org.springframework.dao.DataAccessException; +import org.springframework.ldap.NamingException; +import org.springframework.ldap.core.DistinguishedName; +import org.springframework.ldap.core.support.BaseLdapPathContextSource; + /** * * @author Luke Taylor * @version $Id$ */ -public class MockSpringSecurityContextSource implements SpringSecurityContextSource { +public class MockSpringSecurityContextSource implements BaseLdapPathContextSource { //~ Instance fields ================================================================================================ private DirContext ctx; @@ -52,7 +54,7 @@ public class MockSpringSecurityContextSource implements SpringSecurityContextSou return ctx; } - public DirContext getReadWriteContext(String userDn, Object credentials) { + public DirContext getContext(String principal, String credentials) throws NamingException { return ctx; } diff --git a/core/src/test/java/org/springframework/security/ldap/SpringSecurityAuthenticationSourceTests.java b/core/src/test/java/org/springframework/security/ldap/SpringSecurityAuthenticationSourceTests.java index b465498d77..23e5a9d7d3 100644 --- a/core/src/test/java/org/springframework/security/ldap/SpringSecurityAuthenticationSourceTests.java +++ b/core/src/test/java/org/springframework/security/ldap/SpringSecurityAuthenticationSourceTests.java @@ -64,8 +64,8 @@ public class SpringSecurityAuthenticationSourceTests { user.setDn(new DistinguishedName("uid=joe,ou=users")); AuthenticationSource source = new SpringSecurityAuthenticationSource(); SecurityContextHolder.getContext().setAuthentication( - new TestingAuthenticationToken(user.createUserDetails(), null)); + new TestingAuthenticationToken(user.createUserDetails(), null)); - assertEquals("uid=joe, ou=users", source.getPrincipal()); + assertEquals("uid=joe,ou=users", source.getPrincipal()); } } diff --git a/core/src/test/java/org/springframework/security/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java b/core/src/test/java/org/springframework/security/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java index 0a239c20e1..f7beeee001 100644 --- a/core/src/test/java/org/springframework/security/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java +++ b/core/src/test/java/org/springframework/security/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java @@ -60,7 +60,7 @@ public class PasswordComparisonAuthenticatorMockTests { final Attributes searchResults = new BasicAttributes("", null); context.checking(new Expectations() {{ - oneOf(dirCtx).search(with(equal("cn=Bob, ou=people")), + oneOf(dirCtx).search(with(equal("cn=Bob,ou=people")), with(equal("(userPassword={0})")), with(aNonNull(Object[].class)), with(aNonNull(SearchControls.class))); diff --git a/core/src/test/java/org/springframework/security/userdetails/ldap/LdapUserDetailsManagerTests.java b/core/src/test/java/org/springframework/security/userdetails/ldap/LdapUserDetailsManagerTests.java index 2a52bd76db..f3c0ebdb3b 100644 --- a/core/src/test/java/org/springframework/security/userdetails/ldap/LdapUserDetailsManagerTests.java +++ b/core/src/test/java/org/springframework/security/userdetails/ldap/LdapUserDetailsManagerTests.java @@ -95,7 +95,7 @@ public class LdapUserDetailsManagerTests extends AbstractLdapIntegrationTests { 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("uid=bob,ou=people,dc=springframework,dc=org", bob.getDn()); assertEquals("bobspassword", bob.getPassword()); assertEquals(1, bob.getAuthorities().size()); diff --git a/core/src/test/resources/org/springframework/security/ldap/ldapIntegrationTestContext.xml b/core/src/test/resources/org/springframework/security/ldap/ldapIntegrationTestContext.xml deleted file mode 100644 index cc9f92086a..0000000000 --- a/core/src/test/resources/org/springframework/security/ldap/ldapIntegrationTestContext.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file