diff --git a/core/src/test/java/org/acegisecurity/ldap/AbstractLdapServerTestCase.java b/core/src/test/java/org/acegisecurity/ldap/AbstractLdapServerTestCase.java new file mode 100644 index 0000000000..0c5b984e40 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/ldap/AbstractLdapServerTestCase.java @@ -0,0 +1,67 @@ +/* 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 junit.framework.TestCase; + +import java.util.Hashtable; + +import org.apache.ldap.server.jndi.CoreContextFactory; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public abstract class AbstractLdapServerTestCase extends TestCase { + private static final String ROOT_DN = "dc=acegisecurity,dc=org"; + protected static final String MANAGER_USER = "cn=manager," + ROOT_DN; + 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(); + + + // 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(); + + protected AbstractLdapServerTestCase() { + } + + protected AbstractLdapServerTestCase(String string) { + super(string); + } + + private DefaultInitialDirContextFactory idf; + + public final void setUp() { + idf = new DefaultInitialDirContextFactory(PROVIDER_URL); + idf.setInitialContextFactory(CONTEXT_FACTORY); + idf.setExtraEnvVars(EXTRA_ENV); + + onSetUp(); + } + + protected void onSetUp() {} + + protected DefaultInitialDirContextFactory getInitialCtxFactory() { + return idf; + } +} diff --git a/core/src/test/java/org/acegisecurity/ldap/DefaultInitialDirContextFactoryTests.java b/core/src/test/java/org/acegisecurity/ldap/DefaultInitialDirContextFactoryTests.java new file mode 100644 index 0000000000..ddead660ff --- /dev/null +++ b/core/src/test/java/org/acegisecurity/ldap/DefaultInitialDirContextFactoryTests.java @@ -0,0 +1,173 @@ +package org.acegisecurity.ldap; + +import javax.naming.Context; +import javax.naming.directory.DirContext; +import java.util.Hashtable; + +import org.acegisecurity.BadCredentialsException; + +/** + * Tests {@link org.acegisecurity.ldap.DefaultInitialDirContextFactory}. + * + * @author Luke Taylor + * @version $Id$ + */ +public class DefaultInitialDirContextFactoryTests extends AbstractLdapServerTestCase { + DefaultInitialDirContextFactory idf; + + public void onSetUp() { + idf = getInitialCtxFactory(); + } + +// public void testNonLdapUrlIsRejected() throws Exception { +// DefaultInitialDirContextFactory idf = new DefaultInitialDirContextFactory(); +// +// idf.setUrl("http://acegisecurity.org/dc=acegisecurity,dc=org"); +// idf.setInitialContextFactory(CoreContextFactory.class.getName()); +// +// try { +// idf.afterPropertiesSet(); +// fail("Expected exception for non 'ldap://' URL"); +// } catch(IllegalArgumentException expected) { +// } +// } + + public void testServiceLocationUrlIsSupported() { + idf = new DefaultInitialDirContextFactory("ldap:///dc=acegisecurity,dc=org"); + assertEquals("dc=acegisecurity,dc=org", idf.getRootDn()); + + } + + public void testSecureLdapUrlIsSupported() { + idf = new DefaultInitialDirContextFactory("ldaps://localhost/dc=acegisecurity,dc=org"); + assertEquals("dc=acegisecurity,dc=org", idf.getRootDn()); + } + + public void testConnectionFailure() throws Exception { + // Use the wrong port + idf = new DefaultInitialDirContextFactory("ldap://localhost:60389"); + idf.setInitialContextFactory("com.sun.jndi.ldap.LdapCtxFactory"); + Hashtable env = new Hashtable(); + env.put("com.sun.jndi.ldap.connect.timeout", "200"); + idf.setExtraEnvVars(env); + + try { + idf.newInitialDirContext(); + fail("Connection succeeded unexpectedly"); + } catch(LdapDataAccessException expected) { + } + } + + public void testAnonymousBindSucceeds() throws Exception { + DirContext ctx = idf.newInitialDirContext(); + // Connection pooling should be set by default for anon users. + // Can't rely on this property being there with embedded server + // assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); + ctx.close(); + } + + public void testBindAsManagerSucceeds() throws Exception { + idf.setManagerPassword(MANAGER_PASSWORD); + idf.setManagerDn(MANAGER_USER); + + DirContext ctx = idf.newInitialDirContext(); +// Can't rely on this property being there with embedded server +// assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); + ctx.close(); + } + + public void testBindAsManagerFailsIfNoPasswordSet() throws Exception { + idf.setManagerDn(MANAGER_USER); + + DirContext ctx = null; + + try { + ctx = idf.newInitialDirContext(); + fail("Binding with no manager password should fail."); +// Can't rely on this property being there with embedded server +// assertEquals("true",ctx.getEnvironment().get("com.sun.jndi.ldap.connect.pool")); + } catch(BadCredentialsException expected) { + } + + LdapUtils.closeContext(ctx); + } + + public void testInvalidPasswordCausesBadCredentialsException() throws Exception { + idf.setManagerDn(MANAGER_USER); + idf.setManagerPassword("wrongpassword"); + + DirContext ctx = null; + try { + ctx = idf.newInitialDirContext(); + fail("Binding with wrong credentials should fail."); + } catch(BadCredentialsException expected) { + } + + LdapUtils.closeContext(ctx); + } + + public void testConnectionAsSpecificUserSucceeds() throws Exception { + 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")); +// com.sun.jndi.ldap.LdapPoolManager.showStats(System.out); + ctx.close(); + } + + public void testEnvironment() { + idf = new DefaultInitialDirContextFactory("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 { + idf = new DefaultInitialDirContextFactory("ldap://acegisecurity.org/dc=acegisecurity,dc=org"); + assertEquals("dc=acegisecurity,dc=org", idf.getRootDn()); + + // Check with an empty root + idf = new DefaultInitialDirContextFactory("ldap://acegisecurity.org/"); + assertEquals("", idf.getRootDn()); + + // Empty root without trailing slash + idf = new DefaultInitialDirContextFactory("ldap://acegisecurity.org"); + assertEquals("", idf.getRootDn()); + } + + public void testMultipleProviderUrlsAreAccepted() { + idf = new DefaultInitialDirContextFactory("ldaps://acegisecurity.org/dc=acegisecurity,dc=org " + + "ldap://monkeymachine.co.uk/dc=acegisecurity,dc=org"); + } + + public void testMultipleProviderUrlsWithDifferentRootsAreRejected() { + try { + idf = new DefaultInitialDirContextFactory("ldap://acegisecurity.org/dc=acegisecurity,dc=org " + + "ldap://monkeymachine.co.uk/dc=someotherplace,dc=org"); + fail("Different root DNs should cause an exception"); + } catch (IllegalArgumentException expected) { + } + } +} diff --git a/core/src/test/java/org/acegisecurity/ldap/LdapTestServer.java b/core/src/test/java/org/acegisecurity/ldap/LdapTestServer.java new file mode 100644 index 0000000000..9f5d3a0d2a --- /dev/null +++ b/core/src/test/java/org/acegisecurity/ldap/LdapTestServer.java @@ -0,0 +1,230 @@ +/* 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 org.apache.ldap.server.configuration.MutableDirectoryPartitionConfiguration; +import org.apache.ldap.server.configuration.MutableStartupConfiguration; +import org.apache.ldap.server.configuration.Configuration; +import org.apache.ldap.server.jndi.CoreContextFactory; +import org.acegisecurity.ldap.LdapUtils; + +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.util.Properties; +import java.util.Set; +import java.util.HashSet; +import java.io.File; + +/** + * An embedded LDAP test server, complete with test data for running the + * unit tests against. + * + * @author Luke Taylor + * @version $Id$ + */ +public class LdapTestServer { + + //~ Instance fields ======================================================== + + private DirContext serverContext; + + private MutableStartupConfiguration cfg; + + // Move the working dir to the temp directory + private File workingDir = new File( System.getProperty("java.io.tmpdir") + + File.separator + "apacheds-work" ); + + + //~ Constructors ================================================================ + + /** + * Starts up and configures ApacheDS. + */ + public LdapTestServer() { + startLdapServer(); + createManagerUser(); + initTestData(); + } + + //~ Methods ================================================================ + + private void startLdapServer() { + + cfg = new MutableStartupConfiguration(); + ((MutableStartupConfiguration)cfg).setWorkingDirectory(workingDir); + + System.out.println("Working directory is " + workingDir.getAbsolutePath()); + + initConfiguration(); + + Properties env = new Properties(); + + env.setProperty( Context.PROVIDER_URL, "dc=acegisecurity,dc=org" ); + env.setProperty( Context.INITIAL_CONTEXT_FACTORY, CoreContextFactory.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 initTestData() { + createOu("people"); + createOu("groups"); + createUser("bob","Bob Hamilton", "bobspassword"); + createUser("ben","Ben Alex", "{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ="); + String[] developers = new String[] + {"uid=ben,ou=people,dc=acegisecurity,dc=org", "uid=bob,ou=people,dc=acegisecurity,dc=org"}; + createGroup("developers","developer",developers); + createGroup("managers","manager", new String[] { developers[0]}); + } + + 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 void createUser( String uid, String cn, String password ) { + Attributes user = new BasicAttributes("uid", uid); + user.put( "cn", cn); + user.put( "userPassword", LdapUtils.getUtf8Bytes(password) ); + Attribute objectClass = new BasicAttribute( "objectClass" ); + user.put( objectClass ); + objectClass.add( "top" ); + objectClass.add( "person" ); + objectClass.add( "organizationalPerson" ); + objectClass.add( "inetOrgPerson" ); + user.put( "sn", uid ); + + try { + serverContext.createSubcontext( "uid="+uid+",ou=people", user ); + } catch(NameAlreadyBoundException ignore) { +// System.out.println(" user " + uid + " already exists."); + } catch (NamingException ne) { + System.err.println("Failed to create user."); + ne.printStackTrace(); + } + } + + public void createOu(String name) { + Attributes ou = new BasicAttributes( "ou", name ); + Attribute objectClass = new BasicAttribute( "objectClass" ); + objectClass.add("top"); + objectClass.add("organizationalUnit"); + ou.put(objectClass); + + try { + serverContext.createSubcontext( "ou="+name, ou); + } catch(NameAlreadyBoundException ignore) { + // System.out.println(" ou " + name + " already exists."); + } catch (NamingException ne) { + System.err.println("Failed to create ou."); + ne.printStackTrace(); + } + + } + + public void createGroup( String cn, String ou, String[] memberDns ) { + Attributes group = new BasicAttributes("cn", cn); + Attribute members = new BasicAttribute("member"); + Attribute orgUnit = new BasicAttribute("ou", ou); + + for(int i=0; i < memberDns.length; i++) { + members.add(memberDns[i]); + } + + Attribute objectClass = new BasicAttribute( "objectClass" ); + objectClass.add( "top" ); + objectClass.add( "groupOfNames" ); + + group.put(objectClass); + group.put(members); + group.put(orgUnit); + + try { + serverContext.createSubcontext( "cn="+cn+",ou=groups", group ); + } catch(NameAlreadyBoundException ignore) { +// System.out.println(" group " + cn + " already exists."); + } catch (NamingException ne) { + System.err.println("Failed to create group."); + ne.printStackTrace(); + } + } + + private void initConfiguration() { + + // Create the partition for the acegi tests + MutableDirectoryPartitionConfiguration acegiDit = new MutableDirectoryPartitionConfiguration(); + acegiDit.setName("acegisecurity"); + acegiDit.setSuffix("dc=acegisecurity,dc=org"); + BasicAttributes attributes = new BasicAttributes(); + BasicAttribute objectClass = new BasicAttribute("objectClass"); + objectClass.add("top"); + objectClass.add("domain"); + objectClass.add("extensibleObject"); + attributes.put(objectClass); + acegiDit.setContextEntry(attributes); + + Set indexedAttrs = new HashSet(); + indexedAttrs.add("objectClass"); + indexedAttrs.add("uid"); + indexedAttrs.add("cn"); + indexedAttrs.add("ou"); + indexedAttrs.add("member"); + + acegiDit.setIndexedAttributes(indexedAttrs); + + Set partitions = new HashSet(); + partitions.add(acegiDit); + + cfg.setContextPartitionConfigurations(partitions); + } + + public Configuration getConfiguration() { + return cfg; + } + + public static void main(String[] args) { + LdapTestServer server = new LdapTestServer(); + } + +} diff --git a/core/src/test/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearchTests.java b/core/src/test/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearchTests.java new file mode 100644 index 0000000000..0770b45b3b --- /dev/null +++ b/core/src/test/java/org/acegisecurity/ldap/search/FilterBasedLdapUserSearchTests.java @@ -0,0 +1,88 @@ +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; + +/** + * Tests for FilterBasedLdapUserSearch. + * + * @author Luke Taylor + * @version $Id$ + */ +public class FilterBasedLdapUserSearchTests extends AbstractLdapServerTestCase { + private DefaultInitialDirContextFactory dirCtxFactory; + + public void onSetUp() { + dirCtxFactory = getInitialCtxFactory(); + dirCtxFactory.setManagerDn(MANAGER_USER); + dirCtxFactory.setManagerPassword(MANAGER_PASSWORD); + } + + public FilterBasedLdapUserSearchTests(String string) { + super(string); + } + + public FilterBasedLdapUserSearchTests() { + super(); + } + + public void testBasicSearch() throws Exception { + FilterBasedLdapUserSearch locator = + new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory); + LdapUserInfo bob = locator.searchForUser("bob"); + locator.setSearchSubtree(false); + locator.setSearchTimeLimit(0); + // name is wrong with embedded apacheDS +// assertEquals("uid=bob,ou=people,"+ROOT_DN, bob.getDn()); + } + + public void testSubTreeSearchSucceeds() throws Exception { + // Don't set the searchBase, so search from the root. + FilterBasedLdapUserSearch locator = + new FilterBasedLdapUserSearch("", "(cn={0})", dirCtxFactory); + locator.setSearchSubtree(true); + + LdapUserInfo ben = locator.searchForUser("Ben Alex"); +// assertEquals("uid=ben,ou=people,"+ROOT_DN, bob.getDn()); + } + + public void testSearchForInvalidUserFails() { + FilterBasedLdapUserSearch locator = + new FilterBasedLdapUserSearch("ou=people", "(uid={0})", dirCtxFactory); + + try { + locator.searchForUser("Joe"); + fail("Expected UsernameNotFoundException for non-existent user."); + } catch (UsernameNotFoundException expected) { + } + } + + public void testFailsOnMultipleMatches() { + FilterBasedLdapUserSearch locator = + new FilterBasedLdapUserSearch("ou=people", "(cn=*)", dirCtxFactory); + + try { + locator.searchForUser("Ignored"); + fail("Expected exception for multiple search matches."); + } catch (BadCredentialsException expected) { + } + } + + // Try some funny business with filters. + + public void testExtraFilterPartToExcludeBob() throws Exception { + FilterBasedLdapUserSearch locator = + new FilterBasedLdapUserSearch("ou=people", + "(&(cn=*)(!(|(uid={0})(uid=marissa))))", + dirCtxFactory); + + // Search for bob, get back ben... + LdapUserInfo 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()); + } +}