From a9edb3548b0b5794eefc4a02fb9d274ed70588a8 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 21 Mar 2013 18:04:28 +0000
Subject: [PATCH] Partial fix for OPENDJ-694: Implement HTTP BASIC authentication

---
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplate.java                                    |   83 +++++++---
 opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json                                                |  281 +++++++++++++++++++++++-----------
 opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java                                          |   12 +
 opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplateTest.java                                |   12 +
 opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java |   55 +-----
 5 files changed, 270 insertions(+), 173 deletions(-)

diff --git a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java
index ba36044..011afac 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/java/org/forgerock/opendj/rest2ldap/servlet/Rest2LDAPConnectionFactoryProvider.java
@@ -21,6 +21,7 @@
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 
+import org.codehaus.jackson.JsonParser;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.forgerock.json.fluent.JsonValue;
 import org.forgerock.json.resource.CollectionResourceProvider;
@@ -36,54 +37,14 @@
  */
 public final class Rest2LDAPConnectionFactoryProvider {
     private static final String INIT_PARAM_CONFIG_FILE = "config-file";
-    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+    private static final ObjectMapper JSON_MAPPER = new ObjectMapper().configure(
+            JsonParser.Feature.ALLOW_COMMENTS, true);
 
     /**
      * Returns a JSON resource connection factory configured using the
      * configuration file named in the {@code config-file} Servlet
-     * initialization parameter. The configuration file should have the
-     * following JSON structure excluding the C-like comments:
-     *
-     * <pre>
-     * {
-     *     // LDAP connection factory configurations.
-     *     "ldapConnectionFactories" : {
-     *         "default" : {
-     *             // See Rest2LDAP.configureConnectionFactory(JsonValue, String)
-     *         },
-     *         "root" : {
-     *             ...
-     *         }
-     *     },
-     *
-     *     // This is optional.
-     *     "authorization" : {
-     *         // The LDAP connection factory which should be used for LDAP operations, or
-     *         // re-use cached connection from authentication filter if not present.
-     *         "ldapConnectionFactory" : "root",
-     *
-     *         // The optional authorization ID template to use if proxied authorization is
-     *         // to be performed.
-     *         "proxyAuthzIdTemplate"  : "dn:uid={uid},ou=people,dc=example,dc=com"
-     *     },
-     *
-     *     // The LDAP mappings
-     *     "mappings" : {
-     *         "/users" : {
-     *             // The LDAP mapping for /users - Rest2LDAP.Builder.configureMapping(JsonValue).
-     *             "baseDN" : "ou=people,dc=example,dc=com",
-     *
-     *             ...
-     *         },
-     *         "/groups" : {
-     *             // The LDAP mapping for /groups - Rest2LDAP.Builder.configureMapping(JsonValue).
-     *             "baseDN" : "ou=groups,dc=example,dc=com",
-     *
-     *             ...
-     *         }
-     *     }
-     * }
-     * </pre>
+     * initialization parameter. See the sample configuration file for a
+     * detailed description of its content.
      *
      * @param config
      *            The Servlet configuration.
@@ -117,9 +78,9 @@
 
             // Parse the authorization configuration.
             final String proxyAuthzTemplate =
-                    configuration.get("authorization").get("proxyAuthzIdTemplate").asString();
+                    configuration.get("servlet").get("proxyAuthzIdTemplate").asString();
             final String ldapFactoryName =
-                    configuration.get("authorization").get("ldapConnectionFactory").asString();
+                    configuration.get("servlet").get("ldapConnectionFactory").asString();
             final org.forgerock.opendj.ldap.ConnectionFactory ldapFactory;
             if (ldapFactoryName != null) {
                 ldapFactory =
@@ -131,7 +92,7 @@
 
             // Create the router.
             final Router router = new Router();
-            final JsonValue mappings = configuration.get("mappings").required();
+            final JsonValue mappings = configuration.get("servlet").get("mappings").required();
             for (final String mappingUrl : mappings.keys()) {
                 final JsonValue mapping = mappings.get(mappingUrl);
                 final CollectionResourceProvider provider =
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
index cae4009..fe6c9d4 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
+++ b/opendj-sdk/opendj3/opendj-rest2ldap-servlet/src/main/webapp/opendj-rest2ldap-servlet.json
@@ -1,17 +1,33 @@
 {
+    // The array of connection factories which will be used by the Rest2LDAP
+    // Servlet and authentication filter.
     "ldapConnectionFactories" : {
+        // Unauthenticated connections used for performing bind requests.
         "default" : {
-            "primaryLDAPServers" : [
+            "connectionPoolSize"       : 10,
+            "heartBeatIntervalSeconds" : 30,
+            
+            // The preferred load-balancing pool.
+            "primaryLDAPServers"       : [
                 {
                     "hostname" : "localhost",
                     "port"     : 1389
                 }
             ],
-            "connectionPoolSize"       : 10,
-            "heartBeatIntervalSeconds" : 30
+            // The fail-over load-balancing pool (optional).
+            "secondaryLDAPServers"     : [
+                // 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" : {
                 "simple" : {
                     "bindDN"       : "cn=directory manager",
@@ -20,104 +36,179 @@
             }
         }
     },
-
-    "authorization" : {
-        "ldapConnectionFactory" : "root"
+    
+    // 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
+    // following principals:
+    //
+    // "dn" - the DN of the user if known (may not be the case for sasl-plain)
+    // "id" - the username used for authentication.
+    "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, 
+        "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:
+        //
+        // "simple"        - the username is an LDAP DN
+        // "sasl-plain"    - the username is an authzid which will be
+        //                   substituted into the "saslAuthzIdTemplate" using
+        //                   %s substitution
+        // "search+simple" - the user's DN will be resolved by performing an
+        //                   LDAP search using a filter constructed by
+        //                   substituting the username into the
+        //                   "searchFilterTemplate" using %s substitution.
+        "method" : "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.
+        "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.
+        "searchBaseDN"         : "ou=people,dc=example,dc=com",
+        "searchScope"          : "sub", // Or "one".
+        "searchFilterTemplate" : "(&(objectClass=inetOrgPerson)(uid=%s))"
+        
+        // TODO: support for HTTP sessions?
     },
 
-    "mappings" : {
-        "/users" : {
-            "baseDN" : "ou=people,dc=example,dc=com",
-            "readOnUpdatePolicy" : "controls",
-            "additionalLDAPAttributes" : [
-                {
-                    "type" : "objectClass",
-                    "values" : [
-                        "top",
-                        "person",
-                        "organizationalPerson",
-                        "inetOrgPerson"
-                    ]
+    // The Rest2LDAP Servlet configuration.
+    "servlet" : {
+        // The connection factory which will be used for performing LDAP
+        // operations. Pre-authenticated connections passed through from the
+        // authentication filter see "reuseAuthenticatedConnection") will be
+        // used in preference to this factory. Specifically, a connection
+        // factory does not need to be configured if a connection will always
+        // be passed on from the filter, which may not always be the case
+        // if the filter is configured to use HTTP sessions.
+        "ldapConnectionFactory" : "root",
+        
+        // The AuthzID template which will be used for proxied authorization. If
+        // no template is specified then proxied authorization will be disabled.
+        // 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" : {
+                "baseDN" : "ou=people,dc=example,dc=com",
+                "readOnUpdatePolicy" : "controls",
+                "additionalLDAPAttributes" : [
+                    {
+                        "type" : "objectClass",
+                        "values" : [
+                            "top",
+                            "person",
+                            "organizationalPerson",
+                            "inetOrgPerson"
+                        ]
+                    }
+                ],
+                "namingStrategy" : {
+                    "strategy" : "clientDNNaming",
+                    "dnAttribute" : "uid"
+                },
+                "etagAttribute" : "etag",
+                "attributes" : {
+                    "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
+                    "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
+                    "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
+                    "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
+                    "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
+                    "name"        : { "object"   : {
+                        "givenName"  : { "simple" : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
+                        "familyName" : { "simple" : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } }
+                    } },
+                    "manager"     : { "reference" : {
+                        "ldapAttribute" : "manager",
+                        "baseDN"        : "ou=people,dc=example,dc=com",
+                        "primaryKey"    : "uid",
+                        "mapper"         : { "object" : {
+                            "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
+                            "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
+                        } }
+                    } },
+                    "groups"     : { "reference" : {
+                        "ldapAttribute" : "isMemberOf",
+                        "baseDN"        : "ou=groups,dc=example,dc=com",
+                        "writability"   : "readOnly",
+                        "primaryKey"    : "cn",
+                        "mapper"        : { "object" : {
+                            "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
+                        } }
+                    } },
+                    "contactInformation" : { "object" : {
+                        "telephoneNumber" : { "simple" : { "ldapAttribute" : "telephoneNumber", "isSingleValued" : true } },
+                        "emailAddress"    : { "simple" : { "ldapAttribute" : "mail", "isSingleValued" : true } }
+                    } },
+                    "meta"        : { "object" : {
+                        "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
+                        "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
+                    } }
                 }
-            ],
-            "namingStrategy" : {
-                "strategy" : "clientDNNaming",
-                "dnAttribute" : "uid"
             },
-            "etagAttribute" : "etag",
-            "attributes" : {
-                "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
-                "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
-                "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
-                "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
-                "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
-                "name"        : { "object"   : {
-                    "givenName"  : { "simple" : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
-                    "familyName" : { "simple" : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } }
-                } },
-                "manager"     : { "reference" : {
-                    "ldapAttribute" : "manager",
-                    "baseDN"        : "ou=people,dc=example,dc=com",
-                    "primaryKey"    : "uid",
-                    "mapper"         : { "object" : {
-                        "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
-                        "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
+            "/groups" : {
+                "baseDN" : "ou=groups,dc=example,dc=com",
+                "readOnUpdatePolicy" : "controls",
+                "additionalLDAPAttributes" : [
+                    {
+                        "type" : "objectClass",
+                        "values" : [
+                            "top",
+                            "groupOfUniqueNames"
+                        ]
+                    }
+                ],
+                "namingStrategy" : {
+                    "strategy" : "clientDNNaming",
+                    "dnAttribute" : "cn"
+                },
+                "etagAttribute" : "etag",
+                "attributes" : {
+                    "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
+                    "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
+                    "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
+                    "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "readOnly" } },
+                    "members"    : { "reference" : {
+                        "ldapAttribute" : "uniqueMember",
+                        "baseDN"        : "dc=example,dc=com",
+                        "primaryKey"    : "uid",
+                        "mapper"        : { "object" : {
+                            "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
+                            "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
+                        } }
+                    } },
+                    "meta"        : { "object" : {
+                        "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
+                        "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
                     } }
-                } },
-                "groups"     : { "reference" : {
-                    "ldapAttribute" : "isMemberOf",
-                    "baseDN"        : "ou=groups,dc=example,dc=com",
-                    "writability"   : "readOnly",
-                    "primaryKey"    : "cn",
-                    "mapper"        : { "object" : {
-                        "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
-                    } }
-                } },
-                "contactInformation" : { "object" : {
-                    "telephoneNumber" : { "simple" : { "ldapAttribute" : "telephoneNumber", "isSingleValued" : true } },
-                    "emailAddress"    : { "simple" : { "ldapAttribute" : "mail", "isSingleValued" : true } }
-                } },
-                "meta"        : { "object" : {
-                    "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
-                    "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
-                } }
-            }
-        },
-        "/groups" : {
-            "baseDN" : "ou=groups,dc=example,dc=com",
-            "readOnUpdatePolicy" : "controls",
-            "additionalLDAPAttributes" : [
-                {
-                    "type" : "objectClass",
-                    "values" : [
-                        "top",
-                        "groupOfUniqueNames"
-                    ]
                 }
-            ],
-            "namingStrategy" : {
-                "strategy" : "clientDNNaming",
-                "dnAttribute" : "cn"
-            },
-            "etagAttribute" : "etag",
-            "attributes" : {
-                "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
-                "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
-                "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
-                "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "readOnly" } },
-                "members"    : { "reference" : {
-                    "ldapAttribute" : "uniqueMember",
-                    "baseDN"        : "dc=example,dc=com",
-                    "primaryKey"    : "uid",
-                    "mapper"        : { "object" : {
-                        "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
-                        "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
-                    } }
-                } },
-                "meta"        : { "object" : {
-                    "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
-                    "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
-                } }
             }
         }
     }
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplate.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplate.java
index 07fccaf..50e5472 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplate.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplate.java
@@ -37,11 +37,57 @@
  * <code>u:{uid}@{realm}.example.com</code>.
  */
 final class AuthzIdTemplate {
+    private static interface Impl {
+        String formatAsAuthzId(AuthzIdTemplate t, Object[] templateVariables, Schema schema)
+                throws ResourceException;
+    }
+
+    private static final Impl DN_IMPL = new Impl() {
+
+        @Override
+        public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables,
+                final Schema schema) throws ResourceException {
+            final String authzId = String.format(Locale.ENGLISH, t.formatString, templateVariables);
+            try {
+                // Validate the DN.
+                DN.valueOf(authzId.substring(3), schema);
+            } catch (final IllegalArgumentException e) {
+                throw new ForbiddenException(
+                        "The request could not be authorized because the required security principal "
+                                + " was not a valid LDAP DN");
+            }
+            return authzId;
+        }
+    };
+
+    private static final Pattern DN_PATTERN = Pattern.compile("^dn:\\{[^}]+\\}$");
+
+    private static final Impl DN_TEMPLATE_IMPL = new Impl() {
+
+        @Override
+        public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables,
+                final Schema schema) throws ResourceException {
+            return "dn:" + DN.format(t.dnFormatString, schema, templateVariables).toString();
+        }
+
+    };
+
     private static final Pattern KEY_RE = Pattern.compile("\\{([^}]+)\\}");
 
