From 5b7afcc00e450bf639610f27af7a1c3a3562a020 Mon Sep 17 00:00:00 2001
From: Yannick Lecaillez <yannick.lecaillez@forgerock.com>
Date: Thu, 19 May 2016 15:10:45 +0000
Subject: [PATCH] Rest2Ldap: Removed connection reuse, simplify authorization filtering, use factory methods, add more unit tests.

---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SimpleBindStrategy.java                 |   11 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ConditionalFilters.java                 |  130 +++++
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractorsTest.java           |   70 +++
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java        |   29 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilter.java             |    2 
 opendj-server-legacy/resource/config/http-config.json                                                       |   12 
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilterTest.java  |  126 ++---
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java                 |  239 ++--------
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractors.java               |  132 ++++++
 opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2FilterTest.java            |    2 
 opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/InternalProxyAuthzFilter.java |   34 
 /dev/null                                                                                                   |   65 --
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2Filter.java                |   71 --
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SearchThenBindStrategy.java             |   11 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategy.java             |    9 
 opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/opendj-rest2ldap-config.json                       |   13 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Authorizations.java                     |  100 ++++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SASLPlainStrategy.java                  |   17 
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategies.java           |   98 ++++
 opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilter.java      |  109 ----
 20 files changed, 731 insertions(+), 549 deletions(-)

diff --git a/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/opendj-rest2ldap-config.json b/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/opendj-rest2ldap-config.json
index 830a126..54d9927 100644
--- a/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/opendj-rest2ldap-config.json
+++ b/opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/opendj-rest2ldap-config.json
@@ -78,12 +78,7 @@
         "anonymous": {
             // Specify the connection factory to use to perform LDAP operations.
             // If missing, "root" factory will be used.
-            "ldapConnectionFactory": "root",
-			
-            // Enable proxied authorization using the specified user DN.
-            // If empty, anonymous proxied authorization will be used.
-            // If missing, connection from the ldapConnectionFactory will be used as-is. 
-            "userDN": ""
+            "ldapConnectionFactory": "root"
         },
 
 		// Use HTTP Basic authentication's information to bind to the LDAP server.
@@ -95,12 +90,6 @@
 			"altAuthenticationUsernameHeader" : "X-OpenIDM-Username",
 			"altAuthenticationPasswordHeader" : "X-OpenIDM-Password",
 
-			// For server which are not supporting proxied-authorization control, you can
-			// set this flag to true. Subsequent LDAP operations will then be performed
-			// by re-using the authenticated connection.
-			// If missing, proxied-authorization control will be used. 
-			"reuseAuthenticatedConnection": false,
-
 			// Define which LDAP bind mechanism to use
 			// Supported mechanisms are "simple", "sasl-plain", "search"
 			"bind": "search",
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java
index 41a3b08..986463d 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java
@@ -17,18 +17,22 @@
 package org.forgerock.opendj.rest2ldap;
 
 import static org.forgerock.http.util.Json.readJsonLenient;
+import static org.forgerock.json.JsonValueFunctions.enumConstant;
+import static org.forgerock.json.JsonValueFunctions.setOf;
 import static org.forgerock.opendj.rest2ldap.Rest2LDAP.configureConnectionFactory;
+import static org.forgerock.opendj.rest2ldap.authz.AuthenticationStrategies.*;
+import static org.forgerock.opendj.rest2ldap.authz.Authorizations.*;
+import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.*;
+import static org.forgerock.opendj.rest2ldap.authz.CredentialExtractors.*;
 import static org.forgerock.util.Reject.checkNotNull;
 import static org.forgerock.util.Utils.closeSilently;
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.TreeMap;
+import java.util.Set;
 
 import org.forgerock.http.Filter;
 import org.forgerock.http.Handler;
@@ -40,6 +44,7 @@
 import org.forgerock.http.protocol.Headers;
 import org.forgerock.http.protocol.Request;
 import org.forgerock.http.protocol.Response;
+import org.forgerock.http.protocol.Status;
 import org.forgerock.json.JsonValue;
 import org.forgerock.json.resource.RequestHandler;
 import org.forgerock.json.resource.Router;
@@ -47,21 +52,10 @@
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.SearchScope;
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.rest2ldap.authz.AuthenticationStrategy;
-import org.forgerock.opendj.rest2ldap.authz.DirectConnectionFilter;
-import org.forgerock.opendj.rest2ldap.authz.HttpBasicAuthenticationFilter;
-import org.forgerock.opendj.rest2ldap.authz.HttpBasicAuthenticationFilter.CustomHeaderExtractor;
-import org.forgerock.opendj.rest2ldap.authz.HttpBasicAuthenticationFilter.HttpBasicExtractor;
-import org.forgerock.opendj.rest2ldap.authz.OptionalFilter;
-import org.forgerock.opendj.rest2ldap.authz.OptionalFilter.ConditionalFilter;
-import org.forgerock.opendj.rest2ldap.authz.ProxiedAuthV2Filter;
-import org.forgerock.opendj.rest2ldap.authz.ProxiedAuthV2Filter.IntrospectionAuthzProvider;
-import org.forgerock.opendj.rest2ldap.authz.SASLPlainStrategy;
-import org.forgerock.opendj.rest2ldap.authz.SearchThenBindStrategy;
-import org.forgerock.opendj.rest2ldap.authz.SimpleBindStrategy;
+import org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.ConditionalFilter;
 import org.forgerock.services.context.Context;
 import org.forgerock.services.context.SecurityContext;
 import org.forgerock.util.Factory;
@@ -87,21 +81,9 @@
 
     private final Map<String, ConnectionFactory> connectionFactories = new HashMap<>();
 
-    private enum Policy {
-        oauth2    (0),
-        basic     (50),
-        anonymous (100);
+    private enum Policy { BASIC, ANONYMOUS }
 
-        private final int priority;
-
-        Policy(int priority) {
-            this.priority = priority;
-        }
-    }
-
-    private enum BindStrategy {
-        simple, search, sasl_plain
-    }
+    private enum BindStrategy { SIMPLE, SEARCH, SASL_PLAIN }
 
     /**
      * Default constructor called by the HTTP Framework which will use the default configuration file location.
@@ -179,102 +161,57 @@
     }
 
     private Filter newAuthorizationFilter(final JsonValue config) {
-        final List<Policy> configuredPolicies = new ArrayList<>();
-        for (String policy : config.get("policies").required().asList(String.class)) {
-            configuredPolicies.add(Policy.valueOf(policy.toLowerCase()));
-        }
-        final TreeMap<Integer, Filter> policyFilters = new TreeMap<>();
-        final int lastIndex = configuredPolicies.size() - 1;
-        for (int i = 0; i < configuredPolicies.size(); i++) {
-            final Policy policy = configuredPolicies.get(i);
-            policyFilters.put(policy.priority,
-                    buildAuthzPolicyFilter(policy, config.get(policy.toString()), i != lastIndex));
-        }
-        return Filters.chainOf(new ArrayList<>(policyFilters.values()));
-    }
-
-    private Filter buildAuthzPolicyFilter(final Policy policy, final JsonValue config, boolean optional) {
-        switch (policy) {
-        case anonymous:
-            return buildAnonymousFilter(config);
-        case basic:
-            final ConditionalFilter basicFilter = buildBasicFilter(config.required());
-            final Filter basicFilterChain =
-                    config.get("reuseAuthenticatedConnection").defaultTo(Boolean.FALSE).asBoolean()
-                        ? basicFilter
-                        : Filters.chainOf(basicFilter, newProxyAuthzFilter(getConnectionFactory(DEFAULT_ROOT_FACTORY),
-                                                                           IntrospectionAuthzProvider.INSTANCE));
-            return optional ? new OptionalFilter(basicFilterChain, basicFilter) : basicFilterChain;
-        default:
-            throw new IllegalArgumentException("Unsupported policy '" + policy + "'");
-        }
-    }
-
-    /**
-     * Create a new {@link Filter} in charge of injecting {@link AuthenticatedConnectionContext}.
-     *
-     * @param connectionFactory
-     *            The {@link ConnectionFactory} providing the {@link Connection} injected as
-     *            {@link AuthenticatedConnectionContext}
-     * @param authzIdProvider
-     *            Function computing the authzId to use for the LDAP's ProxiedAuth control.
-     * @return a newly created {@link Filter}
-     */
-    protected Filter newProxyAuthzFilter(final ConnectionFactory connectionFactory,
-            final Function<SecurityContext, String, LdapException> authzIdProvider) {
-        return new ProxiedAuthV2Filter(connectionFactory, authzIdProvider);
-    }
-
-    private Filter buildAnonymousFilter(final JsonValue config) {
-        if (config.contains("userDN")) {
-            final DN userDN = DN.valueOf(config.get("userDN").asString(), schema);
-            final Map<String, Object> authz = new HashMap<>(1);
-            authz.put(SecurityContext.AUTHZID_DN, userDN.toString());
-            return Filters.chainOf(
-                    newStaticSecurityContextFilter(null, authz),
-                    newProxyAuthzFilter(
-                            getConnectionFactory(config.get("ldapConnectionFactory")
-                                    .defaultTo(DEFAULT_ROOT_FACTORY)
-                                    .asString()),
-                            IntrospectionAuthzProvider.INSTANCE));
-        }
-        return newDirectConnectionFilter(getConnectionFactory(config.get("ldapConnectionFactory")
-                                                             .defaultTo(DEFAULT_ROOT_FACTORY).asString()));
-    }
-
-    /**
-     * Create a new {@link Filter} injecting a predefined {@link SecurityContext}.
-     *
-     * @param authenticationId
-     *            AuthenticationID of the {@link SecurityContext}.
-     * @param authorization
-     *            Authorization of the {@link SecurityContext}
-     * @return a newly created {@link Filter}
-     */
-    protected Filter newStaticSecurityContextFilter(final String authenticationId,
-            final Map<String, Object> authorization) {
+        final Set<Policy> policies = config.get("policies").as(setOf(enumConstant(Policy.class)));
+        final ConditionalFilter anonymous =
+                policies.contains(Policy.ANONYMOUS) ? buildAnonymousFilter(config.get("anonymous")) : NEVER_APPLICABLE;
+        final ConditionalFilter basic =
+                policies.contains(Policy.BASIC) ? buildBasicFilter(config.get("basic")) : NEVER_APPLICABLE;
         return new Filter() {
             @Override
             public Promise<Response, NeverThrowsException> filter(Context context, Request request, Handler next) {
-                return next.handle(new SecurityContext(context, authenticationId, authorization), request);
+                if (basic.getCondition().canApplyFilter(context, request)) {
+                    return basic.getFilter().filter(context, request, next);
+                }
+                if (anonymous.getCondition().canApplyFilter(context, request)) {
+                    return anonymous.getFilter().filter(context, request, next);
+                }
+                return Response.newResponsePromise(new Response(Status.FORBIDDEN));
             }
         };
     }
 
     /**
-     * Create a new {@link Filter} in charge of injecting {@link AuthenticatedConnectionContext} directly from a
+     * Creates a new {@link Filter} in charge of injecting {@link AuthenticatedConnectionContext}.
+     *
+     * @param connectionFactory
+     *            The {@link ConnectionFactory} providing the {@link Connection} injected as
+     *            {@link AuthenticatedConnectionContext}
+     * @return a newly created {@link Filter}
+     */
+    protected Filter newProxyAuthzFilter(final ConnectionFactory connectionFactory) {
+        return newProxyAuthorizationFilter(connectionFactory);
+    }
+
+    private ConditionalFilter buildAnonymousFilter(final JsonValue config) {
+        return newAnonymousFilter(getConnectionFactory(config.get("ldapConnectionFactory")
+                                                             .defaultTo(DEFAULT_ROOT_FACTORY)
+                                                             .asString()));
+    }
+
+    /**
+     * Creates a new {@link Filter} in charge of injecting {@link AuthenticatedConnectionContext} directly from a
      * {@link ConnectionFactory}.
      *
      * @param connectionFactory
      *            The {@link ConnectionFactory} used to get the {@link Connection}
      * @return a newly created {@link Filter}
      */
