From 245fc9613746918e52a0c039bd4b231ed5641c8d Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Tue, 1 Sep 2009 23:21:44 +0000 Subject: [PATCH] SEC-1075: Update the embedded LDAP server to use Apache DS 1.5. Updated to use the new 1.5.5 release for the embedded server. --- config/pom.xml | 18 +- .../ldap/LdapServerBeanDefinitionParser.java | 59 +---- ldap/pom.xml | 16 +- .../ldap/server/ApacheDSContainer.java | 209 ++++++++++-------- .../ldap/AbstractLdapIntegrationTests.java | 50 +---- .../ldap/server/ApacheDSContainerTests.java | 27 +++ ldap/src/test/resources/log4j.properties | 2 +- ldap/template.mf | 1 + 8 files changed, 182 insertions(+), 200 deletions(-) create mode 100644 ldap/src/test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java diff --git a/config/pom.xml b/config/pom.xml index 91c7ef7170..5692a875e0 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -59,6 +59,19 @@ org.springframework spring-web + + org.apache.directory.server + apacheds-core + 1.5.5 + true + + + org.apache.directory.server + apacheds-server-jndi + 1.5.5 + true + + org.slf4j slf4j-log4j12 diff --git a/config/src/main/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParser.java index 59856f5039..103108434f 100644 --- a/config/src/main/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/ldap/LdapServerBeanDefinitionParser.java @@ -1,23 +1,16 @@ package org.springframework.security.config.ldap; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; -import javax.naming.directory.BasicAttributes; - -import org.springframework.beans.factory.xml.BeanDefinitionParser; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.ManagedSet; -import org.springframework.security.config.BeanIds; -import org.springframework.util.StringUtils; - -import org.w3c.dom.Element; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.security.config.BeanIds; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; /** * @author Luke Taylor @@ -97,25 +90,8 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser { * * @see ApacheDSContainer */ - @SuppressWarnings("unchecked") private RootBeanDefinition createEmbeddedServer(Element element, ParserContext parserContext) { Object source = parserContext.extractSource(element); - BeanDefinitionBuilder configuration = - BeanDefinitionBuilder.rootBeanDefinition("org.apache.directory.server.configuration.MutableServerStartupConfiguration"); - BeanDefinitionBuilder partition = - BeanDefinitionBuilder.rootBeanDefinition("org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration"); - configuration.getRawBeanDefinition().setSource(source); - partition.getRawBeanDefinition().setSource(source); - - Attributes rootAttributes = new BasicAttributes("dc", "springsecurity"); - Attribute a = new BasicAttribute("objectClass"); - a.add("top"); - a.add("domain"); - a.add("extensibleObject"); - rootAttributes.put(a); - - partition.addPropertyValue("name", "springsecurity"); - partition.addPropertyValue("contextEntry", rootAttributes); String suffix = element.getAttribute(ATT_ROOT_SUFFIX); @@ -123,25 +99,12 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser { suffix = OPT_DEFAULT_ROOT_SUFFIX; } - partition.addPropertyValue("suffix", suffix); - - ManagedSet partitions = new ManagedSet(1); - partitions.add(partition.getBeanDefinition()); - String port = element.getAttribute(ATT_PORT); if (!StringUtils.hasText(port)) { port = OPT_DEFAULT_PORT; } - configuration.addPropertyValue("ldapPort", port); - - // We shut down the server ourself when the app context is closed so we don't need - // the extra shutdown hook from apache DS itself. - configuration.addPropertyValue("shutdownHookEnabled", Boolean.FALSE); - configuration.addPropertyValue("exitVmOnShutdown", Boolean.FALSE); - configuration.addPropertyValue("contextPartitionConfigurations", partitions); - String url = "ldap://127.0.0.1:" + port + "/" + suffix; BeanDefinitionBuilder contextSource = BeanDefinitionBuilder.rootBeanDefinition(CONTEXT_SOURCE_CLASS); @@ -151,8 +114,7 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser { RootBeanDefinition apacheContainer = new RootBeanDefinition("org.springframework.security.ldap.server.ApacheDSContainer", null, null); apacheContainer.setSource(source); - apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(configuration.getBeanDefinition()); - apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(contextSource.getBeanDefinition()); + apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(suffix); String ldifs = element.getAttribute(ATT_LDIF_FILE); if (!StringUtils.hasText(ldifs)) { @@ -160,6 +122,7 @@ public class LdapServerBeanDefinitionParser implements BeanDefinitionParser { } apacheContainer.getConstructorArgumentValues().addGenericArgumentValue(ldifs); + apacheContainer.getPropertyValues().addPropertyValue("port", port); logger.info("Embedded LDAP server bean created for URL: " + url); diff --git a/ldap/pom.xml b/ldap/pom.xml index 095939e923..2757bd1df5 100644 --- a/ldap/pom.xml +++ b/ldap/pom.xml @@ -28,21 +28,30 @@ ldapsdk 4.1 true - + + org.apache.directory.server apacheds-core - 1.0.2 + 1.5.5 compile true org.apache.directory.server apacheds-server-jndi - 1.0.2 + 1.5.5 compile true + org.slf4j slf4j-log4j12 diff --git a/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java b/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java index 525bb88b13..9b0baddd74 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java +++ b/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java @@ -1,31 +1,26 @@ package org.springframework.security.ldap.server; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.DisposableBean; +import java.io.File; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.directory.server.core.DefaultDirectoryService; +import org.apache.directory.server.core.entry.ServerEntry; +import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition; +import org.apache.directory.server.ldap.LdapServer; +import org.apache.directory.server.protocol.shared.store.LdifFileLoader; +import org.apache.directory.server.protocol.shared.transport.TcpTransport; +import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException; +import org.apache.directory.shared.ldap.name.LdapDN; import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContextAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.Lifecycle; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.ldap.core.ContextSource; import org.springframework.util.Assert; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.directory.server.configuration.MutableServerStartupConfiguration; -import org.apache.directory.server.jndi.ServerContextFactory; -import org.apache.directory.server.protocol.shared.store.LdifFileLoader; -import org.apache.directory.server.core.configuration.ShutdownConfiguration; -import org.apache.directory.server.core.DirectoryService; - -import javax.naming.Context; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import javax.naming.directory.DirContext; -import javax.naming.directory.InitialDirContext; -import java.util.Properties; -import java.io.File; -import java.io.IOException; /** * Provides lifecycle services for the embedded apacheDS server defined by the supplied configuration. @@ -49,18 +44,30 @@ import java.io.IOException; public class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle, ApplicationContextAware { private Log logger = LogFactory.getLog(getClass()); - private MutableServerStartupConfiguration configuration; + DefaultDirectoryService service; + LdapServer server; + private ApplicationContext ctxt; private File workingDir; - private ContextSource contextSource; private boolean running; private String ldifResources; + private JdbmPartition partition; + private String root; + private int port = 53389; - public ApacheDSContainer(MutableServerStartupConfiguration config, ContextSource contextSource, String ldifs) { - this.configuration = config; - this.contextSource = contextSource; + public ApacheDSContainer(String root, String ldifs) throws Exception { this.ldifResources = ldifs; + service = new DefaultDirectoryService(); + partition = new JdbmPartition(); + partition.setId("rootPartition"); + partition.setSuffix(root); + this.root = root; + service.addPartition(partition); + service.setExitVmOnShutdown(false); + service.setShutdownHookEnabled(false); + service.getChangeLog().setEnabled(false); + service.setDenormalizeOpAttrsEnabled(true); } public void afterPropertiesSet() throws Exception { @@ -73,6 +80,10 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life setWorkingDirectory(new File(apacheWorkDir)); } + + server = new LdapServer(); + server.setDirectoryService(service); + server.setTransports(new TcpTransport(port)); start(); } @@ -84,20 +95,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life ctxt = applicationContext; } - private boolean deleteDir(File dir) { - if (dir.isDirectory()) { - String[] children = dir.list(); - for (int i=0; i < children.length; i++) { - boolean success = deleteDir(new File(dir, children[i])); - if (!success) { - return false; - } - } - } - - return dir.delete(); - } - public void setWorkingDirectory(File workingDir) { Assert.notNull(workingDir); @@ -112,35 +109,52 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life this.workingDir = workingDir; - configuration.setWorkingDirectory(workingDir); + service.setWorkingDirectory(workingDir); + } + + public void setPort(int port) { + this.port = port; + } + + public DefaultDirectoryService getService() { + return service; } - @SuppressWarnings("unchecked") public void start() { if (isRunning()) { return; } - DirectoryService ds = DirectoryService.getInstance(configuration.getInstanceId()); - - if (ds.isStarted()) { - throw new IllegalStateException("A DirectoryService with Id '" + configuration.getInstanceId() + "' is already running."); + if (service.isStarted()) { + throw new IllegalStateException("DirectoryService is already running."); } - logger.info("Starting directory server with Id '" + configuration.getInstanceId() + "'"); - Properties env = new Properties(); - - env.setProperty(Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName()); - env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); - env.setProperty(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system"); - env.setProperty(Context.SECURITY_CREDENTIALS, "secret"); - env.putAll(configuration.toJndiEnvironment()); + logger.info("Starting directory server..."); + try { + service.startup(); + server.start(); + } catch (Exception e) { + logger.error("Server startup failed ", e); + return; + } try { - new InitialDirContext(env); - } catch (NamingException e) { - logger.error("Failed to start directory service", e); - return; + service.getAdminSession().lookup(partition.getSuffixDn()); + } + catch (LdapNameNotFoundException e) { + try { + LdapDN dn = new LdapDN(root); + Assert.isTrue(root.startsWith("dc=")); + String dc = root.substring(3,root.indexOf(',')); + ServerEntry entry = service.newEntry(dn); + entry.add("objectClass", "top", "domain", "extensibleObject"); + entry.add("dc",dc); + service.getAdminSession().add( entry ); + } catch (Exception e1) { + logger.error("Failed to create dc entry", e1); + } + } catch (Exception e) { + logger.error("Lookup failed", e); } running = true; @@ -152,7 +166,29 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life } } - private void importLdifs() throws IOException, NamingException { + public void stop() { + if (!isRunning()) { + return; + } + + logger.info("Shutting down directory server ..."); + try { + server.stop(); + service.shutdown(); + } catch (Exception e) { + logger.error("Shutdown failed", e); + return; + } + + running = false; + + if (workingDir.exists()) { + logger.info("Deleting working directory " + workingDir.getAbsolutePath()); + deleteDir(workingDir); + } + } + + private void importLdifs() throws Exception { // Import any ldif files Resource[] ldifs; @@ -166,51 +202,28 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life // Note that we can't just import using the ServerContext returned // from starting Apace DS, apparently because of the long-running issue DIRSERVER-169. // We need a standard context. - DirContext dirContext = contextSource.getReadWriteContext(); + //DirContext dirContext = contextSource.getReadWriteContext(); if(ldifs != null && ldifs.length > 0) { - try { - String ldifFile = ldifs[0].getFile().getAbsolutePath(); - logger.info("Loading LDIF file: " + ldifFile); - LdifFileLoader loader = new LdifFileLoader(dirContext, ldifFile); - loader.execute(); - } finally { - dirContext.close(); + String ldifFile = ldifs[0].getFile().getAbsolutePath(); + logger.info("Loading LDIF file: " + ldifFile); + LdifFileLoader loader = new LdifFileLoader(service.getAdminSession(), ldifFile); + loader.execute(); + } + } + + private boolean deleteDir(File dir) { + if (dir.isDirectory()) { + String[] children = dir.list(); + for (int i=0; i < children.length; i++) { + boolean success = deleteDir(new File(dir, children[i])); + if (!success) { + return false; + } } } - } - - @SuppressWarnings("unchecked") - public void stop() { - if (!isRunning()) { - return; - } - - Properties env = new Properties(); - env.setProperty(Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName()); - env.setProperty(Context.SECURITY_AUTHENTICATION, "simple"); - env.setProperty(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system"); - env.setProperty(Context.SECURITY_CREDENTIALS, "secret"); - - ShutdownConfiguration shutdown = new ShutdownConfiguration(configuration.getInstanceId()); - env.putAll(shutdown.toJndiEnvironment()); - - logger.info("Shutting down directory server with Id '" + configuration.getInstanceId() + "'"); - - try { - new InitialContext(env); - } catch (NamingException e) { - logger.error("Failed to shutdown directory server", e); - return; - } - - running = false; - - if (workingDir.exists()) { - logger.info("Deleting working directory " + workingDir.getAbsolutePath()); - deleteDir(workingDir); - } + return dir.delete(); } public boolean isRunning() { diff --git a/ldap/src/test/java/org/springframework/security/ldap/AbstractLdapIntegrationTests.java b/ldap/src/test/java/org/springframework/security/ldap/AbstractLdapIntegrationTests.java index e9f6ce4cee..b22dda91c3 100644 --- a/ldap/src/test/java/org/springframework/security/ldap/AbstractLdapIntegrationTests.java +++ b/ldap/src/test/java/org/springframework/security/ldap/AbstractLdapIntegrationTests.java @@ -14,24 +14,14 @@ */ package org.springframework.security.ldap; -import java.util.HashSet; -import java.util.Set; - import javax.naming.Binding; import javax.naming.ContextNotEmptyException; import javax.naming.Name; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; -import javax.naming.directory.Attribute; -import javax.naming.directory.Attributes; -import javax.naming.directory.BasicAttribute; -import javax.naming.directory.BasicAttributes; import javax.naming.directory.DirContext; -import org.apache.directory.server.configuration.MutableServerStartupConfiguration; -import org.apache.directory.server.core.DirectoryService; -import org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration; import org.apache.directory.server.protocol.shared.store.LdifFileLoader; import org.junit.After; import org.junit.AfterClass; @@ -56,35 +46,11 @@ public abstract class AbstractLdapIntegrationTests { protected AbstractLdapIntegrationTests() { } - @SuppressWarnings("unchecked") @BeforeClass public static void startServer() throws Exception { - shutdownRunningServers(); - MutableBTreePartitionConfiguration partition = new MutableBTreePartitionConfiguration(); - partition.setName("springsecurity"); - - Attributes rootAttributes = new BasicAttributes("dc", "springsecurity"); - Attribute a = new BasicAttribute("objectClass"); - a.add("top"); - a.add("domain"); - a.add("extensibleObject"); - rootAttributes.put(a); - - partition.setContextEntry(rootAttributes); - partition.setSuffix("dc=springframework,dc=org"); - - Set partitions = new HashSet(); - partitions.add(partition); - - MutableServerStartupConfiguration cfg = new MutableServerStartupConfiguration(); - cfg.setLdapPort(53389); - cfg.setShutdownHookEnabled(false); - cfg.setExitVmOnShutdown(false); - cfg.setContextPartitionConfigurations(partitions); - contextSource = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:53389/dc=springframework,dc=org"); ((DefaultSpringSecurityContextSource)contextSource).afterPropertiesSet(); - server = new ApacheDSContainer(cfg, contextSource, "classpath:test-server.ldif"); + server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); server.afterPropertiesSet(); } @@ -93,20 +59,8 @@ public abstract class AbstractLdapIntegrationTests { if (server != null) { server.stop(); } - shutdownRunningServers(); } - private static void shutdownRunningServers() throws NamingException { - DirectoryService ds = DirectoryService.getInstance(); - - if (ds.isStarted()) { - System.out.println("WARNING: Discovered running DirectoryService with configuration: " + ds.getConfiguration().getStartupConfiguration().toString()); - System.out.println("Shutting it down..."); - ds.shutdown(); - } - } - - @Before public void onSetUp() throws Exception { } @@ -127,7 +81,7 @@ public abstract class AbstractLdapIntegrationTests { try { clearSubContexts(ctx, startingPoint); - LdifFileLoader loader = new LdifFileLoader(ctx, ldifs.getFile().getAbsolutePath()); + LdifFileLoader loader = new LdifFileLoader(server.getService().getAdminSession(), ldifs.getFile().getAbsolutePath()); loader.execute(); } finally { ctx.close(); diff --git a/ldap/src/test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java b/ldap/src/test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java new file mode 100644 index 0000000000..f849b30702 --- /dev/null +++ b/ldap/src/test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java @@ -0,0 +1,27 @@ +package org.springframework.security.ldap.server; + +import org.apache.directory.shared.ldap.name.LdapDN; +import org.junit.Test; + +/** + * Useful for debugging the container by itself. + * + * @author Luke Taylor + * @version $Id$ + * @since 3.0 + */ +public class ApacheDSContainerTests { + + @Test + public void successfulStartupAndShutdown() throws Exception { + LdapDN people = new LdapDN("ou=people,dc=springframework,dc=org"); + people.toString(); + +// ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", "classpath:test-server.ldif"); +// server.afterPropertiesSet(); +// +// server.getService().getAdminSession().lookup(people); +// +// server.stop(); + } +} diff --git a/ldap/src/test/resources/log4j.properties b/ldap/src/test/resources/log4j.properties index 63f39ae846..9900ef07b4 100644 --- a/ldap/src/test/resources/log4j.properties +++ b/ldap/src/test/resources/log4j.properties @@ -11,4 +11,4 @@ log4j.appender.stdout.layout.ConversionPattern=%p %c{1} - %m%n log4j.logger.org.springframework.security=DEBUG log4j.logger.org.springframework.ldap=DEBUG -log4j.logger.org.apache.directory=ERROR +log4j.logger.org.apache.directory=INFO diff --git a/ldap/template.mf b/ldap/template.mf index 7bca39354e..834b107695 100644 --- a/ldap/template.mf +++ b/ldap/template.mf @@ -9,6 +9,7 @@ Ignored-Existing-Headers: Import-Template: org.apache.commons.logging.*;version="[1.0.4, 2.0.0)", org.apache.directory.server.*;version="[1.0.2, 1.5)";resolution:=optional, + org.apache.directory.shared.ldap.*;version="[1.5.5,1.6)";resolution:=optional, org.springframework.ldap.*;version="[1.3.0,1.4.0)", org.springframework.security.core.*;version="[${version}, 3.1.0)", org.springframework.security.authentication.*;version="[${version}, 3.1.0)",