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

---
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java |  211 +++++++++++++++++++++++++++++++---------------------
 1 files changed, 124 insertions(+), 87 deletions(-)

diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
index ef84e2f..11f43c4 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -23,8 +23,10 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
@@ -69,6 +71,7 @@
         private ConnectionFactory factory;
         private MVCCStrategy mvccStrategy;
         private NameStrategy nameStrategy;
+        private AuthzIdTemplate proxiedAuthzTemplate;
         private ReadOnUpdatePolicy readOnUpdatePolicy = CONTROLS;
         private AttributeMapper rootMapper;
         private Schema schema = Schema.getDefaultSchema();
@@ -98,30 +101,17 @@
         }
 
         public CollectionResourceProvider build() {
-            ensureNotNull(factory);
             ensureNotNull(baseDN);
             if (rootMapper == null) {
                 throw new IllegalStateException("No mappings provided");
             }
-            return new LDAPCollectionResourceProvider(baseDN, rootMapper, factory, nameStrategy,
-                    mvccStrategy, new Config(readOnUpdatePolicy, schema), additionalLDAPAttributes);
-        }
-
-        /**
-         * Configures the connection factory using the provided JSON
-         * configuration. See
-         * {@link Rest2LDAP#configureConnectionFactory(JsonValue)} for a
-         * detailed specification of the JSON configuration.
-         *
-         * @param configuration
-         *            The JSON configuration.
-         * @return A reference to this builder.
-         * @throws IllegalArgumentException
-         *             If the configuration is invalid.
-         */
-        public Builder configureConnectionFactory(final JsonValue configuration) {
-            connectionFactory(Rest2LDAP.configureConnectionFactory(configuration));
-            return this;
+            if (proxiedAuthzTemplate != null && factory == null) {
+                throw new IllegalStateException(
+                        "No connection factory specified for use with proxied authorization");
+            }
+            return new LDAPCollectionResourceProvider(baseDN, rootMapper, nameStrategy,
+                    mvccStrategy, new Config(factory, readOnUpdatePolicy, proxiedAuthzTemplate,
+                            schema), additionalLDAPAttributes);
         }
 
         /**
@@ -243,7 +233,6 @@
         }
 
         public Builder connectionFactory(final ConnectionFactory factory) {
-            ensureNotNull(factory);
             this.factory = factory;
             return this;
         }
@@ -313,6 +302,11 @@
             return useEtagAttribute(ad(attribute));
         }
 
+        public Builder useProxiedAuthorization(final String template) {
+            this.proxiedAuthzTemplate = template != null ? new AuthzIdTemplate(template) : null;
+            return this;
+        }
+
         public Builder useServerEntryUUIDNaming(final AttributeType dnAttribute) {
             return useServerNaming(dnAttribute, AttributeDescription
                     .create(getEntryUUIDAttributeType()));
@@ -539,61 +533,99 @@
     }
 
     /**
-     * Creates a new connection factory using the provided JSON configuration.
-     * The configuration should look like this, excluding the C-like comments:
+     * Creates a new connection factory using the named configuration in the
+     * provided JSON list of factory configurations. Excluding the C-like
+     * comments, the configuration should look like this:
      *
      * <pre>
      * {
-     *     // The primary data center, must contain at least one LDAP server.
-     *     "primaryLDAPServers" : [
-     *         {
-     *             "hostname" : "host1.example.com",
-     *             "port"     : 389
-     *         },
-     *         {
-     *             "hostname" : "host2.example.com",
-     *             "port"     : 389
-     *         },
-     *     ],
+     *     // A default pool of servers using no authentication.
+     *     "default" : {
+     *         // The primary data center, must contain at least one LDAP server.
+     *         "primaryLDAPServers" : [
+     *             {
+     *                 "hostname" : "host1.example.com",
+     *                 "port"     : 389
+     *             },
+     *             {
+     *                 "hostname" : "host2.example.com",
+     *                 "port"     : 389
+     *             },
+     *         ],
      *
-     *     // The optional secondary (fail-over) data center.
-     *     "secondaryLDAPServers" : [
-     *         {
-     *             "hostname" : "host3.example.com",
-     *             "port"     : 389
-     *         },
-     *         {
-     *             "hostname" : "host4.example.com",
-     *             "port"     : 389
-     *         },
-     *     ],
+     *         // The optional secondary (fail-over) data center.
+     *         "secondaryLDAPServers" : [
+     *             {
+     *                 "hostname" : "host3.example.com",
+     *                 "port"     : 389
+     *             },
+     *             {
+     *                 "hostname" : "host4.example.com",
+     *                 "port"     : 389
+     *             },
+     *         ],
      *
-     *     // Connection pool configuration.
-     *     "connectionPoolSize"       : 10,
-     *     "heartBeatIntervalSeconds" : 30,
-     *
-     *     // SSL/TLS configuration (optional and TBD).
-     *     "useSSL" : {
-     *         // Elect to use StartTLS instead of SSL.
-     *         "useStartTLS" : true,
-     *         ...
+     *         // Connection pool configuration.
+     *         "connectionPoolSize"       : 10,
+     *         "heartBeatIntervalSeconds" : 30
      *     },
      *
-     *     // Authentication configuration (optional and TBD).
-     *     "authentication" : {
-     *         "bindDN"   : "cn=directory manager",
-     *         "password" : "password"
-     *     },
+     *     // The same pool of servers except authenticated as cn=directory manager.
+     *     "root" : {
+     *         "inheritFrom"    : "default",
+     *         "authentication" : {
+     *             "simple" : {
+     *                 "bindDN"       : "cn=directory manager",
+     *                 "bindPassword" : "password"
+     *             }
+     *         }
+     *     }
      * }
      * </pre>
      *
      * @param configuration
      *            The JSON configuration.
+     * @param name
+     *            The name of the connection factory configuration to be parsed.
      * @return A new connection factory using the provided JSON configuration.
      * @throws IllegalArgumentException
      *             If the configuration is invalid.
      */
