/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.security.ldap;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import javax.naming.Binding;
import javax.naming.ContextNotEmptyException;
import javax.naming.Name;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import org.apache.commons.io.IOUtils;
import org.apache.directory.server.core.DefaultDirectoryService;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapAttributes;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.ldif.parser.LdifParser;
/**
* copied and modified from org.springframework.ldap.test.LdapTestUtils
* to allow anonymous access (there was no alternative way)
*
* @author "Mauro Bartolomeoli - mauro.bartolomeoli@geo-solutions.it"
* @author Mattias Hellborg Arthursson
* @author Niels Charlier
*
*/
public class LDAPTestUtils {
private static final int LDAP_SERVER_PORT = 10389;
public static final String LDAP_SERVER_URL = "ldap://127.0.0.1:10389";
public static final String LDAP_BASE_PATH = "dc=example,dc=com";
public static final String DEFAULT_PRINCIPAL = "uid=admin,ou=system";
public static final String DEFAULT_PASSWORD = "secret";
private static EmbeddedLdapServer embeddedServer;
/**
* Start an embedded Apache Directory Server. Only one embedded server will be permitted in the same JVM.
*
* @param port the port on which the server will be listening.
* @param defaultPartitionSuffix The default base suffix that will be used
* for the LDAP server.
* @param defaultPartitionName The name to use in the directory server
* configuration for the default base suffix.
*
* @throws IllegalStateException if an embedded server is already started.
* @since 1.3.2
*/
public static void startEmbeddedServer(int port, String defaultPartitionSuffix, String defaultPartitionName, boolean allowAnonymousAccess) {
if(embeddedServer != null) {
throw new IllegalStateException("An embedded server is already started");
}
try {
embeddedServer = EmbeddedLdapServer.newEmbeddedServer(defaultPartitionName, defaultPartitionSuffix, port,
allowAnonymousAccess);
} catch (Exception e) {
throw new RuntimeException("Failed to start embedded server");
}
}
/**
* Shuts down the embedded server, if there is one. If no server was previously started in this JVM
* this is silently ignored.
*
* @since 1.3.2
*/
public static void shutdownEmbeddedServer() throws Exception {
if(embeddedServer != null) {
embeddedServer.shutdown();
embeddedServer = null;
}
}
/**
* Initializes an in-memory LDAP server to use for testing.
*
* @param allowAnonymous
* anonymous access is allowed or not
*/
public static boolean initLdapServer(boolean allowAnonymous, String ldapServerUrl, String basePath) throws Exception {
return initLdapServer(allowAnonymous, ldapServerUrl, basePath, "data.ldif");
}
/**
* Initializes an in-memory LDAP server to use for testing.
*
* @param allowAnonymous
* anonymous access is allowed or not
*/
public static boolean initLdapServer(boolean allowAnonymous, String ldapServerUrl, String basePath, String ldifPath) throws Exception {
try {
if (!portIsBusy("127.0.0.1", LDAP_SERVER_PORT)) {
startEmbeddedServer(LDAP_SERVER_PORT, basePath, "test", allowAnonymous);
// Bind to the directory
LdapContextSource contextSource = new LdapContextSource();
contextSource.setUrl(ldapServerUrl);
contextSource.setUserDn(DEFAULT_PRINCIPAL);
contextSource.setPassword(DEFAULT_PASSWORD);
contextSource.setPooled(false);
contextSource.afterPropertiesSet();
// Create the Sprint LDAP template
LdapTemplate template = new LdapTemplate(contextSource);
// Clear out any old data - and load the test data
cleanAndSetup(template.getContextSource(),
new DistinguishedName("dc=example,dc=com"),
new ClassPathResource(ldifPath));
return true;
}
return false;
} catch (Exception ee) {
return false;
}
}
/**
* Checks if a network host / port is already occupied.
*
* @param host
* @param port
*
*/
private static boolean portIsBusy(String host, int port) {
ServerSocket ss = null;
DatagramSocket ds = null;
try {
ss = new ServerSocket(port);
ss.setReuseAddress(true);
ds = new DatagramSocket(port);
ds.setReuseAddress(true);
return false;
} catch (IOException e) {
} finally {
if (ds != null) {
ds.close();
}
if (ss != null) {
try {
ss.close();
} catch (IOException e) {
/* should not be thrown */
}
}
}
return true;
}
/**
* Clear the directory sub-tree starting with the node represented by the
* supplied distinguished name.
*
* @param contextSource the ContextSource to use for getting a DirContext.
* @param name the distinguished name of the root node.
* @throws NamingException if anything goes wrong removing the sub-tree.
*/
public static void clearSubContexts(ContextSource contextSource, Name name) throws NamingException {
DirContext ctx = null;
try {
ctx = contextSource.getReadWriteContext();
clearSubContexts(ctx, name);
} finally {
try {
ctx.close();
} catch (Exception e) {
// Never mind this
}
}
}
/**
* Clear the directory sub-tree starting with the node represented by the
* supplied distinguished name.
*
* @param ctx The DirContext to use for cleaning the tree.
* @param name the distinguished name of the root node.
* @throws NamingException if anything goes wrong removing the sub-tree.
*/
public static void clearSubContexts(DirContext ctx, Name name) throws NamingException {
NamingEnumeration enumeration = null;
try {
enumeration = ctx.listBindings(name);
while (enumeration.hasMore()) {
Binding element = (Binding) enumeration.next();
DistinguishedName childName = new DistinguishedName(element.getName());
childName.prepend((DistinguishedName) name);
try {
ctx.destroySubcontext(childName);
} catch (ContextNotEmptyException e) {
clearSubContexts(ctx, childName);
ctx.destroySubcontext(childName);
}
}
} catch (NamingException e) {
e.printStackTrace();
} finally {
try {
enumeration.close();
} catch (Exception e) {
// Never mind this
}
}
}
/**
* Load an Ldif file into an LDAP server.
*
* @param contextSource ContextSource to use for getting a DirContext to
* interact with the LDAP server.
* @param ldifFile a Resource representing a valid LDIF file.
* @throws IOException if the Resource cannot be read.
*/
public static void loadLdif(ContextSource contextSource, Resource ldifFile) throws IOException {
DirContext context = contextSource.getReadWriteContext();
try {
loadLdif(context, ldifFile);
} finally {
try {
context.close();
} catch (Exception e) {
// This is not the exception we are interested in.
}
}
}
public static void cleanAndSetup(ContextSource contextSource, DistinguishedName rootNode, Resource ldifFile)
throws NamingException, IOException {
clearSubContexts(contextSource, rootNode);
loadLdif(contextSource, ldifFile);
}
private static void loadLdif(DirContext context, Resource ldifFile) throws IOException {
try {
DistinguishedName baseDn = (DistinguishedName)
context.getEnvironment().get(DefaultDirObjectFactory.JNDI_ENV_BASE_PATH_KEY);
LdifParser parser = new LdifParser(ldifFile);
parser.open();
while (parser.hasMoreRecords()) {
LdapAttributes record = parser.getRecord();
DistinguishedName dn = record.getDN();
if(baseDn != null) {
dn.removeFirst(baseDn);
}
context.bind(dn, null, record);
}
} catch (NamingException e) {
throw new RuntimeException("Failed to populate LDIF", e);
}
}
public static void loadLdif(DefaultDirectoryService directoryService, Resource ldifFile) throws IOException {
File tempFile = File.createTempFile("spring_ldap_test", ".ldif");
try {
InputStream inputStream = ldifFile.getInputStream();
IOUtils.copy(inputStream, new FileOutputStream(tempFile));
LdifFileLoader fileLoader = new LdifFileLoader(directoryService.getSession(), tempFile.getAbsolutePath());
fileLoader.execute();
} finally {
try {
tempFile.delete();
} catch (Exception e) {
// Ignore this
}
}
}
}