-    protected Filter newDirectConnectionFilter(ConnectionFactory connectionFactory) {
-        return new DirectConnectionFilter(connectionFactory);
+    protected ConditionalFilter newAnonymousFilter(ConnectionFactory connectionFactory) {
+        return newConditionalDirectConnectionFilter(connectionFactory);
     }
 
     /**
-     * Get a {@link ConnectionFactory} from its name.
+     * Gets a {@link ConnectionFactory} from its name.
      *
      * @param name
      *            Name of the {@link ConnectionFactory} as specified in the configuration
@@ -286,42 +223,41 @@
 
     private ConditionalFilter buildBasicFilter(final JsonValue config) {
         final String bind = config.get("bind").required().asString();
-        final BindStrategy strategy = BindStrategy.valueOf(bind.toLowerCase().replace('-', '_'));
+        final BindStrategy strategy = BindStrategy.valueOf(bind.toUpperCase().replace('-', '_'));
         return newBasicAuthenticationFilter(buildBindStrategy(strategy, config.get(bind).required()),
                 config.get("supportAltAuthentication").defaultTo(Boolean.FALSE).asBoolean()
-                        ? new CustomHeaderExtractor(
+                        ? newCustomHeaderExtractor(
                                 config.get("altAuthenticationUsernameHeader").required().asString(),
                                 config.get("altAuthenticationPasswordHeader").required().asString())
-                        : HttpBasicExtractor.INSTANCE,
-                config.get("reuseAuthenticatedConnection").defaultTo(Boolean.FALSE).asBoolean());
+                        : httpBasicExtractor());
     }
 
     /**
-     * Get a {@link Filter} in charge of performing the HTTP-Basic Authentication. This filter create a
+     * Gets a {@link Filter} in charge of performing the HTTP-Basic Authentication. This filter create a
      * {@link SecurityContext} reflecting the authenticated users.
      *
      * @param authenticationStrategy
      *            The {@link AuthenticationStrategy} to use to authenticate the user.
      * @param credentialsExtractor
      *            Extract the user's credentials from the {@link Headers}.
-     * @param reuseAuthenticatedConnection
-     *            Let the bound connection open so that it can be reused to perform the LDAP operations.
      * @return A new {@link Filter}
      */
     protected ConditionalFilter newBasicAuthenticationFilter(AuthenticationStrategy authenticationStrategy,
-            Function<Headers, Pair<String, String>, NeverThrowsException> credentialsExtractor,
-            boolean reuseAuthenticatedConnection) {
-        return new HttpBasicAuthenticationFilter(authenticationStrategy, credentialsExtractor,
-                reuseAuthenticatedConnection);
+            Function<Headers, Pair<String, String>, NeverThrowsException> credentialsExtractor) {
+        final ConditionalFilter httpBasicFilter =
+                newConditionalHttpBasicAuthenticationFilter(authenticationStrategy, credentialsExtractor);
+        return newConditionalFilter(Filters.chainOf(httpBasicFilter.getFilter(),
+                                                    newProxyAuthzFilter(getConnectionFactory(DEFAULT_ROOT_FACTORY))),
+                                    httpBasicFilter.getCondition());
     }
 
     private AuthenticationStrategy buildBindStrategy(final BindStrategy strategy, final JsonValue config) {
         switch (strategy) {
-        case simple:
+        case SIMPLE:
             return buildSimpleBindStrategy(config);
-        case search:
+        case SEARCH:
             return buildSearchThenBindStrategy(config);
-        case sasl_plain:
+        case SASL_PLAIN:
             return buildSASLBindStrategy(config);
         default:
             throw new IllegalArgumentException("Unsupported strategy '" + strategy + "'");
@@ -335,41 +271,10 @@
                                      schema);
     }
 
-    /**
-     * {@link AuthenticationStrategy} performing an LDAP Bind request with a computed DN.
-     *
-     * @param connectionFactory
-     *            The {@link ConnectionFactory} to use to perform the bind operation
-     * @param schema
-     *            {@link Schema} used to perform the DN validation.
-     * @param bindDNTemplate
-     *            DN template containing a single %s which will be replaced by the authenticating user's name. (i.e:
-     *            uid=%s,ou=people,dc=example,dc=com)
-     * @return A new {@link AuthenticationStrategy}
-     */
-    protected AuthenticationStrategy newSimpleBindStrategy(ConnectionFactory connectionFactory, String bindDNTemplate,
-            Schema schema) {
-        return new SimpleBindStrategy(connectionFactory, bindDNTemplate, schema);
-    }
-
     private AuthenticationStrategy buildSASLBindStrategy(JsonValue config) {
-        return newSASLBindStrategy(getConnectionFactory(config.get("ldapConnectionFactory")
-                                                              .defaultTo(DEFAULT_BIND_FACTORY).asString()),
-                                   config.get("authcIdTemplate").defaultTo("u:%s").asString());
-    }
-
-    /**
-     * {@link AuthenticationStrategy} performing an LDAP SASL-Plain Bind.
-     *
-     * @param connectionFactory
-     *            The {@link ConnectionFactory} to use to perform the bind operation
-     * @param authcIdTemplate
-     *            Authentication identity template containing a single %s which will be replaced by the authenticating
-     *            user's name. (i.e: (u:%s)
-     * @return A new {@link AuthenticationStrategy}
-     */
-    protected AuthenticationStrategy newSASLBindStrategy(ConnectionFactory connectionFactory, String authcIdTemplate) {
-        return new SASLPlainStrategy(connectionFactory, schema, authcIdTemplate);
+        return newSASLPlainStrategy(
+                getConnectionFactory(config.get("ldapConnectionFactory").defaultTo(DEFAULT_BIND_FACTORY).asString()),
+                schema, config.get("authcIdTemplate").defaultTo("u:%s").asString());
     }
 
     private AuthenticationStrategy buildSearchThenBindStrategy(JsonValue config) {
@@ -382,26 +287,4 @@
                 SearchScope.valueOf(config.get("scope").required().asString().toLowerCase()),
                 config.get("filterTemplate").required().asString());
     }
