From ce3d6f212914af9fa7c33d5166a75957d462e5ef Mon Sep 17 00:00:00 2001
From: Luke Taylor
+ * The directory location is configured using by setting the url property.
+ * This should be in the form ldap://monkeymachine.co.uk:389/dc=acegisecurity,dc=org.
+ *
+ * To obtain an initial context, th client calls the newInitialDirContext
+ * method. There are two signatures - one with no arguments and one which allows
+ * binding with a specific username and password.
+ *
+ * The no-args version will bind anonymously or if a manager login has been configured
+ * using the properties managerDn and managerPassword it will bind as
+ * that user.
+ *
+ * Connection pooling is enabled for anonymous or manager connections, but not when binding
+ * as a specific user.
+ *
+ * There are many ways in which an LDAP directory can be configured so this class
+ * delegates most of its responsibilites to two separate strategy interfaces,
+ * {@link LdapAuthenticator} and {@link LdapAuthoritiesPopulator}.
+ *
+ * The task of retrieving the user attributes is delegated to the authenticator
+ * because the permissions on the attributes may depend on the type of authentication
+ * being used; for example, if binding as the user, it may be necessary to read them
+ * with the user's own permissions (using the same context used for the bind operation).
+ *
+ * A custom implementation could obtain the roles from a completely different source,
+ * for example from a database.
+ *
+ * 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.
+ *
+ * The LdapAuthenticationProvider calls this interface to authenticate a user
+ * and obtain the information for that user from the directory.
+ *
+ * Used by the LdapAuthenticationProvider once a user has been
+ * authenticated to create the final user details object.
+ *
+ * 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.
+ *
+ * If the DN is "cn=bob,ou=people,dc=acegisecurity,dc=org" and the base context
+ * name is "ou=people,dc=acegisecurity,dc=org" it would return "cn=bob".
+ *
+ * In this case, the username is the only parameter.
+ *
+ * May be optionally used to configure the LDAP authentication implementation when
+ * a more sophisticated approach is required than just using a simple username->DN
+ * mapping.
+ *
+ * This can be achieved either by retrieving the password attribute for the user
+ * and comparing it locally, or by peforming an LDAP "compare" operation.
+ * If the password attribute (default "userPassword") is found in the retrieved
+ * attributes it will be compared locally. If not, the remote comparison will be
+ * attempted.
+ *
+ * If passwords are stored in digest form in the repository, then a suitable
+ * {@link PasswordEncoder} implementation must be supplied. By default, passwords are
+ * encoded using the {@link LdapShaPasswordEncoder}.
+ *
+ * It obtains roles by
+ * LdapAuthenticator
+ *
+ * This interface is responsible for performing the user authentication and retrieving
+ * the user's information from the directory. Example implementations are
+ * {@link org.acegisecurity.providers.ldap.authenticator.BindAuthenticator BindAuthenticator}
+ * which authenticates the user by "binding" as that user, and
+ * {@link org.acegisecurity.providers.ldap.authenticator.PasswordComparisonAuthenticator PasswordComparisonAuthenticator}
+ * which performs a comparison of the supplied password with the value stored in the directory,
+ * either by retrieving the password or performing an LDAP "compare" operation.
+ * LdapAuthoritiesPopulator
+ *
+ * Once the user has been authenticated, this interface is called to obtain the set of
+ * granted authorities for the user. The
+ * {@link org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator DefaultLdapAuthoritiesPopulator}
+ * can be configured to obtain user role information from the user's attributes and/or to perform
+ * a search for "groups" that the user is a member of and map these to roles.
+ *
+ *
+ * TODO: more examples.
+ *
+ */
+ private String searchFilter;
+
+ /**
+ * The time (in milliseconds) which to wait before the search fails;
+ * the default is zero, meaning forever.
+ */
+ private int searchTimeLimit = 0;
+
+ private InitialDirContextFactory initialDirContextFactory;
+
+ //~ Methods ================================================================
+
+ /**
+ * Return the LdapUserDetails containing the user's information, or null if
+ * no SearchResult is found.
+ *
+ * @param username the username to search for.
+ */
+ public LdapUserDetails searchForUser(String username) {
+ DirContext ctx = initialDirContextFactory.newInitialDirContext();
+ SearchControls ctls = new SearchControls();
+ ctls.setTimeLimit( searchTimeLimit );
+ ctls.setSearchScope( searchScope );
+
+ try {
+ String[] args = new String[] { LdapUtils.escapeNameForFilter(username) };
+
+ NamingEnumeration results = ctx.search(searchBase, searchFilter, args, ctls);
+
+ 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);
+ }
+
+ userDn.append(",");
+ userDn.append(ctx.getNameInNamespace());
+
+ return new LdapUserDetails(userDn.toString(), searchResult.getAttributes());
+
+ } catch(NamingException ne) {
+ throw new LdapDataAccessException("User Couldn't be found due to exception", ne);
+ } finally {
+ LdapUtils.closeContext(ctx);
+ }
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(initialDirContextFactory, "initialDirContextFactory must be set");
+ Assert.notNull(searchFilter, "searchFilter must be set.");
+
+ if(searchBase.equals("")) {
+ logger.info("No search base DN supplied. Search will be performed from the root: " +
+ initialDirContextFactory.getRootDn());
+ }
+ }
+
+ public void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) {
+ this.initialDirContextFactory = initialDirContextFactory;
+ }
+
+ public void setSearchFilter(String searchFilter) {
+ this.searchFilter = searchFilter;
+ }
+
+ public void setSearchSubtree(boolean searchSubtree) {
+// this.searchSubtree = searchSubtree;
+ this.searchScope = searchSubtree ?
+ SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE;
+ }
+
+ public void setSearchTimeLimit(int searchTimeLimit) {
+ this.searchTimeLimit = searchTimeLimit;
+ }
+
+ public void setSearchBase(String searchBase) {
+ this.searchBase = searchBase;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoder.java b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoder.java
new file mode 100644
index 0000000000..d00b126879
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoder.java
@@ -0,0 +1,36 @@
+/* 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.providers.ldap.authenticator;
+
+import org.acegisecurity.providers.encoding.ShaPasswordEncoder;
+
+/**
+ * A version of {@link ShaPasswordEncoder} which always uses
+ * Base-64 encoding and prepends the string
+ * "{SHA}" to the encoded hash.
+ *
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class LdapShaPasswordEncoder extends ShaPasswordEncoder {
+ public LdapShaPasswordEncoder() {
+ super.setEncodeHashAsBase64(true);
+ }
+
+ public String encodePassword(String rawPass, Object salt) {
+ return "{SHA}" + super.encodePassword(rawPass, salt);
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapUserSearch.java b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapUserSearch.java
new file mode 100644
index 0000000000..68cc9399db
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapUserSearch.java
@@ -0,0 +1,43 @@
+/* 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.providers.ldap.authenticator;
+
+import org.acegisecurity.providers.ldap.LdapUserDetails;
+
+/**
+ * Obtains a user's information from the LDAP directory given a login name.
+ *
+ *
+ *
+ * If the userRolesAttributes property is set, any matching + * attributes amongst those retrieved for the user will have their values added + * to the list of roles. + * If userRolesAttributes is null, no attributes will be mapped to roles. + *
+ *+ * A typical group search scenario would be where each group/role is specified using + * the groupOfNames (or groupOfUniqueNames) LDAP objectClass + * and the user's DN is listed in the member (or uniqueMember) attribute + * to indicate that they should be assigned that role. The following LDIF sample + * has the groups stored under the DN ou=groups,dc=acegisecurity,dc=org + * and a group called "developers" with "ben" and "marissa" as members: + * + *
+ * dn: ou=groups,dc=acegisecurity,dc=org + * objectClass: top + * objectClass: organizationalUnit + * ou: groups + * + * dn: cn=developers,ou=groups,dc=acegisecurity,dc=org + * objectClass: groupOfNames + * objectClass: top + * cn: developers + * description: Acegi Security Developers + * member: uid=ben,ou=people,dc=acegisecurity,dc=org + * member: uid=marissa,ou=people,dc=acegisecurity,dc=org + * ou: developer + *+ * + *
+ * The group search is performed within a DN specified by the groupSearchBase + * property, which should be relative to the root DN of its InitialDirContextFactory. + * If the search base is null, group searching is disabled. The filter used in the search is defined by the + * groupSearchFilter property, with the filter argument {0} being the full DN of the user. You can also specify which attribute defines the role name by + * setting the groupRoleAttribute property (the default is "cn"). + *
+ *+ * <bean id="ldapAuthoritiesPopulator" class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator"> + * TODO + * </bean> + *
+ * + * + * @author Luke Taylor + * @version $Id$ + */ +public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator, + InitializingBean { + //~ Static fields/initializers ============================================= + + private static final Log logger = LogFactory.getLog(DefaultLdapAuthoritiesPopulator.class); + + //~ Instance fields ======================================================== + + /** Attributes of the User's LDAP Object that contain role name information. */ + private String[] userRoleAttributes = null; + + private String rolePrefix = ""; + + /** The base DN from which the search for group membership should be performed */ + private String groupSearchBase = null; + + /** The pattern to be used for the user search. {0} is the user's DN */ + private String groupSearchFilter = "(member={0})"; + + /** The ID of the attribute which contains the role name for a group */ + private String groupRoleAttribute = "cn"; + + /** Whether group searches should be performed over the full sub-tree from the base DN */ + // private boolean searchSubtree = false; + + /** Internal variable, tied to searchSubTree property */ + private int searchScope = SearchControls.ONELEVEL_SCOPE; + + private boolean convertToUpperCase = true; + + /** An initial context factory is only required if searching for groups is required. */ + private InitialDirContextFactory initialDirContextFactory = null; + + //~ 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. + */ + public GrantedAuthority[] getGrantedAuthorities(String username, String userDn, Attributes userAttributes) { + logger.debug("Getting authorities for user " + userDn); + + Set roles = getRolesFromUserAttributes(userDn, userAttributes); + + Set groupRoles = getGroupMembershipRoles(userDn, userAttributes); + + if(groupRoles != null) { + roles.addAll(groupRoles); + } + + 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 + * @return the set of roles obtained from a group membership search. + */ + protected Set getGroupMembershipRoles(String userDn, Attributes userAttributes) { + Set userRoles = new HashSet(); + + if (groupSearchBase == null) { + return null; + } + + DirContext ctx = initialDirContextFactory.newInitialDirContext(); + SearchControls ctls = new SearchControls(); + + ctls.setSearchScope(searchScope); + ctls.setReturningAttributes(new String[] {groupRoleAttribute}); + + 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) { + + } finally { + LdapUtils.closeContext(ctx); + } + + 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(); + } + + 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); + } + } + + 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; + } + + public void setGroupSearchBase(String groupSearchBase) { + this.groupSearchBase = groupSearchBase; + } + + public void setGroupSearchFilter(String groupSearchFilter) { + Assert.notNull(groupSearchFilter, "groupSearchFilter must not be null"); + this.groupSearchFilter = groupSearchFilter; + } + + public void setGroupRoleAttribute(String groupRoleAttribute) { + Assert.notNull(groupRoleAttribute, "groupRoleAttribute must not be null"); + this.groupRoleAttribute = groupRoleAttribute; + } + + public void setSearchSubtree(boolean searchSubtree) { + // this.searchSubtree = searchSubtree; + this.searchScope = searchSubtree ? + SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE; + } + + public void setConvertToUpperCase(boolean convertToUpperCase) { + this.convertToUpperCase = convertToUpperCase; + } + + public void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) { + this.initialDirContextFactory = initialDirContextFactory; + } + + public void afterPropertiesSet() throws Exception { + if(initialDirContextFactory == null && groupSearchBase != null) { + throw new IllegalArgumentException("initialDirContextFactory is required for group role searches."); + } + } +} diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/AbstractLdapServerTestCase.java b/core/src/test/java/org/acegisecurity/providers/ldap/AbstractLdapServerTestCase.java new file mode 100644 index 0000000000..3e0d3a4c0d --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/AbstractLdapServerTestCase.java @@ -0,0 +1,25 @@ +package org.acegisecurity.providers.ldap; + +import junit.framework.TestCase; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public abstract class AbstractLdapServerTestCase extends TestCase { + protected static final String ROOT_DN = "dc=acegisecurity,dc=org"; + protected static final String PROVIDER_URL = "ldap://monkeymachine:389/"+ROOT_DN; + //protected static final String PROVIDER_URL = "ldap://localhost:10389/" + ROOT_DN; + protected static final String MANAGER_USER = "cn=manager," + ROOT_DN; + protected static final String MANAGER_PASSWORD = "acegisecurity"; + + +// protected static final LdapTestServer server = new LdapTestServer(); + + protected AbstractLdapServerTestCase() { + } + + protected AbstractLdapServerTestCase(String string) { + super(string); + } +} diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/InitialDirContextFactoryTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/InitialDirContextFactoryTests.java new file mode 100644 index 0000000000..ffe263eb33 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/InitialDirContextFactoryTests.java @@ -0,0 +1,140 @@ +package org.acegisecurity.providers.ldap; + +import javax.naming.Context; +import javax.naming.directory.DirContext; +import java.util.Hashtable; + +import org.springframework.dao.DataAccessResourceFailureException; +import org.acegisecurity.BadCredentialsException; + +/** + * Tests {@link InitialDirContextFactory}. + * + * @author Luke Taylor + * @version $Id$ + */ +public class InitialDirContextFactoryTests extends AbstractLdapServerTestCase { + + public void testNonLdapUrlIsRejected() throws Exception { + DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); + + idf.setUrl("http://acegisecurity.org/dc=acegisecurity,dc=org"); + + try { + idf.afterPropertiesSet(); + fail("Expected exception for non 'ldap://' URL"); + } catch(IllegalArgumentException expected) { + } + } + + public void testConnectionFailure() throws Exception { + DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); + // Use the wrong port + idf.setUrl("ldap://localhost:60389"); + Hashtable env = new Hashtable(); + env.put("com.sun.jndi.ldap.connect.timeout", "200"); + idf.setExtraEnvVars(env); + idf.afterPropertiesSet(); + try { + idf.newInitialDirContext(); + fail("Connection succeeded unexpectedly"); + } catch(DataAccessResourceFailureException expected) { + } + } + + public void testAnonymousBindSucceeds() throws Exception { + DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); + idf.setUrl(PROVIDER_URL); + idf.afterPropertiesSet(); + DirContext ctx = idf.newInitialDirContext(); + // Connection pooling should be set by default for anon users. + assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); + ctx.close(); + } + + public void testBindAsManagerSucceeds() throws Exception { + DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); + idf.setUrl(PROVIDER_URL); + idf.setManagerPassword(MANAGER_PASSWORD); + idf.setManagerDn(MANAGER_USER); + idf.afterPropertiesSet(); + DirContext ctx = idf.newInitialDirContext(); + assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); + ctx.close(); + } + + public void testInvalidPasswordCausesBadCredentialsException() throws Exception { + DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); + idf.setUrl(PROVIDER_URL); + idf.setManagerDn(MANAGER_USER); + idf.setManagerPassword("wrongpassword"); + idf.afterPropertiesSet(); + try { + DirContext ctx = idf.newInitialDirContext(); + fail("Authentication with wrong credentials should fail."); + } catch(BadCredentialsException expected) { + } + } + + public void testConnectionAsSpecificUserSucceeds() throws Exception { + DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); + idf.setUrl(PROVIDER_URL); + idf.afterPropertiesSet(); + DirContext ctx = idf.newInitialDirContext("uid=Bob,ou=people,dc=acegisecurity,dc=org", + "bobspassword"); + // We don't want pooling for specific users. + assertNull(ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); + ctx.close(); + } + + public void testEnvironment() { + DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); + idf.setUrl("ldap://acegisecurity.org/"); + + // check basic env + Hashtable env = idf.getEnvironment(); + assertEquals("com.sun.jndi.ldap.LdapCtxFactory", env.get(Context.INITIAL_CONTEXT_FACTORY)); + assertEquals("ldap://acegisecurity.org/", env.get(Context.PROVIDER_URL)); + assertEquals("simple",env.get(Context.SECURITY_AUTHENTICATION)); + assertNull(env.get(Context.SECURITY_PRINCIPAL)); + assertNull(env.get(Context.SECURITY_CREDENTIALS)); + + // Ctx factory. + idf.setInitialContextFactory("org.acegisecurity.NonExistentCtxFactory"); + env = idf.getEnvironment(); + assertEquals("org.acegisecurity.NonExistentCtxFactory", env.get(Context.INITIAL_CONTEXT_FACTORY)); + + // Auth type + idf.setAuthenticationType("myauthtype"); + env = idf.getEnvironment(); + assertEquals("myauthtype", env.get(Context.SECURITY_AUTHENTICATION)); + + // Check extra vars + Hashtable extraVars = new Hashtable(); + extraVars.put("extravar", "extravarvalue"); + idf.setExtraEnvVars(extraVars); + env = idf.getEnvironment(); + assertEquals("extravarvalue", env.get("extravar")); + } + + public void testBaseDnIsParsedFromCorrectlyFromUrl() throws Exception { + DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); + + idf.setUrl("ldap://acegisecurity.org/dc=acegisecurity,dc=org"); + idf.afterPropertiesSet(); + assertEquals("dc=acegisecurity,dc=org", idf.getRootDn()); + + // Check with an empty root + idf = new DefaultInitialDirContextFactory(); + idf.setUrl("ldap://acegisecurity.org/"); + idf.afterPropertiesSet(); + assertEquals("", idf.getRootDn()); + + // Empty root without trailing slash + idf = new DefaultInitialDirContextFactory(); + idf.setUrl("ldap://acegisecurity.org"); + idf.afterPropertiesSet(); + assertEquals("", idf.getRootDn()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java new file mode 100644 index 0000000000..c8e6c0801f --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/LdapAuthenticationProviderTests.java @@ -0,0 +1,102 @@ +package org.acegisecurity.providers.ldap; + +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; + +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.GrantedAuthorityImpl; +import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.Authentication; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.acegisecurity.providers.ldap.authenticator.FilterBasedLdapUserSearch; +import org.acegisecurity.providers.ldap.authenticator.BindAuthenticator; +import org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator; +import org.acegisecurity.userdetails.UserDetails; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class LdapAuthenticationProviderTests extends AbstractLdapServerTestCase { + DefaultInitialDirContextFactory dirCtxFactory; + + + public LdapAuthenticationProviderTests(String string) { + super(string); + } + + public LdapAuthenticationProviderTests() { + super(); + } + + public void testNormalUsage() throws Exception { + LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(); + + ldapProvider.setAuthenticator(new MockAuthenticator()); + ldapProvider.setLdapAuthoritiesPopulator(new MockAuthoritiesPopulator()); + ldapProvider.afterPropertiesSet(); + + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("bob","bobspassword"); + UserDetails user = ldapProvider.retrieveUser("bob", token); + assertEquals(1, user.getAuthorities().length); + assertTrue(user.getAuthorities()[0].equals("ROLE_USER")); + ldapProvider.additionalAuthenticationChecks(user, token); + + } + + public void testIntegration() throws Exception { + LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(); + + // Connection information + DefaultInitialDirContextFactory dirCtxFactory = new DefaultInitialDirContextFactory(); + dirCtxFactory.setUrl(PROVIDER_URL); + dirCtxFactory.setManagerDn(MANAGER_USER); + dirCtxFactory.setManagerPassword(MANAGER_PASSWORD); + dirCtxFactory.afterPropertiesSet(); + BindAuthenticator authenticator = new BindAuthenticator(); + //PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(); + authenticator.setInitialDirContextFactory(dirCtxFactory); + //authenticator.setUserDnPattern("cn={0},ou=people"); + + FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch(); + userSearch.setSearchBase("ou=people"); + userSearch.setSearchFilter("(cn={0})"); + userSearch.setInitialDirContextFactory(dirCtxFactory); + userSearch.afterPropertiesSet(); + + authenticator.setUserSearch(userSearch); + + authenticator.afterPropertiesSet(); + + DefaultLdapAuthoritiesPopulator populator; + populator = new DefaultLdapAuthoritiesPopulator(); + populator.setRolePrefix("ROLE_"); + populator.setInitialDirContextFactory(dirCtxFactory); + populator.setGroupSearchBase("ou=groups"); + populator.afterPropertiesSet(); + + ldapProvider.setLdapAuthoritiesPopulator(populator); + ldapProvider.setAuthenticator(authenticator); + Authentication auth = ldapProvider.authenticate(new UsernamePasswordAuthenticationToken("Ben Alex","benspassword")); + assertEquals(2, auth.getAuthorities().length); + } + + class MockAuthoritiesPopulator implements LdapAuthoritiesPopulator { + + public GrantedAuthority[] getGrantedAuthorities(String userDn, String dn, Attributes userAttributes) { + return new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_USER") }; + } + } + + class MockAuthenticator implements LdapAuthenticator { + Attributes userAttributes = new BasicAttributes("cn","bob"); + + public LdapUserDetails authenticate(String username, String password) { + if(username.equals("bob") && password.equals("bobspassword")) { + + return new LdapUserDetails("cn=bob,ou=people,dc=acegisecurity,dc=org", userAttributes); + } + throw new BadCredentialsException("Authentication of Bob failed."); + } + } +} diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/LdapTestServer.java b/core/src/test/java/org/acegisecurity/providers/ldap/LdapTestServer.java new file mode 100644 index 0000000000..ec101c62ca --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/LdapTestServer.java @@ -0,0 +1,94 @@ +package org.acegisecurity.providers.ldap; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.io.ClassPathResource; +import org.apache.ldap.server.configuration.MutableServerStartupConfiguration; +import org.apache.ldap.server.jndi.ServerContextFactory; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.NameAlreadyBoundException; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.Attribute; +import javax.naming.directory.BasicAttribute; +import javax.naming.directory.DirContext; +import java.io.IOException; +import java.util.Properties; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class LdapTestServer { + + //~ Instance fields ======================================================== + + private DirContext serverContext; + + //~ Constructors ================================================================ + + public LdapTestServer() { + startLdapServer(); + createManagerUser(); + } + + //~ Methods ================================================================ + + private void startLdapServer() { + ApplicationContext factory = new ClassPathXmlApplicationContext( "org/acegisecurity/providers/ldap/apacheds-context.xml"); + MutableServerStartupConfiguration cfg = ( MutableServerStartupConfiguration ) factory.getBean( "configuration" ); + ClassPathResource ldifDir = new ClassPathResource("org/acegisecurity/providers/ldap/ldif"); + + try { + cfg.setLdifDirectory(ldifDir.getFile()); + } catch (IOException e) { + System.err.println("Failed to set LDIF directory for server"); + e.printStackTrace(); + } + + Properties env = ( Properties ) factory.getBean( "environment" ); + + env.setProperty( Context.PROVIDER_URL, "dc=acegisecurity,dc=org" ); + env.setProperty( Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName() ); + env.putAll( cfg.toJndiEnvironment() ); + + try { + serverContext = new InitialDirContext( env ); + } catch (NamingException e) { + System.err.println("Failed to start Apache DS"); + e.printStackTrace(); + } + } + + private void createManagerUser() { + Attributes user = new BasicAttributes( "cn", "manager" , true ); + user.put( "userPassword", "acegisecurity" ); + Attribute objectClass = new BasicAttribute("objectClass"); + user.put( objectClass ); + objectClass.add( "top" ); + objectClass.add( "person" ); + objectClass.add( "organizationalPerson" ); + objectClass.add( "inetOrgPerson" ); + user.put( "sn", "Manager" ); + user.put( "cn", "manager" ); + try { + serverContext.createSubcontext("cn=manager", user ); + } catch(NameAlreadyBoundException ignore) { + System.out.println("Manager user already exists."); + } catch (NamingException ne) { + System.err.println("Failed to create manager user."); + ne.printStackTrace(); + } + } + + public DirContext getServerContext() { + return serverContext; + } + + public static void main(String[] args) { + new LdapTestServer(); + } +} 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 new file mode 100644 index 0000000000..1879440fee --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticatorTests.java @@ -0,0 +1,73 @@ +package org.acegisecurity.providers.ldap.authenticator; + +import org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory; +import org.acegisecurity.providers.ldap.LdapUserDetails; +import org.acegisecurity.providers.ldap.AbstractLdapServerTestCase; +import org.acegisecurity.BadCredentialsException; + +/** + * Tests {@link BindAuthenticator}. + * + * @author Luke Taylor + * @version $Id$ + */ +public class BindAuthenticatorTests extends AbstractLdapServerTestCase { + + private DefaultInitialDirContextFactory dirCtxFactory; + private BindAuthenticator authenticator; + + public void setUp() throws Exception { + // Connection information + dirCtxFactory = new DefaultInitialDirContextFactory(); + dirCtxFactory.setUrl(PROVIDER_URL); + dirCtxFactory.afterPropertiesSet(); + authenticator = new BindAuthenticator(); + authenticator.setInitialDirContextFactory(dirCtxFactory); + } + + public void testUserDnPatternReturnsCorrectDn() throws Exception { + authenticator.setUserDnPattern("cn={0},ou=people"); + assertEquals("cn=Joe,ou=people,"+ ROOT_DN, authenticator.getUserDn("Joe")); + } + + public void testAuthenticationWithCorrectPasswordSucceeds() throws Exception { + authenticator.setUserDnPattern("uid={0},ou=people"); + LdapUserDetails user = authenticator.authenticate("bob","bobspassword"); + } + + public void testAuthenticationWithWrongPasswordFails() { + BindAuthenticator authenticator = new BindAuthenticator(); + + authenticator.setInitialDirContextFactory(dirCtxFactory); + authenticator.setUserDnPattern("uid={0},ou=people"); + + try { + authenticator.authenticate("bob","wrongpassword"); + fail("Shouldn't be able to bind with wrong password"); + } catch(BadCredentialsException expected) { + } + } + + public void testAuthenticationWithUserSearch() throws Exception { + LdapUserDetails user = new LdapUserDetails("uid=bob,ou=people," + ROOT_DN, null); + authenticator.setUserSearch(new MockUserSearch(user)); + authenticator.afterPropertiesSet(); + authenticator.authenticate("bob","bobspassword"); + } + + +// Apache DS falls apart with unknown DNs. +// +// public void testAuthenticationWithInvalidUserNameFails() { +// BindAuthenticator authenticator = new BindAuthenticator(); +// +// authenticator.setInitialDirContextFactory(dirCtxFactory); +// authenticator.setUserDnPattern("cn={0},ou=people"); +// try { +// authenticator.authenticate("Baz","bobspassword"); +// fail("Shouldn't be able to bind with invalid username"); +// } catch(BadCredentialsException expected) { +// } +// } +} + diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/FilterBasedLdapUserSearchTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/FilterBasedLdapUserSearchTests.java new file mode 100644 index 0000000000..bde70301bd --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/FilterBasedLdapUserSearchTests.java @@ -0,0 +1,87 @@ +package org.acegisecurity.providers.ldap.authenticator; + +import org.acegisecurity.providers.ldap.AbstractLdapServerTestCase; +import org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory; +import org.acegisecurity.providers.ldap.LdapUserDetails; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.acegisecurity.BadCredentialsException; + +/** + * Tests for FilterBasedLdapUserSearch. + * + * @author Luke Taylor + * @version $Id$ + */ +public class FilterBasedLdapUserSearchTests extends AbstractLdapServerTestCase { + private DefaultInitialDirContextFactory dirCtxFactory; + private FilterBasedLdapUserSearch locator; + + public void setUp() throws Exception { + dirCtxFactory = new DefaultInitialDirContextFactory(); + dirCtxFactory.setUrl(PROVIDER_URL); + dirCtxFactory.setManagerDn(MANAGER_USER); + dirCtxFactory.setManagerPassword(MANAGER_PASSWORD); + dirCtxFactory.afterPropertiesSet(); + locator = new FilterBasedLdapUserSearch(); + locator.setSearchSubtree(false); + locator.setSearchTimeLimit(0); + locator.setInitialDirContextFactory(dirCtxFactory); + } + + public FilterBasedLdapUserSearchTests(String string) { + super(string); + } + + public FilterBasedLdapUserSearchTests() { + super(); + } + + public void testBasicSearch() throws Exception { + locator.setSearchBase("ou=people"); + locator.setSearchFilter("(uid={0})"); + locator.afterPropertiesSet(); + LdapUserDetails bob = locator.searchForUser("Bob"); + assertEquals("uid=bob,ou=people,"+ROOT_DN, bob.getDn()); + } + + public void testSubTreeSearchSucceeds() throws Exception { + // Don't set the searchBase, so search from the root. + locator.setSearchFilter("(uid={0})"); + locator.setSearchSubtree(true); + locator.afterPropertiesSet(); + LdapUserDetails bob = locator.searchForUser("Bob"); + assertEquals("uid=bob,ou=people,"+ROOT_DN, bob.getDn()); + } + + public void testSearchForInvalidUserFails() { + locator.setSearchBase("ou=people"); + locator.setSearchFilter("(uid={0})"); + + try { + locator.searchForUser("Joe"); + fail("Expected UsernameNotFoundException for non-existent user."); + } catch (UsernameNotFoundException expected) { + } + } + + public void testFailsOnMultipleMatches() { + locator.setSearchBase("ou=people"); + locator.setSearchFilter("(cn=*)"); + + try { + locator.searchForUser("Ignored"); + fail("Expected exception for multiple search matches."); + } catch (BadCredentialsException expected) { + } + } + + /** Try some funny business with filters. */ + public void testExtraFilterPartToExcludeBob() { + locator.setSearchBase("ou=people"); + locator.setSearchFilter("(&(cn=*)(!(uid={0})))"); + + // Search for bob, get back ben... + LdapUserDetails ben = locator.searchForUser("bob"); + assertEquals("cn=Ben Alex,ou=people,"+ROOT_DN, ben.getDn()); + } +} 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 new file mode 100644 index 0000000000..7c2fae09a3 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/MockUserSearch.java @@ -0,0 +1,19 @@ +package org.acegisecurity.providers.ldap.authenticator; + +import org.acegisecurity.providers.ldap.LdapUserDetails; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class MockUserSearch implements LdapUserSearch { + LdapUserDetails user; + + public MockUserSearch(LdapUserDetails user) { + this.user = user; + } + + 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 new file mode 100644 index 0000000000..334ceb7879 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorMockTests.java @@ -0,0 +1,58 @@ +package org.acegisecurity.providers.ldap.authenticator; + +import org.jmock.Mock; +import org.jmock.MockObjectTestCase; +import org.acegisecurity.providers.ldap.InitialDirContextFactory; + +import javax.naming.directory.DirContext; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.Attributes; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class PasswordComparisonAuthenticatorMockTests extends MockObjectTestCase { + + public void testLdapCompareIsUsedWhenPasswordIsNotRetrieved() throws Exception { + Mock mockCtx = new Mock(DirContext.class); + + PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(); + authenticator.setUserDnPattern("cn={0},ou=people"); + authenticator.setInitialDirContextFactory( + new MockInitialDirContextFactory((DirContext)mockCtx.proxy(), + "dc=acegisecurity,dc=org")); + // Get the mock to return an empty attribute set + mockCtx.expects(atLeastOnce()).method("getNameInNamespace").will(returnValue("dc=acegisecurity,dc=org")); + 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"); + authenticator.authenticate("Bob", "bobspassword"); + } + + class MockInitialDirContextFactory implements InitialDirContextFactory { + DirContext ctx; + String baseDn; + + public MockInitialDirContextFactory(DirContext ctx, String baseDn) { + this.baseDn = baseDn; + this.ctx = ctx; + } + + public DirContext newInitialDirContext() { + return ctx; + } + + public DirContext newInitialDirContext(String username, String password) { + return ctx; + } + + public String getRootDn() { + return baseDn; + } + } + +} 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 new file mode 100644 index 0000000000..32d13fef86 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/PasswordComparisonAuthenticatorTests.java @@ -0,0 +1,133 @@ +package org.acegisecurity.providers.ldap.authenticator; + +import org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory; +import org.acegisecurity.providers.ldap.LdapUserDetails; +import org.acegisecurity.providers.ldap.AbstractLdapServerTestCase; +import org.acegisecurity.providers.encoding.PlaintextPasswordEncoder; +import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.userdetails.UsernameNotFoundException; + +import javax.naming.directory.BasicAttributes; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class PasswordComparisonAuthenticatorTests extends AbstractLdapServerTestCase { + private DefaultInitialDirContextFactory dirCtxFactory; + private PasswordComparisonAuthenticator authenticator; + + public void setUp() throws Exception { + // Connection information + dirCtxFactory = new DefaultInitialDirContextFactory(); + dirCtxFactory.setUrl(PROVIDER_URL); + dirCtxFactory.setManagerDn(MANAGER_USER); + dirCtxFactory.setManagerPassword(MANAGER_PASSWORD); + dirCtxFactory.afterPropertiesSet(); + authenticator = new PasswordComparisonAuthenticator(); + authenticator.setInitialDirContextFactory(dirCtxFactory); + authenticator.setUserDnPattern("uid={0},ou=people"); + } + + public void tearDown() { + // com.sun.jndi.ldap.LdapPoolManager.showStats(System.out); + } + + public void testLdapCompareSucceedsWithCorrectPassword() { + // Don't retrieve the password + authenticator.setUserAttributes(new String[] {"cn", "sn"}); + // Bob has a plaintext password. + authenticator.setPasswordEncoder(new PlaintextPasswordEncoder()); + authenticator.authenticate("Bob", "bobspassword"); + } + + public void testLdapCompareSucceedsWithShaEncodedPassword() { + authenticator = new PasswordComparisonAuthenticator(); + authenticator.setInitialDirContextFactory(dirCtxFactory); + authenticator.setUserDnPattern("cn={0},ou=people"); + // Don't retrieve the password + authenticator.setUserAttributes(new String[] {"cn", "sn"}); + authenticator.authenticate("Ben Alex", "benspassword"); + } + + public void testPasswordEncoderCantBeNull() { + try { + authenticator.setPasswordEncoder(null); + fail("Password encoder can't be null"); + } catch(IllegalArgumentException expected) { + } + } + + public void testLdapPasswordCompareFailsWithWrongPassword() { + // Don't retrieve the password + authenticator.setUserAttributes(new String[] {"cn", "sn"}); + + try { + authenticator.authenticate("Bob", "wrongpassword"); + fail("Authentication should fail with wrong password."); + } catch(BadCredentialsException expected) { + } + } + + public void testLocalPasswordComparisonSucceedsWithCorrectPassword() { + authenticator.authenticate("Bob", "bobspassword"); + } + + public void testLocalCompareSucceedsWithShaEncodedPassword() { + authenticator = new PasswordComparisonAuthenticator(); + authenticator.setInitialDirContextFactory(dirCtxFactory); + authenticator.setUserDnPattern("cn={0},ou=people"); + authenticator.authenticate("Ben Alex", "benspassword"); + } + + public void testLocalPasswordComparisonFailsWithWrongPassword() { + try { + authenticator.authenticate("Bob", "wrongpassword"); + fail("Authentication should fail with wrong password."); + } catch(BadCredentialsException expected) { + } + } + + public void testAllAttributesAreRetrivedByDefault() { + LdapUserDetails user = authenticator.authenticate("Bob", "bobspassword"); + System.out.println(user.getAttributes().toString()); + assertEquals("User should have 5 attributes", 5, user.getAttributes().size()); + + } + + public void testOnlySpecifiedAttributesAreRetrieved() throws Exception { + authenticator.setUserAttributes(new String[] {"cn", "sn"}); + authenticator.setPasswordEncoder(new PlaintextPasswordEncoder()); + LdapUserDetails user = authenticator.authenticate("Bob", "bobspassword"); + assertEquals("Should have retrieved 2 attributes (cn, sn)",2, user.getAttributes().size()); + assertEquals("Bob Hamilton", user.getAttributes().get("cn").get()); + assertEquals("Hamilton", user.getAttributes().get("sn").get()); + } + + public void testUseOfDifferentPasswordAttribute() { + authenticator.setPasswordAttributeName("sn"); + authenticator.authenticate("Bob", "Hamilton"); + } + + public void testWithUserSearch() { + LdapUserDetails user = new LdapUserDetails("uid=Bob,ou=people" + ROOT_DN, + new BasicAttributes("userPassword","bobspassword")); + authenticator.setUserDnPattern(null); + assertNull(authenticator.getUserDnPattern()); + assertNull(authenticator.getUserDn("Bob")); + authenticator.setUserSearch(new MockUserSearch(user)); + authenticator.authenticate("ShouldntBeUsed","bobspassword"); + } + + public void testFailedSearchGivesUserNotFoundException() throws Exception { + authenticator.setUserDnPattern(null); + authenticator.setUserSearch(new MockUserSearch(null)); + authenticator.afterPropertiesSet(); + + try { + authenticator.authenticate("Joe","password"); + fail("Expected exception on failed user search"); + } catch (UsernameNotFoundException expected) { + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..2ac3cb2d4c --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/ldap/populator/DefaultLdapAuthoritiesPopulatorTests.java @@ -0,0 +1,76 @@ +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.providers.ldap.AbstractLdapServerTestCase; +import org.acegisecurity.providers.ldap.DefaultInitialDirContextFactory; + +import java.util.Set; +import java.util.HashSet; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class DefaultLdapAuthoritiesPopulatorTests extends AbstractLdapServerTestCase { + private DefaultInitialDirContextFactory dirCtxFactory; + private DefaultLdapAuthoritiesPopulator populator; + + public void setUp() { + dirCtxFactory = new DefaultInitialDirContextFactory(); + dirCtxFactory.setUrl(PROVIDER_URL); + dirCtxFactory.setManagerDn(MANAGER_USER); + dirCtxFactory.setManagerPassword(MANAGER_PASSWORD); + + populator = new DefaultLdapAuthoritiesPopulator(); + populator.setRolePrefix("ROLE_"); + } + + public void testCtxFactoryMustBeSetIfSearchBaseIsSet() throws Exception { + populator.setGroupSearchBase(""); + + try { + populator.afterPropertiesSet(); + fail("expected exception."); + } catch (IllegalArgumentException expected) { + } + } + + public void testUserAttributeMappingToRoles() { + 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 testGroupSearch() throws Exception { + populator.setInitialDirContextFactory(dirCtxFactory); + populator.setGroupSearchBase("ou=groups"); + populator.setGroupRoleAttribute("ou"); + populator.setSearchSubtree(true); + populator.setSearchSubtree(false); + populator.setConvertToUpperCase(true); + populator.setGroupSearchFilter("member={0}"); + populator.afterPropertiesSet(); + + GrantedAuthority[] authorities = populator.getGrantedAuthorities("Ben", "cn=Ben Alex,ou=people,"+ROOT_DN, new BasicAttributes()); + assertEquals("Should have 2 roles", 2, authorities.length); + Set roles = new HashSet(); + roles.add(authorities[0].toString()); + roles.add(authorities[1].toString()); + assertTrue(roles.contains("ROLE_DEVELOPER")); + assertTrue(roles.contains("ROLE_MANAGER")); + } +} diff --git a/core/src/test/resources/org/acegisecurity/providers/ldap/apacheds-context.xml b/core/src/test/resources/org/acegisecurity/providers/ldap/apacheds-context.xml new file mode 100644 index 0000000000..9b1259a8d2 --- /dev/null +++ b/core/src/test/resources/org/acegisecurity/providers/ldap/apacheds-context.xml @@ -0,0 +1,101 @@ + + + + +