+    private static final Impl UID_TEMPLATE_IMPL = new Impl() {
+
+        @Override
+        public String formatAsAuthzId(final AuthzIdTemplate t, final Object[] templateVariables,
+                final Schema schema) throws ResourceException {
+            return String.format(Locale.ENGLISH, t.formatString, templateVariables);
+        }
+
+    };
+
     private final String dnFormatString;
     private final String formatString;
     private final List<String> keys = new ArrayList<String>();
+    private final Impl pimpl;
     private final String template;
 
     AuthzIdTemplate(final String template) {
@@ -57,10 +103,16 @@
             keys.add(matcher.group(1));
         }
         matcher.appendTail(buffer);
-
-        this.template = template;
         this.formatString = buffer.toString();
-        this.dnFormatString = template.startsWith("dn:") ? formatString.substring(3) : null;
+        this.template = template;
+
+        if (template.startsWith("dn:")) {
+            this.pimpl = DN_PATTERN.matcher(template).matches() ? DN_IMPL : DN_TEMPLATE_IMPL;
+            this.dnFormatString = formatString.substring(3);
+        } else {
+            this.pimpl = UID_TEMPLATE_IMPL;
+            this.dnFormatString = null;
+        }
     }
 
     @Override
@@ -70,29 +122,8 @@
 
     String formatAsAuthzId(final Map<String, Object> principals, final Schema schema)
             throws ResourceException {
-        if (isDNTemplate()) {
-            final String dn = formatAsDN(principals, schema).toString();
-            final StringBuilder builder = new StringBuilder(dn.length() + 3);
-            builder.append("dn:");
-            builder.append(dn);
-            return builder.toString();
-        } else {
-            final String[] values = getPrincipalsForFormatting(principals);
-            return String.format(Locale.ENGLISH, formatString, (Object[]) values);
-        }
-    }
-
-    DN formatAsDN(final Map<String, Object> principals, final Schema schema)
-            throws ResourceException {
-        if (!isDNTemplate()) {
-            throw new IllegalStateException();
-        }
-        final String[] values = getPrincipalsForFormatting(principals);
-        return DN.format(dnFormatString, schema, (Object[]) values);
-    }
-
-    boolean isDNTemplate() {
-        return dnFormatString != null;
+        final String[] templateVariables = getPrincipalsForFormatting(principals);
+        return pimpl.formatAsAuthzId(this, templateVariables, schema);
     }
 
     private String[] getPrincipalsForFormatting(final Map<String, Object> principals)
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index 11f43c4..b69da29 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -660,10 +660,14 @@
         // Parse secondary data center(s).
         final JsonValue secondaryLDAPServers = configuration.get("secondaryLDAPServers");
         final ConnectionFactory secondary;