-
-    /**
-     * {@link AuthenticationStrategy} performing an LDAP Search to get a DN to bind with.
-     *
-     * @param searchConnectionFactory
-     *            The {@link ConnectionFactory} to sue to perform the search operation.
-     * @param bindConnectionFactory
-     *            The {@link ConnectionFactory} to use to perform the bind operation
-     * @param baseDN
-     *            The base DN of the search request
-     * @param scope
-     *            {@link SearchScope} of the search request
-     * @param filterTemplate
-     *            filter template containing a single %s which will be replaced by the authenticating user's name. (i.e:
-     *            (&(uid=%s)(objectClass=inetOrgPerson))
-     * @return A new {@link AuthenticationStrategy}
-     */
-    protected AuthenticationStrategy newSearchThenBindStrategy(ConnectionFactory searchConnectionFactory,
-            ConnectionFactory bindConnectionFactory, DN baseDN, SearchScope scope, String filterTemplate) {
-        return new SearchThenBindStrategy(
-                searchConnectionFactory, bindConnectionFactory, baseDN, scope, filterTemplate);
-    }
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategies.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategies.java
new file mode 100644
index 0000000..b9f5acb
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategies.java
@@ -0,0 +1,98 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ */
+package org.forgerock.opendj.rest2ldap.authz;
+
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.SearchScope;
+import org.forgerock.opendj.ldap.schema.Schema;
+
+
+/**
+ * Factory methods of {@link AuthenticationStrategy} allowing to perform authentication against LDAP server through
+ * different method.
+ */
+public final class AuthenticationStrategies {
+
+    private AuthenticationStrategies() {
+    }
+
+    /**
+     * Creates an {@link AuthenticationStrategy} performing simple BIND authentication against an LDAP server.
+     *
+     * @param connectionFactory
+     *            {@link ConnectionFactory} to the LDAP server used to perform the bind operation.
+     * @param bindDNTemplate
+     *            Tempalte of the DN to use for the bind operation. The first %s will be replaced by the provided
+     *            authentication-id (i.e: uid=%s,dc=example,dc=com)
+     * @param schema
+     *            {@link Schema} used to validate the DN format.*
+     * @return a new simple bind {@link AuthenticationStrategy}
+     * @throws NullPointerException
+     *             If a parameter is null
+     */
+    public static AuthenticationStrategy newSimpleBindStrategy(ConnectionFactory connectionFactory,
+            String bindDNTemplate, Schema schema) {
+        return new SimpleBindStrategy(connectionFactory, bindDNTemplate, schema);
+    }
+
+    /**
+     * Creates an {@link AuthenticationStrategy} performing authentication against an LDAP server by first performing a
+     * lookup of the entry to bind with. This is to find the user DN to bind with from its metadata (i.e: email
+     * address).
+     *
+     * @param searchConnectionFactory
+     *            {@link ConnectionFactory} to the LDAP server used to perform the lookup of the entry.
+     * @param bindConnectionFactory
+     *            {@link ConnectionFactory} to the LDAP server used to perform the bind one the user's DN has been
+     *            found. Can be the same than the searchConnectionFactory.
+     * @param baseDN
+     *            Base DN of the search request performed to find the user's DN.
+     * @param searchScope
+     *            {@link SearchScope} of the search request performed to find the user's DN.
+     * @param filterTemplate
+     *            Filter of the search request (i.e: (&(email=%s)(objectClass=inetOrgPerson)) where the first %s will be
+     *            replaced by the user's provided authentication-id.
+     * @return a new search then bind {@link AuthenticationStrategy}
+     * @throws NullPointerException
+     *             If a parameter is null
+     */
+    public static AuthenticationStrategy newSearchThenBindStrategy(ConnectionFactory searchConnectionFactory,
+            ConnectionFactory bindConnectionFactory, DN baseDN, SearchScope searchScope, String filterTemplate) {
+        return new SearchThenBindStrategy(searchConnectionFactory, bindConnectionFactory, baseDN, searchScope,
+                filterTemplate);
+    }
+
+    /**
+     * Creates an {@link AuthenticationStrategy} performing authentication against an LDAP server using a plain SASL
+     * bind request.
+     *
+     * @param connectionFactory
+     *            {@link ConnectionFactory} to the LDAP server to authenticate with.
+     * @param authcIdTemplate
+     *            Authentication identity template containing a single %s which will be replaced by the authenticating
+     *            user's name. (i.e: (u:%s)
+     * @param schema
+     *            Schema used to perform DN validation.
+     * @return a new SASL plain bind {@link AuthenticationStrategy}
+     * @throws NullPointerException
+     *             If a parameter is null
+     */
+    public static AuthenticationStrategy newSASLPlainStrategy(ConnectionFactory connectionFactory, Schema schema,
+            String authcIdTemplate) {
+        return new SASLPlainStrategy(connectionFactory, schema, authcIdTemplate);
+    }
+}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategy.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategy.java
index 9101def..a84d497 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategy.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategy.java
@@ -15,9 +15,6 @@
  */
 package org.forgerock.opendj.rest2ldap.authz;
 
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.services.context.Context;
 import org.forgerock.services.context.SecurityContext;
@@ -34,11 +31,7 @@
      *            Password used to perform the authentication.
      * @param parentContext
      *            Context to use as parent for the created {@link SecurityContext}
-     * @param authenticatedConnectionHolder
-     *            Output parameter. If supported, the implementations will set the reference to a ready to be used LDAP
-     *            connection bound to the given credentials.
      * @return A {@link Context} if the authentication succeed or an {@link LdapException} otherwise.
      */
