diff --git a/core/src/main/java/org/acegisecurity/ldap/LdapEntryMapper.java b/core/src/main/java/org/acegisecurity/ldap/LdapEntryMapper.java new file mode 100644 index 0000000000..cd658ae905 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ldap/LdapEntryMapper.java @@ -0,0 +1,31 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acegisecurity.ldap; + +import javax.naming.directory.Attributes; +import javax.naming.NamingException; + +/** + * A mapper for use with {@link LdapTemplate}. Creates a customized object from + * a set of attributes retrieved from a directory entry. + * + * @author Luke Taylor + * @version $Id$ + */ +public interface LdapEntryMapper { + + public Object mapAttributes(String dn, Attributes attributes) throws NamingException; +} diff --git a/core/src/main/java/org/acegisecurity/ldap/LdapTemplate.java b/core/src/main/java/org/acegisecurity/ldap/LdapTemplate.java index 1cd936452b..28f3c09500 100644 --- a/core/src/main/java/org/acegisecurity/ldap/LdapTemplate.java +++ b/core/src/main/java/org/acegisecurity/ldap/LdapTemplate.java @@ -25,7 +25,10 @@ import javax.naming.directory.Attributes; import javax.naming.directory.Attribute; import org.springframework.dao.DataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import java.util.Set; import java.util.HashSet; @@ -45,37 +48,66 @@ public class LdapTemplate { public static final String[] NO_ATTRS = new String[0]; private InitialDirContextFactory dirContextFactory; - private String managerDn = null; + private String principalDn = null; private String password = null; - /** Default search scope */ - private int searchScope = SearchControls.SUBTREE_SCOPE; + /** Default search controls */ + private SearchControls searchControls = new SearchControls(); public LdapTemplate(InitialDirContextFactory dirContextFactory) { Assert.notNull(dirContextFactory, "An InitialDirContextFactory is required"); this.dirContextFactory = dirContextFactory; + + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); } + /** + * + * @param dirContextFactory the source of DirContexts + * @param userDn the user name to authenticate as when obtaining new contexts + * @param password the user's password + */ public LdapTemplate(InitialDirContextFactory dirContextFactory, String userDn, String password) { this(dirContextFactory); - Assert.hasLength(userDn, "managerDn must not be null or empty"); + Assert.hasLength(userDn, "userDn must not be null or empty"); Assert.notNull(password, "password cannot be null"); - this.managerDn = userDn; + this.principalDn = userDn; this.password = password; } public void setSearchScope(int searchScope) { - this.searchScope = searchScope; + searchControls.setSearchScope(searchScope); + } + + /** + * The time (in milliseconds) which to wait before the search fails; + * the default is zero, meaning forever. + */ + public void setSearchTimeLimit(int searchTimeLimit) { + searchControls.setTimeLimit(searchTimeLimit); + } + + /** + * Sets the corresponding property on the SearchControls instance used + * in the search. + * + */ + public void setDerefLinkFlag(boolean deref) { + searchControls.setDerefLinkFlag(deref); + } + + public void setSearchControls(SearchControls searchControls) { + this.searchControls = searchControls; } public Object execute(LdapCallback callback) throws DataAccessException { DirContext ctx = null; try { - ctx = (managerDn == null) ? + ctx = (principalDn == null) ? dirContextFactory.newInitialDirContext() : - dirContextFactory.newInitialDirContext(managerDn, password); + dirContextFactory.newInitialDirContext(principalDn, password); return callback.execute(ctx); @@ -98,8 +130,10 @@ public class LdapTemplate { ctls.setReturningAttributes(NO_ATTRS); ctls.setSearchScope(SearchControls.OBJECT_SCOPE); + String relativeName = LdapUtils.getRelativeName(dn, ctx); + NamingEnumeration results = - ctx.search(dn, comparisonFilter, new Object[]{value}, ctls); + ctx.search(relativeName, comparisonFilter, new Object[]{value}, ctls); return Boolean.valueOf(results.hasMore()); } @@ -132,7 +166,9 @@ public class LdapTemplate { SearchControls ctls = new SearchControls(); - ctls.setSearchScope(searchScope); + ctls.setSearchScope(searchControls.getSearchScope()); + ctls.setTimeLimit(searchControls.getTimeLimit()); + ctls.setDerefLinkFlag(searchControls.getDerefLinkFlag()); ctls.setReturningAttributes(new String[] {attributeName}); NamingEnumeration matchingEntries = @@ -191,13 +227,65 @@ public class LdapTemplate { * @param attributesToRetrieve the named attributes which will be retrieved from the directory entry. * @return the object created by the mapper */ - public Object retrieveEntry(final String dn, final AttributesMapper mapper, final String[] attributesToRetrieve) { + public Object retrieveEntry(final String dn, final LdapEntryMapper mapper, final String[] attributesToRetrieve) { return execute ( new LdapCallback() { public Object execute(DirContext ctx) throws NamingException { - return mapper.mapAttributes( ctx.getAttributes(LdapUtils.getRelativeName(dn, ctx), attributesToRetrieve) ); + return mapper.mapAttributes(dn, ctx.getAttributes(LdapUtils.getRelativeName(dn, ctx), attributesToRetrieve) ); } } ); } + + /** + * 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. + * + * @param base + * @param filter + * @param params + * @param mapper + * @return the object created by the mapper from the matching entry + * @throws EmptyResultDataAccessException if no results are found. + * @throws IncorrectResultSizeDataAccessException if the search returns more than one result. + */ + public Object searchForSingleEntry(final String base, final String filter, final Object[] params, final LdapEntryMapper mapper) { + return execute ( new LdapCallback() { + + public Object execute(DirContext ctx) throws NamingException { + NamingEnumeration results = ctx.search(base, filter, params, searchControls); + + if (!results.hasMore()) { + throw new EmptyResultDataAccessException(1); + } + + SearchResult searchResult = (SearchResult)results.next(); + + if (results.hasMore()) { + throw new IncorrectResultSizeDataAccessException(1); + } + + // Work out the DN of the matched entry + StringBuffer dn = new StringBuffer(searchResult.getName()); + + if (base.length() > 0) { + dn.append(","); + dn.append(base); + } + + String nameInNamespace = ctx.getNameInNamespace(); + + if(StringUtils.hasLength(nameInNamespace)) { + dn.append(","); + dn.append(nameInNamespace); + } + + return mapper.mapAttributes(dn.toString(), searchResult.getAttributes()); + + } + + } + ); + } + } diff --git a/core/src/main/java/org/acegisecurity/ldap/LdapUserInfo.java b/core/src/main/java/org/acegisecurity/ldap/LdapUserInfo.java index 54b64d1653..bd1f03c345 100644 --- a/core/src/main/java/org/acegisecurity/ldap/LdapUserInfo.java +++ b/core/src/main/java/org/acegisecurity/ldap/LdapUserInfo.java @@ -22,16 +22,9 @@ import javax.naming.NamingException; /** * A user representation which is used internally by the Ldap provider. * - * It contains the user's distinguished name and a set of attributes that - * have been retrieved from the Ldap server. - *
- * An instance may be created as the result of a search, or when user information - * is retrieved during authentication. - *
- *- * An instance of this class will be used by the LdapAuthenticationProvider - * to construct the final user details object that it returns. - *
+ * + * @deprecated in favour of {@link org.acegisecurity.userdetails.ldap.LdapUserDetails} + * * * @author Luke Taylor * @version $Id$ diff --git a/core/src/main/java/org/acegisecurity/ldap/LdapUserSearch.java b/core/src/main/java/org/acegisecurity/ldap/LdapUserSearch.java index 4768404ea1..c14b5ae508 100644 --- a/core/src/main/java/org/acegisecurity/ldap/LdapUserSearch.java +++ b/core/src/main/java/org/acegisecurity/ldap/LdapUserSearch.java @@ -15,6 +15,8 @@ package org.acegisecurity.ldap; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; + /** * Obtains a user's information from the LDAP directory given a login name. *@@ -33,9 +35,8 @@ public interface LdapUserSearch { * for that user. * * @param username the login name supplied to the authentication service. - * @return an LdapUserInfo object containing the user's full DN and requested attributes. - * TODO: Need to optionally supply required attributes here for the search. + * @return an LdapUserDetailsImpl object containing the user's full DN and requested attributes. */ - LdapUserInfo searchForUser(String username); + LdapUserDetails searchForUser(String username); } diff --git a/core/src/main/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearch.java b/core/src/main/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearch.java index aebba8cc31..7b614a153f 100644 --- a/core/src/main/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearch.java +++ b/core/src/main/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearch.java @@ -16,24 +16,22 @@ package org.acegisecurity.ldap.search; import org.acegisecurity.userdetails.UsernameNotFoundException; -import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.userdetails.ldap.LdapUserDetailsMapper; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; import org.acegisecurity.ldap.LdapUserSearch; -import org.acegisecurity.ldap.LdapUtils; import org.acegisecurity.ldap.InitialDirContextFactory; -import org.acegisecurity.ldap.LdapUserInfo; -import org.acegisecurity.ldap.LdapDataAccessException; +import org.acegisecurity.ldap.LdapTemplate; +import org.acegisecurity.ldap.LdapEntryMapper; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; + +import org.springframework.dao.EmptyResultDataAccessException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; import javax.naming.directory.DirContext; -import javax.naming.NamingException; -import javax.naming.NamingEnumeration; /** * LdapUserSearch implementation which uses an Ldap filter to locate the user. @@ -82,6 +80,8 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch { private InitialDirContextFactory initialDirContextFactory; + private LdapEntryMapper userDetailsMapper = new LdapUserDetailsMapper(); + //~ Methods ================================================================ public FilterBasedLdapUserSearch(String searchBase, @@ -104,12 +104,12 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch { //~ Methods ================================================================ /** - * Return the LdapUserInfo containing the user's information, or null if - * no SearchResult is found. + * Return the LdapUserDetailsImpl containing the user's information * * @param username the username to search for. + * @throws UsernameNotFoundException if no matching entry is found. */ - public LdapUserInfo searchForUser(String username) { + public LdapUserDetails searchForUser(String username) { DirContext ctx = initialDirContextFactory.newInitialDirContext(); if (logger.isDebugEnabled()) { @@ -117,42 +117,20 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch { ", with user search " + this.toString()); } + LdapTemplate template = new LdapTemplate(initialDirContextFactory); + + template.setSearchControls(searchControls); + try { - String[] args = new String[] { LdapUtils.escapeNameForFilter(username) }; + Object user = template.searchForSingleEntry(searchBase, searchFilter, new String[] { username }, userDetailsMapper); + Assert.isInstanceOf(LdapUserDetails.class, user, "Entry mapper must return an LdapUserDetailsImpl instance"); - NamingEnumeration results = ctx.search(searchBase, searchFilter, args, searchControls); + return (LdapUserDetails)user; - if (!results.hasMore()) { - throw new UsernameNotFoundException("User " + username + " not found in directory."); - } - - SearchResult searchResult = (SearchResult)results.next(); - - if (results.hasMore()) { - throw new BadCredentialsException("Expected a single user but search returned multiple results"); - } - - StringBuffer userDn = new StringBuffer(searchResult.getName()); - - if (searchBase.length() > 0) { - userDn.append(","); - userDn.append(searchBase); - } - - String nameInNamespace = ctx.getNameInNamespace(); - - if(StringUtils.hasLength(nameInNamespace)) { - userDn.append(","); - userDn.append(nameInNamespace); - } - - return new LdapUserInfo(userDn.toString(), searchResult.getAttributes()); - - } catch(NamingException ne) { - throw new LdapDataAccessException("User Couldn't be found due to exception", ne); - } finally { - LdapUtils.closeContext(ctx); + } catch(EmptyResultDataAccessException notFound) { + throw new UsernameNotFoundException("User " + username + " not found in directory."); } + } /** diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticationProvider.java index 61a85eb043..5f8e66bcc8 100644 --- a/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticationProvider.java +++ b/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticationProvider.java @@ -17,9 +17,9 @@ package org.acegisecurity.providers.ldap; import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider; import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.ldap.LdapUserInfo; import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.User; +import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; import org.acegisecurity.AuthenticationException; import org.acegisecurity.BadCredentialsException; @@ -29,8 +29,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import javax.naming.directory.Attributes; - /** * An {@link org.acegisecurity.providers.AuthenticationProvider} implementation that * provides integration with an LDAP server. @@ -156,33 +154,35 @@ public class LdapAuthenticationProvider extends AbstractUserDetailsAuthenticatio String password = (String)authentication.getCredentials(); Assert.notNull(password, "Null password was supplied in authentication token"); - LdapUserInfo ldapUser = authenticator.authenticate(username, password); + LdapUserDetails ldapUser = authenticator.authenticate(username, password); - return createUserDetails(username, password, ldapUser.getDn(), ldapUser.getAttributes()); + return createUserDetails(ldapUser); } /** - * Creates the user final UserDetails object that will be returned by the provider + * Creates the final UserDetails object that will be returned by the provider * once the user has been authenticated. *
* The LdapAuthoritiesPopulator will be used to create the granted authorites for the * user. *
*- * Can be overridden to customize the mapping of user attributes to additional user information. + * Can be overridden to customize the creation of the final UserDetails instance. The + * default will merge any additional authorities retrieved from the populator with the + * original ldapUser object. *
* - * @param username The user login, as passed to the provider - * @param password The submitted password - * @param userDn The DN of the user in the Ldap system. - * @param attributes The user attributes retrieved from the Ldap system. + * @param ldapUser The intermediate LdapUserDetails instance returned from the authenticator. + * * @return The UserDetails for the successfully authenticated user. */ - protected UserDetails createUserDetails(String username, String password, String userDn, Attributes attributes) { + protected UserDetails createUserDetails(LdapUserDetails ldapUser) { - return new User(username, password, true, true, true, true, - authoritiesPopulator.getGrantedAuthorities(username, userDn, attributes)); + LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser); + user.setAuthorities(authoritiesPopulator.getGrantedAuthorities(ldapUser)); + + return user.createUserDetails(); } protected LdapAuthoritiesPopulator getAuthoritiesPoulator() { diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticator.java b/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticator.java index 7b4d524a2f..24c93375b3 100644 --- a/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticator.java +++ b/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticator.java @@ -15,7 +15,7 @@ package org.acegisecurity.providers.ldap; -import org.acegisecurity.ldap.LdapUserInfo; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; /** * The strategy interface for locating and authenticating an Ldap user. @@ -37,5 +37,5 @@ public interface LdapAuthenticator { * @param password the user's password supplied at login. * @return the details of the successfully authenticated user. */ - LdapUserInfo authenticate(String username, String password); + LdapUserDetails authenticate(String username, String password); } diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthoritiesPopulator.java b/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthoritiesPopulator.java index 5c7a52efc8..2661a2c20a 100644 --- a/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthoritiesPopulator.java +++ b/core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthoritiesPopulator.java @@ -16,10 +16,9 @@ package org.acegisecurity.providers.ldap; import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; import org.acegisecurity.ldap.LdapDataAccessException; -import javax.naming.directory.Attributes; - /** * Obtains a list of granted authorities for an Ldap user. *@@ -35,13 +34,11 @@ public interface LdapAuthoritiesPopulator { /** * Get the list of authorities for the user. * - * @param username the login name which was passed to the LDAP provider. - * @param userDn the full DN of the user - * @param userAttributes the user's LDAP attributes that were retrieved from the directory. + * @param userDetails the user details object which was returned by the LDAP authenticator. * @return the granted authorities for the given user. * @throws org.acegisecurity.ldap.LdapDataAccessException if there is a problem accessing the directory. */ - GrantedAuthority[] getGrantedAuthorities(String username, String userDn, Attributes userAttributes) + GrantedAuthority[] getGrantedAuthorities(LdapUserDetails userDetails) throws LdapDataAccessException; } diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/AbstractLdapAuthenticator.java b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/AbstractLdapAuthenticator.java index 1c5c9ad21a..3544dcc058 100644 --- a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/AbstractLdapAuthenticator.java +++ b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/AbstractLdapAuthenticator.java @@ -18,7 +18,9 @@ package org.acegisecurity.providers.ldap.authenticator; import org.acegisecurity.providers.ldap.LdapAuthenticator; import org.acegisecurity.ldap.InitialDirContextFactory; import org.acegisecurity.ldap.LdapUserSearch; +import org.acegisecurity.ldap.LdapEntryMapper; import org.acegisecurity.AcegiMessageSource; +import org.acegisecurity.userdetails.ldap.LdapUserDetailsMapper; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; @@ -55,6 +57,8 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, /** The attributes which will be retrieved from the directory. Null means all attributes */ private String[] userAttributes = null; + private LdapEntryMapper userDetailsMapper = new LdapUserDetailsMapper(); + /** * The suffix to be added to the DN patterns, worked out internally from the root DN of the * configured InitialDirContextFactory. @@ -137,6 +141,15 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, this.userSearch = userSearch; } + public void setUserDetailsMapper(LdapEntryMapper userDetailsMapper) { + Assert.notNull("userDetailsMapper must not be null"); + this.userDetailsMapper = userDetailsMapper; + } + + protected LdapEntryMapper getUserDetailsMapper() { + return userDetailsMapper; + } + protected LdapUserSearch getUserSearch() { return userSearch; } diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticator.java b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticator.java index f1a46b5140..24b9a6b1f7 100644 --- a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticator.java +++ b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticator.java @@ -15,18 +15,14 @@ package org.acegisecurity.providers.ldap.authenticator; -import org.acegisecurity.ldap.LdapUtils; -import org.acegisecurity.ldap.LdapUserInfo; -import org.acegisecurity.ldap.LdapDataAccessException; import org.acegisecurity.ldap.InitialDirContextFactory; +import org.acegisecurity.ldap.LdapTemplate; import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - -import javax.naming.directory.DirContext; -import javax.naming.directory.Attributes; -import javax.naming.NamingException; +import org.springframework.util.Assert; import java.util.Iterator; @@ -44,6 +40,7 @@ public class BindAuthenticator extends AbstractLdapAuthenticator { private static final Log logger = LogFactory.getLog(BindAuthenticator.class); + //~ Constructors =========================================================== public BindAuthenticator(InitialDirContextFactory initialDirContextFactory) { @@ -52,9 +49,9 @@ public class BindAuthenticator extends AbstractLdapAuthenticator { //~ Methods ================================================================ - public LdapUserInfo authenticate(String username, String password) { + public LdapUserDetails authenticate(String username, String password) { - LdapUserInfo user = null; + LdapUserDetails user = null; // If DN patterns are configured, try authenticating with them directly Iterator dns = getUserDns(username).iterator(); @@ -66,7 +63,7 @@ public class BindAuthenticator extends AbstractLdapAuthenticator { // Otherwise use the configured locator to find the user // and authenticate with the returned DN. if (user == null && getUserSearch() != null) { - LdapUserInfo userFromSearch = getUserSearch().searchForUser(username); + LdapUserDetails userFromSearch = getUserSearch().searchForUser(username); user = bindWithDn(userFromSearch.getDn(), password); } @@ -80,18 +77,19 @@ public class BindAuthenticator extends AbstractLdapAuthenticator { } - LdapUserInfo bindWithDn(String userDn, String password) { - DirContext ctx = null; - LdapUserInfo user = null; + LdapUserDetails bindWithDn(String userDn, String password) { + LdapTemplate template = new LdapTemplate(getInitialDirContextFactory(), userDn, password); if (logger.isDebugEnabled()) { logger.debug("Attempting to bind with DN = " + userDn); } try { - ctx = getInitialDirContextFactory().newInitialDirContext(userDn, password); - Attributes attributes = loadAttributes(ctx, userDn); - user = new LdapUserInfo(userDn, attributes); + + Object user = (LdapUserDetails)template.retrieveEntry(userDn, getUserDetailsMapper(), getUserAttributes()); + Assert.isInstanceOf(LdapUserDetails.class, user, "Entry mapper must return an LdapUserDetails instance"); + + return (LdapUserDetails) user; } catch(BadCredentialsException e) { // This will be thrown if an invalid user name is used and the method may @@ -99,24 +97,8 @@ public class BindAuthenticator extends AbstractLdapAuthenticator { if (logger.isDebugEnabled()) { logger.debug("Failed to bind as " + userDn + ": " + e.getCause()); } - } finally { - LdapUtils.closeContext(ctx); } - return user; + return null; } - - Attributes loadAttributes(DirContext ctx, String userDn) { - try { - return ctx.getAttributes( - LdapUtils.getRelativeName(userDn, ctx), - getUserAttributes()); - - } catch(NamingException ne) { - throw new LdapDataAccessException(messages.getMessage( - "BindAuthenticator.failedToLoadAttributes", new String[] {userDn}, - "Failed to load attributes for user {0}"), ne); - } - } - } diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticator.java b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticator.java index 504d8a70d6..050e4183ab 100644 --- a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticator.java +++ b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticator.java @@ -15,11 +15,10 @@ package org.acegisecurity.providers.ldap.authenticator; -import org.acegisecurity.ldap.LdapUserInfo; import org.acegisecurity.ldap.LdapUtils; import org.acegisecurity.ldap.InitialDirContextFactory; import org.acegisecurity.ldap.LdapTemplate; -import org.acegisecurity.ldap.AttributesMapper; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; import org.acegisecurity.providers.encoding.PasswordEncoder; import org.acegisecurity.BadCredentialsException; import org.acegisecurity.userdetails.UsernameNotFoundException; @@ -29,13 +28,6 @@ import org.springframework.util.Assert; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; -import javax.naming.directory.SearchControls; -import javax.naming.directory.DirContext; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; - import java.util.Iterator; /** @@ -62,14 +54,10 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic private static final Log logger = LogFactory.getLog(PasswordComparisonAuthenticator.class); - private static final String[] NO_ATTRS = new String[0]; - //~ Instance fields ======================================================== private String passwordAttributeName = "userPassword"; - private String passwordCompareFilter = "(userPassword={0})"; - private PasswordEncoder passwordEncoder = new LdapShaPasswordEncoder(); //~ Constructors =========================================================== @@ -80,10 +68,10 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic //~ Methods ================================================================ - public LdapUserInfo authenticate(String username, String password) { + public LdapUserDetails authenticate(final String username, final String password) { // locate the user and check the password - LdapUserInfo user = null; + LdapUserDetails user = null; Iterator dns = getUserDns(username).iterator(); @@ -93,13 +81,7 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic final String userDn = (String)dns.next(); if(ldapTemplate.nameExists(userDn)) { - AttributesMapper mapper = new AttributesMapper() { - public Object mapAttributes(Attributes attributes) { - return new LdapUserInfo(userDn, attributes); - } - }; - - user = (LdapUserInfo)ldapTemplate.retrieveEntry(userDn, mapper, getUserAttributes()); + user = (LdapUserDetails)ldapTemplate.retrieveEntry(userDn, getUserDetailsMapper(), getUserAttributes()); } } @@ -111,40 +93,36 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic throw new UsernameNotFoundException(username); } - DirContext ctx = getInitialDirContextFactory().newInitialDirContext(); + String retrievedPassword = user.getPassword(); - try { - Attribute passwordAttribute = user.getAttributes().get(passwordAttributeName); - - if(passwordAttribute != null) { - Object retrievedPassword = passwordAttribute.get(); - - if (!(retrievedPassword instanceof String)) { - // Assume it's binary - retrievedPassword = new String((byte[])retrievedPassword); - } - - if (!verifyPassword(password, (String)retrievedPassword)) { - throw new BadCredentialsException(messages.getMessage( - "PasswordComparisonAuthenticator.badCredentials", - "Bad credentials")); - } - - } else { - if (logger.isDebugEnabled()) { - logger.debug("Password attribute " + passwordAttributeName - + " wasn't retrieved for user " + username); - } - - doPasswordCompare(ctx, user.getRelativeName(ctx), password); + if(retrievedPassword != null) { + if (!verifyPassword(password, retrievedPassword)) { + throw new BadCredentialsException(messages.getMessage( + "PasswordComparisonAuthenticator.badCredentials", + "Bad credentials")); } return user; - } catch(NamingException ne) { - throw new BadCredentialsException("Authentication failed due to exception ", ne); - } finally { - LdapUtils.closeContext(ctx); } + + if (logger.isDebugEnabled()) { + logger.debug("Password attribute wasn't retrieved for user '" + username + + "' using mapper " + getUserDetailsMapper() + + ". Performing LDAP compare of password attribute '" + + passwordAttributeName + "'" ); + } + + String encodedPassword = passwordEncoder.encodePassword(password, null); + byte[] passwordBytes = LdapUtils.getUtf8Bytes(encodedPassword); + + if(!ldapTemplate.compare(user.getDn(), passwordAttributeName, passwordBytes)) { + + throw new BadCredentialsException(messages.getMessage( + "PasswordComparisonAuthenticator.badCredentials", + "Bad credentials")); + } + + return user; } /** @@ -162,32 +140,9 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic return false; } - private void doPasswordCompare(DirContext ctx, String name, String password) throws NamingException { - if (logger.isDebugEnabled()) { - logger.debug("Performing LDAP compare of password for " + name); - } - - password = passwordEncoder.encodePassword(password, null); - byte[] passwordBytes = LdapUtils.getUtf8Bytes(password); - - SearchControls ctls = new SearchControls(); - ctls.setReturningAttributes(NO_ATTRS); - ctls.setSearchScope(SearchControls.OBJECT_SCOPE); - - NamingEnumeration results = ctx.search(name, passwordCompareFilter, - new Object[]{passwordBytes}, ctls); - - if(!results.hasMore()) { - throw new BadCredentialsException(messages.getMessage( - "PasswordComparisonAuthenticator.badCredentials", - "Bad credentials")); - } - } - public void setPasswordAttributeName(String passwordAttribute) { Assert.hasLength(passwordAttribute, "passwordAttributeName must not be empty or null"); this.passwordAttributeName = passwordAttribute; - this.passwordCompareFilter = "(" + passwordAttributeName + "={0})"; } public void setPasswordEncoder(PasswordEncoder passwordEncoder) { diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulator.java b/core/src/main/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulator.java index c0bc2df549..eab2b87898 100644 --- a/core/src/main/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulator.java +++ b/core/src/main/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulator.java @@ -16,24 +16,20 @@ package org.acegisecurity.providers.ldap.populator; import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator; -import org.acegisecurity.ldap.LdapDataAccessException; import org.acegisecurity.ldap.InitialDirContextFactory; -import org.acegisecurity.ldap.LdapUtils; +import org.acegisecurity.ldap.LdapTemplate; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.GrantedAuthorityImpl; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.util.Assert; import javax.naming.directory.Attributes; -import javax.naming.directory.Attribute; import javax.naming.directory.SearchControls; -import javax.naming.directory.SearchResult; -import javax.naming.directory.DirContext; -import javax.naming.NamingEnumeration; -import javax.naming.NamingException; import java.util.Set; import java.util.HashSet; +import java.util.Iterator; /** * The default strategy for obtaining user role information from the directory. @@ -114,7 +110,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator //~ Instance fields ======================================================== /** Attributes of the User's LDAP Object that contain role name information. */ - private String[] userRoleAttributes = null; +// private String[] userRoleAttributes = null; private String rolePrefix = "ROLE_"; @@ -175,21 +171,26 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator //~ Methods ================================================================ /** - * - * @param username the login name passed to the authentication provider. - * @param userDn the user's DN. - * @param userAttributes the attributes retrieved from the user's directory entry. - * @return the full set of roles granted to the user. + * @return the set of roles granted to the user. */ - public GrantedAuthority[] getGrantedAuthorities(String username, String userDn, Attributes userAttributes) { + public final GrantedAuthority[] getGrantedAuthorities(LdapUserDetails userDetails) { + String userDn = userDetails.getDn(); + logger.debug("Getting authorities for user " + userDn); - Set roles = getRolesFromUserAttributes(userDn, userAttributes); + Set roles = getGroupMembershipRoles(userDn); - Set groupRoles = getGroupMembershipRoles(userDn, userAttributes); + // Temporary use of deprecated method + Set oldGroupRoles = getGroupMembershipRoles(userDn, userDetails.getAttributes()); - if(groupRoles != null) { - roles.addAll(groupRoles); + if(oldGroupRoles != null) { + roles.addAll(oldGroupRoles); + } + + Set extraRoles = getAdditionalRoles(userDetails); + + if(extraRoles != null) { + roles.addAll(extraRoles); } if(defaultRole != null) { @@ -199,31 +200,23 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator return (GrantedAuthority[])roles.toArray(new GrantedAuthority[roles.size()]); } - protected Set getRolesFromUserAttributes(String userDn, Attributes userAttributes) { - Set userRoles = new HashSet(); +// protected Set getRolesFromUserAttributes(String userDn, Attributes userAttributes) { +// Set userRoles = new HashSet(); +// +// for(int i=0; userRoleAttributes != null && i < userRoleAttributes.length; i++) { +// Attribute roleAttribute = userAttributes.get(userRoleAttributes[i]); +// +// addAttributeValuesToRoleSet(roleAttribute, userRoles); +// } +// +// return userRoles; +// } - for(int i=0; userRoleAttributes != null && i < userRoleAttributes.length; i++) { - Attribute roleAttribute = userAttributes.get(userRoleAttributes[i]); - - addAttributeValuesToRoleSet(roleAttribute, userRoles); - } - - return userRoles; - } - - /** - * Searches for groups the user is a member of. - * - * @param userDn the user's distinguished name. - * @param userAttributes the retrieved user's attributes (unused by default). - * @return the set of roles obtained from a group membership search, or null if - * groupSearchBase has been set. - */ - protected Set getGroupMembershipRoles(String userDn, Attributes userAttributes) { - Set userRoles = new HashSet(); + private Set getGroupMembershipRoles(String userDn) { + Set authorities = new HashSet(); if (groupSearchBase == null) { - return null; + return authorities; } if (logger.isDebugEnabled()) { @@ -232,82 +225,60 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator + " in search base '" + groupSearchBase + "'"); } - DirContext ctx = initialDirContextFactory.newInitialDirContext(); - SearchControls ctls = new SearchControls(); + LdapTemplate template = new LdapTemplate(initialDirContextFactory); - ctls.setSearchScope(searchScope); - ctls.setReturningAttributes(new String[] {groupRoleAttribute}); + template.setSearchScope(searchScope); - try { - NamingEnumeration groups = - ctx.search(groupSearchBase, groupSearchFilter, new String[]{userDn}, ctls); - - while (groups.hasMore()) { - SearchResult result = (SearchResult) groups.next(); - Attributes attrs = result.getAttributes(); - - // There should only be one role attribute. - NamingEnumeration groupRoleAttributes = attrs.getAll(); - - while(groupRoleAttributes.hasMore()) { - Attribute roleAttribute = (Attribute) groupRoleAttributes.next(); - - addAttributeValuesToRoleSet(roleAttribute, userRoles); - } - } - } catch (NamingException e) { - throw new LdapDataAccessException("Group search failed for user " + userDn, e); - } finally { - LdapUtils.closeContext(ctx); - } + Set userRoles = template.searchForSingleAttributeValues(groupSearchBase, groupSearchFilter, new String[]{userDn}, groupRoleAttribute); if (logger.isDebugEnabled()) { logger.debug("Roles from search: " + userRoles); } - return userRoles; + Iterator it = userRoles.iterator(); + + while(it.hasNext()) { + Object role = it.next(); + + // We only handle Strings for the time being + if(role instanceof String) { + if(convertToUpperCase) { + role = ((String)role).toUpperCase(); + } + + authorities.add(new GrantedAuthorityImpl(rolePrefix + role)); + } else { + logger.warn("Non-String value found for role: " + role); + } + } + + return authorities; + } - private void addAttributeValuesToRoleSet(Attribute roleAttribute, Set roles) { - if (roleAttribute == null) { - return; - } - try { - NamingEnumeration attributeRoles = roleAttribute.getAll(); + protected Set getAdditionalRoles(LdapUserDetails ldapUser) { + return null; + } - while(attributeRoles.hasMore()) { - Object role = attributeRoles.next(); - - // We only handle Strings for the time being - if(role instanceof String) { - if(convertToUpperCase) { - role = ((String)role).toUpperCase(); - } - - roles.add(new GrantedAuthorityImpl(rolePrefix + role)); - } else { - logger.warn("Non-String value found for role attribute " + roleAttribute.getID()); - } - } - } catch(NamingException ne) { - throw new LdapDataAccessException("Error retrieving values for role attribute " + - roleAttribute.getID(), ne); - } + /** + * Searches for groups the user is a member of. + * + * @deprecated Subclasses should implement getAdditionalRoles instead. + * + * @param userDn the user's distinguished name. + * @param userAttributes the retrieved user's attributes (unused by default). + * @return the set of roles obtained from a group membership search, or null if + * groupSearchBase has been set. + */ + protected Set getGroupMembershipRoles(String userDn, Attributes userAttributes) { + return new HashSet(); } protected InitialDirContextFactory getInitialDirContextFactory() { return initialDirContextFactory; } - protected String[] getUserRoleAttributes() { - return userRoleAttributes; - } - - public void setUserRoleAttributes(String[] userRoleAttributes) { - this.userRoleAttributes = userRoleAttributes; - } - public void setRolePrefix(String rolePrefix) { Assert.notNull(rolePrefix, "rolePrefix must not be null"); this.rolePrefix = rolePrefix; diff --git a/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java b/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java index 20f8888bd4..91f6d07516 100644 --- a/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java +++ b/core/src/main/java/org/acegisecurity/util/FilterChainProxy.java @@ -163,13 +163,26 @@ public class FilterChainProxy implements Filter, InitializingBean, } chain.doFilter(request, response); - } else { - Filter[] filters = obtainAllDefinedFilters(cad); - VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, - filters); - virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse()); + return; } + + Filter[] filters = obtainAllDefinedFilters(cad); + + if(filters.length == 0) { + if (logger.isDebugEnabled()) { + logger.debug(fi.getRequestUrl() + " has an empty filter list"); + } + + chain.doFilter(request, response); + + return; + + } + + VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, + filters); + virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse()); } public FilterInvocationDefinitionSource getFilterInvocationDefinitionSource() { @@ -224,7 +237,7 @@ public class FilterChainProxy implements Filter, InitializingBean, } } - return (Filter[]) list.toArray(new Filter[] {null}); + return (Filter[]) list.toArray(new Filter[0]); } /** @@ -247,9 +260,10 @@ public class FilterChainProxy implements Filter, InitializingBean, ConfigAttribute attr = (ConfigAttribute) attributes.next(); String filterName = attr.getAttribute(); - Assert.notNull(filterName, - "Configuration attribute: '" + attr + if(filterName == null) { + throw new IllegalArgumentException("Configuration attribute: '" + attr + "' returned null to the getAttribute() method, which is invalid when used with FilterChainProxy"); + } if (!filterName.equals(TOKEN_NONE)) { list.add(this.applicationContext.getBean(filterName, @@ -257,7 +271,7 @@ public class FilterChainProxy implements Filter, InitializingBean, } } - return (Filter[]) list.toArray(new Filter[] {null}); + return (Filter[]) list.toArray(new Filter[list.size()]); } public void setApplicationContext(ApplicationContext applicationContext) diff --git a/core/src/test/java/org/acegisecurity/ldap/AbstractLdapServerTestCase.java b/core/src/test/java/org/acegisecurity/ldap/AbstractLdapServerTestCase.java index e937fe1271..a573d9f238 100644 --- a/core/src/test/java/org/acegisecurity/ldap/AbstractLdapServerTestCase.java +++ b/core/src/test/java/org/acegisecurity/ldap/AbstractLdapServerTestCase.java @@ -19,8 +19,6 @@ import junit.framework.TestCase; import java.util.Hashtable; -import org.apache.directory.server.core.jndi.CoreContextFactory; - /** * @author Luke Taylor * @version $Id$ @@ -31,16 +29,16 @@ public abstract class AbstractLdapServerTestCase extends TestCase { protected static final String MANAGER_PASSWORD = "acegisecurity"; // External server config -// private static final String PROVIDER_URL = "ldap://monkeymachine:389/"+ROOT_DN; -// private static final String CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; -// private static final Hashtable EXTRA_ENV = new Hashtable(); + private static final String PROVIDER_URL = "ldap://monkeymachine:389/"+ROOT_DN; + private static final String CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; + private static final Hashtable EXTRA_ENV = new Hashtable(); // Embedded (non-networked) server config - private static final LdapTestServer SERVER = new LdapTestServer(); - private static final String PROVIDER_URL = ROOT_DN; - private static final String CONTEXT_FACTORY = CoreContextFactory.class.getName(); - private static final Hashtable EXTRA_ENV = SERVER.getConfiguration().toJndiEnvironment(); +// private static final LdapTestServer SERVER = new LdapTestServer(); +// private static final String PROVIDER_URL = ROOT_DN; +// private static final String CONTEXT_FACTORY = CoreContextFactory.class.getName(); +// private static final Hashtable EXTRA_ENV = SERVER.getConfiguration().toJndiEnvironment(); protected AbstractLdapServerTestCase() { } diff --git a/core/src/test/java/org/acegisecurity/ldap/LdapTemplateTests.java b/core/src/test/java/org/acegisecurity/ldap/LdapTemplateTests.java index f6d346d707..136ea9e134 100644 --- a/core/src/test/java/org/acegisecurity/ldap/LdapTemplateTests.java +++ b/core/src/test/java/org/acegisecurity/ldap/LdapTemplateTests.java @@ -33,27 +33,27 @@ public class LdapTemplateTests extends AbstractLdapServerTestCase { public void testCompareOfCorrectValueSucceeds() { - assertTrue(template.compare("uid=bob,ou=people", "uid", "bob")); + assertTrue(template.compare("uid=bob,ou=people,dc=acegisecurity,dc=org", "uid", "bob")); } public void testCompareOfWrongValueFails() { - assertFalse(template.compare("uid=bob,ou=people", "uid", "wrongvalue")); + assertFalse(template.compare("uid=bob,ou=people,dc=acegisecurity,dc=org", "uid", "wrongvalue")); } public void testCompareOfCorrectByteValueSucceeds() { // Doesn't work with embedded server due to bugs in apacheds -// assertTrue(template.compare("uid=bob,ou=people", "userPassword", LdapUtils.getUtf8Bytes("bobspassword"))); +// assertTrue(template.compare("uid=bob,ou=people,dc=acegisecurity,dc=org", "userPassword", LdapUtils.getUtf8Bytes("bobspassword"))); } public void testCompareOfWrongByteValueFails() { // Doesn't work with embedded server due to bugs in apacheds -// assertFalse(template.compare("uid=bob,ou=people", "userPassword", LdapUtils.getUtf8Bytes("wrongvalue"))); +// assertFalse(template.compare("uid=bob,ou=people,dc=acegisecurity,dc=org", "userPassword", LdapUtils.getUtf8Bytes("wrongvalue"))); } public void testSearchForSingleAttributeValues() { - String param = "uid=ben,ou=people," + getInitialCtxFactory().getRootDn(); + String param = "uid=ben,ou=people,dc=acegisecurity,dc=org"; Set values = template.searchForSingleAttributeValues("ou=groups", "(member={0})", new String[] {param}, "ou"); diff --git a/core/src/test/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearchTests.java b/core/src/test/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearchTests.java index 0770b45b3b..138748425f 100644 --- a/core/src/test/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearchTests.java +++ b/core/src/test/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearchTests.java @@ -2,9 +2,9 @@ package org.acegisecurity.ldap.search; import org.acegisecurity.ldap.AbstractLdapServerTestCase; import org.acegisecurity.ldap.DefaultInitialDirContextFactory; -import org.acegisecurity.ldap.LdapUserInfo; import org.acegisecurity.userdetails.UsernameNotFoundException; -import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; +import org.springframework.dao.IncorrectResultSizeDataAccessException; /** * Tests for FilterBasedLdapUserSearch. @@ -32,7 +32,7 @@ public class FilterBasedLdapUserSearchTests extends AbstractLdapServerTestCase { public void testBasicSearch() throws Exception { FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory); - LdapUserInfo bob = locator.searchForUser("bob"); + LdapUserDetails bob = locator.searchForUser("bob"); locator.setSearchSubtree(false); locator.setSearchTimeLimit(0); // name is wrong with embedded apacheDS @@ -45,7 +45,7 @@ public class FilterBasedLdapUserSearchTests extends AbstractLdapServerTestCase { new FilterBasedLdapUserSearch("", "(cn={0})", dirCtxFactory); locator.setSearchSubtree(true); - LdapUserInfo ben = locator.searchForUser("Ben Alex"); + LdapUserDetails ben = locator.searchForUser("Ben Alex"); // assertEquals("uid=ben,ou=people,"+ROOT_DN, bob.getDn()); } @@ -67,7 +67,7 @@ public class FilterBasedLdapUserSearchTests extends AbstractLdapServerTestCase { try { locator.searchForUser("Ignored"); fail("Expected exception for multiple search matches."); - } catch (BadCredentialsException expected) { + } catch (IncorrectResultSizeDataAccessException expected) { } } @@ -80,7 +80,7 @@ public class FilterBasedLdapUserSearchTests extends AbstractLdapServerTestCase { dirCtxFactory); // Search for bob, get back ben... - LdapUserInfo ben = locator.searchForUser("bob"); + LdapUserDetails ben = locator.searchForUser("bob"); String cn = (String)ben.getAttributes().get("cn").get(); assertEquals("Ben Alex", cn); // assertEquals("uid=ben,ou=people,"+ROOT_DN, ben.getDn()); diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java index 4b412c23c3..52d2f0df01 100644 --- a/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java +++ b/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java @@ -6,11 +6,12 @@ import javax.naming.directory.BasicAttributes; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.GrantedAuthorityImpl; import org.acegisecurity.BadCredentialsException; -import org.acegisecurity.ldap.LdapUserInfo; -import org.acegisecurity.ldap.AbstractLdapServerTestCase; +import org.acegisecurity.ldap.*; import org.acegisecurity.ldap.DefaultInitialDirContextFactory; import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; /** * @author Luke Taylor @@ -82,18 +83,20 @@ public class LdapAuthenticationProviderTests extends AbstractLdapServerTestCase */ class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator { - public GrantedAuthority[] getGrantedAuthorities(String userDn, String dn, Attributes userAttributes) { - return new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_USER") }; + public GrantedAuthority[] getGrantedAuthorities(LdapUserDetails userDetailsll) { + return new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_USER") }; } } class MockAuthenticator implements LdapAuthenticator { Attributes userAttributes = new BasicAttributes("cn","bob"); - public LdapUserInfo authenticate(String username, String password) { + public LdapUserDetails authenticate(String username, String password) { if(username.equals("bob") && password.equals("bobspassword")) { - - return new LdapUserInfo("cn=bob,ou=people,dc=acegisecurity,dc=org", userAttributes); + LdapUserDetailsImpl.Essence creator = new LdapUserDetailsImpl.Essence(); + creator.setDn("cn=bob,ou=people,dc=acegisecurity,dc=org"); + creator.setAttributes(userAttributes); + return creator.createUserDetails(); } throw new BadCredentialsException("Authentication of Bob failed."); } diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticatorTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticatorTests.java index 5b6d977dbb..fa5fa6a20b 100644 --- a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticatorTests.java +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticatorTests.java @@ -1,8 +1,11 @@ package org.acegisecurity.providers.ldap.authenticator; -import org.acegisecurity.ldap.LdapUserInfo; import org.acegisecurity.ldap.AbstractLdapServerTestCase; import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.GrantedAuthorityImpl; +import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; +import org.acegisecurity.userdetails.ldap.LdapUserDetailsMapper; /** * Tests for {@link BindAuthenticator}. @@ -26,12 +29,10 @@ public class BindAuthenticatorTests extends AbstractLdapServerTestCase { public void testAuthenticationWithCorrectPasswordSucceeds() throws Exception { authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"}); - LdapUserInfo user = authenticator.authenticate("bob","bobspassword"); + LdapUserDetails user = authenticator.authenticate("bob","bobspassword"); } public void testAuthenticationWithWrongPasswordFails() { -// BindAuthenticator authenticator = new BindAuthenticator(dirCtxFactory); - authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"}); try { @@ -42,25 +43,36 @@ public class BindAuthenticatorTests extends AbstractLdapServerTestCase { } public void testAuthenticationWithUserSearch() throws Exception { - LdapUserInfo user = new LdapUserInfo("uid=bob,ou=people," + getInitialCtxFactory().getRootDn(), null); - authenticator.setUserSearch(new MockUserSearch(user)); + LdapUserDetailsImpl.Essence userEssence = new LdapUserDetailsImpl.Essence(); + userEssence.setDn("uid=bob,ou=people,dc=acegisecurity,dc=org"); + + authenticator.setUserSearch(new MockUserSearch(userEssence.createUserDetails())); authenticator.afterPropertiesSet(); authenticator.authenticate("bob","bobspassword"); } + public void testAuthenticationWithInvalidUserNameFails() { + authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"}); -// Apache DS falls apart with unknown DNs. -// -// public void testAuthenticationWithInvalidUserNameFails() { -// BindAuthenticator authenticator = new BindAuthenticator(); -// -// authenticator.setInitialDirContextFactory(dirCtxFactory); -// authenticator.setUserDnPatterns("cn={0},ou=people"); -// try { -// authenticator.authenticate("Baz","bobspassword"); -// fail("Shouldn't be able to bind with invalid username"); -// } catch(BadCredentialsException expected) { -// } -// } + try { + authenticator.authenticate("nonexistentsuser","bobspassword"); + fail("Shouldn't be able to bind with invalid username"); + } catch(BadCredentialsException expected) { + } + } + + // TODO: Create separate tests for base class + public void testRoleRetrieval() { + authenticator.setUserDnPatterns(new String[] {"uid={0},ou=people"}); + LdapUserDetailsMapper userMapper = new LdapUserDetailsMapper(); + userMapper.setRoleAttributes(new String[] {"uid"}); + + authenticator.setUserDetailsMapper(userMapper); + + LdapUserDetails user = authenticator.authenticate("bob","bobspassword"); + + assertEquals(1, user.getAuthorities().length); + assertEquals(new GrantedAuthorityImpl("ROLE_BOB"), user.getAuthorities()[0]); + } } diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/MockUserSearch.java b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/MockUserSearch.java index af1c57caa1..882d2b3da8 100644 --- a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/MockUserSearch.java +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/MockUserSearch.java @@ -1,20 +1,20 @@ package org.acegisecurity.providers.ldap.authenticator; -import org.acegisecurity.ldap.LdapUserInfo; import org.acegisecurity.ldap.LdapUserSearch; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; /** * @author Luke Taylor * @version $Id$ */ public class MockUserSearch implements LdapUserSearch { - LdapUserInfo user; + LdapUserDetails user; - public MockUserSearch(LdapUserInfo user) { + public MockUserSearch(LdapUserDetails user) { this.user = user; } - public LdapUserInfo searchForUser(String username) { + public LdapUserDetails searchForUser(String username) { return user; } } diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java index 16c74c6d5d..0f4db7ea48 100644 --- a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java @@ -27,12 +27,13 @@ public class PasswordComparisonAuthenticatorMockTests extends MockObjectTestCase // Get the mock to return an empty attribute set mockCtx.expects(atLeastOnce()).method("getNameInNamespace").will(returnValue("dc=acegisecurity,dc=org")); + mockCtx.expects(once()).method("lookup").with(eq("cn=Bob,ou=people")).will(returnValue(true)); mockCtx.expects(once()).method("getAttributes").with(eq("cn=Bob,ou=people"), NULL).will(returnValue(new BasicAttributes())); // Setup a single return value (i.e. success) Attributes searchResults = new BasicAttributes("", null); mockCtx.expects(once()).method("search").with(eq("cn=Bob,ou=people"), eq("(userPassword={0})"), NOT_NULL, NOT_NULL).will(returnValue(searchResults.getAll())); - mockCtx.expects(once()).method("close"); + mockCtx.expects(atLeastOnce()).method("close"); authenticator.authenticate("Bob", "bobspassword"); } diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorTests.java index c9ec1b219a..0c1b46328a 100644 --- a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorTests.java +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorTests.java @@ -1,11 +1,11 @@ package org.acegisecurity.providers.ldap.authenticator; -import org.acegisecurity.ldap.LdapUserInfo; import org.acegisecurity.ldap.AbstractLdapServerTestCase; import org.acegisecurity.BadCredentialsException; import org.acegisecurity.userdetails.UsernameNotFoundException; - -import javax.naming.directory.BasicAttributes; +import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl; +import org.acegisecurity.userdetails.ldap.LdapUserDetailsMapper; +import org.acegisecurity.userdetails.ldap.LdapUserDetails; /** * Tests for {@link PasswordComparisonAuthenticator}. @@ -87,7 +87,7 @@ public class PasswordComparisonAuthenticatorTests extends AbstractLdapServerTest } public void testAllAttributesAreRetrivedByDefault() { - LdapUserInfo user = authenticator.authenticate("Bob", "bobspassword"); + LdapUserDetails user = authenticator.authenticate("Bob", "bobspassword"); System.out.println(user.getAttributes().toString()); assertEquals("User should have 5 attributes", 5, user.getAttributes().size()); @@ -103,7 +103,10 @@ public class PasswordComparisonAuthenticatorTests extends AbstractLdapServerTest } */ public void testUseOfDifferentPasswordAttribute() { + LdapUserDetailsMapper mapper = new LdapUserDetailsMapper(); + mapper.setPasswordAttributeName("uid"); authenticator.setPasswordAttributeName("uid"); + authenticator.setUserDetailsMapper(mapper); authenticator.authenticate("bob", "bob"); } /* @@ -119,10 +122,11 @@ public class PasswordComparisonAuthenticatorTests extends AbstractLdapServerTest authenticator = new PasswordComparisonAuthenticator(getInitialCtxFactory()); assertTrue("User DN matches shouldn't be available", authenticator.getUserDns("Bob").isEmpty()); - LdapUserInfo user = new LdapUserInfo("uid=Bob,ou=people" + - getInitialCtxFactory().getRootDn(), - new BasicAttributes("userPassword","bobspassword")); - authenticator.setUserSearch(new MockUserSearch(user)); + LdapUserDetailsImpl.Essence userEssence = new LdapUserDetailsImpl.Essence(); + userEssence.setDn("uid=Bob,ou=people,dc=acegisecurity,dc=org"); + userEssence.setPassword("bobspassword"); + + authenticator.setUserSearch(new MockUserSearch(userEssence.createUserDetails())); authenticator.authenticate("ShouldntBeUsed","bobspassword"); } diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulatorTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulatorTests.java index 1b3eb9793f..57f400ad26 100644 --- a/core/src/test/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulatorTests.java +++ b/core/src/test/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulatorTests.java @@ -1,10 +1,9 @@ package org.acegisecurity.providers.ldap.populator; -import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; -import javax.naming.directory.BasicAttribute; import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.userdetails.ldap.LdapUserDetailsImpl; import org.acegisecurity.ldap.AbstractLdapServerTestCase; import java.util.Set; @@ -21,30 +20,39 @@ public class DefaultLdapAuthoritiesPopulatorTests extends AbstractLdapServerTest getInitialCtxFactory().setManagerPassword(MANAGER_PASSWORD); } - public void testUserAttributeMappingToRoles() { - DefaultLdapAuthoritiesPopulator populator = new DefaultLdapAuthoritiesPopulator(); - populator.setUserRoleAttributes(new String[] {"userRole", "otherUserRole"}); - populator.getUserRoleAttributes(); - - Attributes userAttrs = new BasicAttributes(); - BasicAttribute attr = new BasicAttribute("userRole", "role1"); - attr.add("role2"); - userAttrs.put(attr); - attr = new BasicAttribute("otherUserRole", "role3"); - attr.add("role2"); // duplicate - userAttrs.put(attr); - - GrantedAuthority[] authorities = - populator.getGrantedAuthorities("Ignored", "Ignored", userAttrs); - assertEquals("User should have three roles", 3, authorities.length); - } +// public void testUserAttributeMappingToRoles() { +// DefaultLdapAuthoritiesPopulator populator = new DefaultLdapAuthoritiesPopulator(); +// populator.setUserRoleAttributes(new String[] {"userRole", "otherUserRole"}); +// populator.getUserRoleAttributes(); +// +// Attributes userAttrs = new BasicAttributes(); +// BasicAttribute attr = new BasicAttribute("userRole", "role1"); +// attr.add("role2"); +// userAttrs.put(attr); +// attr = new BasicAttribute("otherUserRole", "role3"); +// attr.add("role2"); // duplicate +// userAttrs.put(attr); +// +// LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(); +// user.setDn("Ignored"); +// user.setUsername("Ignored"); +// user.setAttributes(userAttrs); +// +// GrantedAuthority[] authorities = +// populator.getGrantedAuthorities(user.createUserDetails()); +// assertEquals("User should have three roles", 3, authorities.length); +// } public void testDefaultRoleIsAssignedWhenSet() { DefaultLdapAuthoritiesPopulator populator = new DefaultLdapAuthoritiesPopulator(); populator.setDefaultRole("ROLE_USER"); + LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(); + user.setDn("Ignored"); + user.setUsername("Ignored"); + user.setAttributes(new BasicAttributes()); GrantedAuthority[] authorities = - populator.getGrantedAuthorities("Ignored", "Ignored", new BasicAttributes()); + populator.getGrantedAuthorities(user.createUserDetails()); assertEquals(1, authorities.length); assertEquals("ROLE_USER", authorities[0].getAuthority()); } @@ -59,9 +67,14 @@ public class DefaultLdapAuthoritiesPopulatorTests extends AbstractLdapServerTest populator.setConvertToUpperCase(true); populator.setGroupSearchFilter("(member={0})"); + LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(); + user.setUsername("ben"); + user.setDn("uid=ben,ou=people,dc=acegisecurity,dc=org"); + user.setAttributes(new BasicAttributes()); + GrantedAuthority[] authorities = - populator.getGrantedAuthorities("ben", "uid=ben,ou=people,"+ - getInitialCtxFactory().getRootDn(), new BasicAttributes()); + populator.getGrantedAuthorities(user.createUserDetails()); + assertEquals("Should have 2 roles", 2, authorities.length); Set roles = new HashSet(); roles.add(authorities[0].toString());