com.nimbusds.common.ldap.SampleDirectory Maven / Gradle / Ivy
Show all versions of common Show documentation
package com.nimbusds.common.ldap;
import com.nimbusds.common.servlet.ResourceRetriever;
import com.thetransactioncompany.util.PropertyParseException;
import com.thetransactioncompany.util.PropertyRetriever;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.OperationType;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.ldif.LDIFException;
import com.unboundid.ldif.LDIFReader;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* Sample in-memory LDAP directory server for demonstration and testing
* purposes. Access is limited to read and bind (authenticate) only.
*
* The directory server is configured by a set of "sampleDirectoryServer.*"
* properties which can be overridden with Java system properties, see
* {@link Configuration}.
*
*
The sample directory implements {@code ServletContextListener}. This
* enables its automatic startup and shutdown in a servlet container (Java web
* server), such as Apache Tomcat. When started from a servlet container the
* directory configuration is obtained from a properties file specified by a
* context parameter named {@code sampleDirectoryServer.configurationFile}.
*/
public class SampleDirectory implements ServletContextListener {
/**
* The sample directory server configuration.
*/
public static class Configuration {
/**
* If {@code true} the sample directory server must be
* enabled.
*
*
Property key: sampleDirectoryServer.enable
*/
public final boolean enable;
/**
* The default enable policy.
*/
public static final boolean DEFAULT_ENABLE = false;
/**
* The port number on which the sample directory server
* must accept LDAP client connections.
*
*
Property key: sampleDirectoryServer.port
*/
public final int port;
/**
* The default port number.
*/
public static final int DEFAULT_PORT = 10389;
/**
* Specifies the permitted LDAP operations.
*
*
Property key: sampleDirectoryServer.operations
*/
public final Set operations;
/**
* The default permitted LDAP operations.
*/
public static final Set DEFAULT_OPERATIONS = new HashSet<>(
Arrays.asList(OperationType.BIND,
OperationType.COMPARE,
OperationType.SEARCH,
OperationType.EXTENDED));
/**
* Specifies an alternative schema for the sample directory +
* server, supplied in a single LDIF file. If {@code null} the
* default built-in server schema must be used.
*
* Property key: sampleDirectoryServer.schema
*/
public final String schema;
/**
* The base distinguished name (DN) of the directory information
* tree.
*
*
Property key: sampleDirectoryServer.baseDN
*/
public final String baseDN;
/**
* The initial directory information tree, supplied in a single
* LDIF file. If {@code null} the directory will be left
* empty.
*
*
Property key: sampleDirectoryServer.content
*/
public final String content;
/**
* Creates a new sample directory server configuration from the
* specified properties.
*
* @param props The configuration properties. Must not be
* {@code null}.
*
* @throws PropertyParseException On a missing or invalid
* property.
*/
public Configuration(final Properties props)
throws PropertyParseException {
var pr = new PropertyRetriever(props, true);
enable = pr.getOptBoolean("sampleDirectoryServer.enable", DEFAULT_ENABLE);
if (! enable) {
port = DEFAULT_PORT;
operations = DEFAULT_OPERATIONS;
schema = null;
baseDN = null;
content = null;
return;
}
// We're okay to read rest of config
port = pr.getOptInt("sampleDirectoryServer.port", DEFAULT_PORT);
String s = pr.getOptString("sampleDirectoryServer.operations", null);
if (s != null && ! s.trim().isEmpty()) {
String[] tokens = s.split("[\\s,]+");
Set ops = new HashSet<>();
for (String t: tokens) {
try {
ops.add(OperationType.valueOf(t.toUpperCase()));
} catch (Exception e) {
throw new PropertyParseException("Invalid LDAP operation: " + t,
"sampleDirectoryServer.operations",
s);
}
}
operations = Collections.unmodifiableSet(ops);
} else {
operations = DEFAULT_OPERATIONS;
}
s = pr.getOptString("sampleDirectoryServer.schema", null);
if (s == null || s.isEmpty())
schema = null;
else
schema = s;
baseDN = pr.getString("sampleDirectoryServer.baseDN");
s = pr.getOptString("sampleDirectoryServer.content", null);
if (s == null || s.isEmpty())
content = null;
else
content = s;
}
}
/**
* The sample in-memory directory server.
*/
private InMemoryDirectoryServer ds = null;
/**
* The servlet context.
*/
private ServletContext servletContext;
/**
* The logger.
*/
private final Logger log = LogManager.getLogger("MAIN");
/**
* Starts the sample in-memory directory server.
*
* @param config The sample directory server configuration. Must not
* be {@code null}.
*
* @throws LDAPException If the in-memory directory server couldn't be
* started or its initialisation failed.
* @throws IOException If a schema file was specified and it couldn't
* be read.
* @throws LDIFException If a schema file was specified that is not
* valid LDIF.
*/
public void start(final Configuration config)
throws LDAPException,
IOException,
LDIFException {
if (! config.enable) {
log.info("Sample directory server: disabled");
return;
}
InMemoryListenerConfig listenerConfig =
InMemoryListenerConfig.createLDAPConfig("sample-ds", config.port);
// Get alternative schema, if any
Schema schema = null;
if (config.schema != null) {
InputStream ldifInput;
if (servletContext != null) {
ldifInput = servletContext.getResourceAsStream(config.schema);
} else {
ldifInput = new FileInputStream(config.schema);
}
if (ldifInput == null) {
throw new IOException("Couldn't find schema LDIF file: " + config.schema);
}
LDIFReader ldifReader = new LDIFReader(ldifInput);
schema = new Schema(ldifReader.readEntry());
log.info("Sample directory server: Schema LDIF file: {}", config.schema);
}
InMemoryDirectoryServerConfig dsConfig = new InMemoryDirectoryServerConfig(config.baseDN);
log.info("Sample directory server: Base DN: {}", config.baseDN);
dsConfig.setSchema(schema);
dsConfig.setListenerConfigs(listenerConfig);
// Set the allowed LDAP operations
dsConfig.setAllowedOperationTypes(config.operations);
// Start server
ds = new InMemoryDirectoryServer(dsConfig);
// Populate directory with LDIF, if any
if (config.content != null) {
InputStream ldifInput;
if (servletContext != null) {
ldifInput = servletContext.getResourceAsStream(config.content);
} else {
ldifInput = new FileInputStream(config.content);
}
if (ldifInput == null) {
throw new IOException("Couldn't find directory content LDIF file: " + config.content);
}
ds.importFromLDIF(true, new LDIFReader(ldifInput));
ldifInput.close();
log.info("Sample directory server: Populated from LDIF file {}", config.content);
}
// Start listening on selected port
ds.startListening();
log.info("Sample directory server: Started on port {}", ds.getListenPort());
}
/**
* Stops the sample in-memory directory server (if previously started).
* Information and status messages are logged at INFO level.
*/
public void stop() {
if (ds == null)
return;
// Clean all connections and stop server
ds.shutDown(true);
log.info("Sample directory server: Shut down");
}
/**
* Handler for servlet context startup events. Launches the sample
* in-memory directory server (if enabled per configuration). Exceptions
* are logged at ERROR level, information and status messages at INFO
* level.
*
* The sample directory server configuration is retrieved from a
* properties file which location is specified by a servlet context
* parameter named {@code sampleDirectory.configurationFile}.
*
* @param sce A servlet context event.
*/
@Override
public void contextInitialized(final ServletContextEvent sce) {
servletContext = sce.getServletContext();
// Read configuration
Configuration config;
try {
Properties props = ResourceRetriever.getProperties(servletContext,
"sampleDirectory.configurationFile",
log);
config = new Configuration(props);
} catch (Exception e) {
log.error("Couldn't configure sample directory server: {}", e.getMessage());
return;
}
// Start server
try {
start(config);
} catch (LDAPException e) {
log.error("Couldn't start sample directory server: {}", e.getMessage());
} catch (IOException | LDIFException e) {
log.error("Couldn't read schema file: {}", e.getMessage());
}
}
/**
* Handler for servlet context shutdown events. Stops the sample
* in-memory directory server (if previously started).
*
* @param sce A servlet context event.
*/
@Override
public void contextDestroyed(final ServletContextEvent sce) {
stop();
}
}