Prechádzať zdrojové kódy

SEC-264: changes to LDAP services.

Luke Taylor 19 rokov pred
rodič
commit
65fe641900
22 zmenil súbory, kde vykonal 435 pridanie a 381 odobranie
  1. 31 0
      core/src/main/java/org/acegisecurity/ldap/LdapEntryMapper.java
  2. 100 12
      core/src/main/java/org/acegisecurity/ldap/LdapTemplate.java
  3. 3 10
      core/src/main/java/org/acegisecurity/ldap/LdapUserInfo.java
  4. 4 3
      core/src/main/java/org/acegisecurity/ldap/LdapUserSearch.java
  5. 20 42
      core/src/main/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearch.java
  6. 15 15
      core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticationProvider.java
  7. 2 2
      core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthenticator.java
  8. 3 6
      core/src/main/java/org/acegisecurity/providers/ldap/LdapAuthoritiesPopulator.java
  9. 13 0
      core/src/main/java/org/acegisecurity/providers/ldap/authenticator/AbstractLdapAuthenticator.java
  10. 15 33
      core/src/main/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticator.java
  11. 27 72
      core/src/main/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticator.java
  12. 67 96
      core/src/main/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulator.java
  13. 23 9
      core/src/main/java/org/acegisecurity/util/FilterChainProxy.java
  14. 7 9
      core/src/test/java/org/acegisecurity/ldap/AbstractLdapServerTestCase.java
  15. 5 5
      core/src/test/java/org/acegisecurity/ldap/LdapTemplateTests.java
  16. 6 6
      core/src/test/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearchTests.java
  17. 10 7
      core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java
  18. 31 19
      core/src/test/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticatorTests.java
  19. 4 4
      core/src/test/java/org/acegisecurity/providers/ldap/authenticator/MockUserSearch.java
  20. 2 1
      core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java
  21. 12 8
      core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorTests.java
  22. 35 22
      core/src/test/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulatorTests.java

+ 31 - 0
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;
+}

+ 100 - 12
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());
+
+            }
+
+        }
+        );
+    }
+
 }

+ 3 - 10
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.
- * <p>
- * An instance may be created as the result of a search, or when user information
- * is retrieved during authentication.
- * </p>
- * <p>
- * An instance of this class will be used by the <tt>LdapAuthenticationProvider</tt>
- * to construct the final user details object that it returns.
- * </p>
+ *
+ * @deprecated in favour of {@link org.acegisecurity.userdetails.ldap.LdapUserDetails}
+ *
  *
  * @author Luke Taylor
  * @version $Id$

+ 4 - 3
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.
  * <p>
@@ -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);
 
 }

+ 20 - 42
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());
         }
 
-        try {
-            String[] args = new String[] { LdapUtils.escapeNameForFilter(username) };
-
-            NamingEnumeration results = ctx.search(searchBase, searchFilter, args, searchControls);
-
-            if (!results.hasMore()) {
-                throw new UsernameNotFoundException("User " + username + " not found in directory.");
-            }
-
-            SearchResult searchResult = (SearchResult)results.next();
+        LdapTemplate template = new LdapTemplate(initialDirContextFactory);
 
-            if (results.hasMore()) {
-               throw new BadCredentialsException("Expected a single user but search returned multiple results");
-            }
+        template.setSearchControls(searchControls);
 
-            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);
-            }
+        try {
+            Object user = template.searchForSingleEntry(searchBase, searchFilter, new String[] { username }, userDetailsMapper);
+            Assert.isInstanceOf(LdapUserDetails.class, user, "Entry mapper must return an LdapUserDetailsImpl instance");
 
-            return new LdapUserInfo(userDn.toString(), searchResult.getAttributes());
+            return (LdapUserDetails)user;
 
-        } 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.");
         }