-    Promise<SecurityContext, LdapException> authenticate(String username, String password, Context parentContext,
-            AtomicReference<Connection> authenticatedConnectionHolder);
+    Promise<SecurityContext, LdapException> authenticate(String username, String password, Context parentContext);
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Authorizations.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Authorizations.java
new file mode 100644
index 0000000..471c03e
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Authorizations.java
@@ -0,0 +1,100 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ */
+package org.forgerock.opendj.rest2ldap.authz;
+
+import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.asConditionalFilter;
+import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.newConditionalFilter;
+
+import org.forgerock.http.Filter;
+import org.forgerock.http.protocol.Headers;
+import org.forgerock.http.protocol.Request;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.controls.ProxiedAuthV2RequestControl;
+import org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext;
+import org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.Condition;
+import org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.ConditionalFilter;
+import org.forgerock.services.context.Context;
+import org.forgerock.services.context.SecurityContext;
+import org.forgerock.util.Function;
+import org.forgerock.util.Pair;
+import org.forgerock.util.promise.NeverThrowsException;
+
+/**
+ * Factory methods to create {@link Filter} performing authentication and authorizations.
+ */
+public final class Authorizations {
+
+    private Authorizations() {
+    }
+
+    /**
+     * Creates a new {@link ConditionalFilter} performing authentication. If authentication succeed, it injects a
+     * {@link SecurityContext} with the authenticationId provided by the user. Otherwise, returns a HTTP 401 -
+     * Unauthorized response. The condition of this {@link ConditionalFilter} will return true if the supplied requests
+     * contains credentials information, false otherwise.
+     *
+     * @param authenticationStrategy
+     *            {@link AuthenticationStrategy} to validate the user's provided credentials.
+     * @param credentialsExtractor
+     *            Function to extract the credentials from the received request.
+     * @throws NullPointerException
+     *             if a parameter is null.
+     * @return a new {@link ConditionalFilter}
+     */
+    public static ConditionalFilter newConditionalHttpBasicAuthenticationFilter(
+            final AuthenticationStrategy authenticationStrategy,
+            final Function<Headers, Pair<String, String>, NeverThrowsException> credentialsExtractor) {
+        return newConditionalFilter(
+                new HttpBasicAuthenticationFilter(authenticationStrategy, credentialsExtractor),
+                new Condition() {
+                    @Override
+                    public boolean canApplyFilter(Context context, Request request) {
+                        return credentialsExtractor.apply(request.getHeaders()) != null;
+                    }
+                });
+    }
+
+    /**
+     * Creates a {@link ConditionalFilter} injecting an {@link AuthenticatedConnectionContext} with a connection issued
+     * from the given connectionFactory. The condition is always true.
+     *
+     * @param connectionFactory
+     *            The factory used to get the {@link Connection} to inject.
+     * @return A new {@link ConditionalFilter}.
+     * @throws NullPointerException
+     *             if connectionFactory is null
+     */
+    public static ConditionalFilter newConditionalDirectConnectionFilter(ConnectionFactory connectionFactory) {
+        return asConditionalFilter(new DirectConnectionFilter(connectionFactory));
+    }
+
+    /**
+     * Creates a filter injecting an {@link AuthenticatedConnectionContext} given the information provided in the
+     * {@link SecurityContext}. The connection contained in the created {@link AuthenticatedConnectionContext} will add
+     * a {@link ProxiedAuthV2RequestControl} to each LDAP requests.
+     *
+     * @param connectionFactory
+     *            The connection factory used to create the connection which will be injected in the
+     *            {@link AuthenticatedConnectionContext}
+     * @return A new filter.
+     * @throws NullPointerException
+     *             if connectionFactory is null
+     */
+    public static Filter newProxyAuthorizationFilter(ConnectionFactory connectionFactory) {
+        return new ProxiedAuthV2Filter(connectionFactory);
+    }
+}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ConditionalFilters.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ConditionalFilters.java
new file mode 100644
index 0000000..fa80e02
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ConditionalFilters.java
@@ -0,0 +1,130 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ */
+
+package org.forgerock.opendj.rest2ldap.authz;
+
+import static org.forgerock.util.Reject.checkNotNull;
+
+import org.forgerock.http.Filter;
+import org.forgerock.http.Handler;
+import org.forgerock.http.protocol.Request;
+import org.forgerock.http.protocol.Response;
+import org.forgerock.http.protocol.Status;
+import org.forgerock.services.context.Context;
+import org.forgerock.util.promise.NeverThrowsException;
+import org.forgerock.util.promise.Promise;
+
+/** Encapsulate a {@link Condition} which must be fulfilled in order to apply the Filter. */
+public final class ConditionalFilters {
+
+    /** Encapsulate a {@link Filter} which will be processed only if the attached {@link Condition} is true. */
+    public interface ConditionalFilter {
+        /**
+         * Get the filter which must be processed if the {@link Condition} evaluates to true.
+         *
+         * @return The filter to process.
+         */
+        Filter getFilter();
+
+        /**
+         * Get the {@link Condition} to evaluate.
+         *
+         * @return the {@link Condition} to evaluate.
+         */
+        Condition getCondition();
+    }
+
+    /** Condition which have to be fulfilled in order to apply the {@link Filter}. */
+    public interface Condition {
+        /**
+         * Check if a {@link Filter} must be executed or not.
+         *
+         * @param context
+         *            Current {@link Context} of the request processing.
+         * @param request
+         *            the {@link Request} currently processed.
+         * @return true if the filter must be applied.
+         */
+        boolean canApplyFilter(Context context, Request request);
+    }
+
+    /** {@link Condition} which always returns true. */
+    public static final Condition ALWAYS_TRUE = new Condition() {
+        @Override
+        public boolean canApplyFilter(Context context, Request request) {
+            return true;
+        }
+    };
+
+    /** {@link Condition} which always returns false. */
+    public static final Condition ALWAYS_FALSE = new Condition() {
+        @Override
+        public boolean canApplyFilter(Context context, Request request) {
+            return false;
+        }
+    };
+
+    /** {@link ConditionalFilter} with an ALWAYS_FALSE {@link Condition}. */
+    public static final ConditionalFilter NEVER_APPLICABLE = newConditionalFilter(new Filter() {
+        @Override
+        public Promise<Response, NeverThrowsException> filter(Context context, Request request, Handler next) {
+            return Response.newResponsePromise(new Response(Status.NOT_IMPLEMENTED));
+        }
+    }, ALWAYS_FALSE);
+
+    private ConditionalFilters() {
+    }
+
+    /**
+     * Wrap a {@link Filter} into a {@link ConditionalFilter} with an ALWAYS_TRUE condition.
+     *
+     * @param filter
+     *            The {@link Filter} to wrap.
+     * @return a new {@link ConditionalFilter}
+     * @throws NullPointerException
+     *             if filter is null
+     */
+    public static ConditionalFilter asConditionalFilter(final Filter filter) {
+        return newConditionalFilter(filter, ALWAYS_TRUE);
+    }
+
+    /**
+     * Create a {@link ConditionalFilter} from a {@link Filter} and a {@link Condition}.
+     *
+     * @param filter
+     *            {@link Filter} which must be processed if the condition is true.
+     * @param condition
+     *            {@link Condition} to evaluate.
+     * @return a new {@link ConditionalFilter}
+     * @throws NullPointerException
+     *             if a parameter is null
+     */
+    public static ConditionalFilter newConditionalFilter(final Filter filter, final Condition condition) {
+        checkNotNull(filter, "filter cannot be null");
+        checkNotNull(condition, "condition cannot be null");
+        return new ConditionalFilter() {
+            @Override
+            public Filter getFilter() {
+                return filter;
+            }
+
+            @Override
+            public Condition getCondition() {
+                return condition;
+            }
+        };
+    }
+}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractors.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractors.java
new file mode 100644
index 0000000..50d5e9a
--- /dev/null
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractors.java
@@ -0,0 +1,132 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ */
+package org.forgerock.opendj.rest2ldap.authz;
+
+import static org.forgerock.util.Reject.checkNotNull;
+
+import org.forgerock.http.protocol.Headers;
+import org.forgerock.util.Function;
+import org.forgerock.util.Pair;
+import org.forgerock.util.encode.Base64;
+import org.forgerock.util.promise.NeverThrowsException;
+
+/**
+ * Factory method for function extracting credentials from HTTP request {@link Headers}.
+ */
+public final class CredentialExtractors {
+
+    /** HTTP Header sent by the client with HTTP basic authentication. */
+    public static final String HTTP_BASIC_AUTH_HEADER = "Authorization";
+
+    private CredentialExtractors() {
+    }
+
+    /**
+     * Creates a function which extracts the user's credentials from the standard HTTP Basic header.
+     *
+     * @return the basic extractor singleton
+     */
+    public static Function<Headers, Pair<String, String>, NeverThrowsException> httpBasicExtractor() {
+        return HttpBasicExtractor.INSTANCE;
+    }
+
+    /**
+     * Creates a function which extracts the user's credentials from custom HTTP header in addition of the standard HTTP
+     * Basic one.
+     *
+     * @param customHeaderUsername
+     *            Name of the additional header to check for the user's name
+     * @param customHeaderPassword
+     *            Name of the additional header to check for the user's password
+     * @return A new credentials extractors looking for custom header.
+     */
+    public static Function<Headers, Pair<String, String>, NeverThrowsException> newCustomHeaderExtractor(
+            String customHeaderUsername, String customHeaderPassword) {
+        return new CustomHeaderExtractor(customHeaderUsername, customHeaderPassword);
+    }
+
+    /** Extract the user's credentials from custom {@link Headers}. */
+    private static final class CustomHeaderExtractor
+            implements Function<Headers, Pair<String, String>, NeverThrowsException> {
+
+        private final String customHeaderUsername;
+        private final String customHeaderPassword;
+
+        /**
+         * Create a new CustomHeaderExtractor.
+         *
+         * @param customHeaderUsername
+         *            Name of the header containing the username
+         * @param customHeaderPassword
+         *            Name of the header containing the password
+         * @throws NullPointerException
+         *             if a parameter is null.
+         */
+        public CustomHeaderExtractor(String customHeaderUsername, String customHeaderPassword) {
+            this.customHeaderUsername = checkNotNull(customHeaderUsername, "customHeaderUsername cannot be null");
+            this.customHeaderPassword = checkNotNull(customHeaderPassword, "customHeaderPassword cannot be null");
+        }
+
+        @Override
+        public Pair<String, String> apply(Headers headers) {
+            final String userName = headers.getFirst(customHeaderUsername);
+            final String password = headers.getFirst(customHeaderPassword);
+            if (userName != null && password != null) {
+                return Pair.of(userName, password);
+            }
+            return HttpBasicExtractor.INSTANCE.apply(headers);
+        }
+    }
+
+    /** Extract the user's credentials from the standard HTTP Basic {@link Headers}. */
+    private static final class HttpBasicExtractor
+            implements Function<Headers, Pair<String, String>, NeverThrowsException> {
+
+        /** Reference to the HttpBasicExtractor Singleton. */
+        public static final HttpBasicExtractor INSTANCE = new HttpBasicExtractor();
+
+        private HttpBasicExtractor() { }
+
+        @Override
+        public Pair<String, String> apply(Headers headers) {
+            final String httpBasicAuthHeader = headers.getFirst(HTTP_BASIC_AUTH_HEADER);
+            if (httpBasicAuthHeader != null) {
+                final Pair<String, String> userCredentials = parseUsernamePassword(httpBasicAuthHeader);
+                if (userCredentials != null) {
+                    return userCredentials;
+                }
+            }
+            return null;
+        }
+
+        private Pair<String, String> parseUsernamePassword(String authHeader) {
+            if (authHeader != null && (authHeader.toLowerCase().startsWith("basic"))) {
+                // We received authentication info
+                // Example received header:
+                // "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+                final String base64UserCredentials = authHeader.substring("basic".length() + 1);
+                // Example usage of base64:
+                // Base64("Aladdin:open sesame") = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
+                final String userCredentials = new String(Base64.decode(base64UserCredentials));
+                String[] split = userCredentials.split(":");
+                if (split.length == 2) {
+                    return Pair.of(split[0], split[1]);
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilter.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilter.java
index ecb187a..f3dcceb 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilter.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/DirectConnectionFilter.java
@@ -36,7 +36,7 @@
 /**
  * Inject {@link Connection} into a {@link AuthenticatedConnectionContext}.
  */
-public final class DirectConnectionFilter implements Filter {
+final class DirectConnectionFilter implements Filter {
 
     private final ConnectionFactory connectionFactory;
 
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilter.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilter.java
index 43c1aa8..0a53865 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilter.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilter.java
@@ -17,26 +17,20 @@
 
 import static org.forgerock.opendj.rest2ldap.authz.Utils.asErrorResponse;
 import static org.forgerock.util.Reject.checkNotNull;
-import static org.forgerock.opendj.rest2ldap.authz.Utils.close;
 
-import java.util.concurrent.atomic.AtomicReference;
-
+import org.forgerock.http.Filter;
 import org.forgerock.http.Handler;
 import org.forgerock.http.protocol.Headers;
 import org.forgerock.http.protocol.Request;
 import org.forgerock.http.protocol.Response;
-import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.EntryNotFoundException;
 import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.ResultCode;
-import org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext;
-import org.forgerock.opendj.rest2ldap.authz.OptionalFilter.ConditionalFilter;
 import org.forgerock.services.context.Context;
 import org.forgerock.services.context.SecurityContext;
 import org.forgerock.util.AsyncFunction;
 import org.forgerock.util.Function;
 import org.forgerock.util.Pair;
-import org.forgerock.util.encode.Base64;
 import org.forgerock.util.promise.NeverThrowsException;
 import org.forgerock.util.promise.Promise;
 
@@ -44,11 +38,10 @@
  * Inject a {@link SecurityContext} if the credentials provided in the {@link Request} headers have been successfully
  * verified.
  */
-public final class HttpBasicAuthenticationFilter implements ConditionalFilter {
+final class HttpBasicAuthenticationFilter implements Filter {
 
     private final AuthenticationStrategy authenticationStrategy;
     private final Function<Headers, Pair<String, String>, NeverThrowsException> credentialsExtractor;
-    private final boolean reuseAuthenticatedConnection;
 
     /**
      * Create a new HttpBasicAuthenticationFilter.
@@ -57,22 +50,13 @@
      *            The strategy to use to perform the authentication.
      * @param credentialsExtractor
      *            The function to use to extract credentials from the {@link Headers}.
-     * @param reuseAuthenticatedConnection
-     *            Let the bound connection open so that it can be reused to perform the LDAP operations.
      * @throws NullPointerException
      *             If a parameter is null.
      */
     public HttpBasicAuthenticationFilter(AuthenticationStrategy authenticationStrategy,
-            Function<Headers, Pair<String, String>, NeverThrowsException> credentialsExtractor,
-            boolean reuseAuthenticatedConnection) {
+            Function<Headers, Pair<String, String>, NeverThrowsException> credentialsExtractor) {
         this.authenticationStrategy = checkNotNull(authenticationStrategy, "authenticationStrategy cannot be null");
         this.credentialsExtractor = checkNotNull(credentialsExtractor, "credentialsExtractor cannot be null");
-        this.reuseAuthenticatedConnection = reuseAuthenticatedConnection;
-    }
-
-    @Override
-    public boolean canApplyFilter(Context context, Request request) {
-        return credentialsExtractor.apply(request.getHeaders()) != null;
     }
 
     @Override
@@ -82,18 +66,11 @@
         if (credentials == null) {
             return asErrorResponse(LdapException.newLdapException(ResultCode.INVALID_CREDENTIALS));
         }
-        final AtomicReference<Connection> authConnHolder = new AtomicReference<Connection>();
         return authenticationStrategy
-                .authenticate(credentials.getFirst(), credentials.getSecond(), context, authConnHolder)
+                .authenticate(credentials.getFirst(), credentials.getSecond(), context)
                 .thenAsync(new AsyncFunction<SecurityContext, Response, NeverThrowsException>() {
                     @Override
                     public Promise<Response, NeverThrowsException> apply(final SecurityContext securityContext) {
-                        if (reuseAuthenticatedConnection) {
-                            return next
-                                    .handle(new AuthenticatedConnectionContext(securityContext, authConnHolder.get()),
-                                            request);
-                        }
-                        close(authConnHolder);
                         return next.handle(securityContext, request);
                     }
                 }, new AsyncFunction<LdapException, Response, NeverThrowsException>() {
@@ -103,82 +80,6 @@
                                 ? LdapException.newLdapException(ResultCode.INVALID_CREDENTIALS, exception.getMessage())
                                 : exception);
                     }
-                })
-                .thenFinally(close(authConnHolder));
-    }
-
-    /** Extract the user's credentials from custom {@link Headers}. */
-    public static final class CustomHeaderExtractor
-            implements Function<Headers, Pair<String, String>, NeverThrowsException> {
-
-        private final String customHeaderUsername;
-        private final String customHeaderPassword;
-
-        /**
-         * Create a new CustomHeaderExtractor.
-         *
-         * @param customHeaderUsername
-         *            Name of the header containing the username
-         * @param customHeaderPassword
-         *            Name of the header containing the password
-         * @throws NullPointerException
-         *             if a parameter is null.
-         */
-        public CustomHeaderExtractor(String customHeaderUsername, String customHeaderPassword) {
-            this.customHeaderUsername = checkNotNull(customHeaderUsername, "customHeaderUsername cannot be null");
-            this.customHeaderPassword = checkNotNull(customHeaderPassword, "customHeaderPassword cannot be null");
-        }
-
-        @Override
-        public Pair<String, String> apply(Headers headers) {
-            final String userName = headers.getFirst(customHeaderUsername);
-            final String password = headers.getFirst(customHeaderPassword);
-            if (userName != null && password != null) {
-                return Pair.of(userName, password);
-            }
-            return HttpBasicExtractor.INSTANCE.apply(headers);
-        }
-    }
-
-    /** Extract the user's credentials from the standard HTTP Basic {@link Headers}. */
-    public static final class HttpBasicExtractor
-            implements Function<Headers, Pair<String, String>, NeverThrowsException> {
-
-        /** HTTP Header sent by the client with HTTP basic authentication. */
-        public static final String HTTP_BASIC_AUTH_HEADER = "Authorization";
-
-        /** Reference to the HttpBasicExtractor Singleton. */
-        public static final HttpBasicExtractor INSTANCE = new HttpBasicExtractor();
-
-        private HttpBasicExtractor() { }
-
-        @Override
-        public Pair<String, String> apply(Headers headers) {
-            final String httpBasicAuthHeader = headers.getFirst(HTTP_BASIC_AUTH_HEADER);
-            if (httpBasicAuthHeader != null) {
-                final Pair<String, String> userCredentials = parseUsernamePassword(httpBasicAuthHeader);
-                if (userCredentials != null) {
-                    return userCredentials;
-                }
-            }
-            return null;
-        }
-
-        private Pair<String, String> parseUsernamePassword(String authHeader) {
-            if (authHeader != null && (authHeader.toLowerCase().startsWith("basic"))) {
-                // We received authentication info
-                // Example received header:
-                // "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
-                final String base64UserCredentials = authHeader.substring("basic".length() + 1);
-                // Example usage of base64:
-                // Base64("Aladdin:open sesame") = "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
-                final String userCredentials = new String(Base64.decode(base64UserCredentials));
-                String[] split = userCredentials.split(":");
-                if (split.length == 2) {
-                    return Pair.of(split[0], split[1]);
-                }
-            }
-            return null;
-        }
+                });
     }
 }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilter.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilter.java
deleted file mode 100644
index cc448ba..0000000
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilter.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * The contents of this file are subject to the terms of the Common Development and
- * Distribution License (the License). You may not use this file except in compliance with the
- * License.
- *
- * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
- * specific language governing permission and limitations under the License.
- *
- * When distributing Covered Software, include this CDDL Header Notice in each file and include
- * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
- * Header, with the fields enclosed by brackets [] replaced by your own identifying
- * information: "Portions copyright [year] [name of copyright owner]".
- *
- * Copyright 2016 ForgeRock AS.
- */
-
-package org.forgerock.opendj.rest2ldap.authz;
-
-import org.forgerock.http.Filter;
-import org.forgerock.http.Handler;
-import org.forgerock.http.protocol.Request;
-import org.forgerock.http.protocol.Response;
-import org.forgerock.services.context.Context;
-import org.forgerock.util.promise.NeverThrowsException;
-import org.forgerock.util.promise.Promise;
-
-/** Encapsulate a {@link Filter} which can be conditionally applied. */
-public final class OptionalFilter implements Filter {
-
-    /** Condition which have to be fulfilled in order to apply the {@link Filter}. */
-    public interface Condition {
-        /**
-         * Check if a {@link Filter} must be executed or not.
-         *
-         * @param context
-         *            Current {@link Context} of the request processing.
-         * @param request
-         *            the {@link Request} currently processed.
-         * @return true if the filter must be applied.
-         */
-        boolean canApplyFilter(Context context, Request request);
-    }
-
-    /** A {@link Filter} which can be conditionally applied. */
-    public interface ConditionalFilter extends Filter, Condition {
-    }
-
-    private final Filter delegate;
-    private final Condition condition;
-
-    /**
-     * Make {@link Filter} optional.
-     *
-     * @param delegate
-     *            {@link Filter} which will be conditionally applied;
-     * @param condition
-     *            The {@link Condition} which have to be fulfilled in order to apply the filter.
-     */
-    public OptionalFilter(Filter delegate, Condition condition) {
-        this.delegate = delegate;
-        this.condition = condition;
-    }
-
-    @Override
-    public Promise<Response, NeverThrowsException> filter(Context context, Request request, Handler next) {
-        if (condition.canApplyFilter(context, request)) {
-            return delegate.filter(context, request, next);
-        }
-        return next.handle(context, request);
-    }
-}
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2Filter.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2Filter.java
index 7cd316c..db76fb9 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2Filter.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2Filter.java
@@ -47,7 +47,6 @@
 import org.forgerock.opendj.ldap.responses.CompareResult;
 import org.forgerock.opendj.ldap.responses.ExtendedResult;
 import org.forgerock.opendj.ldap.responses.Result;
-import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext;
 import org.forgerock.services.context.Context;
 import org.forgerock.services.context.SecurityContext;
@@ -60,10 +59,9 @@
  * Inject an {@link AuthenticatedConnectionContext} following the information provided in the {@link SecurityContext}.
  * This connection will add a {@link ProxiedAuthV2RequestControl} to each LDAP requests.
  */
-public final class ProxiedAuthV2Filter implements Filter {
+final class ProxiedAuthV2Filter implements Filter {
 
     private final ConnectionFactory connectionFactory;
-    private final Function<SecurityContext, String, LdapException> authzProvider;
 
     /**
      * Create a new ProxyAuthzFilter. The {@link Connection} contained in the injected
@@ -78,10 +76,8 @@
      * @throws NullPointerException
      *             If a parameter is null
      */
-    public ProxiedAuthV2Filter(final ConnectionFactory connectionFactory,
-            final Function<SecurityContext, String, LdapException> authzIdProvider) {
+    public ProxiedAuthV2Filter(final ConnectionFactory connectionFactory) {
         this.connectionFactory = checkNotNull(connectionFactory, "connectionFactory cannot be null");
-        this.authzProvider = checkNotNull(authzIdProvider, "authzIdProvider cannot be null");
     }
 
     @Override
@@ -95,7 +91,7 @@
                     public Connection apply(Connection connection) throws LdapException {
                         connectionHolder.set(connection);
                         final Connection proxiedConnection = newProxiedConnection(
-                                connection, authzProvider.apply(context.asContext(SecurityContext.class)));
+                                connection, resolveAuthorizationId(context.asContext(SecurityContext.class)));
                         connectionHolder.set(proxiedConnection);
                         return proxiedConnection;
                     }
@@ -114,59 +110,24 @@
                 .thenFinally(close(connectionHolder));
     }
 
+    private String resolveAuthorizationId(SecurityContext securityContext) throws LdapException {
+        Object candidate;
+        candidate = securityContext.getAuthorization().get(AUTHZID_DN);
+        if (candidate != null) {
+            return "dn:" + candidate;
+        }
+        candidate = securityContext.getAuthorization().get(AUTHZID_ID);
+        if (candidate != null) {
+            return "u:" + candidate;
+        }
+        throw LdapException.newLdapException(ResultCode.AUTH_METHOD_NOT_SUPPORTED);
+    }
+
     private Connection newProxiedConnection(Connection baseConnection, String authzId) {
         return new CachedReadConnectionDecorator(
                 new ProxiedAuthConnectionDecorator(baseConnection, newControl(authzId)));
     }
 
-    /**
-     * Introspect the content of the {@link SecurityContext} and return the contained DN, or the user's ID if DN is not
-     * present.
-     */
-    public static final class IntrospectionAuthzProvider implements Function<SecurityContext, String, LdapException> {
-
-        /** Singleton instance. */
-        public static final Function<SecurityContext, String, LdapException> INSTANCE =
-                new IntrospectionAuthzProvider();
-
-        private IntrospectionAuthzProvider() {
-        }
-
-        @Override
-        public String apply(SecurityContext securityContext) throws LdapException {
-            Object candidate;
-            candidate = securityContext.getAuthorization().get(AUTHZID_DN);
-            if (candidate != null) {
-                return "dn:" + candidate;
-            }
-            candidate = securityContext.getAuthorization().get(AUTHZID_ID);
-            if (candidate != null) {
-                return "u:" + candidate;
-            }
-            throw LdapException.newLdapException(ResultCode.AUTH_METHOD_NOT_SUPPORTED);
-        }
-    }
-
-    /** Use a {@link AuthzIdTemplate} to compute the authzId from the provided {@link SecurityContext}. */
-    public static final class TemplateAuthzProvider implements Function<SecurityContext, String, LdapException> {
-        private final AuthzIdTemplate template;
-        private final Schema schema;
-
-        TemplateAuthzProvider(AuthzIdTemplate template, Schema schema) {
-            this.template = checkNotNull(template, "template cannot be null");
-            this.schema = checkNotNull(schema, "schema cannot be null");
-        }
-
-        @Override
-        public String apply(SecurityContext securityContext) throws LdapException {
-            try {
-                return template.formatAsAuthzId(securityContext.getAuthorization(), schema);
-            } catch (IllegalArgumentException e) {
-                throw LdapException.newLdapException(ResultCode.OPERATIONS_ERROR);
-            }
-        }
-    }
-
     private static final class ProxiedAuthConnectionDecorator extends AbstractAsynchronousConnectionDecorator {
 
         private final Control proxiedAuthzControl;
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SASLPlainStrategy.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SASLPlainStrategy.java
index 11d8e25..b3c9750 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SASLPlainStrategy.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SASLPlainStrategy.java
@@ -19,6 +19,7 @@
 import static org.forgerock.services.context.SecurityContext.AUTHZID_DN;
 import static org.forgerock.services.context.SecurityContext.AUTHZID_ID;
 import static org.forgerock.util.Reject.checkNotNull;
+import static org.forgerock.opendj.rest2ldap.authz.Utils.close;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -42,9 +43,8 @@
 import org.forgerock.util.Function;
 import org.forgerock.util.promise.Promise;
 
-
 /** Bind using a computed DN from a template and the current request/context. */
-public final class SASLPlainStrategy implements AuthenticationStrategy {
+final class SASLPlainStrategy implements AuthenticationStrategy {
 
     private final ConnectionFactory connectionFactory;
     private final Function<String, String, LdapException> formatter;
@@ -90,21 +90,22 @@
 
     @Override
     public Promise<SecurityContext, LdapException> authenticate(final String username, final String password,
-            final Context parentContext, final AtomicReference<Connection> authenticateConnectionHolder) {
+            final Context parentContext) {
+        final AtomicReference<Connection> connectionHolder = new AtomicReference<Connection>();
         return connectionFactory
                 .getConnectionAsync()
                 .thenAsync(new AsyncFunction<Connection, SecurityContext, LdapException>() {
                     @Override
                     public Promise<SecurityContext, LdapException> apply(Connection connection) throws LdapException {
-                        authenticateConnectionHolder.set(connection);
-                        final String authcId = formatter.apply(username);
-                        return doSASLPlainBind(connection, parentContext, username, authcId, password);
+                        connectionHolder.set(connection);
+                        return doSASLPlainBind(connection, parentContext, username, password);
                     }
-                });
+                }).thenFinally(close(connectionHolder));
     }
 
     private Promise<SecurityContext, LdapException> doSASLPlainBind(final Connection connection,
-            final Context parentContext, final String authzId, final String authcId, final String password) {
+            final Context parentContext, final String authzId, final String password) throws LdapException {
+        final String authcId = formatter.apply(authzId);
         return connection
                 .bindAsync(newPlainSASLBindRequest(authcId, password.toCharArray())
                             .addControl(AuthorizationIdentityRequestControl.newControl(true)))
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SearchThenBindStrategy.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SearchThenBindStrategy.java
index c0f1d00..c2a1d76 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SearchThenBindStrategy.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SearchThenBindStrategy.java
@@ -35,7 +35,7 @@
 import org.forgerock.util.promise.Promise;
 
 /** Bind using the result of a search request computed from the current request/context. */
-public final class SearchThenBindStrategy implements AuthenticationStrategy {
+final class SearchThenBindStrategy implements AuthenticationStrategy {
     private final ConnectionFactory searchConnectionFactory;
     private final ConnectionFactory bindConnectionFactory;
 
@@ -70,7 +70,7 @@
 
     @Override
     public Promise<SecurityContext, LdapException> authenticate(final String username, final String password,
-            final Context parentContext, final AtomicReference<Connection> authenticatedConnectionHolder) {
+            final Context parentContext) {
         final AtomicReference<Connection> searchConnectionHolder = new AtomicReference<>();
         return searchConnectionFactory
                 .getConnectionAsync()
@@ -90,11 +90,12 @@
                     @Override
                     public Promise<SecurityContext, LdapException> apply(final SearchResultEntry searchResult)
                             throws LdapException {
+                        final AtomicReference<Connection> bindConnectionHolder = new AtomicReference<>();
                         return bindConnectionFactory
                                 .getConnectionAsync()
-                                .thenAsync(
-                                        doSimpleBind(authenticatedConnectionHolder, parentContext, username,
-                                                searchResult.getName(), password));
+                                .thenAsync(doSimpleBind(bindConnectionHolder, parentContext, username,
+                                                        searchResult.getName(), password))
+                                .thenFinally(close(bindConnectionHolder));
                     }
                 });
     }
diff --git a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SimpleBindStrategy.java b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SimpleBindStrategy.java
index 4d77253..dc4dcd3 100644
--- a/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SimpleBindStrategy.java
+++ b/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SimpleBindStrategy.java
@@ -16,6 +16,7 @@
 package org.forgerock.opendj.rest2ldap.authz;
 
 import static org.forgerock.opendj.ldap.requests.Requests.newSimpleBindRequest;
+import static org.forgerock.opendj.rest2ldap.authz.Utils.close;
 import static org.forgerock.services.context.SecurityContext.AUTHZID_DN;
 import static org.forgerock.services.context.SecurityContext.AUTHZID_ID;
 import static org.forgerock.util.Reject.checkNotNull;
@@ -37,7 +38,7 @@
 import org.forgerock.util.promise.Promise;
 
 /** Bind using a computed DN from a template and the current request/context. */
-public final class SimpleBindStrategy implements AuthenticationStrategy {
+final class SimpleBindStrategy implements AuthenticationStrategy {
 
     private final ConnectionFactory connectionFactory;
     private final Schema schema;
@@ -64,11 +65,13 @@
 
     @Override
     public Promise<SecurityContext, LdapException> authenticate(final String username, final String password,
-            final Context parentContext, final AtomicReference<Connection> authenticatedConnectionHolder) {
+            final Context parentContext) {
+        final AtomicReference<Connection> connectionHolder = new AtomicReference<>();
         return connectionFactory
                 .getConnectionAsync()
-                .thenAsync(doSimpleBind(authenticatedConnectionHolder, parentContext, username,
-                           DN.format(bindDNTemplate, schema, username), password));
+                .thenAsync(doSimpleBind(connectionHolder, parentContext, username,
+                           DN.format(bindDNTemplate, schema, username), password))
+                .thenFinally(close(connectionHolder));
     }
 
     static AsyncFunction<Connection, SecurityContext, LdapException> doSimpleBind(
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractorsTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractorsTest.java
new file mode 100644
index 0000000..54e0209
--- /dev/null
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractorsTest.java
@@ -0,0 +1,70 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2016 ForgeRock AS.
+ */
+package org.forgerock.opendj.rest2ldap.authz;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.forgerock.opendj.rest2ldap.authz.CredentialExtractors.HTTP_BASIC_AUTH_HEADER;
+import static org.forgerock.opendj.rest2ldap.authz.CredentialExtractors.httpBasicExtractor;
+import static org.forgerock.opendj.rest2ldap.authz.CredentialExtractors.newCustomHeaderExtractor;
+
+import org.forgerock.http.protocol.Headers;
+import org.forgerock.testng.ForgeRockTestCase;
+import org.forgerock.util.Pair;
+import org.forgerock.util.encode.Base64;
+import org.testng.annotations.Test;
+
+@Test
+public class CredentialExtractorsTest extends ForgeRockTestCase {
+
+    @Test
+    public void testBasicCanExtractValidCredentials() {
+        final Headers headers = new Headers();
+        headers.put(HTTP_BASIC_AUTH_HEADER, "basic " + Base64.encode("foo:bar".getBytes()));
+        assertThat(httpBasicExtractor().apply(headers)).isEqualTo(Pair.of("foo", "bar"));
+    }
+
+    @Test
+    public void testBasicReturnNullOnInvalidCredentials() {
+        final Headers headers = new Headers();
+        headers.put(HTTP_BASIC_AUTH_HEADER, "*invalid*");
+        assertThat(httpBasicExtractor().apply(new Headers())).isNull();
+    }
+
+    @Test
+    public void testBasicReturnNullOnMissingCredentials() {
+        assertThat(httpBasicExtractor().apply(new Headers())).isNull();
+    }
+
+    @Test
+    public void testCustomCanExtractValidCredentials() {
+        final Headers headers = new Headers();
+        headers.put("X-user", "foo");
+        headers.put("X-password", "bar");
+        assertThat(newCustomHeaderExtractor("X-user", "X-password").apply(headers)).isEqualTo(Pair.of("foo", "bar"));
+    }
+
+    @Test
+    public void testCustomFallbackOnBasicIfMissingCustomCredentials() {
+        final Headers headers = new Headers();
+        headers.put(HTTP_BASIC_AUTH_HEADER, "basic " + Base64.encode("foo:bar".getBytes()));
+        assertThat(newCustomHeaderExtractor("X-user", "X-password").apply(headers)).isEqualTo(Pair.of("foo", "bar"));
+    }
+
+    @Test
+    public void testCustomReturnNullOnMissingCredentials() {
+        assertThat(httpBasicExtractor().apply(new Headers())).isNull();
+    }
+}
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilterTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilterTest.java
index a77a9a9..cc0e877 100644
--- a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilterTest.java
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilterTest.java
@@ -15,13 +15,17 @@
  */
 package org.forgerock.opendj.rest2ldap.authz;
 
-import static org.fest.assertions.Assertions.*;
-import static org.forgerock.opendj.rest2ldap.authz.HttpBasicAuthenticationFilter.HttpBasicExtractor.*;
-import static org.mockito.Matchers.*;
-import static org.mockito.Mockito.*;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.forgerock.opendj.rest2ldap.authz.Authorizations.newConditionalHttpBasicAuthenticationFilter;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import java.io.IOException;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
 
 import org.assertj.core.api.SoftAssertions;
 import org.forgerock.http.Handler;
@@ -29,67 +33,69 @@
 import org.forgerock.http.protocol.Request;
 import org.forgerock.http.protocol.Response;
 import org.forgerock.opendj.ldap.LdapException;
-import org.forgerock.opendj.rest2ldap.authz.HttpBasicAuthenticationFilter.CustomHeaderExtractor;
-import org.forgerock.opendj.rest2ldap.authz.HttpBasicAuthenticationFilter.HttpBasicExtractor;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.ConditionalFilter;
 import org.forgerock.services.context.Context;
+import org.forgerock.services.context.RootContext;
 import org.forgerock.services.context.SecurityContext;
 import org.forgerock.testng.ForgeRockTestCase;
+import org.forgerock.util.Function;
 import org.forgerock.util.Pair;
-import org.forgerock.util.encode.Base64;
+import org.forgerock.util.promise.NeverThrowsException;
 import org.forgerock.util.promise.Promises;
-import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 @Test
 public class HttpBasicAuthenticationFilterTest extends ForgeRockTestCase {
 
-    private static final String USERNAME = "Aladdin";
-    private static final String PASSWORD = "open sesame";
-    private static final String BASE64_USERPASS = Base64.encode((USERNAME + ":" + PASSWORD).getBytes());
-
-    @DataProvider(name = "Invalid HTTP basic auth strings")
-    public Object[][] getInvalidHttpBasicAuthStrings() {
-        return new Object[][] { { null }, { "bla" }, { "basic " + Base64.encode("la:bli:blu".getBytes()) } };
-    }
-
-    @Test(dataProvider = "Invalid HTTP basic auth strings")
-    public void parseUsernamePasswordFromInvalidAuthZHeader(String authZHeader) throws Exception {
-        final AuthenticationStrategy strategy = mock(AuthenticationStrategy.class);
-        final HttpBasicAuthenticationFilter filter =
-                new HttpBasicAuthenticationFilter(strategy, HttpBasicExtractor.INSTANCE, false);
-
-        final Request req = new Request();
-        req.getHeaders().put(HTTP_BASIC_AUTH_HEADER, authZHeader);
-
-        assertThat(filter.canApplyFilter(null, req)).isFalse();
-    }
-
-    @DataProvider(name = "Valid HTTP basic auth strings")
-    public Object[][] getValidHttpBasicAuthStrings() {
-        return new Object[][] { { "basic " + BASE64_USERPASS }, { "Basic " + BASE64_USERPASS } };
-    }
-
-    @Test(dataProvider = "Valid HTTP basic auth strings")
-    public void parseUsernamePasswordFromValidAuthZHeader(String authZHeader) throws Exception {
-        final Headers headers = new Headers();
-        headers.put(HTTP_BASIC_AUTH_HEADER, authZHeader);
-        assertThat(HttpBasicExtractor.INSTANCE.apply(headers)).isEqualTo(Pair.of(USERNAME, PASSWORD));
-    }
-
+    @SuppressWarnings("unchecked")
     @Test
-    public void sendUnauthorizedResponseWithHttpBasicAuthWillChallengeUserAgent() throws Exception {
-        final AuthenticationStrategy failureStrategy = mock(AuthenticationStrategy.class);
-        when(failureStrategy
-                .authenticate(any(String.class), any(String.class), any(Context.class), any(AtomicReference.class)))
-                .thenReturn(Promises.<SecurityContext, LdapException>newResultPromise(null));
+    public void testRespondUnauthorizedIfCredentialMissing()
+            throws InterruptedException, ExecutionException, IOException {
+        final ConditionalFilter filter = newConditionalHttpBasicAuthenticationFilter(mock(AuthenticationStrategy.class),
+                mock(Function.class));
 
-        final HttpBasicAuthenticationFilter filter =
-                new HttpBasicAuthenticationFilter(failureStrategy, HttpBasicExtractor.INSTANCE, false);
+        assertThat(filter.getCondition().canApplyFilter(new RootContext(), new Request())).isFalse();
+        verifyUnauthorizedOutputMessage(
+                filter.getFilter().filter(mock(Context.class), new Request(), mock(Handler.class)).get());
+    }
 
-        final Response response = filter.filter(null, new Request(), mock(Handler.class)).get();
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testRespondUnauthorizedIfCredentialWrong()
+            throws InterruptedException, ExecutionException, IOException {
+        final Function<Headers, Pair<String, String>, NeverThrowsException> credentials = mock(Function.class);
+        when(credentials.apply(any(Headers.class))).thenReturn(Pair.of("user", "password"));
+
+        final AuthenticationStrategy authStrategy = mock(AuthenticationStrategy.class);
+        when(authStrategy.authenticate(eq("user"), eq("password"), any(Context.class)))
+                .thenReturn(Promises.<SecurityContext, LdapException> newExceptionPromise(
+                        LdapException.newLdapException(ResultCode.INVALID_CREDENTIALS)));
+
+        final Response response = new HttpBasicAuthenticationFilter(authStrategy, credentials)
+                .filter(mock(Context.class), new Request(), mock(Handler.class)).get();
+
         verifyUnauthorizedOutputMessage(response);
     }
 
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testContinueProcessOnSuccessfullAuthentication() {
+        final Function<Headers, Pair<String, String>, NeverThrowsException> credentials = mock(Function.class);
+        when(credentials.apply(any(Headers.class))).thenReturn(Pair.of("user", "password"));
+
+        final AuthenticationStrategy authStrategy = mock(AuthenticationStrategy.class);
+        when(authStrategy.authenticate(eq("user"), eq("password"), any(Context.class)))
+                .thenReturn(Promises.<SecurityContext, LdapException> newResultPromise(
+                        new SecurityContext(new RootContext(), "user", Collections.<String, Object> emptyMap())));
+
+        final Handler handler = mock(Handler.class);
+        new HttpBasicAuthenticationFilter(authStrategy, credentials)
+            .filter(mock(Context.class), new Request(), handler);
+
+        verify(handler).handle(any(SecurityContext.class), any(Request.class));
+    }
+
     private void verifyUnauthorizedOutputMessage(Response response) throws IOException {
         final SoftAssertions softly = new SoftAssertions();
         softly.assertThat(response.getStatus().getCode()).isEqualTo(401);
@@ -98,24 +104,4 @@
                 .isEqualTo("{code=401, reason=Unauthorized, message=Invalid Credentials}");
         softly.assertAll();
     }
-
-    @Test
-    public void extractUsernamePasswordHttpBasicAuthWillAcceptUserAgent() throws Exception {
-        final Headers headers = new Headers();
-        headers.add(HTTP_BASIC_AUTH_HEADER, "Basic " + BASE64_USERPASS);
-        assertThat(HttpBasicExtractor.INSTANCE.apply(headers)).isEqualTo(Pair.of(USERNAME, PASSWORD));
-    }
-
-    @Test
-    public void extractUsernamePasswordCustomHeaders() throws Exception {
-        final String customHeaderUsername = "X-OpenIDM-Username";
-        final String customHeaderPassword = "X-OpenIDM-Password";
-        CustomHeaderExtractor cha = new CustomHeaderExtractor(customHeaderUsername, customHeaderPassword);
-        Headers headers = new Headers();
-        headers.add(customHeaderUsername, USERNAME);
-        headers.add(customHeaderPassword, PASSWORD);
-
-        assertThat(cha.apply(headers)).isEqualTo(Pair.of(USERNAME, PASSWORD));
-    }
-
 }
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilterTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilterTest.java
deleted file mode 100644
index bfac595..0000000
--- a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/OptionalFilterTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * The contents of this file are subject to the terms of the Common Development and
- * Distribution License (the License). You may not use this file except in compliance with the
- * License.
- *
- * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
- * specific language governing permission and limitations under the License.
- *
- * When distributing Covered Software, include this CDDL Header Notice in each file and include
- * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
- * Header, with the fields enclosed by brackets [] replaced by your own identifying
- * information: "Portions copyright [year] [name of copyright owner]".
- *
- * Copyright 2016 ForgeRock AS.
- */
-package org.forgerock.opendj.rest2ldap.authz;
-
-import static org.mockito.Matchers.*;
-import static org.mockito.Mockito.*;
-
-import org.forgerock.http.Filter;
-import org.forgerock.http.Handler;
-import org.forgerock.http.protocol.Request;
-import org.forgerock.opendj.rest2ldap.authz.OptionalFilter.Condition;
-import org.forgerock.services.context.Context;
-import org.forgerock.services.context.RootContext;
-import org.forgerock.testng.ForgeRockTestCase;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-@Test
-public class OptionalFilterTest extends ForgeRockTestCase {
-
-    private OptionalFilter optionalFilter;
-    private Filter filter;
-    private Condition condition;
-
-    @BeforeMethod
-    public void setUp() {
-        filter = mock(Filter.class);
-        condition = mock(Condition.class);
-        optionalFilter = new OptionalFilter(filter, condition);
-    }
-
-    @Test
-    public void testFilterNotAppliedIfConditionIsFalse() {
-        when(condition.canApplyFilter(any(Context.class), any(Request.class))).thenReturn(false);
-
-        optionalFilter.filter(new RootContext(), new Request(), mock(Handler.class));
-
-        verify(filter, never()).filter(any(RootContext.class), any(Request.class), any(Handler.class));
-    }
-
-    @Test
-    public void testFilterAppliedIfConditionIsTrue() {
-        when(condition.canApplyFilter(any(Context.class), any(Request.class))).thenReturn(true);
-
-        final Context context = new RootContext();
-        final Request request = new Request();
-        final Handler handler = mock(Handler.class);
-        optionalFilter.filter(context, request, handler);
-
-        verify(filter).filter(same(context), same(request), same(handler));
-    }
-}
diff --git a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2FilterTest.java b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2FilterTest.java
index b61e35b..d0747a2 100644
--- a/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2FilterTest.java
+++ b/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/ProxiedAuthV2FilterTest.java
@@ -95,7 +95,7 @@
                                 .isEqualTo(ByteString.valueOfUtf8("dn:uid=whatever,ou=people,dc=com"));
                     }
                 }));
-        filter = new ProxiedAuthV2Filter(connectionFactory, ProxiedAuthV2Filter.IntrospectionAuthzProvider.INSTANCE);
+        filter = new ProxiedAuthV2Filter(connectionFactory);
 
         final Map<String, Object> authz = new HashMap<>();
         authz.put(SecurityContext.AUTHZID_DN, "uid=whatever,ou=people,dc=com");
diff --git a/opendj-server-legacy/resource/config/http-config.json b/opendj-server-legacy/resource/config/http-config.json
index da8f01b..3c0311b 100644
--- a/opendj-server-legacy/resource/config/http-config.json
+++ b/opendj-server-legacy/resource/config/http-config.json
@@ -3,16 +3,8 @@
 		// The authorization policies to use. Supported policies are "anonymous", "basic" and "oauth2".
 		"policies": [ "basic" ],
 		
-		// Perform all operations using a pre-authorization connection. 
-		"anonymous": {
-			// Specify the connection factory to use to perform LDAP operations.
-			// If missing, "root" factory will be used.
-			"ldapConnectionFactory": "root",
-			
-			// Enable proxied authorization using the specified user DN.
-			// If empty, anonymous proxied authorization will be used.
-			// If missing, connection from the ldapConnectionFactory will be used as-is. 
-			"userDN": ""
+		"anonymous": { 
+			// Perform all operations using anonymous user
 		},
 
 	    // Use HTTP Basic authentication's information to bind to the LDAP server.
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/InternalProxyAuthzFilter.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/InternalProxyAuthzFilter.java
index 39a75c2..21452d9 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/InternalProxyAuthzFilter.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/InternalProxyAuthzFilter.java
@@ -16,9 +16,13 @@
 package org.opends.server.protocols.http.rest2ldap;
 
 import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException;
+import static org.forgerock.services.context.SecurityContext.AUTHZID_DN;
+import static org.forgerock.services.context.SecurityContext.AUTHZID_ID;
 import static org.forgerock.util.Reject.checkNotNull;
 import static org.forgerock.util.Utils.closeSilently;
 
+import java.util.Map;
+
 import org.forgerock.http.Filter;
 import org.forgerock.http.Handler;
 import org.forgerock.http.protocol.Request;
@@ -34,7 +38,6 @@
 import org.forgerock.opendj.rest2ldap.AuthenticatedConnectionContext;
 import org.forgerock.services.context.Context;
 import org.forgerock.services.context.SecurityContext;
-import org.forgerock.util.Function;
 import org.forgerock.util.promise.NeverThrowsException;
 import org.forgerock.util.promise.Promise;
 import org.forgerock.util.promise.Promises;
@@ -53,14 +56,11 @@
 {
   private final IdentityMapper<?> identityMapper;
   private final Schema schema;
-  private final Function<SecurityContext, String, LdapException> authzIdProvider;
 
-  InternalProxyAuthzFilter(IdentityMapper<?> identityMapper, Schema schema,
-      Function<SecurityContext, String, LdapException> authzIdProvider)
+  InternalProxyAuthzFilter(IdentityMapper<?> identityMapper, Schema schema)
   {
     this.identityMapper = checkNotNull(identityMapper, "identityMapper cannot be null");
     this.schema = checkNotNull(schema, "schema cannot be null");
-    this.authzIdProvider = checkNotNull(authzIdProvider, "authzIdProvider cannot be null");
   }
 
   @Override
@@ -92,27 +92,27 @@
 
   private DN getUserDN(final SecurityContext securityContext) throws LdapException, DirectoryException
   {
-    final String authzId = authzIdProvider.apply(securityContext);
-    if (authzId.startsWith("u:"))
-    {
-      final Entry entry = identityMapper.getEntryForID(authzId);
-      if (entry == null)
-      {
-        throw LdapException.newLdapException(ResultCode.INVALID_CREDENTIALS);
-      }
-      return entry.getName();
-    }
-    else if (authzId.startsWith("dn:"))
+    final Map<String, Object> authz = securityContext.getAuthorization();
+    if (authz.containsKey(AUTHZID_DN))
     {
       try
       {
-        return DN.valueOf(authzId.substring(3), schema);
+        return DN.valueOf(authz.get(AUTHZID_DN).toString(), schema);
       }
       catch (LocalizedIllegalArgumentException e)
       {
         throw LdapException.newLdapException(ResultCode.INVALID_DN_SYNTAX, e);
       }
     }
+    if (authz.containsKey(AUTHZID_ID))
+    {
+      final Entry entry = identityMapper.getEntryForID(authz.get(AUTHZID_ID).toString());
+      if (entry == null)
+      {
+        throw LdapException.newLdapException(ResultCode.INVALID_CREDENTIALS);
+      }
+      return entry.getName();
+    }
     throw LdapException.newLdapException(ResultCode.AUTHORIZATION_DENIED);
   }
 
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
index 706347e..f87f501 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/protocols/http/rest2ldap/Rest2LdapEndpoint.java
@@ -15,8 +15,9 @@
  */
 package org.opends.server.protocols.http.rest2ldap;
 
-import static org.opends.messages.ConfigMessages.*;
-import static org.opends.server.util.StaticUtils.*;
+import static org.opends.messages.ConfigMessages.ERR_CONFIG_REST2LDAP_MALFORMED_URL;
+import static org.opends.server.util.StaticUtils.getFileForPath;
+import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
 
 import java.net.MalformedURLException;
 import java.net.URI;
@@ -27,15 +28,15 @@
 import org.forgerock.http.HttpApplication;
 import org.forgerock.opendj.adapter.server3x.Adapters;
 import org.forgerock.opendj.ldap.ConnectionFactory;
-import org.forgerock.opendj.ldap.LdapException;
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.rest2ldap.Rest2LDAPHttpApplication;
+import org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.ConditionalFilter;
 import org.forgerock.opendj.server.config.server.Rest2ldapEndpointCfg;
-import org.forgerock.services.context.SecurityContext;
-import org.forgerock.util.Function;
 import org.opends.server.api.HttpEndpoint;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ServerContext;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.types.AuthenticationInfo;
 import org.opends.server.types.InitializationException;
 
 /**
@@ -82,22 +83,28 @@
   private final class InternalRest2LDAPHttpApplication extends Rest2LDAPHttpApplication
   {
     private final ConnectionFactory rootInternalConnectionFactory = Adapters.newRootConnectionFactory();
+    private final ConnectionFactory anonymousInternalConnectionFactory =
+        Adapters.newConnectionFactory(new InternalClientConnection((AuthenticationInfo) null));
 
-    InternalRest2LDAPHttpApplication(URL configURL, Schema schema)
+    InternalRest2LDAPHttpApplication(final URL configURL, final Schema schema)
     {
       super(configURL, schema);
     }
 
     @Override
-    protected Filter newProxyAuthzFilter(final ConnectionFactory connectionFactory,
-        final Function<SecurityContext, String, LdapException> authzIdProvider)
+    protected ConditionalFilter newAnonymousFilter(final ConnectionFactory connectionFactory)
     {
-      return new InternalProxyAuthzFilter(DirectoryServer.getProxiedAuthorizationIdentityMapper(), schema,
-          authzIdProvider);
+      return super.newAnonymousFilter(anonymousInternalConnectionFactory);
     }
 
     @Override
-    protected ConnectionFactory getConnectionFactory(String name)
+    protected Filter newProxyAuthzFilter(final ConnectionFactory connectionFactory)
+    {
+      return new InternalProxyAuthzFilter(DirectoryServer.getProxiedAuthorizationIdentityMapper(), schema);
+    }
+
+    @Override
+    protected ConnectionFactory getConnectionFactory(final String name)
     {
       return rootInternalConnectionFactory;
     }

--
Gitblit v1.10.0