mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
05.40.2013 e50b79850923484252c328b421a4100968601407
Fix OPENDJ-1033: The Rest2LDAP servlet does not support SSL

* add support for LDAPS and LDAP+StartTLS
* add support for 3 key managers: trust all, jvm, and file-based (e.g. JKS, PKCS12)
* functionally equivalent to our existing DSML gateway's support.
2 files modified
137 ■■■■ changed files
opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json 65 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java 72 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
@@ -4,9 +4,40 @@
    "ldapConnectionFactories" : {
        // Unauthenticated connections used for performing bind requests.
        "default" : {
            "connectionPoolSize"       : 10,
            // Indicates whether or not LDAP connections should be secured using
            // SSL or StartTLS. Acceptable values are:
            //
            // "none"     - use plain LDAP connections (default)
            // "ssl"      - secure connection using LDAPS
            // "startTLS" - secure connection using LDAP+StartTLS
            //
            "connectionSecurity"       : "none",
            // Specifies the policy for trusting server certificates exchanged
            // during SSL/StartTLS negotiation. This setting and the following
            // trust policy settings will be ignored if there is no connection
            // security. Acceptable values are:
            //
            // "trustAll" - blindly trust all server certificates (default)
            // "jvm"      - only certificates signed by the authorities
            //              associated with the host JVM will be accepted
            // "file"     - use a file-based trust store for validating
            //              certificates. This option requires the following
            //              "fileBasedTrustManager*" settings to be configured.
            //
            "trustManager"             : "trustAll",
            // File based trust manager configuration (see above).
            "fileBasedTrustManagerType"     : "JKS",
            "fileBasedTrustManagerFile"     : "/path/to/truststore",
            "fileBasedTrustManagerPassword" : "password",
            // Re-usable pool of 24 connections per server.
            "connectionPoolSize"       : 24,
            // Check pooled connections are alive every 30 seconds.
            "heartBeatIntervalSeconds" : 30,
            // The preferred load-balancing pool.
            "primaryLDAPServers"       : [
                {
@@ -19,13 +50,13 @@
                // Empty.
            ]
        },
        // Authenticated connections which will be used for searches during
        // authentication and proxied operations (if enabled). This factory
        // will re-use the server "default" configuration.
        "root" : {
            "inheritFrom"    : "default",
            // Defines how authentication should be performed. Only "simple"
            // authentication is supported at the moment.
            "authentication" : {
@@ -36,7 +67,7 @@
            }
        }
    },
    // The Rest2LDAP authentication filter configuration. The filter will be
    // disabled if the configuration is not present. Upon successful
    // authentication the filter will create a security context containing the
@@ -47,21 +78,21 @@
    "authenticationFilter" : {
        // Indicates whether the filter should allow HTTP BASIC authentication.
        "supportHTTPBasicAuthentication" : true,
        // Indicates whether the filter should allow alternative authentication
        // and, if so, which HTTP headers it should obtain the username and
        // password from.
        "supportAltAuthentication"        : true,
        "supportAltAuthentication"        : true,
        "altAuthenticationUsernameHeader" : "X-OpenIDM-Username",
        "altAuthenticationPasswordHeader" : "X-OpenIDM-Password",
        // Indicates whether the authenticated LDAP connection should be cached
        // for use within the Rest2LDAP Servlet for subsequent LDAP operations.
        // If this is set to true then the Servlet will not need its own LDAP
        // connection factory and will also not need to use proxied
        // authorization.
        "reuseAuthenticatedConnection" : true,
        // Specifies how LDAP authentications should be performed. The method
        // must be one of:
        //
@@ -74,29 +105,29 @@
        //                   substituting the username into the
        //                   "searchFilterTemplate" using %s substitution.
        "method" : "search-simple",
        // The connection factory which will be exclusively used for
        // authenticating users using LDAP bind operations.
        "bindLDAPConnectionFactory" : "default",
        // The SASL AuthzID template which will be used for "sasl-plain"
        // authentication. The %s format parameters will be substituted with
        // the client-provided username, using DN character escaping for DN
        // AuthzIDs.
        "saslAuthzIdTemplate" : "dn:uid=%s,ou=people,dc=example,dc=com",
        // The connection factory which will be used for performing LDAP
        // searches to locate users when "search-simple" authentication is
        // enabled.
        "searchLDAPConnectionFactory" : "root",
        // The search parameters to use for "search-simple" authentication. The
        // %s filter format parameters will be substituted with the
        // client-provided username, using LDAP filter string character escaping.
        "searchBaseDN"         : "ou=people,dc=example,dc=com",
        "searchScope"          : "sub", // Or "one".
        "searchFilterTemplate" : "(&(objectClass=inetOrgPerson)(uid=%s))"
        // TODO: support for HTTP sessions?
    },
@@ -110,7 +141,7 @@
        // be passed on from the filter, which may not always be the case
        // if the filter is configured to use HTTP sessions.
        "ldapConnectionFactory" : "root",
        // Specifies how LDAP authorization should be performed. The method
        // must be one of:
        //
@@ -125,12 +156,12 @@
        //                 authorization will only be used if there is no
        //                 pre-authenticated connection available.
        "authorizationPolicy" : "proxy",
        // The AuthzID template which will be used for proxied authorization.
        // The template should contain fields which are expected to be found in
        // the security context create during authentication, e.g. "dn" and "id".
        "proxyAuthzIdTemplate" : "dn:{dn}",
        // The REST APIs and their LDAP attribute mappings.
        "mappings" : {
            "/users" : {
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -21,6 +21,8 @@
import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
@@ -52,13 +54,16 @@
import org.forgerock.opendj.ldap.FailoverLoadBalancingAlgorithm;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.LDAPOptions;
import org.forgerock.opendj.ldap.LinkedAttribute;
import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
import org.forgerock.opendj.ldap.RDN;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.RoundRobinLoadBalancingAlgorithm;
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.TimeoutResultException;
import org.forgerock.opendj.ldap.TrustManagers;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -70,6 +75,23 @@
 * collections.
 */
public final class Rest2LDAP {
    /**
     * Indicates whether or not LDAP client connections should use SSL or
     * StartTLS.
     */
    private enum ConnectionSecurity {
        NONE, SSL, STARTTLS
    }
    /**
     * Specifies the mechanism which should be used for trusting certificates
     * presented by the LDAP server.
     */
    private enum TrustManagerType {
        TRUSTALL, JVM, FILE
    }
    /**
     * A builder for incrementally constructing LDAP resource collections.
     */
@@ -965,6 +987,48 @@
            bindRequest = null;
        }
        // Parse SSL/StartTLS parameters.
        final ConnectionSecurity connectionSecurity =
                configuration.get("connectionSecurity").defaultTo(ConnectionSecurity.NONE).asEnum(
                        ConnectionSecurity.class);
        final LDAPOptions options = new LDAPOptions();
        if (connectionSecurity != ConnectionSecurity.NONE) {
            try {
                // Configure SSL.
                final SSLContextBuilder builder = new SSLContextBuilder();
                // Parse trust store configuration.
                final TrustManagerType trustManagerType =
                        configuration.get("trustManager").defaultTo(TrustManagerType.TRUSTALL)
                                .asEnum(TrustManagerType.class);
                switch (trustManagerType) {
                case TRUSTALL:
                    builder.setTrustManager(TrustManagers.trustAll());
                    break;
                case JVM:
                    // Do nothing: JVM trust manager is the default.
                    break;
                case FILE:
                    final String fileName =
                            configuration.get("fileBasedTrustManagerFile").required().asString();
                    final String password =
                            configuration.get("fileBasedTrustManagerPassword").asString();
                    final String type = configuration.get("fileBasedTrustManagerType").asString();
                    builder.setTrustManager(TrustManagers.checkUsingTrustStore(fileName,
                            password != null ? password.toCharArray() : null, type));
                    break;
                }
                options.setSSLContext(builder.getSSLContext());
                options.setUseStartTLS(connectionSecurity == ConnectionSecurity.STARTTLS);
            } catch (GeneralSecurityException e) {
                // Rethrow as unchecked exception.
                throw new IllegalArgumentException(e);
            } catch (IOException e) {
                // Rethrow as unchecked exception.
                throw new IllegalArgumentException(e);
            }
        }
        // Parse primary data center.
        final JsonValue primaryLDAPServers = configuration.get("primaryLDAPServers");
        if (!primaryLDAPServers.isList() || primaryLDAPServers.size() == 0) {
@@ -972,7 +1036,7 @@
        }
        final ConnectionFactory primary =
                parseLDAPServers(primaryLDAPServers, bindRequest, connectionPoolSize,
                        heartBeatIntervalSeconds);
                        heartBeatIntervalSeconds, options);
        // Parse secondary data center(s).
        final JsonValue secondaryLDAPServers = configuration.get("secondaryLDAPServers");
@@ -981,7 +1045,7 @@
            if (secondaryLDAPServers.size() > 0) {
                secondary =
                        parseLDAPServers(secondaryLDAPServers, bindRequest, connectionPoolSize,
                                heartBeatIntervalSeconds);
                                heartBeatIntervalSeconds, options);
            } else {
                secondary = null;
            }
@@ -1029,12 +1093,12 @@
    private static ConnectionFactory parseLDAPServers(final JsonValue config,
            final BindRequest bindRequest, final int connectionPoolSize,
            final int heartBeatIntervalSeconds) {
            final int heartBeatIntervalSeconds, final LDAPOptions options) {
        final List<ConnectionFactory> servers = new ArrayList<ConnectionFactory>(config.size());
        for (final JsonValue server : config) {
            final String host = server.get("hostname").required().asString();
            final int port = server.get("port").required().asInteger();
            ConnectionFactory factory = new LDAPConnectionFactory(host, port);
            ConnectionFactory factory = new LDAPConnectionFactory(host, port, options);
            if (bindRequest != null) {
                factory = Connections.newAuthenticatedConnectionFactory(factory, bindRequest);
            }