-        if (secondaryLDAPServers.isList() && secondaryLDAPServers.size() != 0) {
-            secondary =
-                    parseLDAPServers(secondaryLDAPServers, bindRequest, connectionPoolSize,
-                            heartBeatIntervalSeconds);
+        if (secondaryLDAPServers.isList()) {
+            if (secondaryLDAPServers.size() > 0) {
+                secondary =
+                        parseLDAPServers(secondaryLDAPServers, bindRequest, connectionPoolSize,
+                                heartBeatIntervalSeconds);
+            } else {
+                secondary = null;
+            }
         } else if (!secondaryLDAPServers.isNull()) {
             throw new IllegalArgumentException("Invalid secondaryLDAPServers configuration");
         } else {
diff --git a/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplateTest.java b/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplateTest.java
index 7376fce..b2e8897 100644
--- a/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplateTest.java
+++ b/opendj-sdk/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/AuthzIdTemplateTest.java
@@ -49,6 +49,12 @@
                 map("uid", "test.user", "realm", "test+cn=quoting")
             },
             {
+                // Should not perform DN quoting.
+                "dn:{dn}",
+                "dn:uid=test.user,ou=acme,dc=example,dc=com",
+                map("dn", "uid=test.user,ou=acme,dc=example,dc=com")
+            },
+            {
                 "u:{uid}@{realm}.example.com",
                 "u:test.user@acme.example.com",
                 map("uid", "test.user", "realm", "acme")
@@ -82,6 +88,11 @@
                 map("uid", "test.user")
             },
             {
+                // Malformed DN.
+                "dn:{dn}",
+                map("dn", "uid")
+            },
+            {
                 "u:{uid}@{realm}.example.com",
                 map("uid", "test.user")
             },
@@ -105,7 +116,6 @@
             },
         };
         // @formatter:on
-
     }
 
     @Test(dataProvider = "invalidTemplates", expectedExceptions = IllegalArgumentException.class)

--
Gitblit v1.10.0