+
     }
 
     /**

+ 15 - 15
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 <tt>UserDetails</tt> object that will be returned by the provider
+     * Creates the final <tt>UserDetails</tt> object that will be returned by the provider
      * once the user has been authenticated.
      * <p>
      * The <tt>LdapAuthoritiesPopulator</tt> will be used to create the granted authorites for the
      * user.
      * </p>
      * <p>
-     * 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 <tt>ldapUser</tt> object.
      * </p>
      *
-     * @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) {
+
+        LdapUserDetailsImpl.Essence user = new LdapUserDetailsImpl.Essence(ldapUser);
 
-        return new User(username, password, true, true, true, true,
-                authoritiesPopulator.getGrantedAuthorities(username, userDn, attributes));
+        user.setAuthorities(authoritiesPopulator.getGrantedAuthorities(ldapUser));
 
+        return user.createUserDetails();
     }
 
     protected LdapAuthoritiesPopulator getAuthoritiesPoulator() {

+ 2 - 2
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);
 }

+ 3 - 6
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.
  * <p>
@@ -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;
 
 }

+ 13 - 0
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;
     }

+ 15 - 33
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;
-    }
-
-    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);
-        }
+        return null;
     }
-
 }

+ 27 - 72
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();        
-
-        try {
-            Attribute passwordAttribute = user.getAttributes().get(passwordAttributeName);
+        String retrievedPassword = user.getPassword();
 
-            if(passwordAttribute != null) {
-                Object retrievedPassword = passwordAttribute.get();
+        if(retrievedPassword != null) {
+            if (!verifyPassword(password, retrievedPassword)) {
+                throw new BadCredentialsException(messages.getMessage(
+                        "PasswordComparisonAuthenticator.badCredentials",
+                        "Bad credentials"));
+            }
 
-                if (!(retrievedPassword instanceof String)) {
-                    // Assume it's binary
-                    retrievedPassword = new String((byte[])retrievedPassword);
-                }
+            return user;
+        }
 
-                if (!verifyPassword(password, (String)retrievedPassword)) {
-                    throw new BadCredentialsException(messages.getMessage(
-                            "PasswordComparisonAuthenticator.badCredentials",
-                            "Bad credentials"));
-                }
+        if (logger.isDebugEnabled()) {
+            logger.debug("Password attribute wasn't retrieved for user '" + username
+                    + "' using mapper " + getUserDetailsMapper()
+                    + ". Performing LDAP compare of password attribute '"
+                    + passwordAttributeName + "'" );
+        }
 
-            } else {
-                if (logger.isDebugEnabled()) {
-                    logger.debug("Password attribute " + passwordAttributeName
-                            + " wasn't retrieved for user " + username);
-                }
+        String encodedPassword = passwordEncoder.encodePassword(password, null);
+        byte[] passwordBytes = LdapUtils.getUtf8Bytes(encodedPassword);
 
-                doPasswordCompare(ctx, user.getRelativeName(ctx), password);
-            }
+        if(!ldapTemplate.compare(user.getDn(), passwordAttributeName, passwordBytes)) {
 
-            return user;
-        } catch(NamingException ne) {
-            throw new BadCredentialsException("Authentication failed due to exception ", ne);
-        } finally {
-            LdapUtils.closeContext(ctx);
+            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) {

+ 67 - 96
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);
+
+        // Temporary use of deprecated method
+        Set oldGroupRoles = getGroupMembershipRoles(userDn, userDetails.getAttributes());
 
-        Set groupRoles = getGroupMembershipRoles(userDn, userAttributes);
+        if(oldGroupRoles != null) {
+            roles.addAll(oldGroupRoles);
+        }
+
+        Set extraRoles = getAdditionalRoles(userDetails);
 
-        if(groupRoles != null) {
-            roles.addAll(groupRoles);
+        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();
-
-        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
-     *         <tt>groupSearchBase</tt> has been set.
-     */
-    protected Set getGroupMembershipRoles(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;
+//    }
+
+    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);
+        Set userRoles = template.searchForSingleAttributeValues(groupSearchBase, groupSearchFilter, new String[]{userDn}, groupRoleAttribute);
 
-            while (groups.hasMore()) {
-                SearchResult result = (SearchResult) groups.next();
-                Attributes attrs = result.getAttributes();
+        if (logger.isDebugEnabled()) {
+            logger.debug("Roles from search: " + userRoles);
+        }
 
-                // There should only be one role attribute.
-                NamingEnumeration groupRoleAttributes = attrs.getAll();
+        Iterator it = userRoles.iterator();
 
-                while(groupRoleAttributes.hasMore()) {
-                    Attribute roleAttribute = (Attribute) groupRoleAttributes.next();
+        while(it.hasNext()) {
+            Object role = it.next();
 
-                    addAttributeValuesToRoleSet(roleAttribute, userRoles);
+            // 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);
             }
-        } catch (NamingException e) {
-            throw new LdapDataAccessException("Group search failed for user " + userDn, e);
-        } finally {
-            LdapUtils.closeContext(ctx);
         }
 
-        if (logger.isDebugEnabled()) {
-            logger.debug("Roles from search: " + userRoles);
-        }
+        return authorities;
 
-        return userRoles;
     }
 
-    private void addAttributeValuesToRoleSet(Attribute roleAttribute, Set roles) {
-        if (roleAttribute == null) {
-            return;
-        }
-
-        try {
-            NamingEnumeration attributeRoles = roleAttribute.getAll();
-
-            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();
-                    }
+    protected Set getAdditionalRoles(LdapUserDetails ldapUser) {
+        return null;
+    }
 
-                    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 <tt>getAdditionalRoles</tt> 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
+     *         <tt>groupSearchBase</tt> 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;

+ 23 - 9
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)

+ 7 - 9
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() {
     }

+ 5 - 5
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");
 

+ 6 - 6
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());

+ 10 - 7
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.");
         }

+ 31 - 19
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]);
+    }
 }
 

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

+ 2 - 1
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");
     }
 

+ 12 - 8
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");
     }
 

+ 35 - 22
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());