-    public static ConnectionFactory configureConnectionFactory(final JsonValue configuration) {
+    public static ConnectionFactory configureConnectionFactory(final JsonValue configuration,
+            final String name) {
+        final JsonValue normalizedConfiguration =
+                normalizeConnectionFactory(configuration, name, 0);
+        return configureConnectionFactory(normalizedConfiguration);
+    }
+
+    public static AttributeMapper constant(final Object value) {
+        return new JSONConstantAttributeMapper(value);
+    }
+
+    public static ObjectAttributeMapper object() {
+        return new ObjectAttributeMapper();
+    }
+
+    public static ReferenceAttributeMapper reference(final AttributeDescription attribute,
+            final DN baseDN, final AttributeDescription primaryKey, final AttributeMapper mapper) {
+        return new ReferenceAttributeMapper(attribute, baseDN, primaryKey, mapper);
+    }
+
+    public static ReferenceAttributeMapper reference(final String attribute, final String baseDN,
+            final String primaryKey, final AttributeMapper mapper) {
+        return reference(AttributeDescription.valueOf(attribute), DN.valueOf(baseDN),
+                AttributeDescription.valueOf(primaryKey), mapper);
+    }
+
+    public static SimpleAttributeMapper simple(final AttributeDescription attribute) {
+        return new SimpleAttributeMapper(attribute);
+    }
+
+    public static SimpleAttributeMapper simple(final String attribute) {
+        return simple(AttributeDescription.valueOf(attribute));
+    }
+
+    private static ConnectionFactory configureConnectionFactory(final JsonValue configuration) {
         // Parse pool parameters,
         final int connectionPoolSize =
                 Math.max(configuration.get("connectionPoolSize").defaultTo(10).asInteger(), 1);
@@ -604,9 +636,14 @@
         final BindRequest bindRequest;
         if (configuration.isDefined("authentication")) {
             final JsonValue authn = configuration.get("authentication");
-            bindRequest =
-                    Requests.newSimpleBindRequest(authn.get("bindDN").required().asString(), authn
-                            .get("password").required().asString().toCharArray());
+            if (authn.isDefined("simple")) {
+                final JsonValue simple = authn.get("simple");
+                bindRequest =
+                        Requests.newSimpleBindRequest(simple.get("bindDN").required().asString(),
+                                simple.get("bindPassword").required().asString().toCharArray());
+            } else {
+                throw new IllegalArgumentException("Only simple authentication is supported");
+            }
         } else {
             bindRequest = null;
         }
@@ -642,31 +679,31 @@
         }
     }
 
-    public static AttributeMapper constant(final Object value) {
-        return new JSONConstantAttributeMapper(value);
-    }
+    private static JsonValue normalizeConnectionFactory(final JsonValue configuration,
+            final String name, final int depth) {
+        // Protect against infinite recursion in the configuration.
+        if (depth > 100) {
+            throw new IllegalArgumentException(
+                    "The LDAP server configuration '"
+                            + name
+                            + "' could not be parsed because of potential circular inheritance dependencies");
+        }
 
-    public static ObjectAttributeMapper object() {
-        return new ObjectAttributeMapper();
-    }
-
-    public static ReferenceAttributeMapper reference(final AttributeDescription attribute,
-            final DN baseDN, final AttributeDescription primaryKey, final AttributeMapper mapper) {
-        return new ReferenceAttributeMapper(attribute, baseDN, primaryKey, mapper);
-    }
-
-    public static ReferenceAttributeMapper reference(final String attribute, final String baseDN,
-            final String primaryKey, final AttributeMapper mapper) {
-        return reference(AttributeDescription.valueOf(attribute), DN.valueOf(baseDN),
-                AttributeDescription.valueOf(primaryKey), mapper);
-    }
-
-    public static SimpleAttributeMapper simple(final AttributeDescription attribute) {
-        return new SimpleAttributeMapper(attribute);
-    }
-
-    public static SimpleAttributeMapper simple(final String attribute) {
-        return simple(AttributeDescription.valueOf(attribute));
+        final JsonValue current = configuration.get(name).required();
+        if (current.isDefined("inheritFrom")) {
+            // Inherit missing fields from inherited configuration.
+            final JsonValue parent =
+                    normalizeConnectionFactory(configuration,
+                            current.get("inheritFrom").asString(), depth + 1);
+            final Map<String, Object> normalized =
+                    new LinkedHashMap<String, Object>(parent.asMap());
+            normalized.putAll(current.asMap());
+            normalized.remove("inheritFrom");
+            return new JsonValue(normalized);
+        } else {
+            // No normalization required.
+            return current;
+        }
     }
 
     private static ConnectionFactory parseLDAPServers(final JsonValue config,

--
Gitblit v1.10.0