package org.springframework.security.ldap.server; import java.io.File; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.directory.server.core.DefaultDirectoryService; import org.apache.directory.server.core.authn.AuthenticationInterceptor; import org.apache.directory.server.core.exception.ExceptionInterceptor; import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory; import org.apache.directory.server.core.factory.DirectoryServiceFactory; import org.apache.directory.server.core.interceptor.Interceptor; import org.apache.directory.server.core.normalization.NormalizationInterceptor; import org.apache.directory.server.core.operational.OperationalAttributeInterceptor; import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition; import org.apache.directory.server.core.referral.ReferralInterceptor; import org.apache.directory.server.core.subtree.SubentryInterceptor; 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.springframework.beans.BeansException; 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.util.Assert; /** * Provides lifecycle services for the embedded apacheDS server defined by the supplied configuration. * Used by {code LdapServerBeanDefinitionParser}. An instance will be stored in the application context for * each embedded server instance. It will start the server when the context is initialized and shut it down when * it is closed. It is intended for temporary embedded use and will not retain changes across start/stop boundaries. The * working directory is deleted on shutdown. * * <p> * If used repeatedly in a single JVM process with the same configuration (for example, when * repeatedly loading an application context during testing), it's important that the * application context is closed to allow the bean to be disposed of and the server shutdown * prior to attempting to start it again. * <p> * This class is intended for testing and internal security namespace use and is not considered part of * framework public API. * * @author Luke Taylor */ class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle, ApplicationContextAware { private Logger logger = LoggerFactory.getLogger(getClass()); DirectoryServiceFactory factory; DefaultDirectoryService service; LdapServer server; private ApplicationContext ctxt; private File workingDir; private boolean running; private String ldifResources; private JdbmPartition partition; private String root; private int port = 53389; public ApacheDSContainer(String root, String ldifs) throws Exception { this.ldifResources = ldifs; System.setProperty("workingDirectory", "server-work"); // HACK factory = DefaultDirectoryServiceFactory.DEFAULT; service = (DefaultDirectoryService) factory.getDirectoryService(); // service = new DefaultDirectoryService(); List<Interceptor> list = new ArrayList<Interceptor>(); list.add( new NormalizationInterceptor() ); list.add( new AuthenticationInterceptor() ); list.add( new ReferralInterceptor() ); // list.add( new AciAuthorizationInterceptor() ); // list.add( new DefaultAuthorizationInterceptor() ); list.add( new ExceptionInterceptor() ); // list.add( new ChangeLogInterceptor() ); list.add( new OperationalAttributeInterceptor() ); // list.add( new SchemaInterceptor() ); list.add( new SubentryInterceptor() ); // list.add( new CollectiveAttributeInterceptor() ); // list.add( new EventInterceptor() ); // list.add( new TriggerInterceptor() ); // list.add( new JournalInterceptor() ); service.setInterceptors( list ); partition = new JdbmPartition(); partition.setId("rootPartition"); partition.setSuffix(root); partition.setPartitionDir(new File(service.getWorkingDirectory(), "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 { // if (workingDir == null) { // String apacheWorkDir = System.getProperty("apacheDSWorkDir"); // // if (apacheWorkDir == null) { // apacheWorkDir = System.getProperty("java.io.tmpdir") + File.separator + "apacheds-spring-security"; // } // // setWorkingDirectory(new File(apacheWorkDir)); // } server = new LdapServer(); server.setDirectoryService(service); server.setTransports(new TcpTransport(port)); start(); } public void destroy() throws Exception { stop(); } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ctxt = applicationContext; } public void setWorkingDirectory(File workingDir) { Assert.notNull(workingDir); logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath()); if (workingDir.exists()) { throw new IllegalArgumentException("The specified working directory '" + workingDir.getAbsolutePath() + "' already exists. Another directory service instance may be using it or it may be from a " + " previous unclean shutdown. Please confirm and delete it or configure a different " + "working directory"); } this.workingDir = workingDir; service.setWorkingDirectory(workingDir); } public void setPort(int port) { this.port = port; } public DefaultDirectoryService getService() { return service; } public void start() { if (isRunning()) { return; } if (service.isStarted()) { throw new IllegalStateException("DirectoryService is already running."); } logger.info("Starting directory server..."); try { factory.init("test"); server.start(); } catch (Exception e) { throw new RuntimeException(e); } try { 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; try { importLdifs(); } catch (Exception e) { throw new RuntimeException(e); } } 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; if (ctxt == null) { // Not running within an app context ldifs = new PathMatchingResourcePatternResolver().getResources(ldifResources); } else { ldifs = ctxt.getResources(ldifResources); } // 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(); if(ldifs != null && ldifs.length > 0) { 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; } } } return dir.delete(); } public boolean isRunning() { return running; } }